businesslens 0.19.0 → 0.20.0

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/CHANGELOG.md CHANGED
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.20.0] - 2026-09-18
11
+
12
+ - View the Product Model of a GitHub repository, branch, or pull request without cloning it yourself.
13
+
10
14
  ## [0.19.0] - 2026-09-17
11
15
 
12
16
  - Clearer comparison tables with independent filters and smoother navigation.
package/README.md CHANGED
@@ -80,7 +80,7 @@ drift, before release, or for a named/full current-state audit.
80
80
  | Terminal | `npx businesslens install` | Install the three skills |
81
81
  | Terminal | `npx businesslens update` | Refresh managed skill installations |
82
82
  | Terminal | `npx businesslens lint` | Check Product Model structure; no semantic claim |
83
- | Terminal | `npx businesslens view` | View the current Product Model privately on localhost |
83
+ | Terminal | `npx businesslens view` | View the current Product Model, or a GitHub repository's, privately on localhost |
84
84
  | Terminal | `npx businesslens blueprint export` | Compile the model into a source-free Blueprint |
85
85
  | Terminal | `npx businesslens blueprint pull <name>` | Pull a catalog Blueprint |
86
86
  | Terminal | `npx businesslens blueprint open <report>` | Expand a local Blueprint |
package/dist/cli.js CHANGED
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
- import{INTERFACE_TYPES as e,ProductReportV13Schema as t,REPORT_SCHEMA_VERSION as n,bulletList as r,containsPlace as i,containsStructuralHeading as a,decisionPoints as o,interfaceOf as s,isId as c,isQualifiedId as l,operationPlaces as u,parseCodeTarget as d,parseMarkdown as f,parseProductReport as p,permissionTargetSelectsOperation as m,projectPortableReport as h,qualify as g,reportResourceCollections as _,screenStates as v,section as y,supportingSections as b,validateBlueprintReport as x,validatePermissionBehavior as S,validateProductReport as C}from"./portable-DhjMQmE4.js";import{MAX_PRODUCT_LOGO_BYTES as w,PRODUCT_LOGO_FILENAME as ee,validateProductLogo as T}from"./logo-ByUBSRUb.js";import{reportDigest as te}from"./report-digest-BcpxYnhu.js";import{createRequire as ne}from"node:module";import{basename as re,dirname as E,extname as D,isAbsolute as ie,join as O,posix as ae,relative as oe,resolve as k,sep as A}from"node:path";import{Command as se,CommanderError as ce,Help as le,InvalidArgumentError as ue}from"commander";import{execFileSync as de,spawn as fe,spawnSync as pe}from"node:child_process";import{closeSync as me,constants as he,cpSync as ge,existsSync as j,lstatSync as M,mkdirSync as N,mkdtempSync as _e,openSync as ve,readFileSync as P,readdirSync as ye,realpathSync as be,renameSync as F,rmSync as I,rmdirSync as xe,watch as Se,writeFileSync as L}from"node:fs";import{createInterface as Ce}from"node:readline/promises";import{homedir as we,tmpdir as Te}from"node:os";import{parse as Ee,stringify as De}from"yaml";import{fileURLToPath as Oe}from"node:url";import{cancel as ke,isCancel as Ae,multiselect as je,select as Me,spinner as Ne}from"@clack/prompts";import{randomUUID as Pe}from"node:crypto";import{isIP as Fe}from"node:net";import{createServer as Ie}from"node:http";import{isUtf8 as Le}from"node:buffer";import{bundledLanguages as Re,bundledLanguagesAlias as ze,codeToTokens as Be}from"shiki";import{createMarkdownParser as Ve}from"comark/parse";var R=class extends Error{};const He=/^[a-z0-9]+(?:-[a-z0-9]+)*$/,Ue=80;var We=class extends R{};function Ge(e){if(!He.test(e)||e.length>Ue)throw new We(`Blueprint name must be its lowercase kebab-case canonical name.`);return e}function Ke(e,...t){let n=pe(`git`,[`-C`,e,...t],{encoding:`utf8`,maxBuffer:64*1024*1024});if(n.status!==0)throw Error(n.stderr.trim()||`git ${t.join(` `)} failed`);return n.stdout.trim()}function qe(e){return Ke(e,`rev-parse`,`--show-toplevel`)}function Je(e){return Ke(e,`ls-files`,`-z`).split(`\0`).filter(Boolean)}function Ye(e,t,n){let r=e.replace(/\r\n/g,`
2
+ import{INTERFACE_TYPES as e,ProductReportV13Schema as t,REPORT_SCHEMA_VERSION as n,bulletList as r,containsPlace as i,containsStructuralHeading as a,decisionPoints as o,interfaceOf as s,isId as c,isQualifiedId as l,operationPlaces as u,parseCodeTarget as d,parseMarkdown as f,parseProductReport as p,permissionTargetSelectsOperation as m,projectPortableReport as h,qualify as g,reportResourceCollections as _,screenStates as v,section as y,supportingSections as b,validateBlueprintReport as x,validatePermissionBehavior as S,validateProductReport as C}from"./portable-DhjMQmE4.js";import{MAX_PRODUCT_LOGO_BYTES as w,PRODUCT_LOGO_FILENAME as ee,validateProductLogo as T}from"./logo-ByUBSRUb.js";import{reportDigest as te}from"./report-digest-BcpxYnhu.js";import{createRequire as ne}from"node:module";import{basename as re,dirname as ie,extname as E,isAbsolute as ae,join as D,posix as oe,relative as se,resolve as O,sep as k}from"node:path";import{Command as ce,CommanderError as le,Help as ue,InvalidArgumentError as de}from"commander";import{execFileSync as fe,spawn as pe,spawnSync as A}from"node:child_process";import{closeSync as me,constants as he,cpSync as ge,existsSync as j,lstatSync as M,mkdirSync as N,mkdtempSync as _e,openSync as ve,readFileSync as P,readdirSync as ye,realpathSync as F,renameSync as I,rmSync as L,rmdirSync as be,watch as xe,writeFileSync as R}from"node:fs";import{createInterface as Se}from"node:readline/promises";import{homedir as Ce,tmpdir as we}from"node:os";import{parse as Te,stringify as Ee}from"yaml";import{fileURLToPath as De}from"node:url";import{cancel as Oe,isCancel as ke,multiselect as Ae,select as je,spinner as Me}from"@clack/prompts";import{randomUUID as Ne}from"node:crypto";import{isIP as Pe}from"node:net";import{createServer as Fe}from"node:http";import{isUtf8 as Ie}from"node:buffer";import{bundledLanguages as Le,bundledLanguagesAlias as Re,codeToTokens as ze}from"shiki";import{createMarkdownParser as Be}from"comark/parse";var z=class extends Error{};const Ve=/^[a-z0-9]+(?:-[a-z0-9]+)*$/,He=80;var Ue=class extends z{};function We(e){if(!Ve.test(e)||e.length>He)throw new Ue(`Blueprint name must be its lowercase kebab-case canonical name.`);return e}function Ge(e,...t){let n=A(`git`,[`-C`,e,...t],{encoding:`utf8`,maxBuffer:64*1024*1024});if(n.status!==0)throw Error(n.stderr.trim()||`git ${t.join(` `)} failed`);return n.stdout.trim()}function Ke(e){return Ge(e,`rev-parse`,`--show-toplevel`)}function qe(e){return Ge(e,`ls-files`,`-z`).split(`\0`).filter(Boolean)}function Je(e,t,n){let r=e.replace(/\r\n/g,`
3
3
  `);if(!r.startsWith(`---
4
4
  `)&&r.trim()!==`---`)return{data:{},body:r};let i=r.indexOf(`
5
5
  ---`,4);if(i===-1)return t.push(`${n}: frontmatter is not terminated with ---`),{data:{},body:r};let a=r.slice(4,i),o=r.slice(r.indexOf(`
6
- `,i+1)+1);try{let e=Ee(a);return e==null?{data:{},body:o}:typeof e!=`object`||Array.isArray(e)?(t.push(`${n}: frontmatter must be a YAML mapping`),{data:{},body:o}):{data:e,body:o}}catch(e){return t.push(`${n}: frontmatter YAML failed to parse (${e.message})`),{data:{},body:o}}}function z(e,t,n,r){let i=e[t];if(i!=null){if(typeof i!=`string`){n.push(`${r}: "${t}" must be a string`);return}return i}}function Xe(e,t,n,r){let i=e[t];return i==null?[]:!Array.isArray(i)||i.some(e=>typeof e!=`string`)?(n.push(`${r}: "${t}" must be a list of strings`),[]):i}function B(e,t,n,r){let i=Xe(e,t,n,r),a=new Set;for(let e of i)a.has(e)&&n.push(`${r}: "${t}" contains duplicate "${e}"`),a.add(e);return i}function Ze(e,t,n){let r=e.entryPoints;if(r==null)return[];if(!Array.isArray(r))return t.push(`${n}: "entryPoints" must be a list`),[];let i=[];for(let e of r){if(typeof e!=`object`||!e||Array.isArray(e)){t.push(`${n}: each entry point must be a single "type: path" map`);continue}let r=Object.entries(e),a=r[0];if(r.length!==1||!a||typeof a[1]!=`string`){t.push(`${n}: each entry point must be a single "type: path" map`);continue}i.push({type:a[0],path:a[1]})}return i}function Qe(e,t,n){return et(e,`availability`,t,n)}function $e(e,t,n){if(typeof e!=`object`||!e||Array.isArray(e)){t.push(`${n}: context must be a mapping with "place"`);return}let r=e;V(r,[`place`],t,n);let i=r.place;if(typeof i!=`string`||!l(i)){t.push(`${n}: "place" must be a qualified Interface, Experience, or Screen id like "customer-web::storefront"`);return}return{place:i}}function et(e,t,n,r){let i=e[t];if(i==null)return[];if(!Array.isArray(i))return n.push(`${r}: "${t}" must be a list of Context objects`),[];let a=[],o=new Set;for(let[e,s]of i.entries()){let i=$e(s,n,`${r}: ${t} context ${e+1}`);if(!i)continue;if(o.has(i.place)){n.push(`${r}: duplicate ${t} Context place "${i.place}"`);continue}o.add(i.place),a.push(i)}return a}function tt(e,t,n,r){let i=e[t];if(i!=null)return $e(i,n,`${r}: "${t}"`)}function nt(e,t,n){let r=e.assets;if(r==null)return[];if(!Array.isArray(r))return t.push(`${n}: "assets" must be a list`),[];let i=[],a=new Set;for(let e of r){if(typeof e!=`object`||!e||Array.isArray(e)){t.push(`${n}: each asset needs a "file"`);continue}let r=e,o=Object.keys(r).filter(e=>![`file`,`title`,`state`].includes(e));if(o.length){t.push(`${n}: asset has unknown key "${o[0]}"`);continue}let s=r.file;if(typeof s!=`string`||!s.trim()||s.startsWith(`/`)||s.includes(`..`)){t.push(`${n}: asset "file" must be a path inside the resource's expanded folder`);continue}if(a.has(s)){t.push(`${n}: duplicate asset "${s}"`);continue}a.add(s);for(let e of[`title`,`state`]){let i=r[e];i!==void 0&&(typeof i!=`string`||!i.trim()||/[\r\n]/.test(i))&&t.push(`${n}: asset "${e}" must be a non-empty single-line string when present`)}i.push({file:s,title:typeof r.title==`string`?r.title:void 0,state:typeof r.state==`string`?r.state:void 0})}return i}const rt=new Set([`code`,`prd`,`spec`,`proposal`,`doc`,`adr`,`visual`,`research`]),it=new Set([`intent`,`implementation`,`context`]);function at(e){if(e.kind===`code`)return d(e.target,[],`reference`)?.path;if(!/^https?:\/\//i.test(e.target))return e.target.split(/[?#]/,1)[0]?.replace(/^\.\//,``)||void 0}function ot(e,t,n,r){if(!e.trim())return n.push(`${r}: reference "target" must not be empty`),!1;if(/[\r\n]/.test(e))return n.push(`${r}: reference "target" must be a single line`),!1;if(t===`code`)return!!d(e,n,r);if(/^https?:\/\//i.test(e))try{let t=new URL(e);if(!t.hostname)throw Error(`missing hostname`);return!0}catch{return n.push(`${r}: reference target "${e}" is not a valid HTTP(S) URL`),!1}if(/^[a-z][a-z0-9+.-]*:/i.test(e))return n.push(`${r}: reference target "${e}" must use HTTP(S) or a repository-relative path`),!1;let i=e.split(/[?#]/,1)[0]||``;return i.trim()?/^(?:[/\\]|~[/\\]|[a-z]:[/\\])/i.test(e)||e.includes(`\\`)||i.split(`/`).includes(`..`)?(n.push(`${r}: reference target "${e}" must be repository-relative, not an absolute filesystem path`),!1):!0:(n.push(`${r}: reference target "${e}" must name a repository-relative path`),!1)}function st(e,t,n){let r=e.references;if(r==null)return[];if(!Array.isArray(r))return t.push(`${n}: "references" must be a list`),[];let i=[];for(let e of r){if(typeof e!=`object`||!e||Array.isArray(e)){t.push(`${n}: each reference needs "kind", "role", and "target"`);continue}let r=e,a=Object.keys(r).filter(e=>![`kind`,`role`,`target`,`title`,`state`].includes(e));if(a.length){t.push(`${n}: reference has unknown key "${a[0]}"`);continue}if(typeof r.kind!=`string`||typeof r.role!=`string`||typeof r.target!=`string`){t.push(`${n}: each reference needs string "kind", "role", and "target"`);continue}if(!rt.has(r.kind)){t.push(`${n}: reference kind "${r.kind}" must be one of code|prd|spec|proposal|doc|adr|visual|research`);continue}if(!it.has(r.role)){t.push(`${n}: reference role "${r.role}" must be one of intent|implementation|context`);continue}if(!ot(r.target,r.kind,t,n))continue;if(r.title!==void 0&&(typeof r.title!=`string`||!r.title.trim()||/[\r\n]/.test(r.title))){t.push(`${n}: reference "title" must be a non-empty single-line string when present`);continue}if(r.state!==void 0&&(typeof r.state!=`string`||!r.state.trim()||/[\r\n]/.test(r.state))){t.push(`${n}: reference "state" must be a non-empty single-line string when present`);continue}i.push({kind:r.kind,role:r.role,target:r.target,title:typeof r.title==`string`?r.title:void 0,state:typeof r.state==`string`?r.state:void 0})}return i}function V(e,t,n,r){for(let i of Object.keys(e))t.includes(i)||n.push(`${r}: unknown frontmatter key "${i}"`)}function ct(e){let t=O(e,`.businesslens`,`product`,ee);if(!j(t))return;let n=M(t);if(n.isSymbolicLink()||!n.isFile())throw Error(`logo.svg must be a regular file, not a symbolic link`);if(n.size>w)throw Error(`logo.svg must be at most ${w/1024} KiB`);let r=P(t),i=T(r);if(i.length)throw Error(i.join(`; `));return{bytes:r,file:t}}const lt=[`person`,`system`],ut=[`external`,`internal`],dt=[`creates`,`changes`,`removes`,`reads`],ft=[`over`,`under`,`at-least`,`at-most`,`is`,`is-not`,`present`,`absent`];function pt(e){return{interfaces:e.interfaces,experiences:e.experiences,screens:e.screens,domains:e.domains,entities:e.entities,capabilities:e.capabilities,capabilityScenarios:e.capabilityScenarios,businessRules:e.businessRules,journeys:e.journeys,journeyScenarios:e.journeyScenarios}}function mt(e){return Object.values(pt(e)).flat()}const ht=new Set([`one-to-one`,`one-to-many`,`many-to-many`]),gt=`.businesslens`,_t=8;function H(e,t,n,r,i=[]){if(!j(e))return[];let a=[],o=new Map,s=new Map;for(let i of ye(e,{withFileTypes:!0})){if(i.name===`.DS_Store`)continue;if(i.isFile()){if(!i.name.endsWith(`.md`)){n.issues.push(`${r}/${i.name}: expected <id>.md or <id>/${t}.md`);continue}o.set(i.name.slice(0,-3),O(e,i.name));continue}if(i.isDirectory()){s.set(i.name,O(e,i.name));continue}n.issues.push(`${r}/${i.name}: expected a regular resource file or directory`)}let c=new Set([...o.keys(),...s.keys()]);for(let l of[...c].sort((e,t)=>e.localeCompare(t))){let c=o.get(l),u=s.get(l)??O(e,l),d=O(u,`${t}.md`),f=j(d);if(c&&s.has(l)){n.issues.push(f?`${r}/${l}: both ${l}.md and ${l}/${t}.md exist; keep exactly one resource shape`:`${r}/${l}.md cannot also have ${r}/${l}/; move it to ${r}/${l}/${t}.md before adding children or assets`),a.push({id:l,directory:u,file:c,expanded:!1});continue}if(c){a.push({id:l,directory:u,file:c,expanded:!1});continue}if(!f){n.issues.push(`${r}/${l}/ is missing ${t}.md`);continue}let p=!1;for(let e of ye(u,{withFileTypes:!0})){if(e.name===`.DS_Store`||e.name===`${t}.md`)continue;if(e.isFile()){p=!0;continue}if(e.isDirectory()&&(e.name===`implementation`||i.includes(e.name))){let t=ye(O(u,e.name),{withFileTypes:!0}).filter(e=>e.name!==`.DS_Store`);t.length&&(p=!0);continue}if(e.isDirectory()){n.issues.push(`${r}/${l}/${e.name}/ is not a recognized child directory`);continue}n.issues.push(`${r}/${l}/${e.name}: assets must be regular files`)}p||n.notices.push(`${r}/${l}/ has no assets or child resources; use ${r}/${l}.md`),a.push({id:l,directory:u,file:d,expanded:!0})}return a}function vt(e,t){let n=[];if(!j(e))return n;for(let r of ye(e,{withFileTypes:!0})){if(r.name===`.DS_Store`||r.name===t)continue;if(r.isFile())n.push(r.name);else if(r.name===`implementation`)for(let t of ye(O(e,`implementation`),{withFileTypes:!0}))t.isFile()&&t.name!==`.DS_Store`&&n.push(`implementation/${t.name}`)}return n.sort()}function U(e,t,n){let{file:r,directory:i,expanded:a}=e,o=P(r,`utf8`),{data:s,body:c}=Ye(o,n,r);V(s,[...t,`references`,`assets`],n,r);let l=f(c);return{data:s,doc:l,references:st(s,n,r),directory:i,assets:a?vt(i,re(r)):[],assetMeta:nt(s,n,r)}}function yt(e,t,n){return{trigger:y(e,`Trigger`)||``,outcome:y(e,`Outcome`)||``,edgeCases:r(y(e,`Edge cases`)||``),decisionPoints:o(y(e,`Decision points`)||``,t,n)}}function bt(e,t,n){let r=e.routes;if(r==null)return[];if(typeof r!=`object`||Array.isArray(r))return t.push(`${n}: "routes" must be a mapping from route id to route name`),[];let i=[];for(let[e,a]of Object.entries(r)){let r=z({name:a},`name`,t,`${n}: route "${e}"`);i.push({id:e,name:r||``})}return i}function xt(e,t,n,r){let i=e.steps;if(i==null)return[];if(!Array.isArray(i))return t.push(`${n}: "steps" must be a list`),[];let a=[];for(let[e,o]of i.entries()){let i=`${n}: step ${e+1}`;if(typeof o!=`object`||!o||Array.isArray(o)){t.push(`${i} must be a mapping`);continue}let s=o,c=[`entity`,`state`,`changes`,`reads`];for(let e of c)s[e]!==void 0&&t.push(`${i}: "${e}" is now an entry of "entities"`);V(s,[`text`,`kind`,`actor`,`entities`,`contexts`,`unattended`,...c,...r?[`capability`]:[]],t,i),(s.entities===void 0||s.entities===null)&&t.push(`${i}: needs "entities" — what this Step does to the Product's things, or [] when it touches nothing`);let l=St(s.entities,t,i),u=[];if(s.contexts!==void 0&&s.contexts!==null)if(typeof s.contexts!=`object`||Array.isArray(s.contexts))t.push(`${i}: "contexts" must map route ids to Context objects`);else for(let[e,n]of Object.entries(s.contexts)){let r=$e(n,t,`${i}: route "${e}"`);r&&u.push({routeId:e,...r})}let d=s.unattended;d!==void 0&&typeof d!=`boolean`&&t.push(`${i}: "unattended" must be true or false`),a.push({text:z(s,`text`,t,i)||``,kind:z(s,`kind`,t,i)||``,actor:z(s,`actor`,t,i),entities:l,capability:r?z(s,`capability`,t,i):void 0,unattended:d===!0?!0:void 0,contexts:u})}return a}function St(e,t,n){if(e==null)return[];if(!Array.isArray(e))return t.push(`${n}: "entities" must be a list`),[];let r=[],i=new Set;for(let[a,o]of e.entries()){let e=`${n}: entity ${a+1}`;if(typeof o!=`object`||!o||Array.isArray(o)){t.push(`${e} must be a mapping`);continue}let s=o;V(s,[`entity`,`as`,`effect`,`from`,`to`],t,e);let l=z(s,`entity`,t,e)||``;if(!l){t.push(`${e}: needs an "entity"`);continue}let u=z(s,`as`,t,e);u!==void 0&&!c(u)&&t.push(`${e}: "as" must be a lowercase kebab-case alias`);let d=`${l}\0${u??``}`;if(i.has(d)){t.push(`${e}: "${l}${u?` (${u})`:``}" already appears in this Step`);continue}i.add(d);let f=z(s,`effect`,t,e);if(f!==void 0&&!dt.includes(f)){t.push(`${e}: effect "${f}" must be ${dt.join(`|`)}`);continue}let p=f??`changes`,m=z(s,`from`,t,e),h=z(s,`to`,t,e);if(p===`reads`&&(m!==void 0||h!==void 0)){t.push(`${e}: a "reads" entry carries no "from" or "to"`);continue}if(p===`creates`&&m!==void 0){t.push(`${e}: a "creates" entry has no "from"; a creation starts nowhere`);continue}if(p===`removes`&&h!==void 0){t.push(`${e}: a "removes" entry has no "to"; nothing is left in a state after it ends`);continue}if(p===`changes`&&m===void 0!=(h===void 0)){t.push(`${e}: a "changes" entry carries both "from" and "to", or neither`);continue}r.push({entity:l,as:u,effect:f,from:m,to:h})}return r}function Ct(e,t,n,r=[]){if(typeof e!=`object`||!e||Array.isArray(e)){t.push(`${n}: context target must be a mapping`);return}let i=e;return V(i,[`context`,...r],t,n),tt(i,`context`,t,n)}function wt(e,t,n){let r=e.appliesTo;if(r==null)return[];if(!Array.isArray(r))return t.push(`${n}: "appliesTo" must be a list`),[];let i=[];for(let[e,a]of r.entries()){let r=`${n}: appliesTo item ${e+1}`;if(typeof a!=`object`||!a||Array.isArray(a)){t.push(`${r} must be a mapping`);continue}let o=a,s=z(o,`type`,t,r)||``;if(s===`context`){V(o,[`type`,`context`],t,r);let e=Ct(o,t,r,[`type`]);e&&i.push({type:`context`,context:e});continue}if(s===`entity`){V(o,[`type`,`id`,`effect`,`from`,`to`,`facts`,`contexts`],t,r);let e=z(o,`effect`,t,r);e!==void 0&&!dt.includes(e)&&t.push(`${r}: effect "${e}" must be ${dt.join(`|`)}`);let n=z(o,`from`,t,r),a=z(o,`to`,t,r);n!==void 0&&(e===`creates`||e===`reads`)&&t.push(`${r}: "from" selects nothing on a "${e}" target; a ${e===`creates`?`creation starts nowhere`:`read carries no state`}`),a!==void 0&&(e===`removes`||e===`reads`)&&t.push(`${r}: "to" selects nothing on a "${e}" target`),i.push({type:`entity`,id:z(o,`id`,t,r)||``,effect:e,from:n,to:a,facts:B(o,`facts`,t,r),contexts:Tt(o,t,r)});continue}V(o,[`type`,`id`,`contexts`],t,r),i.push({type:s,id:z(o,`id`,t,r)||``,contexts:Tt(o,t,r)})}return i}function Tt(e,t,n){let r=e.contexts,i=[];if(r==null)return i;if(!Array.isArray(r)||r.length===0)return t.push(`${n}: "contexts" must be a non-empty list when present`),i;for(let[e,a]of r.entries()){let r=$e(a,t,`${n}: context ${e+1}`);r&&i.push(r)}return i}function Et(e,t,n,r){let i=e[t];if(i!=null){if(i!==!0){n.push(`${r}: "${t}" is either true or absent`);return}return!0}}function Dt(e,t,n){if(e==null)return[];if(!Array.isArray(e))return t.push(`${n}: "related" must be a list of { verb, entity } segments`),[];if(!e.length)return t.push(`${n}: "related" is empty; the instance itself is "self: true"`),[];let r=[];for(let[i,a]of e.entries()){let e=`${n}: related segment ${i+1}`;if(typeof a!=`object`||!a||Array.isArray(a)){t.push(`${e} must be a { verb, entity } mapping`);continue}let o=a;V(o,[`verb`,`entity`],t,e);let s=z(o,`verb`,t,e)||``,c=z(o,`entity`,t,e)||``;s||t.push(`${e}: needs a "verb"`),c||t.push(`${e}: needs an "entity"`),s&&c&&r.push({verb:s,entity:c})}return r}function Ot(e,t,n){if(e==null)return[];if(!Array.isArray(e))return t.push(`${n}: "when" must be a list of conditions`),[];let r=[];for(let[i,a]of e.entries()){let e=`${n}: condition ${i+1}`;if(typeof a!=`object`||!a||Array.isArray(a)){t.push(`${e} must be a mapping`);continue}let o=a;V(o,[`entity`,`fact`,`state`,...ft],t,e);let s=z(o,`entity`,t,e),c=z(o,`fact`,t,e),l=z(o,`state`,t,e),u=ft.filter(e=>o[e]!==void 0);if(l!==void 0){if(s!==void 0||c!==void 0||u.length){t.push(`${e}: a "state" condition carries nothing else`);continue}r.push({state:l});continue}if(c===void 0){t.push(`${e}: needs a "fact" with one operator, or a "state"`);continue}if(u.length!==1){t.push(`${e}: needs exactly one operator of ${ft.join(`|`)}`);continue}let d=u[0],f=o[d],p;if(d===`present`||d===`absent`){if(f!==!0){t.push(`${e}: "${d}" takes true`);continue}p=!0}else if(typeof f==`string`||typeof f==`number`||typeof f==`boolean`)p=f;else if(typeof f==`object`&&f&&!Array.isArray(f)){let n=f;V(n,[`configuredBy`],t,`${e}: "${d}"`);let r=z(n,`configuredBy`,t,`${e}: "${d}"`);if(!r){t.push(`${e}: "${d}" needs a scalar or { configuredBy: <entity-id> }`);continue}p={configuredBy:r}}else{t.push(`${e}: "${d}" needs a scalar or { configuredBy: <entity-id> }`);continue}r.push({entity:s,fact:c,operator:d,value:p})}return r}function kt(e,t,n){let r=e.permits;if(r==null)return;if(!Array.isArray(r)){t.push(`${n}: "permits" must be a list of grants, or [] to forbid the operation to everyone`);return}let i=[];for(let[e,a]of r.entries()){let r=`${n}: grant ${e+1}`;if(typeof a!=`object`||!a||Array.isArray(a)){t.push(`${r} must be a mapping`);continue}let o=a;V(o,[`actors`,`related`,`self`,`when`,`unattended`,`configuredBy`],t,r);let s={actors:B(o,`actors`,t,r),related:Dt(o.related,t,r),self:Et(o,`self`,t,r),when:Ot(o.when,t,r),unattended:Et(o,`unattended`,t,r),configuredBy:z(o,`configuredBy`,t,r)};!s.actors.length&&!s.related.length&&!s.self&&!s.unattended&&!s.configuredBy&&t.push(`${r}: names nobody; a grant needs at least one of actors, related, self, unattended, configuredBy`),i.push(s)}return i}const At=/^\*\*(.+?)\*\*\s+—\s+(\S.*)$/;function jt(e,t,n){let i=[];if(e===void 0)return i;let a=new Set;for(let o of r(e)){let e=At.exec(o);if(!e){t.push(`${n}: fact "${o}" must read "**Name** — prose"`);continue}let r=e[1].trim();if(a.has(r)){t.push(`${n}: duplicate fact "${r}"`);continue}a.add(r),i.push({name:r,description:e[2].trim()})}return i}function Mt(e){let t=O(e,gt),n=[],i=[],o={issues:n,notices:i};if(!j(t))n.push(`${gt}/ does not exist — use \`businesslens-map\` for established code or \`businesslens-ideate\` for a new product`);else{j(O(t,`README.md`))||n.push(`README.md is missing`);let e=O(t,`.gitignore`);if(!j(e))n.push(`.gitignore is missing`);else{let t=new Set(P(e,`utf8`).split(/\r?\n/).map(e=>e.trim()).filter(e=>e.length>0&&!e.startsWith(`#`)));for(let e of[`build/`,`cache/`])t.has(e)||n.push(`.gitignore must ignore ${e}`)}}if(j(t))try{ct(e)}catch(e){n.push(`logo.svg: ${e.message}`)}let s={schema:_t,sddPaths:[]},c=O(t,`config.yaml`);if(j(c))try{let e=Ee(P(c,`utf8`)),t=Object.keys(e||{}).filter(e=>e!==`schema`&&e!==`sdd`);for(let e of t)n.push(`config.yaml: unknown key "${e}"`);if(e?.sdd!==void 0&&(typeof e.sdd!=`object`||e.sdd===null||Array.isArray(e.sdd)))n.push(`config.yaml: "sdd" must be a mapping`);else if(e?.sdd)for(let t of Object.keys(e.sdd).filter(e=>e!==`paths`))n.push(`config.yaml: sdd has unknown key "${t}"`);s={schema:Number(e?.schema??0),sddPaths:Array.isArray(e?.sdd?.paths)?e.sdd.paths.map(String):[]}}catch(e){n.push(`config.yaml failed to parse (${e.message})`)}else j(t)&&n.push(`config.yaml is missing`);s.schema!==_t&&n.push(`config.yaml: schema ${s.schema} is not supported (expected ${_t})`);let l=[],u=O(t,`taxonomies.yaml`);if(j(u))try{let e=Ee(P(u,`utf8`)),t=e?.scenarioKinds;Array.isArray(t)?l=t.map(e=>({id:String(e.id||``),name:String(e.name||``),description:String(e.description||``),colorSlot:typeof e.colorSlot==`number`?e.colorSlot:void 0})):n.push(`taxonomies.yaml must contain a scenarioKinds list`)}catch(e){n.push(`taxonomies.yaml failed to parse (${e.message})`)}else j(t)&&n.push(`taxonomies.yaml is missing`);let d={id:``,tags:[],authors:[],limitations:[],doc:{title:``,lead:``,sections:[]},references:[]},p=O(t,`product.md`),m=O(t,`product`),h=O(m,`product.md`),_=j(p),b=j(m),x=j(h),S=O(m,`logo.svg`);if(_&&b&&n.push(x?`product: both product.md and product/product.md exist; keep exactly one resource shape`:`product.md cannot also have product/; move it to product/product.md before adding logo.svg`),b&&!x&&!_&&n.push(`product/ is missing product.md`),x&&!j(S)&&n.push(`product/ has no logo asset; use product.md`),b)for(let e of ye(m,{withFileTypes:!0})){if(e.name===`.DS_Store`||e.name===`product.md`||e.name===`logo.svg`)continue;n.push(`product/${e.name}: the Product folder may contain only product.md and logo.svg`)}let C=_?p:x?h:void 0;if(C){let e=P(C,`utf8`),{data:t,body:r}=Ye(e,n,`product.md`);V(t,[`id`,`summary`,`category`,`tags`,`authors`,`license`,`limitations`,`references`],n,`product.md`);let i=t.authors,a=[];if(i!=null)if(!Array.isArray(i))n.push(`product.md: "authors" must be a list`);else for(let[e,t]of i.entries()){if(typeof t!=`object`||!t||Array.isArray(t)){n.push(`product.md: author ${e+1} must contain "name" and optional "url"`);continue}let r=t,i=Object.keys(r).filter(e=>e!==`name`&&e!==`url`);if(i.length&&n.push(`product.md: author ${e+1} has unknown field(s): ${i.join(`, `)}`),typeof r.name!=`string`){n.push(`product.md: author ${e+1} "name" must be a string`);continue}if(r.url!==void 0&&typeof r.url!=`string`){n.push(`product.md: author ${e+1} "url" must be a string`);continue}a.push({name:r.name,...r.url?{url:r.url}:{}})}d={id:z(t,`id`,n,`product.md`)||``,summary:z(t,`summary`,n,`product.md`),category:z(t,`category`,n,`product.md`),tags:B(t,`tags`,n,`product.md`),authors:a,license:z(t,`license`,n,`product.md`),limitations:Xe(t,`limitations`,n,`product.md`),doc:f(r),references:st(t,n,`product.md`)}}else j(t)&&n.push(`product.md is missing`);let w={status:`draft`,method:[],sourceAreas:[],unmapped:[],limitations:[],rationale:``},ee=O(t,`coverage.md`);if(j(ee)){let e=P(ee,`utf8`),{data:t,body:r}=Ye(e,n,`coverage.md`),i=f(r);V(t,[`status`,`method`,`sourceAreas`,`unmapped`,`limitations`],n,`coverage.md`),a(i.lead)&&n.push(`coverage.md: rationale must not contain an H1 or H2 heading`);for(let e of i.sections)n.push(`coverage.md: "## ${e.heading}" sections are not supported; keep the rationale in the lead paragraph`);w={status:z(t,`status`,n,`coverage.md`)||`draft`,method:Xe(t,`method`,n,`coverage.md`),sourceAreas:Xe(t,`sourceAreas`,n,`coverage.md`),unmapped:Xe(t,`unmapped`,n,`coverage.md`),limitations:Xe(t,`limitations`,n,`coverage.md`),rationale:i.lead}}else j(t)&&n.push(`coverage.md is missing`);j(O(t,`actors`))&&n.push(`actors/: there is no Actor resource type; move each file to entities/ and say how it acts with "kind" and "acts"`);let T=[],te=[],ne=[],re=(e,t,i)=>{for(let a of H(O(e,`screens`),`screen`,o,`${i}/screens`)){let{data:e,doc:i,references:o,directory:s,assets:c,assetMeta:l}=U(a,[`capabilities`,`entities`,`entryPoints`],n);ne.push({entities:B(e,`entities`,n,a.file),id:g(t,a.id),file:a.file,doc:i,references:o,directory:s,assets:c,assetMeta:l,containerId:t,capabilities:B(e,`capabilities`,n,a.file),entryPoints:Ze(e,n,a.file),information:r(y(i,`Information presented`)||``),actions:r(y(i,`Available actions`)||``),states:v(y(i,`View states`)||``,n,a.file),capabilityBoundary:y(i,`Capability boundary`)||``})}};for(let e of H(O(t,`interfaces`),`interface`,o,`interfaces`,[`experiences`,`screens`])){let{data:t,doc:r,references:i,directory:a,assets:s,assetMeta:c}=U(e,[`type`,`actors`,`entryPoints`,`screens`],n);T.push({id:e.id,file:e.file,doc:r,references:i,directory:a,assets:s,assetMeta:c,type:z(t,`type`,n,e.file)||``,actors:B(t,`actors`,n,e.file),entryPoints:Ze(t,n,e.file),capabilityBoundary:y(r,`Capability boundary`)||``,screens:B(t,`screens`,n,e.file)});let l=H(O(e.directory,`experiences`),`experience`,o,`interfaces/${e.id}/experiences`,[`screens`]);for(let t of l){let r=g(e.id,t.id),i=U(t,[`actors`,`access`,`entryPoints`,`screens`],n);te.push({id:r,file:t.file,doc:i.doc,references:i.references,directory:i.directory,assets:i.assets,assetMeta:i.assetMeta,actors:B(i.data,`actors`,n,t.file),interface:e.id,access:z(i.data,`access`,n,t.file)||``,entryPoints:Ze(i.data,n,t.file),capabilityBoundary:y(i.doc,`Capability boundary`)||``,screens:B(i.data,`screens`,n,t.file)}),re(t.directory,r,`interfaces/${e.id}/experiences/${t.id}`)}j(O(e.directory,`screens`))&&re(e.directory,e.id,`interfaces/${e.id}`)}let E=H(O(t,`domains`),`domain`,o,`domains`).map(e=>{let{id:t,file:r}=e,{data:i,doc:a,references:o,directory:s,assets:c,assetMeta:l}=U(e,[`colorSlot`],n);return{id:t,file:r,doc:a,references:o,directory:s,assets:c,assetMeta:l,colorSlot:typeof i.colorSlot==`number`?i.colorSlot:void 0,boundary:y(a,`Boundary`)||``}}),D=H(O(t,`entities`),`entity`,o,`entities`).map(e=>{let{id:t,file:r}=e,{data:i,doc:a,references:o,directory:s,assets:c,assetMeta:l}=U(e,[`domain`,`kind`,`acts`,`relations`,`transitions`],n);i.transitions!==void 0&&n.push(`${r}: "transitions" is gone; a Step's "entities" entry says which state it moves the thing from and to`);let u=jt(y(a,`Information kept`),n,r),d=v(y(a,`States`)||``,n,r,`States`,`entity state`),f=y(a,`States`)!==void 0,p=z(i,`kind`,n,r),m=z(i,`acts`,n,r);m!==void 0&&!ut.includes(m)&&n.push(`${r}: acts "${m}" must be external|internal`),p!==void 0&&!lt.includes(p)&&n.push(`${r}: kind "${p}" must be person|system`),m!==void 0&&p===void 0&&n.push(`${r}: an Entity that acts needs "kind": person|system`),m===void 0&&p!==void 0&&n.push(`${r}: "kind" is only valid together with "acts"`),!u.length&&!f&&m===void 0&&n.push(`${r}: an Entity needs "## Information kept", "## States", or "acts"`);let h=[],g=i.relations;if(g!=null)if(!Array.isArray(g))n.push(`${r}: "relations" must be a list`);else for(let[e,t]of g.entries()){let i=`${r}: relation ${e+1}`;if(typeof t!=`object`||!t||Array.isArray(t)){n.push(`${i} must be a mapping`);continue}let a=t;V(a,[`entity`,`verb`,`cardinality`],n,i);let o=z(a,`entity`,n,i)||``,s=z(a,`verb`,n,i)||``,c=z(a,`cardinality`,n,i)||``;if(o||n.push(`${i}: needs an "entity"`),s||n.push(`${i}: needs a "verb"`),c===`many-to-one`){n.push(`${i}: "cardinality" is many-to-one; declare this relation on "${o}", where it reads one-to-many`);continue}if(!ht.has(c)){n.push(`${i}: "cardinality" must be one-to-one, one-to-many, or many-to-many`);continue}h.push({entity:o,verb:s,cardinality:c})}return{id:t,file:r,doc:a,references:o,directory:s,assets:c,assetMeta:l,domain:z(i,`domain`,n,r),informationKept:u,kind:p,acts:m,relations:h,states:d}}),ie=[],ae=[];for(let e of H(O(t,`capabilities`),`capability`,o,`capabilities`,[`scenarios`])){let{data:t,doc:r,references:i,directory:a,assets:s,assetMeta:c}=U(e,[`domain`,`entities`,`availability`],n);t.entities!==void 0&&n.push(`${e.file}: "entities" is gone from a Capability; each Step's "entities" says what it changes`),ie.push({id:e.id,file:e.file,doc:r,references:i,directory:a,assets:s,assetMeta:c,domain:z(t,`domain`,n,e.file),availability:Qe(t,n,e.file)});for(let t of H(O(e.directory,`scenarios`),`capability-scenario`,o,`capabilities/${e.id}/scenarios`)){let r=U(t,[`kind`,`routes`,`steps`],n);ae.push({id:t.id,file:t.file,doc:r.doc,references:r.references,directory:r.directory,assets:r.assets,assetMeta:r.assetMeta,kind:z(r.data,`kind`,n,t.file)||``,capability:e.id,routes:bt(r.data,n,t.file),steps:xt(r.data,n,t.file,!1),...yt(r.doc,n,t.file)})}}let oe=[],k=[];for(let e of H(O(t,`journeys`),`journey`,o,`journeys`,[`scenarios`])){let{data:t,doc:r,references:i,directory:a,assets:s,assetMeta:c}=U(e,[`actors`],n);oe.push({id:e.id,file:e.file,doc:r,references:i,directory:a,assets:s,assetMeta:c,actors:B(t,`actors`,n,e.file),goal:y(r,`Goal`)||``,successCriterion:y(r,`Success criterion`)||``});for(let t of H(O(e.directory,`scenarios`),`journey-scenario`,o,`journeys/${e.id}/scenarios`)){let r=U(t,[`kind`,`result`,`routes`,`steps`],n);k.push({id:t.id,file:t.file,doc:r.doc,references:r.references,directory:r.directory,assets:r.assets,assetMeta:r.assetMeta,kind:z(r.data,`kind`,n,t.file)||``,journey:e.id,result:z(r.data,`result`,n,t.file)||``,routes:bt(r.data,n,t.file),steps:xt(r.data,n,t.file,!0),...yt(r.doc,n,t.file)})}}let A=H(O(t,`business-rules`),`business-rule`,o,`business-rules`).map(e=>{let{id:t,file:r}=e,{data:i,doc:a,references:o,directory:s,assets:c,assetMeta:l}=U(e,[`appliesTo`,`permits`],n);return{id:t,file:r,doc:a,references:o,directory:s,assets:c,assetMeta:l,appliesTo:wt(i,n,r),permits:kt(i,n,r),rationale:y(a,`Rationale`)||``}});return{root:t,config:s,product:d,scenarioKinds:l,coverage:w,interfaces:T,experiences:te,screens:ne,domains:E,entities:D,capabilities:ie,capabilityScenarios:ae,businessRules:A,journeys:oe,journeyScenarios:k,issues:n,notices:i}}function Nt(e){let t;try{t=qe(e)}catch{t=void 0}if(j(O(e,`.businesslens`)))return t?{modelRoot:e,gitRoot:t}:{modelRoot:e};if(t&&j(O(t,`.businesslens`)))return{modelRoot:t,gitRoot:t};throw Error("No .businesslens/ Product Model found here or at the repository root — use `businesslens-map` for established code or `businesslens-ideate` for a new product")}function Pt(e){return e.code===`ENOENT`}function Ft(e){let t=M(e);if(t.isSymbolicLink())throw Error(`Refusing to use a symbolic link for generated BusinessLens output: ${e}`);if(!t.isDirectory())throw Error(`Generated BusinessLens output path is not a directory: ${e}`)}function It(e,...t){if(t.length<2||t.some(e=>!e||e===`.`||e===`..`||e.includes(`/`)||e.includes(`\\`)))throw Error(`Generated BusinessLens output requires safe repository-relative path segments.`);let n=be(k(e)),r=k(n,...t),i=oe(n,r);if(!i||i===`..`||i.startsWith(`..${A}`)||ie(i))throw Error(`Generated BusinessLens output must stay inside the repository.`);let a=n;for(let e of t.slice(0,-1)){a=O(a,e);try{Ft(a)}catch(e){if(!Pt(e))throw e;try{N(a)}catch(e){if(e.code!==`EEXIST`)throw e}Ft(a)}}if(be(a)!==a)throw Error(`Generated BusinessLens output resolves outside its expected directory: ${a}`);try{let e=M(r);if(e.isSymbolicLink())throw Error(`Refusing to overwrite a symbolic link with generated BusinessLens output: ${r}`);if(!e.isFile())throw Error(`Generated BusinessLens output path is not a regular file: ${r}`)}catch(e){if(!Pt(e))throw e}return r}function Lt(e,t,n){let r=It(e,...t),i=he.O_NOFOLLOW??0,a;try{a=ve(r,he.O_WRONLY|he.O_CREAT|he.O_TRUNC|i,384)}catch(e){throw e.code===`ELOOP`?Error(`Refusing to overwrite a symbolic link with generated BusinessLens output: ${r}`):e}try{L(a,n,{encoding:`utf8`})}finally{me(a)}return r}let Rt;function zt(){if(Rt)return Rt;let e=E(Oe(import.meta.url));for(let t of[O(e,`../package.json`),O(e,`../../package.json`)])try{let e=JSON.parse(P(t,`utf8`));if(e.name===`businesslens`&&e.version)return Rt=e.version,Rt}catch{}return Rt=`0.0.0`,Rt}const Bt=new Set([`public`,`authenticated`,`restricted`]),Vt=new Set([`complete`,`partial`,`draft`]),Ht=new Set([`achieved`,`not-achieved`]),Ut=new Set(e);function Wt(e,t){return e.size===t.size&&[...e].every(e=>t.has(e))}function Gt(e){return e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}function Kt(t,n){let r=[...t.issues],o=[...t.notices],d=new Set(n),f=(e,t,n)=>{t||r.push(`${e}: missing H1 title`),n||r.push(`${e}: missing lead paragraph (description)`)},p=(e,t,n,i=[])=>{let o=new Set(n.map(e=>e.toLowerCase())),s=new Set(i.map(e=>e.toLowerCase())),c=new Set;a(t.lead)&&r.push(`${e}: lead paragraph must not contain an H1 or H2 heading`);for(let n of t.sections){let t=n.heading.toLowerCase();o.has(t)&&(c.has(t)&&r.push(`${e}: duplicate "## ${n.heading}" section`),c.add(t)),s.has(t)&&r.push(`${e}: "## ${n.heading}" is not allowed on this resource type`),a(n.body)&&r.push(`${e}: "## ${n.heading}" content must not contain an H1 or H2 heading`)}},h=(e,t,n,i)=>{let a=y(t,n);if(a===void 0)return;let o=i===`ordered`?/^\s*\d+[.)]\s+\S.*$/:/^\s*[-*]\s+\S.*$/;a.split(`
7
- `).some(e=>e.trim()&&!o.test(e))&&r.push(`${e}: "## ${n}" must contain only single-line ${i}-list items`)};t.product.id&&!c(t.product.id)&&r.push(`product.md: id must be lowercase kebab-case`),t.product.id.length>64&&r.push(`product.md: id must be at most 64 characters`),t.product.id||r.push(`product.md: missing id`),f(`product.md`,t.product.doc.title,t.product.doc.lead),p(`product.md`,t.product.doc,[`Intent`]),t.product.summary&&(/\r|\n/.test(t.product.summary)||t.product.summary.length>400)&&r.push(`product.md: summary must be a single line with at most 400 characters`),t.product.category&&!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(t.product.category)&&r.push(`product.md: category must be lowercase kebab-case`),t.product.category&&t.product.category.length>60&&r.push(`product.md: category must be at most 60 characters`),t.product.license&&!/^[A-Za-z0-9][A-Za-z0-9.+-]*$/.test(t.product.license)&&r.push(`product.md: license must be one SPDX license identifier`);for(let[e,n]of t.product.authors.entries())if((!n.name.trim()||n.name.length>120)&&r.push(`product.md: author ${e+1} name must contain 1–120 characters`),n.url)try{let e=new URL(n.url);if(![`http:`,`https:`].includes(e.protocol))throw Error(`unsupported protocol`)}catch{r.push(`product.md: author ${e+1} url must use HTTP(S)`)}Vt.has(t.coverage.status)||r.push(`coverage.md: status "${t.coverage.status}" must be complete|partial|draft`);let g=[...Object.entries(pt(t)),[`scenarioKinds`,t.scenarioKinds]],_=new Set([`interfaces`,`experiences`,`screens`]);for(let[e,t]of g){let n=_.has(e)?l:c;for(let i of t)n(i.id)||r.push(`${e}: id "${i.id}" must be lowercase kebab-case`)}let v=new Set(t.entities.map(e=>e.id)),b=new Map(t.entities.map(e=>[e.id,e])),x=new Set(t.entities.filter(e=>e.acts!==void 0).map(e=>e.id)),C=(e,t)=>{x.has(t)||(v.has(t)?r.push(`${e}: "${t}" does not act; an actor is an Entity with "acts" and "kind"`):r.push(`${e}: references missing entity "${t}"`))},w=new Set(t.interfaces.map(e=>e.id)),ee=new Map(t.interfaces.map(e=>[e.id,e])),T=new Map(t.experiences.map(e=>[e.id,e])),te=new Set(t.experiences.map(e=>e.interface)),ne=new Set([...t.interfaces.filter(e=>!te.has(e.id)).map(e=>e.id),...t.experiences.map(e=>e.id)]),re=new Set([...t.interfaces.map(e=>e.id),...t.experiences.map(e=>e.id),...t.screens.map(e=>e.id)]),E=new Set(t.domains.map(e=>e.id)),D=new Set(t.capabilities.map(e=>e.id)),ie=new Set(t.journeys.map(e=>e.id)),O=new Set(t.capabilityScenarios.map(e=>e.id)),ae=new Set(t.journeyScenarios.map(e=>e.id)),oe=new Set(t.scenarioKinds.map(e=>e.id)),k=(e,t)=>{let n=t.place;return n?l(n)?(re.has(n)||r.push(`${e}: Context references missing place "${n}"`),n):(r.push(`${e}: Context place "${n}" is not a valid Interface, Experience, or Screen id`),n):(r.push(`${e}: Context needs a non-empty place id`),n)},A=(e,n)=>{if(!n)return r.push(`${e}: availability Context needs a non-empty place id`),!1;if(!l(n))return r.push(`${e}: availability Context place "${n}" is not a valid Interface, Experience, or Screen id`),!1;if(ne.has(n))return!0;if(t.screens.some(e=>e.id===n))return r.push(`${e}: availability Context place "${n}" must name its containing Interface or Experience, not a Screen`),!1;let i=s(n);return w.has(i)?n===i?r.push(`${e}: interface "${i}" is divided into Experiences, so name one of them`):r.push(`${e}: availability Context references missing experience "${n}"`):r.push(`${e}: availability Context references missing interface "${i}"`),!1},se=(e,t)=>{let n=new Set;for(let i of t){let t=i.place;n.has(t)&&r.push(`${e}: duplicate availability Context place "${t}"`),A(e,t)&&n.add(t)}return n},ce=(e,t,n)=>{for(let i of t)n.has(i.type)||r.push(`${e}: entry point references undeclared interface "${i.type}"`)};for(let n of t.interfaces){f(n.file,n.doc.title,n.doc.lead),p(n.file,n.doc,[`Intent`,`Capability boundary`]),Ut.has(n.type)||r.push(`${n.file}: type "${n.type}" must be ${e.join(`|`)}`),n.actors.length||r.push(`${n.file}: needs at least one actor`);for(let e of n.actors)C(n.file,e);n.capabilityBoundary||r.push(`${n.file}: missing "## Capability boundary" section`);for(let e of n.entryPoints){if(e.type===n.type)continue;e.type===n.id?r.push(`${n.file}: entry point key "${e.type}" is this Interface's own id; use its type "${n.type}"`):w.has(e.type)||r.push(`${n.file}: entry point key "${e.type}" must be this Interface's type "${n.type}" or another Interface's id`)}}for(let e of t.experiences){f(e.file,e.doc.title,e.doc.lead),p(e.file,e.doc,[`Intent`,`Capability boundary`]),Bt.has(e.access)||r.push(`${e.file}: access "${e.access}" must be public|authenticated|restricted`),e.actors.length||r.push(`${e.file}: needs at least one actor`);for(let t of e.actors)C(e.file,t);let t=ee.get(e.interface);if(t)for(let n of e.actors)t.actors.includes(n)||r.push(`${e.file}: actor "${n}" is not supported by interface "${e.interface}"`);e.capabilityBoundary||r.push(`${e.file}: missing "## Capability boundary" section`),ce(e.file,e.entryPoints,new Set([e.interface]))}for(let e of t.interfaces){if(!te.has(e.id))continue;let n=new Set(t.experiences.filter(t=>t.interface===e.id).flatMap(e=>e.actors));for(let t of e.actors)n.has(t)||r.push(`${e.file}: actor "${t}" is not covered by any Experience declaring this interface`)}let le=new Map;for(let e of t.capabilities){if(!e.domain)continue;le.set(e.domain,(le.get(e.domain)||0)+1)}for(let e of t.domains){f(e.file,e.doc.title,e.doc.lead),p(e.file,e.doc,[`Intent`,`Boundary`]),e.boundary.trim()?/\b(does not|doesn't|not own|never|excludes?|rather than|outside)\b/i.test(e.boundary)||r.push(`${e.file}: "## Boundary" must state what the Domain does not own, not only what it covers`):r.push(`${e.file}: a Domain needs a "## Boundary" section`);let t=le.get(e.id)||0;t<2&&o.push(`${e.file}: names ${t} Capabilit${t===1?`y`:`ies`}; a Domain holding fewer than two is a folder, not a region`)}let ue=new Map;for(let e of t.experiences){let t=ue.get(e.interface)||[];t.push(e),ue.set(e.interface,t)}for(let e of t.interfaces){let n=ue.get(e.id)||[],i=[];for(let n of t.capabilities){let r=n.availability.some(t=>t.place===e.id||t.place.startsWith(`${e.id}::`));if(!r)continue;let a=new Set(e.actors.filter(e=>t.capabilityScenarios.some(t=>t.capability===n.id&&t.steps.some(t=>t.actor===e))));a.size&&i.push(a)}let a=new Map,o=e=>{let t=e;for(;a.get(t)!==void 0&&a.get(t)!==t;)t=a.get(t);return t};for(let e of i){let[t,...n]=[...e];if(!t)continue;a.has(t)||a.set(t,t);for(let e of n)a.has(e)||a.set(e,e),a.set(o(e),o(t))}let s=new Set([...a.keys()].map(o)).size,c=s>1,l=new Set(n.map(e=>e.access).filter(Boolean)),u=l.size>1||c,d=n.some(n=>{let r=n.id.split(`::`).pop();return t.experiences.some(t=>t.interface!==e.id&&t.id.split(`::`).pop()===r)});n.length&&!u&&!d&&r.push(`${e.file}: holds Experiences but serves one audience through one access mode, and none is a counterpart; use direct Interface availability`),!n.length&&c&&r.push(`${e.file}: serves Actor sets no available Capability bridges; these are Experiences, not one context`)}let de=/(?:ing|tion|sion|ment|ance|ence|ity|ness)$/,fe=new Set(`add.answer.apply.approve.archive.assign.back.block.book.browse.build.cancel.change.check.choose.close.collect.compare.complete.compose.configure.confirm.connect.contribute.create.decide.decline.delete.deliver.discover.edit.enter.expire.explore.export.find.follow.gate.generate.grant.handle.import.install.invite.issue.join.keep.leave.link.lint.list.manage.map.mark.merge.move.name.open.order.organize.pause.pay.place.plan.preserve.publish.pull.read.receive.recover.refresh.refund.reject.remove.rename.reorder.reply.republish.report.request.reset.resolve.restore.resume.retry.return.review.revoke.run.save.schedule.search.select.send.serve.set.settle.share.ship.show.sign.start.stop.submit.subscribe.switch.synchronize.track.transfer.unfollow.unlist.update.upload.verify.view.withdraw.write`.split(`.`)),pe=new Set;for(let e of[...t.entities,...t.domains,...t.interfaces,...t.experiences,...t.screens]){pe.add(e.id);let t=e.id.split(`::`).pop();t&&pe.add(t)}let me=e=>fe.has(e)&&!pe.has(e),he=[...t.capabilities.map(e=>({file:e.file,id:e.id,kind:`Capability`})),...t.capabilityScenarios.map(e=>({file:e.file,id:e.id,kind:`Capability Scenario`})),...t.journeys.map(e=>({file:e.file,id:e.id,kind:`Journey`})),...t.journeyScenarios.map(e=>({file:e.file,id:e.id,kind:`Journey Scenario`}))];for(let e of he){let t=e.id.split(`-`),n=t[t.length-1]||``,r=t.some(me);de.test(n)&&!r&&o.push(`${e.file}: ${e.kind} id "${e.id}" reads as a noun phrase; a behavioral id starts with a verb`)}for(let e of he){let t=e.id.split(`-`);if(t.length<2)continue;let n=t.slice(1).join(`-`);if(pe.has(n))continue;let r=[...pe].filter(e=>e.endsWith(`-${n}`)).sort((e,t)=>e.length-t.length)[0];r&&o.push(`${e.file}: ${e.kind} id "${e.id}" names "${n}" where this model declares "${r}"; use the declared name`)}let ge=[...t.entities.map(e=>({file:e.file,id:e.id,kind:`Entity`})),...t.domains.map(e=>({file:e.file,id:e.id,kind:`Domain`})),...t.businessRules.map(e=>({file:e.file,id:e.id,kind:`Business Rule`}))];for(let e of ge){let t=e.id.split(`-`);if(t.length<2)continue;let n=t[0]||``;me(n)&&o.push(`${e.file}: ${e.kind} id "${e.id}" opens with a verb; cross-cutting ids name what a thing is, not what is done`)}let j=[...t.capabilityScenarios,...t.journeyScenarios],M=new Set,N=new Map,_e=new Map;for(let e of j)for(let t of e.steps)for(let n of t.entities){if((n.effect??`changes`)===`reads`)continue;if(M.add(n.entity),n.to!==void 0&&N.set(n.entity,new Set([...N.get(n.entity)??[],n.to])),n.from!==void 0){let t=_e.get(n.entity)??new Map;t.has(n.from)||t.set(n.from,e.file),_e.set(n.entity,t)}}let ve=new Map;for(let e of t.screens)for(let t of e.entities)v.has(t)||r.push(`${e.file}: names missing entity "${t}"`),ve.set(t,[...ve.get(t)||[],e.id]);let P=new Set(t.businessRules.flatMap(e=>(e.permits??[]).flatMap(e=>[...e.configuredBy?[e.configuredBy]:[],...e.when.flatMap(e=>[...e.entity?[e.entity]:[],...typeof e.value==`object`&&e.value!==null?[e.value.configuredBy]:[]])]))),ye=new Set([...j.flatMap(e=>e.steps.flatMap(e=>e.actor?[e.actor]:[])),...t.interfaces.flatMap(e=>e.actors),...t.experiences.flatMap(e=>e.actors),...t.journeys.flatMap(e=>e.actors),...t.businessRules.flatMap(e=>(e.permits??[]).flatMap(e=>[...e.actors,...e.related.length?[e.related[e.related.length-1].entity]:[]]))]);for(let e of t.entities){f(e.file,e.doc.title,e.doc.lead),p(e.file,e.doc,[`Intent`,`Information kept`,`States`],[`Transitions`,`Relations`]),e.domain&&!E.has(e.domain)&&r.push(`${e.file}: names missing domain "${e.domain}"`);let n=e.states[0]?.title,i=N.get(e.id)??new Set;for(let t of e.states.slice(1))i.has(t.title)||o.push(`${e.file}: no Step leaves it in "${t.title}"`);for(let[t,r]of _e.get(e.id)??[])t!==n&&!i.has(t)&&o.push(`${e.file}: ${r} moves it from "${t}", which no Step produces`);let a=new Set;for(let t of e.states)a.has(t.title)&&r.push(`${e.file}: duplicate state "${t.title}"`),a.add(t.title);let s=new Set;for(let n of e.relations){v.has(n.entity)||r.push(`${e.file}: relation names missing entity "${n.entity}"`);let i=`${n.entity}\0${n.verb}`;if(s.has(i)&&r.push(`${e.file}: duplicate relation "${n.verb} ${n.entity}"`),s.add(i),n.entity===e.id)continue;let a=t.entities.find(e=>e.id===n.entity);for(let t of a?.relations??[]){if(t.entity!==e.id)continue;o.push(`${e.file}: "${n.verb} ${n.entity}" faces "${t.verb} ${t.entity}" in ${a.file}; a relation is declared on one side only and now states both ends`)}}!M.has(e.id)&&!(ve.get(e.id)||[]).length&&!ye.has(e.id)&&!P.has(e.id)&&r.push(`${e.file}: no Step changes it, no Screen presents it, nothing names it as an actor, and no Rule reads it`)}let be=new Map;for(let e of t.capabilities)f(e.file,e.doc.title,e.doc.lead),p(e.file,e.doc,[`Intent`]),e.availability.length||r.push(`${e.file}: needs at least one availability Context`),e.domain&&!E.has(e.domain)&&r.push(`${e.file}: references missing domain "${e.domain}"`),be.set(e.id,se(e.file,e.availability));let F=e=>{let t=T.get(e);if(t)return new Set(t.actors);let n=ee.get(e);return n?new Set(n.actors):void 0},I=new Map(t.screens.map(e=>[e.id,e])),xe=new Map;for(let e of t.screens){let t=xe.get(e.containerId)||[];t.push(e),xe.set(e.containerId,t)}let Se=e=>(ue.get(e)||[]).length>0,L=e=>Se(e)?(ue.get(e)||[]).map(e=>e.id):[e],Ce=(e,t)=>L(t).every(t=>e.has(t)),we=(e,t)=>{if(!t){r.push(`${e}: Context needs a non-empty place id`);return}if(!l(t)){r.push(`${e}: Context place "${t}" is not a valid Interface, Experience, or Screen id`);return}let n=I.get(t);if(n)return{place:t,containerId:n.containerId,screen:n};let i=T.get(t);if(i)return(xe.get(t)||[]).length&&r.push(`${e}: Experience "${t}" owns Screens, so the Context must name one of its Screens`),{place:t,containerId:t,screen:void 0};let a=ee.get(t);if(a)return te.has(t)?r.push(`${e}: Interface "${t}" is divided into Experiences, so the Context must name one of them, one of their Screens, or a Screen it shares`):(xe.get(t)||[]).length&&r.push(`${e}: Interface "${t}" owns Screens, so the Context must name one of its Screens`),{place:t,containerId:t,screen:void 0};r.push(`${e}: Context references missing place "${t}"`)},Te=(e,n,i)=>{e.routes.length||r.push(`${e.file}: needs at least one route`);let a=new Set,s=new Set,l=new Map(e.routes.map(e=>[e.id,[]]));for(let t of e.routes){let n=`${e.file}: route "${t.id}"`;t.id?c(t.id)||r.push(`${n}: id must be lowercase kebab-case`):r.push(`${e.file}: route id must not be empty`),a.has(t.id)&&r.push(`${n}: duplicate route id`),a.add(t.id);let i=t.name.trim();i||r.push(`${n}: needs a non-empty name`);let o=i.toLocaleLowerCase();s.has(o)&&r.push(`${n}: duplicate route name "${t.name}"`),s.add(o)}e.steps.length||r.push(`${e.file}: needs at least one step`);let u=new Set,d=new Set,f=new Set,p=[],m=e.steps[0]?.unattended===!0,h=new Map,g=new Map;for(let[i,s]of e.steps.entries()){let c=`${e.file}: step ${i+1}`;s.text.trim()||r.push(`${c}: needs non-empty text`),/\r|\n/.test(s.text)&&r.push(`${c}: text must be a single line`),[`actor`,`product`,`condition`].includes(s.kind)||r.push(`${c}: kind "${s.kind}" must be actor|product|condition`),s.kind===`actor`&&!s.actor&&r.push(`${c}: an actor Step needs one actor`),s.actor&&(f.add(s.actor),C(c,s.actor),m&&r.push(`${c}: an unattended Scenario names no actor; its permission is a Rule's "unattended" grant`)),!n&&!s.capability&&s.entities.some(e=>(e.effect??`changes`)!==`reads`)&&r.push(`${c}: a Journey Step that creates, changes or removes an Entity needs a "capability" to own the change`);for(let e of s.entities){let t=b.get(e.entity);if(!t){r.push(`${c}: references missing entity "${e.entity}"`);continue}let n=e.effect??`changes`,i=e.as?`${e.entity} (${e.as})`:e.entity,a=e.as?`aliased`:`bare`,o=g.get(e.entity);o&&o!==a&&r.push(`${c}: "${e.entity}" is ${o===`aliased`?`aliased`:`bare`} elsewhere in this Scenario; once an Entity is aliased, every mention of it is`),g.set(e.entity,a);let s=t.states.length>0;for(let[n,i]of[[`from`,e.from],[`to`,e.to]]){if(i===void 0)continue;s?t.states.some(e=>e.title===i)||r.push(`${c}: "${i}" is not a state of entity "${e.entity}"`):r.push(`${c}: "${n}: ${i}" names a state, and entity "${e.entity}" declares none`)}s&&n===`creates`&&e.to===void 0&&r.push(`${c}: creating "${i}" needs "to", the state it starts in`),s&&n===`removes`&&e.from===void 0&&r.push(`${c}: removing "${i}" needs "from", the state it ends in`);let l=`${e.entity}\0${e.as??``}`,u=h.get(l);e.from!==void 0&&u!==void 0&&u!==e.from&&r.push(`${c}: "${i}" was left in "${u}" by an earlier Step, not "${e.from}"; if these are different instances, give them aliases`),n===`removes`?h.delete(l):e.to!==void 0&&h.set(l,e.to)}let _=new Set(s.entities.map(e=>e.entity)),v=s.text.replace(/\bthe Product\b/gi,` `),y=e=>{let t=RegExp(`(?:^|[^a-z0-9])(${Gt(e)}(?:'s|s)?)(?=$|[^a-z0-9])`,`gi`);return[...v.matchAll(t)].map(e=>{let t=e.index+e[0].length-e[1].length;return[t,t+e[1].length]})},x=t.entities.filter(e=>e.doc.title&&(_.has(e.id)||e.id===s.actor)).flatMap(e=>y(e.doc.title));for(let e of t.entities){if(!e.doc.title||_.has(e.id)||e.id===s.actor)continue;let n=y(e.doc.title).some(([e,t])=>!x.some(([n,r])=>n<=e&&t<=r));if(!n)continue;let i=`${c}: text names "${e.doc.title}" and "entities" does not declare it`;t.coverage.status===`complete`?r.push(i):o.push(i)}let S=n||s.capability;if(S&&(p.push({capability:S,contextPlaces:new Set,containers:new Set}),D.has(S)||r.push(`${c}: references missing capability "${S}"`)),!s.contexts.length)continue;let w=new Set;for(let e of s.contexts){let t=`${c}: route "${e.routeId}"`;a.has(e.routeId)||r.push(`${t}: references undeclared route`),w.has(e.routeId)&&r.push(`${t}: duplicate Context`),w.add(e.routeId),l.get(e.routeId)?.push(e.place);let n=we(t,e.place);if(!n)continue;u.add(n.place),d.add(n.containerId);let i=S?p.at(-1):void 0;if(i?.contextPlaces.add(n.place),i?.containers.add(n.containerId),S){let e=be.get(S)||new Set;Ce(e,n.containerId)||r.push(`${t}: Context place "${n.place}" is outside capability "${S}"`),n.screen&&!n.screen.capabilities.includes(S)&&r.push(`${t}: Screen "${n.screen.id}" does not expose capability "${S}"`)}if(s.actor){let e=F(n.containerId)||new Set;e.has(s.actor)||r.push(`${t}: Context place does not support actor "${s.actor}"`)}}Wt(w,a)||r.push(`${c}: contexts must assign every declared route or be omitted`)}let _=m;_&&e.steps[0]?.kind!==`condition`&&r.push(`${e.file}: an unattended trigger must be a condition Step`);for(let[t,n]of e.steps.entries())t>0&&n.unattended&&r.push(`${e.file}: step ${t+1}: "unattended" is valid only on the first Step`);!e.steps.some(e=>e.kind===`actor`)&&!_&&r.push(`${e.file}: needs at least one actor Step, or an unattended first condition Step`);for(let t of e.routes){let n=l.get(t.id)||[];n.length||r.push(`${e.file}: route "${t.id}" must have a Context on at least one Step`)}let v=new Map;for(let t of e.routes){let n=(l.get(t.id)||[]).join(`
8
- `);if(!n)continue;let i=v.get(n);i?r.push(`${e.file}: route "${t.id}" repeats every Context place of route "${i}"`):v.set(n,t.id)}let y=new Set;if(!_)for(let t of d){let n=F(t)||new Set,i=[...f].filter(e=>n.has(e));i.length||r.push(`${e.file}: Context place "${t}" permits none of the Scenario Actors`);for(let e of i)y.add(e)}for(let t of f)x.has(t)&&!y.has(t)&&r.push(`${e.file}: actor "${t}" is not supported by any selected Context place`);if(i)for(let t of e.routes){let n=e.steps.find(e=>e.kind===`actor`&&e.contexts.some(e=>e.routeId===t.id));(!n?.actor||!i.has(n.actor))&&r.push(`${e.file}: route "${t.id}" must begin its contextualized Actor Steps with a Journey Actor`)}return{allContextPlaces:u,allContainers:d,scenarioActors:f,capabilitySteps:p}},Ee=e=>{e.doc.title||r.push(`${e.file}: missing H1 title`),e.doc.lead&&r.push(`${e.file}: carries no lead paragraph; move that content into "## Trigger" or another explicit section`),p(e.file,e.doc,[`Intent`,`Trigger`,`Steps`,`Decision points`,`Outcome`,`Edge cases`],[`Goal`,`Success criterion`,`Steps`]),h(e.file,e.doc,`Edge cases`,`bullet`),(!e.kind||!oe.has(e.kind))&&r.push(`${e.file}: kind "${e.kind}" is not defined in taxonomies.yaml`),e.trigger||r.push(`${e.file}: missing "## Trigger" section`),e.outcome||r.push(`${e.file}: missing "## Outcome" section`),y(e.doc,`Edge cases`)!==void 0&&!e.edgeCases.length&&r.push(`${e.file}: "## Edge cases" needs at least one bullet item when present`)},De=new Map;for(let e of[...t.capabilityScenarios,...t.journeyScenarios]){let t=De.get(e.id);t&&r.push(`${e.file}: scenario id "${e.id}" already used in ${t} (ids are global)`),De.set(e.id,e.file)}let Oe=new Map(t.capabilityScenarios.map(e=>[e.id,e])),ke=new Map,Ae=new Map;for(let e of t.capabilityScenarios){Ee(e),e.capability?D.has(e.capability)||r.push(`${e.file}: references missing capability "${e.capability}"`):r.push(`${e.file}: needs one capability`);let{allContextPlaces:t,allContainers:n}=Te(e,e.capability);ke.set(e.id,t);let i=Ae.get(e.capability)||new Set;for(let e of n)for(let t of L(e))i.add(t);Ae.set(e.capability,i);let a=be.get(e.capability)||new Set;for(let t of n)Ce(a,t)||r.push(`${e.file}: Context place "${t}" is outside capability "${e.capability}"`)}for(let e of t.capabilities){let n=Ae.get(e.id)||new Set,i=be.get(e.id)||new Set;for(let a of i){if(n.has(a))continue;let i=`${e.file}: availability Context place "${a}" needs Capability Scenario coverage`;t.coverage.status===`complete`?r.push(i):o.push(i)}}let je=new Map(t.journeys.map(e=>[e.id,e]));for(let e of t.journeys){e.doc.title||r.push(`${e.file}: missing H1 title`),e.doc.lead&&r.push(`${e.file}: carries no lead paragraph; move that content into "## Goal"`),p(e.file,e.doc,[`Intent`,`Goal`,`Success criterion`],[`Trigger`,`Steps`,`Decision points`,`Outcome`,`Edge cases`]),e.actors.length||r.push(`${e.file}: needs at least one actor`);for(let t of e.actors)C(e.file,t);e.goal||r.push(`${e.file}: missing "## Goal" section`),e.successCriterion||r.push(`${e.file}: missing "## Success criterion" section`),t.journeyScenarios.some(t=>t.journey===e.id&&t.result===`achieved`)||r.push(`${e.file}: needs at least one achieved Journey Scenario`)}let Me=new Map(t.journeyScenarios.map(e=>[e.id,e])),Ne=new Map,Pe=new Map;for(let e of t.journeyScenarios){Ee(e);let t=je.get(e.journey);t||r.push(`${e.file}: references missing journey "${e.journey}"`),Ht.has(e.result)||r.push(`${e.file}: result "${e.result}" must be achieved|not-achieved`);let n=new Set(t?.actors||[]),{scenarioActors:i,capabilitySteps:a}=Te(e,void 0,n);t&&![...i].some(e=>n.has(e))&&r.push(`${e.file}: actor Steps must include at least one actor from journey "${e.journey}"`),a.length||r.push(`${e.file}: needs at least one Capability-bearing step`),Ne.set(e.id,a),Pe.set(e.id,i),e.result===`achieved`&&new Set(a.map(e=>e.capability)).size<2&&r.push(`${e.file}: an achieved Journey Scenario needs at least two distinct Capabilities`)}for(let e of t.journeys){let n=new Set(t.journeyScenarios.filter(t=>t.journey===e.id&&t.result===`achieved`).flatMap(e=>[...Pe.get(e.id)||[]]));for(let t of e.actors)n.has(t)||r.push(`${e.file}: actor "${t}" needs an achieved Journey Scenario`)}for(let e of t.screens){f(e.file,e.doc.title,e.doc.lead),p(e.file,e.doc,[`Intent`,`Information presented`,`Available actions`,`View states`,`Capability boundary`]),h(e.file,e.doc,`Information presented`,`bullet`),h(e.file,e.doc,`Available actions`,`bullet`),L(e.containerId).every(e=>ne.has(e))||r.push(`${e.file}: containing place "${e.containerId}" must be an Interface or an Experience`),e.capabilities.length||r.push(`${e.file}: needs at least one capability`);for(let t of e.capabilities){if(!D.has(t)){r.push(`${e.file}: references missing capability "${t}"`);continue}let n=be.get(t)||new Set,i=L(e.containerId).filter(e=>!n.has(e));if(!i.length)continue;r.push(Se(e.containerId)?`${e.file}: capability "${t}" must be available in every Experience of "${e.containerId}", which shares this Screen; missing "${i.join(`", "`)}"`:`${e.file}: capability "${t}" is not available in containing place "${e.containerId}"`)}ce(e.file,e.entryPoints,new Set([s(e.containerId)])),e.information.length||r.push(`${e.file}: "## Information presented" needs at least one bullet item`),e.capabilityBoundary||r.push(`${e.file}: missing "## Capability boundary" section`),y(e.doc,`Available actions`)!==void 0&&!e.actions.length&&r.push(`${e.file}: "## Available actions" needs at least one bullet item when present`),y(e.doc,`View states`)!==void 0&&!e.states.length&&r.push(`${e.file}: "## View states" needs at least one H3 state when present`);let t=new Set;for(let n of e.states){let i=n.title.toLowerCase();t.has(i)&&r.push(`${e.file}: duplicate view state "${n.title}"`),t.add(i)}for(let n of e.references){if(n.state===void 0)continue;t.has(n.state.toLowerCase())||r.push(`${e.file}: reference state "${n.state}" is not a view state of this Screen`)}}let Fe=(e,t,n)=>{let i=e;for(let[e,a]of t.entries()){let t=`${n}: related segment ${e+1}`;if(!v.has(a.entity)){r.push(`${t}: references missing entity "${a.entity}"`);return}if(a.entity===i){r.push(`${t}: "${a.verb}" walks "${i}" to itself, and a self-relation has no direction to follow`);return}let o=(b.get(i)?.relations??[]).filter(e=>e.verb===a.verb&&e.entity===a.entity),s=(b.get(a.entity)?.relations??[]).filter(e=>e.verb===a.verb&&e.entity===i),c=o.length+s.length;if(c===0){r.push(`${t}: no relation "${a.verb}" joins "${i}" and "${a.entity}" in either direction`);return}if(c>1){r.push(`${t}: "${a.verb}" joins "${i}" and "${a.entity}" more than once; give one of them another verb`);return}i=a.entity}return i};for(let e of t.businessRules){f(e.file,e.doc.title,e.doc.lead),p(e.file,e.doc,[`Intent`,`Rationale`]),e.appliesTo.length||r.push(`${e.file}: needs at least one appliesTo target`);let n=e.appliesTo.filter(e=>e.type!==`context`&&e.type!==`entity`),a=e.appliesTo.filter(e=>e.type===`entity`),s=n.some(e=>`contexts`in e&&Array.isArray(e.contexts)&&e.contexts.length>0),c=e.appliesTo.some(e=>e.type===`context`);if(n.length===1&&!s&&!c&&!a.length){let t=n[0],r=t&&`id`in t?t.id:`that resource`;o.push(`${e.file}: governs only "${r}"; a constraint true of one behavior belongs to it as a condition Step or Outcome, not a Business Rule`)}let l=new Set,u=new Set,d=new Set,m=[],h=[],g=[];for(let[n,a]of e.appliesTo.entries()){let o=`${e.file}: appliesTo item ${n+1}`;if(a.type===`context`){let e=k(o,a.context),t=`context\0${e}`;l.has(t)&&r.push(`${o}: duplicate Context target place "${e}"`);let n=g.find(t=>t!==e&&(i(t,e)||i(e,t)));n&&r.push(`${o}: Context target place "${e}" is redundant with "${n}"`),l.add(t),g.push(e);continue}if(a.type===`entity`){let e=b.get(a.id);a.id?e||r.push(`${o}: references missing entity "${a.id}"`):r.push(`${o}: needs a non-empty id`);let n=`entity\0${a.id}\0${a.effect??``}\0${a.from??``}\0${a.to??``}\0${a.facts.join(`,`)}`;if(l.has(n)&&r.push(`${o}: duplicate target "entity:${a.id}"`),l.add(n),e){for(let[t,n]of[[`from`,a.from],[`to`,a.to]])n!==void 0&&!e.states.some(e=>e.title===n)&&r.push(`${o}: "${t}: ${n}" is not a state of entity "${a.id}"`);for(let t of a.facts)e.informationKept.some(e=>e.name===t)||r.push(`${o}: "${t}" is not a fact of entity "${a.id}"`)}let s=t.screens.filter(e=>e.entities.includes(a.id)).map(e=>e.id),c=[];for(let[e,t]of a.contexts.entries()){let n=`${o}: Context ${e+1}`,l=k(n,t);c.includes(l)&&r.push(`${n}: duplicate Context place "${l}"`);let u=c.find(e=>e!==l&&(i(e,l)||i(l,e)));u&&r.push(`${n}: Context place "${l}" is redundant with "${u}"`),c.push(l),re.has(l)&&!s.some(e=>e===l||i(l,e))&&r.push(`${n}: Context place "${l}" presents entity "${a.id}" nowhere`)}continue}if(![`capability`,`capability-scenario`,`journey`,`journey-scenario`].includes(a.type)){r.push(`${o}: type "${a.type}" must be capability|capability-scenario|journey|journey-scenario|context|entity`);continue}a.id||r.push(`${o}: needs a non-empty id`);let s=`${a.type}\0${a.id}`;l.has(s)&&r.push(`${o}: duplicate target "${a.type}:${a.id}"`),l.add(s);let c=new Set;if(a.type===`capability`){D.has(a.id)||r.push(`${o}: references missing capability "${a.id}"`),u.add(a.id),c=new Set(be.get(a.id)||[]);for(let e of t.screens)c.has(e.containerId)&&e.capabilities.includes(a.id)&&c.add(e.id)}else if(a.type===`capability-scenario`)O.has(a.id)||r.push(`${o}: references missing Capability Scenario "${a.id}"`),m.push(a.id),c=ke.get(a.id)||c;else if(a.type===`journey`){ie.has(a.id)||r.push(`${o}: references missing journey "${a.id}"`),d.add(a.id);for(let e of t.journeyScenarios.filter(e=>e.journey===a.id&&e.result===`achieved`))for(let t of Ne.get(e.id)||[])for(let e of t.contextPlaces)c.add(e)}else{ae.has(a.id)||r.push(`${o}: references missing Journey Scenario "${a.id}"`),h.push(a.id);for(let e of Ne.get(a.id)||[])for(let t of e.contextPlaces)c.add(t)}let f=[];for(let[e,t]of a.contexts.entries()){let n=`${o}: Context ${e+1}`,s=k(n,t);f.includes(s)&&r.push(`${n}: duplicate Context place "${s}"`);let l=f.find(e=>e!==s&&(i(e,s)||i(s,e)));l&&r.push(`${n}: Context place "${s}" is redundant with "${l}"`),f.push(s);let u=I.get(s),d=c.has(s)||[...c].some(e=>i(s,e))||u!==void 0&&Ce(c,u.containerId);d||r.push(`${n}: Context place "${s}" is outside target "${a.type}:${a.id}"`)}}for(let t of m){let n=Oe.get(t)?.capability;n&&u.has(n)&&r.push(`${e.file}: target "capability-scenario:${t}" is redundant with capability target "${n}"`)}for(let t of h){let n=Me.get(t)?.journey;n&&d.has(n)&&r.push(`${e.file}: target "journey-scenario:${t}" is redundant with journey target "${n}"`)}if(e.permits!==void 0){e.appliesTo.some(e=>e.type!==`entity`)&&r.push(`${e.file}: "permits" needs Entity targets only; an operation is an Entity effect, and who uses a surface is its Interface's "actors"`);let t=a.length===1?a[0]:void 0,n=t?b.get(t.id):void 0;for(let[i,o]of e.permits.entries()){let s=`${e.file}: grant ${i+1}`;for(let e of o.actors)C(s,e);if(o.configuredBy!==void 0&&!v.has(o.configuredBy)&&r.push(`${s}: "configuredBy" references missing entity "${o.configuredBy}"`),o.self)for(let e of a)v.has(e.id)&&!x.has(e.id)&&r.push(`${s}: "self" needs entity "${e.id}" to act`);if(o.related.length)if(!t)r.push(`${s}: "related" needs exactly one Entity target to start from`);else{let e=Fe(t.id,o.related,s);e!==void 0&&!x.has(e)&&r.push(`${s}: "related" ends on "${e}", which does not act`),e!==void 0&&o.actors.length&&!o.actors.includes(e)&&r.push(`${s}: "actors" excludes "${e}", where "related" ends; the grant can never be satisfied`)}for(let[e,i]of o.when.entries()){let a=`${s}: condition ${e+1}`;if(i.state!==void 0){if(!t){r.push(`${a}: a "state" condition needs exactly one Entity target`);continue}t.effect===`creates`&&r.push(`${a}: a "state" condition on a "creates" target; there is no instance yet`),n&&!n.states.some(e=>e.title===i.state)&&r.push(`${a}: "${i.state}" is not a state of entity "${t.id}"`);continue}let o=n,c=t?.id;if(i.entity!==void 0)o=b.get(i.entity),c=i.entity,o||r.push(`${a}: references missing entity "${i.entity}"`);else if(!t){r.push(`${a}: a "fact" without "entity" needs exactly one Entity target to read it on`);continue}o&&i.fact!==void 0&&!o.informationKept.some(e=>e.name===i.fact)&&r.push(`${a}: "${i.fact}" is not a fact of entity "${c}"`),typeof i.value==`object`&&i.value!==null&&!v.has(i.value.configuredBy)&&r.push(`${a}: "configuredBy" references missing entity "${i.value.configuredBy}"`)}}}}let Ie=t.businessRules.filter(e=>e.permits!==void 0&&e.appliesTo.length>0&&e.appliesTo.every(e=>e.type===`entity`)),Le=e=>({entityId:e.id,effect:e.effect??null,from:e.from??null,to:e.to??null,facts:e.facts,contextPlaces:e.contexts.map(e=>e.place)}),Re=e=>({actorIds:e.actors,relatedActorId:e.related.at(-1)?.entity??null,self:e.self===!0,unattended:e.unattended===!0,stateConditions:e.when.flatMap(e=>e.state===void 0?[]:[e.state])}),ze=new Map(j.map(e=>[e.file,[...new Set(e.steps.flatMap(e=>e.contexts.map(e=>e.place)))]])),Be=(e,t,n,r,i)=>({label:e,actorId:t.actor??null,unattended:r,entityId:n.entity,alias:n.as??null,effect:n.effect??`changes`,from:n.from??null,to:n.to??null,contextPlaces:u(t.contexts.map(e=>e.place),ze.get(i)??[])}),Ve=Ie.map(e=>({id:e.id,targets:e.appliesTo.filter(e=>e.type===`entity`).map(Le),grants:(e.permits??[]).map(Re)})),R=(e,t,n,r,i=!1)=>m(Le(e),Be(``,n,t,!1,r),i),He=new Map;for(let e of Ie){let t=e.appliesTo.map(e=>e.type===`entity`?[e.id,e.effect??``,e.from??``,e.to??``,e.facts.join(`,`),e.contexts.map(e=>e.place).join(`,`)].join(`|`):``).sort().join(`
9
- `),n=He.get(t);n?o.push(`${e.file}: selects exactly what ${n} selects; grants meant as alternatives sit in one Rule, and apart they AND`):He.set(t,e.file)}for(let e of Ie)for(let[t,n]of e.appliesTo.entries()){if(n.type!==`entity`)continue;let r=`${e.file}: appliesTo item ${t+1}`,i=[],a=[];for(let e of j)for(let t of e.steps)for(let r of t.entities)R(n,r,t,e.file)&&i.push(r),R(n,r,t,e.file,!0)&&a.push(r);n.from!==void 0&&a.length&&a.every(e=>e.from===n.from)&&o.push(`${r}: "from: ${n.from}" is redundant; every Step it could select already leaves from it`);for(let[t,n]of(e.permits??[]).entries())for(let r of n.when){if(r.state===void 0)continue;i.length&&i.every(e=>e.from===r.state)&&o.push(`${e.file}: grant ${t+1}: "state: ${r.state}" is redundant; every selected Step already leaves from it`)}}r.push(...S({rules:Ve,operations:j.flatMap(e=>{let t=e.steps[0]?.unattended===!0;return e.steps.flatMap((n,r)=>n.entities.map(i=>Be(`${e.file}: step ${r+1}`,n,i,t,e.file)))}),screens:t.screens.map(e=>({label:e.file,id:e.id,containerId:e.containerId,entityIds:e.entities,actorIds:[...F(e.containerId)??[]]}))})),t.interfaces.length===0&&r.push(`interfaces/: the model needs at least one interface`),t.coverage.status===`complete`&&t.capabilities.length===0&&r.push(`capabilities/: a complete model needs at least one capability`);let Ue=mt(t);for(let e of Ue){let n=new Set(e.assets),i=new Set(t.screens.find(t=>t.file===e.file)?.states.map(e=>e.title.toLowerCase())??[]),a=t.screens.some(t=>t.file===e.file);for(let t of e.assetMeta){if(n.has(t.file)||r.push(`${e.file}: asset "${t.file}" is not a file in this resource's expanded folder`),t.state===void 0)continue;a?i.has(t.state.toLowerCase())||r.push(`${e.file}: asset state "${t.state}" is not a view state of this Screen`):r.push(`${e.file}: asset "state" is only valid on a Screen`)}}let We=[{file:`product.md`,references:t.product.references},...Ue],Ge=new Set(t.screens.map(e=>e.file));for(let e of We){let t=new Set;for(let n of e.references){t.has(n.target)&&r.push(`${e.file}: duplicate reference target "${n.target}"`),t.add(n.target),n.state!==void 0&&!Ge.has(e.file)&&r.push(`${e.file}: reference "state" is only valid on a Screen`);let i=at(n);if(!i||d.has(i))continue;n.kind===`code`?r.push(`${e.file}: code reference path "${i}" is not a tracked file`):o.push(`${e.file}: reference target "${n.target}" does not exist in the repository`)}}return{ok:r.length===0,errors:r,warnings:o,counts:{interfaces:t.interfaces.length,experiences:t.experiences.length,screens:t.screens.length,domains:t.domains.length,entities:t.entities.length,capabilities:t.capabilities.length,capabilityScenarios:t.capabilityScenarios.length,journeys:t.journeys.length,journeyScenarios:t.journeyScenarios.length,businessRules:t.businessRules.length}}}function qt(e,t){let n,r;try{({modelRoot:n,gitRoot:r}=Nt(e))}catch(e){let n=e.message;return t?console.log(JSON.stringify({ok:!1,errors:[n],warnings:[],counts:{}},null,2)):console.error(`error: ${n}`),1}let i=Mt(n),a=Kt(i,r?Je(r):[]);if(t)console.log(JSON.stringify(a,null,2));else{for(let e of a.errors)console.error(`error: ${e}`);for(let e of a.warnings)console.warn(`warning: ${e}`);console.log(a.ok?`Product Model structure is sound.`:`Lint failed with ${a.errors.length} error(s).`)}return a.ok?0:1}const W=e=>[...e].sort((e,t)=>e.id.localeCompare(t.id)),G=e=>[...e].sort(),Jt=e=>[...e].sort((e,t)=>e.place.localeCompare(t.place)).map(e=>({placeId:e.place})),Yt=/\.(png|jpe?g|gif|webp|avif|svg)$/i;function Xt(e,t){let n=new Map(e.assetMeta.map(e=>[e.file,e]));return e.assets.map(r=>{let i=n.get(r);return{kind:Yt.test(r)?`visual`:`doc`,role:r.startsWith(`implementation/`)?`implementation`:`intent`,target:oe(t,O(e.directory,r)).split(A).join(`/`),...i?.title?{title:i.title}:{},...i?.state?{state:i.state}:{}}})}function K(e,t,n){return{intent:y(e.doc,`Intent`)||``,supportingSections:b(e.doc,[`Intent`,...t]),references:[...Xt(e,n),...e.references].map(e=>({kind:e.kind,role:e.role,target:e.target,...e.title?{title:e.title}:{},...e.state?{state:e.state}:{}}))}}function Zt(e,r,i=e.root){let a=new Map(e.capabilities.map(e=>[e.id,e])),o=new Map(e.journeys.map(t=>[t.id,e.journeyScenarios.filter(e=>e.journey===t.id)])),s=e=>G([...new Set(e.steps.flatMap(e=>e.actor?[e.actor]:[]))]),c=e=>e.routes.map(e=>({id:e.id,name:e.name})),l=(e,t)=>e.steps.map(n=>({text:n.text,kind:n.kind,actorId:n.actor??null,capabilityId:t??n.capability??null,entities:n.entities.map(e=>({entityId:e.entity,as:e.as??null,effect:e.effect??`changes`,from:e.from??null,to:e.to??null})),unattended:n.unattended===!0,contexts:e.routes.flatMap(e=>{let t=n.contexts.find(t=>t.routeId===e.id);return t?[{routeId:e.id,placeId:t.place}]:[]})})),u=(t,n)=>G((n===`capability`?e.capabilityScenarios:e.journeyScenarios).filter(e=>e.steps.some(e=>e.contexts.some(e=>e.place===t))).map(e=>e.id)),d={schemaVersion:n,id:e.product.id,title:e.product.doc.title,summary:e.product.summary||e.product.doc.lead,description:e.product.doc.lead,category:e.product.category??null,authors:e.product.authors,license:e.product.license??null,intent:y(e.product.doc,`Intent`)||``,supportingSections:b(e.product.doc,[`Intent`]),references:e.product.references.map(e=>({kind:e.kind,role:e.role,target:e.target,...e.title?{title:e.title}:{},...e.state?{state:e.state}:{}})),referenceProfile:`workspace`,tags:G(e.product.tags),generatedAt:r,generator:{name:`businesslens-cli`,version:zt()},counts:{interfaces:e.interfaces.length,experiences:e.experiences.length,screens:e.screens.length,domains:e.domains.length,entities:e.entities.length,capabilities:e.capabilities.length,capabilityScenarios:e.capabilityScenarios.length,journeys:e.journeys.length,journeyScenarios:e.journeyScenarios.length,businessRules:e.businessRules.length},limitations:e.product.limitations,model:{taxonomies:{scenarioKinds:W(e.scenarioKinds).map(e=>({id:e.id,name:e.name,description:e.description,...e.colorSlot===void 0?{}:{colorSlot:e.colorSlot}}))},interfaces:W(e.interfaces).map(e=>({id:e.id,title:e.doc.title,description:e.doc.lead,type:e.type,actorIds:G(e.actors),entryPoints:e.entryPoints,capabilityBoundary:e.capabilityBoundary,...K(e,[`Capability boundary`],i)})),experiences:W(e.experiences).map(e=>({id:e.id,title:e.doc.title,description:e.doc.lead,actorIds:G(e.actors),interfaceIds:[e.interface],accessMode:e.access,entryPoints:e.entryPoints,capabilityBoundary:e.capabilityBoundary,...K(e,[`Capability boundary`],i)})),screens:W(e.screens).map(e=>({id:e.id,title:e.doc.title,description:e.doc.lead,capabilityIds:G(e.capabilities),entityIds:G(e.entities),capabilityScenarioIds:u(e.id,`capability`),journeyScenarioIds:u(e.id,`journey`),entryPoints:e.entryPoints,information:e.information,actions:e.actions,states:e.states,capabilityBoundary:e.capabilityBoundary,...K(e,[`Information presented`,`Available actions`,`View states`,`Capability boundary`],i)})),domains:W(e.domains).map(e=>({id:e.id,name:e.doc.title,description:e.doc.lead,boundary:e.boundary,...e.colorSlot===void 0?{}:{colorSlot:e.colorSlot},...K(e,[`Boundary`],i)})),entities:W(e.entities).map(e=>({id:e.id,title:e.doc.title,description:e.doc.lead,...e.domain?{domainId:e.domain}:{},kind:e.kind??null,acts:e.acts??null,informationKept:e.informationKept.map(e=>({name:e.name,description:e.description})),relations:e.relations.map(e=>({entityId:e.entity,verb:e.verb,cardinality:e.cardinality})),states:e.states.map(e=>({name:e.title,content:e.description})),...K(e,[`Information kept`,`States`],i)})),capabilities:W(e.capabilities).map(e=>({id:e.id,title:e.doc.title,description:e.doc.lead,...e.domain?{domainId:e.domain}:{},availability:Jt(e.availability),...K(e,[],i)})),capabilityScenarios:W(e.capabilityScenarios).map(e=>({id:e.id,capabilityId:e.capability,title:e.doc.title,kindId:e.kind,actorIds:s(e),routes:c(e),trigger:e.trigger,steps:l(e,e.capability),decisionPoints:e.decisionPoints,outcome:e.outcome,edgeCases:e.edgeCases,...K(e,[`Trigger`,`Steps`,`Decision points`,`Outcome`,`Edge cases`],i)})),journeys:W(e.journeys).map(e=>{let t=o.get(e.id)||[],n=new Set(t.filter(e=>e.result===`achieved`).flatMap(e=>e.steps.flatMap(e=>e.capability?[e.capability]:[]))),r=new Set(t.filter(e=>e.result===`not-achieved`).flatMap(e=>e.steps.flatMap(e=>e.capability?[e.capability]:[]))),s=[...r].filter(e=>!n.has(e)),c=[...n].map(e=>a.get(e)?.domain).filter(e=>!!e);return{id:e.id,title:e.doc.title,goal:e.goal,successCriterion:e.successCriterion,actorIds:G(e.actors),capabilityIds:G([...n]),failureOnlyCapabilityIds:G(s),domainIds:G([...new Set(c)]),...K(e,[`Goal`,`Success criterion`],i)}}),journeyScenarios:W(e.journeyScenarios).map(e=>({id:e.id,journeyId:e.journey,title:e.doc.title,kindId:e.kind,actorIds:s(e),result:e.result,routes:c(e),steps:l(e),trigger:e.trigger,decisionPoints:e.decisionPoints,outcome:e.outcome,edgeCases:e.edgeCases,...K(e,[`Trigger`,`Decision points`,`Outcome`,`Edge cases`],i)})),businessRules:W(e.businessRules).map(e=>({id:e.id,title:e.doc.title,statement:e.doc.lead,rationale:e.rationale,appliesTo:e.appliesTo.map(e=>e.type===`context`?{type:`context`,context:{placeId:e.context.place}}:e.type===`entity`?{type:`entity`,entityId:e.id,effect:e.effect??null,from:e.from??null,to:e.to??null,facts:e.facts,contexts:e.contexts.map(e=>({placeId:e.place}))}:{type:e.type,id:e.id,contexts:e.contexts.map(e=>({placeId:e.place}))}),permits:e.permits===void 0?null:e.permits.map(e=>({actorIds:G(e.actors),related:e.related.map(e=>({verb:e.verb,entityId:e.entity})),self:e.self===!0,when:e.when.map(e=>({entityId:e.entity??null,fact:e.fact??null,state:e.state??null,operator:e.operator??null,value:e.value===void 0?null:typeof e.value==`object`?{configuredByEntityId:e.value.configuredBy}:e.value})),unattended:e.unattended===!0,configuredByEntityId:e.configuredBy??null})),...K(e,[`Rationale`],i)}))},coverage:{status:e.coverage.status,method:e.coverage.method,sourceAreas:e.coverage.sourceAreas,unmapped:e.coverage.unmapped,limitations:e.coverage.limitations,rationale:e.coverage.rationale}},f=t.parse(d),p=C(f);if(p.length)throw Error(`Report validation failed:\n- ${p.join(`
10
- - `)}`);return f}function Qt({modelRoot:e,gitRoot:t}){let n=Mt(e),r=t?Je(t):[],i=Kt(n,r);if(!i.ok)throw Error(`Lint failed:\n${i.errors.map(e=>`- ${e}`).join(`
11
- `)}`);let a=new Date().toISOString().slice(0,10);return Zt(n,a,t??e)}function $t(e){let t=Nt(e),r=h(Qt(t)),i=Lt(t.modelRoot,[`.businesslens`,`build`,`report.json`],`${JSON.stringify(r,null,2)}\n`);return Lt(t.modelRoot,[`.businesslens`,`cache`,`build.json`],`${JSON.stringify({builtAt:new Date().toISOString(),schemaVersion:n},null,2)}\n`),{report:r,outputFile:i}}function en(e){try{let{outputFile:t}=$t(e);return console.log(`Exported Blueprint to ${t}.`),0}catch(e){return console.error(e.message),1}}const tn=`# Product Model
6
+ `,i+1)+1);try{let e=Te(a);return e==null?{data:{},body:o}:typeof e!=`object`||Array.isArray(e)?(t.push(`${n}: frontmatter must be a YAML mapping`),{data:{},body:o}):{data:e,body:o}}catch(e){return t.push(`${n}: frontmatter YAML failed to parse (${e.message})`),{data:{},body:o}}}function B(e,t,n,r){let i=e[t];if(i!=null){if(typeof i!=`string`){n.push(`${r}: "${t}" must be a string`);return}return i}}function Ye(e,t,n,r){let i=e[t];return i==null?[]:!Array.isArray(i)||i.some(e=>typeof e!=`string`)?(n.push(`${r}: "${t}" must be a list of strings`),[]):i}function V(e,t,n,r){let i=Ye(e,t,n,r),a=new Set;for(let e of i)a.has(e)&&n.push(`${r}: "${t}" contains duplicate "${e}"`),a.add(e);return i}function Xe(e,t,n){let r=e.entryPoints;if(r==null)return[];if(!Array.isArray(r))return t.push(`${n}: "entryPoints" must be a list`),[];let i=[];for(let e of r){if(typeof e!=`object`||!e||Array.isArray(e)){t.push(`${n}: each entry point must be a single "type: path" map`);continue}let r=Object.entries(e),a=r[0];if(r.length!==1||!a||typeof a[1]!=`string`){t.push(`${n}: each entry point must be a single "type: path" map`);continue}i.push({type:a[0],path:a[1]})}return i}function Ze(e,t,n){return $e(e,`availability`,t,n)}function Qe(e,t,n){if(typeof e!=`object`||!e||Array.isArray(e)){t.push(`${n}: context must be a mapping with "place"`);return}let r=e;H(r,[`place`],t,n);let i=r.place;if(typeof i!=`string`||!l(i)){t.push(`${n}: "place" must be a qualified Interface, Experience, or Screen id like "customer-web::storefront"`);return}return{place:i}}function $e(e,t,n,r){let i=e[t];if(i==null)return[];if(!Array.isArray(i))return n.push(`${r}: "${t}" must be a list of Context objects`),[];let a=[],o=new Set;for(let[e,s]of i.entries()){let i=Qe(s,n,`${r}: ${t} context ${e+1}`);if(!i)continue;if(o.has(i.place)){n.push(`${r}: duplicate ${t} Context place "${i.place}"`);continue}o.add(i.place),a.push(i)}return a}function et(e,t,n,r){let i=e[t];if(i!=null)return Qe(i,n,`${r}: "${t}"`)}function tt(e,t,n){let r=e.assets;if(r==null)return[];if(!Array.isArray(r))return t.push(`${n}: "assets" must be a list`),[];let i=[],a=new Set;for(let e of r){if(typeof e!=`object`||!e||Array.isArray(e)){t.push(`${n}: each asset needs a "file"`);continue}let r=e,o=Object.keys(r).filter(e=>![`file`,`title`,`state`].includes(e));if(o.length){t.push(`${n}: asset has unknown key "${o[0]}"`);continue}let s=r.file;if(typeof s!=`string`||!s.trim()||s.startsWith(`/`)||s.includes(`..`)){t.push(`${n}: asset "file" must be a path inside the resource's expanded folder`);continue}if(a.has(s)){t.push(`${n}: duplicate asset "${s}"`);continue}a.add(s);for(let e of[`title`,`state`]){let i=r[e];i!==void 0&&(typeof i!=`string`||!i.trim()||/[\r\n]/.test(i))&&t.push(`${n}: asset "${e}" must be a non-empty single-line string when present`)}i.push({file:s,title:typeof r.title==`string`?r.title:void 0,state:typeof r.state==`string`?r.state:void 0})}return i}const nt=new Set([`code`,`prd`,`spec`,`proposal`,`doc`,`adr`,`visual`,`research`]),rt=new Set([`intent`,`implementation`,`context`]);function it(e){if(e.kind===`code`)return d(e.target,[],`reference`)?.path;if(!/^https?:\/\//i.test(e.target))return e.target.split(/[?#]/,1)[0]?.replace(/^\.\//,``)||void 0}function at(e,t,n,r){if(!e.trim())return n.push(`${r}: reference "target" must not be empty`),!1;if(/[\r\n]/.test(e))return n.push(`${r}: reference "target" must be a single line`),!1;if(t===`code`)return!!d(e,n,r);if(/^https?:\/\//i.test(e))try{let t=new URL(e);if(!t.hostname)throw Error(`missing hostname`);return!0}catch{return n.push(`${r}: reference target "${e}" is not a valid HTTP(S) URL`),!1}if(/^[a-z][a-z0-9+.-]*:/i.test(e))return n.push(`${r}: reference target "${e}" must use HTTP(S) or a repository-relative path`),!1;let i=e.split(/[?#]/,1)[0]||``;return i.trim()?/^(?:[/\\]|~[/\\]|[a-z]:[/\\])/i.test(e)||e.includes(`\\`)||i.split(`/`).includes(`..`)?(n.push(`${r}: reference target "${e}" must be repository-relative, not an absolute filesystem path`),!1):!0:(n.push(`${r}: reference target "${e}" must name a repository-relative path`),!1)}function ot(e,t,n){let r=e.references;if(r==null)return[];if(!Array.isArray(r))return t.push(`${n}: "references" must be a list`),[];let i=[];for(let e of r){if(typeof e!=`object`||!e||Array.isArray(e)){t.push(`${n}: each reference needs "kind", "role", and "target"`);continue}let r=e,a=Object.keys(r).filter(e=>![`kind`,`role`,`target`,`title`,`state`].includes(e));if(a.length){t.push(`${n}: reference has unknown key "${a[0]}"`);continue}if(typeof r.kind!=`string`||typeof r.role!=`string`||typeof r.target!=`string`){t.push(`${n}: each reference needs string "kind", "role", and "target"`);continue}if(!nt.has(r.kind)){t.push(`${n}: reference kind "${r.kind}" must be one of code|prd|spec|proposal|doc|adr|visual|research`);continue}if(!rt.has(r.role)){t.push(`${n}: reference role "${r.role}" must be one of intent|implementation|context`);continue}if(!at(r.target,r.kind,t,n))continue;if(r.title!==void 0&&(typeof r.title!=`string`||!r.title.trim()||/[\r\n]/.test(r.title))){t.push(`${n}: reference "title" must be a non-empty single-line string when present`);continue}if(r.state!==void 0&&(typeof r.state!=`string`||!r.state.trim()||/[\r\n]/.test(r.state))){t.push(`${n}: reference "state" must be a non-empty single-line string when present`);continue}i.push({kind:r.kind,role:r.role,target:r.target,title:typeof r.title==`string`?r.title:void 0,state:typeof r.state==`string`?r.state:void 0})}return i}function H(e,t,n,r){for(let i of Object.keys(e))t.includes(i)||n.push(`${r}: unknown frontmatter key "${i}"`)}function st(e){let t=D(e,`.businesslens`,`product`,ee);if(!j(t))return;let n=M(t);if(n.isSymbolicLink()||!n.isFile())throw Error(`logo.svg must be a regular file, not a symbolic link`);if(n.size>w)throw Error(`logo.svg must be at most ${w/1024} KiB`);let r=P(t),i=T(r);if(i.length)throw Error(i.join(`; `));return{bytes:r,file:t}}const ct=[`person`,`system`],lt=[`external`,`internal`],ut=[`creates`,`changes`,`removes`,`reads`],dt=[`over`,`under`,`at-least`,`at-most`,`is`,`is-not`,`present`,`absent`];function ft(e){return{interfaces:e.interfaces,experiences:e.experiences,screens:e.screens,domains:e.domains,entities:e.entities,capabilities:e.capabilities,capabilityScenarios:e.capabilityScenarios,businessRules:e.businessRules,journeys:e.journeys,journeyScenarios:e.journeyScenarios}}function pt(e){return Object.values(ft(e)).flat()}const mt=new Set([`one-to-one`,`one-to-many`,`many-to-many`]),ht=`.businesslens`,gt=8;function U(e,t,n,r,i=[]){if(!j(e))return[];let a=[],o=new Map,s=new Map;for(let i of ye(e,{withFileTypes:!0})){if(i.name===`.DS_Store`)continue;if(i.isFile()){if(!i.name.endsWith(`.md`)){n.issues.push(`${r}/${i.name}: expected <id>.md or <id>/${t}.md`);continue}o.set(i.name.slice(0,-3),D(e,i.name));continue}if(i.isDirectory()){s.set(i.name,D(e,i.name));continue}n.issues.push(`${r}/${i.name}: expected a regular resource file or directory`)}let c=new Set([...o.keys(),...s.keys()]);for(let l of[...c].sort((e,t)=>e.localeCompare(t))){let c=o.get(l),u=s.get(l)??D(e,l),d=D(u,`${t}.md`),f=j(d);if(c&&s.has(l)){n.issues.push(f?`${r}/${l}: both ${l}.md and ${l}/${t}.md exist; keep exactly one resource shape`:`${r}/${l}.md cannot also have ${r}/${l}/; move it to ${r}/${l}/${t}.md before adding children or assets`),a.push({id:l,directory:u,file:c,expanded:!1});continue}if(c){a.push({id:l,directory:u,file:c,expanded:!1});continue}if(!f){n.issues.push(`${r}/${l}/ is missing ${t}.md`);continue}let p=!1;for(let e of ye(u,{withFileTypes:!0})){if(e.name===`.DS_Store`||e.name===`${t}.md`)continue;if(e.isFile()){p=!0;continue}if(e.isDirectory()&&(e.name===`implementation`||i.includes(e.name))){let t=ye(D(u,e.name),{withFileTypes:!0}).filter(e=>e.name!==`.DS_Store`);t.length&&(p=!0);continue}if(e.isDirectory()){n.issues.push(`${r}/${l}/${e.name}/ is not a recognized child directory`);continue}n.issues.push(`${r}/${l}/${e.name}: assets must be regular files`)}p||n.notices.push(`${r}/${l}/ has no assets or child resources; use ${r}/${l}.md`),a.push({id:l,directory:u,file:d,expanded:!0})}return a}function _t(e,t){let n=[];if(!j(e))return n;for(let r of ye(e,{withFileTypes:!0})){if(r.name===`.DS_Store`||r.name===t)continue;if(r.isFile())n.push(r.name);else if(r.name===`implementation`)for(let t of ye(D(e,`implementation`),{withFileTypes:!0}))t.isFile()&&t.name!==`.DS_Store`&&n.push(`implementation/${t.name}`)}return n.sort()}function W(e,t,n){let{file:r,directory:i,expanded:a}=e,o=P(r,`utf8`),{data:s,body:c}=Je(o,n,r);H(s,[...t,`references`,`assets`],n,r);let l=f(c);return{data:s,doc:l,references:ot(s,n,r),directory:i,assets:a?_t(i,re(r)):[],assetMeta:tt(s,n,r)}}function vt(e,t,n){return{trigger:y(e,`Trigger`)||``,outcome:y(e,`Outcome`)||``,edgeCases:r(y(e,`Edge cases`)||``),decisionPoints:o(y(e,`Decision points`)||``,t,n)}}function yt(e,t,n){let r=e.routes;if(r==null)return[];if(typeof r!=`object`||Array.isArray(r))return t.push(`${n}: "routes" must be a mapping from route id to route name`),[];let i=[];for(let[e,a]of Object.entries(r)){let r=B({name:a},`name`,t,`${n}: route "${e}"`);i.push({id:e,name:r||``})}return i}function bt(e,t,n,r){let i=e.steps;if(i==null)return[];if(!Array.isArray(i))return t.push(`${n}: "steps" must be a list`),[];let a=[];for(let[e,o]of i.entries()){let i=`${n}: step ${e+1}`;if(typeof o!=`object`||!o||Array.isArray(o)){t.push(`${i} must be a mapping`);continue}let s=o,c=[`entity`,`state`,`changes`,`reads`];for(let e of c)s[e]!==void 0&&t.push(`${i}: "${e}" is now an entry of "entities"`);H(s,[`text`,`kind`,`actor`,`entities`,`contexts`,`unattended`,...c,...r?[`capability`]:[]],t,i),(s.entities===void 0||s.entities===null)&&t.push(`${i}: needs "entities" — what this Step does to the Product's things, or [] when it touches nothing`);let l=xt(s.entities,t,i),u=[];if(s.contexts!==void 0&&s.contexts!==null)if(typeof s.contexts!=`object`||Array.isArray(s.contexts))t.push(`${i}: "contexts" must map route ids to Context objects`);else for(let[e,n]of Object.entries(s.contexts)){let r=Qe(n,t,`${i}: route "${e}"`);r&&u.push({routeId:e,...r})}let d=s.unattended;d!==void 0&&typeof d!=`boolean`&&t.push(`${i}: "unattended" must be true or false`),a.push({text:B(s,`text`,t,i)||``,kind:B(s,`kind`,t,i)||``,actor:B(s,`actor`,t,i),entities:l,capability:r?B(s,`capability`,t,i):void 0,unattended:d===!0?!0:void 0,contexts:u})}return a}function xt(e,t,n){if(e==null)return[];if(!Array.isArray(e))return t.push(`${n}: "entities" must be a list`),[];let r=[],i=new Set;for(let[a,o]of e.entries()){let e=`${n}: entity ${a+1}`;if(typeof o!=`object`||!o||Array.isArray(o)){t.push(`${e} must be a mapping`);continue}let s=o;H(s,[`entity`,`as`,`effect`,`from`,`to`],t,e);let l=B(s,`entity`,t,e)||``;if(!l){t.push(`${e}: needs an "entity"`);continue}let u=B(s,`as`,t,e);u!==void 0&&!c(u)&&t.push(`${e}: "as" must be a lowercase kebab-case alias`);let d=`${l}\0${u??``}`;if(i.has(d)){t.push(`${e}: "${l}${u?` (${u})`:``}" already appears in this Step`);continue}i.add(d);let f=B(s,`effect`,t,e);if(f!==void 0&&!ut.includes(f)){t.push(`${e}: effect "${f}" must be ${ut.join(`|`)}`);continue}let p=f??`changes`,m=B(s,`from`,t,e),h=B(s,`to`,t,e);if(p===`reads`&&(m!==void 0||h!==void 0)){t.push(`${e}: a "reads" entry carries no "from" or "to"`);continue}if(p===`creates`&&m!==void 0){t.push(`${e}: a "creates" entry has no "from"; a creation starts nowhere`);continue}if(p===`removes`&&h!==void 0){t.push(`${e}: a "removes" entry has no "to"; nothing is left in a state after it ends`);continue}if(p===`changes`&&m===void 0!=(h===void 0)){t.push(`${e}: a "changes" entry carries both "from" and "to", or neither`);continue}r.push({entity:l,as:u,effect:f,from:m,to:h})}return r}function St(e,t,n,r=[]){if(typeof e!=`object`||!e||Array.isArray(e)){t.push(`${n}: context target must be a mapping`);return}let i=e;return H(i,[`context`,...r],t,n),et(i,`context`,t,n)}function Ct(e,t,n){let r=e.appliesTo;if(r==null)return[];if(!Array.isArray(r))return t.push(`${n}: "appliesTo" must be a list`),[];let i=[];for(let[e,a]of r.entries()){let r=`${n}: appliesTo item ${e+1}`;if(typeof a!=`object`||!a||Array.isArray(a)){t.push(`${r} must be a mapping`);continue}let o=a,s=B(o,`type`,t,r)||``;if(s===`context`){H(o,[`type`,`context`],t,r);let e=St(o,t,r,[`type`]);e&&i.push({type:`context`,context:e});continue}if(s===`entity`){H(o,[`type`,`id`,`effect`,`from`,`to`,`facts`,`contexts`],t,r);let e=B(o,`effect`,t,r);e!==void 0&&!ut.includes(e)&&t.push(`${r}: effect "${e}" must be ${ut.join(`|`)}`);let n=B(o,`from`,t,r),a=B(o,`to`,t,r);n!==void 0&&(e===`creates`||e===`reads`)&&t.push(`${r}: "from" selects nothing on a "${e}" target; a ${e===`creates`?`creation starts nowhere`:`read carries no state`}`),a!==void 0&&(e===`removes`||e===`reads`)&&t.push(`${r}: "to" selects nothing on a "${e}" target`),i.push({type:`entity`,id:B(o,`id`,t,r)||``,effect:e,from:n,to:a,facts:V(o,`facts`,t,r),contexts:wt(o,t,r)});continue}H(o,[`type`,`id`,`contexts`],t,r),i.push({type:s,id:B(o,`id`,t,r)||``,contexts:wt(o,t,r)})}return i}function wt(e,t,n){let r=e.contexts,i=[];if(r==null)return i;if(!Array.isArray(r)||r.length===0)return t.push(`${n}: "contexts" must be a non-empty list when present`),i;for(let[e,a]of r.entries()){let r=Qe(a,t,`${n}: context ${e+1}`);r&&i.push(r)}return i}function Tt(e,t,n,r){let i=e[t];if(i!=null){if(i!==!0){n.push(`${r}: "${t}" is either true or absent`);return}return!0}}function Et(e,t,n){if(e==null)return[];if(!Array.isArray(e))return t.push(`${n}: "related" must be a list of { verb, entity } segments`),[];if(!e.length)return t.push(`${n}: "related" is empty; the instance itself is "self: true"`),[];let r=[];for(let[i,a]of e.entries()){let e=`${n}: related segment ${i+1}`;if(typeof a!=`object`||!a||Array.isArray(a)){t.push(`${e} must be a { verb, entity } mapping`);continue}let o=a;H(o,[`verb`,`entity`],t,e);let s=B(o,`verb`,t,e)||``,c=B(o,`entity`,t,e)||``;s||t.push(`${e}: needs a "verb"`),c||t.push(`${e}: needs an "entity"`),s&&c&&r.push({verb:s,entity:c})}return r}function Dt(e,t,n){if(e==null)return[];if(!Array.isArray(e))return t.push(`${n}: "when" must be a list of conditions`),[];let r=[];for(let[i,a]of e.entries()){let e=`${n}: condition ${i+1}`;if(typeof a!=`object`||!a||Array.isArray(a)){t.push(`${e} must be a mapping`);continue}let o=a;H(o,[`entity`,`fact`,`state`,...dt],t,e);let s=B(o,`entity`,t,e),c=B(o,`fact`,t,e),l=B(o,`state`,t,e),u=dt.filter(e=>o[e]!==void 0);if(l!==void 0){if(s!==void 0||c!==void 0||u.length){t.push(`${e}: a "state" condition carries nothing else`);continue}r.push({state:l});continue}if(c===void 0){t.push(`${e}: needs a "fact" with one operator, or a "state"`);continue}if(u.length!==1){t.push(`${e}: needs exactly one operator of ${dt.join(`|`)}`);continue}let d=u[0],f=o[d],p;if(d===`present`||d===`absent`){if(f!==!0){t.push(`${e}: "${d}" takes true`);continue}p=!0}else if(typeof f==`string`||typeof f==`number`||typeof f==`boolean`)p=f;else if(typeof f==`object`&&f&&!Array.isArray(f)){let n=f;H(n,[`configuredBy`],t,`${e}: "${d}"`);let r=B(n,`configuredBy`,t,`${e}: "${d}"`);if(!r){t.push(`${e}: "${d}" needs a scalar or { configuredBy: <entity-id> }`);continue}p={configuredBy:r}}else{t.push(`${e}: "${d}" needs a scalar or { configuredBy: <entity-id> }`);continue}r.push({entity:s,fact:c,operator:d,value:p})}return r}function Ot(e,t,n){let r=e.permits;if(r==null)return;if(!Array.isArray(r)){t.push(`${n}: "permits" must be a list of grants, or [] to forbid the operation to everyone`);return}let i=[];for(let[e,a]of r.entries()){let r=`${n}: grant ${e+1}`;if(typeof a!=`object`||!a||Array.isArray(a)){t.push(`${r} must be a mapping`);continue}let o=a;H(o,[`actors`,`related`,`self`,`when`,`unattended`,`configuredBy`],t,r);let s={actors:V(o,`actors`,t,r),related:Et(o.related,t,r),self:Tt(o,`self`,t,r),when:Dt(o.when,t,r),unattended:Tt(o,`unattended`,t,r),configuredBy:B(o,`configuredBy`,t,r)};!s.actors.length&&!s.related.length&&!s.self&&!s.unattended&&!s.configuredBy&&t.push(`${r}: names nobody; a grant needs at least one of actors, related, self, unattended, configuredBy`),i.push(s)}return i}const kt=/^\*\*(.+?)\*\*\s+—\s+(\S.*)$/;function At(e,t,n){let i=[];if(e===void 0)return i;let a=new Set;for(let o of r(e)){let e=kt.exec(o);if(!e){t.push(`${n}: fact "${o}" must read "**Name** — prose"`);continue}let r=e[1].trim();if(a.has(r)){t.push(`${n}: duplicate fact "${r}"`);continue}a.add(r),i.push({name:r,description:e[2].trim()})}return i}function jt(e){let t=D(e,ht),n=[],i=[],o={issues:n,notices:i};if(!j(t))n.push(`${ht}/ does not exist — use \`businesslens-map\` for established code or \`businesslens-ideate\` for a new product`);else{j(D(t,`README.md`))||n.push(`README.md is missing`);let e=D(t,`.gitignore`);if(!j(e))n.push(`.gitignore is missing`);else{let t=new Set(P(e,`utf8`).split(/\r?\n/).map(e=>e.trim()).filter(e=>e.length>0&&!e.startsWith(`#`)));for(let e of[`build/`,`cache/`])t.has(e)||n.push(`.gitignore must ignore ${e}`)}}if(j(t))try{st(e)}catch(e){n.push(`logo.svg: ${e.message}`)}let s={schema:gt,sddPaths:[]},c=D(t,`config.yaml`);if(j(c))try{let e=Te(P(c,`utf8`)),t=Object.keys(e||{}).filter(e=>e!==`schema`&&e!==`sdd`);for(let e of t)n.push(`config.yaml: unknown key "${e}"`);if(e?.sdd!==void 0&&(typeof e.sdd!=`object`||e.sdd===null||Array.isArray(e.sdd)))n.push(`config.yaml: "sdd" must be a mapping`);else if(e?.sdd)for(let t of Object.keys(e.sdd).filter(e=>e!==`paths`))n.push(`config.yaml: sdd has unknown key "${t}"`);s={schema:Number(e?.schema??0),sddPaths:Array.isArray(e?.sdd?.paths)?e.sdd.paths.map(String):[]}}catch(e){n.push(`config.yaml failed to parse (${e.message})`)}else j(t)&&n.push(`config.yaml is missing`);s.schema!==gt&&n.push(`config.yaml: schema ${s.schema} is not supported (expected ${gt})`);let l=[],u=D(t,`taxonomies.yaml`);if(j(u))try{let e=Te(P(u,`utf8`)),t=e?.scenarioKinds;Array.isArray(t)?l=t.map(e=>({id:String(e.id||``),name:String(e.name||``),description:String(e.description||``),colorSlot:typeof e.colorSlot==`number`?e.colorSlot:void 0})):n.push(`taxonomies.yaml must contain a scenarioKinds list`)}catch(e){n.push(`taxonomies.yaml failed to parse (${e.message})`)}else j(t)&&n.push(`taxonomies.yaml is missing`);let d={id:``,tags:[],authors:[],limitations:[],doc:{title:``,lead:``,sections:[]},references:[]},p=D(t,`product.md`),m=D(t,`product`),h=D(m,`product.md`),_=j(p),b=j(m),x=j(h),S=D(m,`logo.svg`);if(_&&b&&n.push(x?`product: both product.md and product/product.md exist; keep exactly one resource shape`:`product.md cannot also have product/; move it to product/product.md before adding logo.svg`),b&&!x&&!_&&n.push(`product/ is missing product.md`),x&&!j(S)&&n.push(`product/ has no logo asset; use product.md`),b)for(let e of ye(m,{withFileTypes:!0})){if(e.name===`.DS_Store`||e.name===`product.md`||e.name===`logo.svg`)continue;n.push(`product/${e.name}: the Product folder may contain only product.md and logo.svg`)}let C=_?p:x?h:void 0;if(C){let e=P(C,`utf8`),{data:t,body:r}=Je(e,n,`product.md`);H(t,[`id`,`summary`,`category`,`tags`,`authors`,`license`,`limitations`,`references`],n,`product.md`);let i=t.authors,a=[];if(i!=null)if(!Array.isArray(i))n.push(`product.md: "authors" must be a list`);else for(let[e,t]of i.entries()){if(typeof t!=`object`||!t||Array.isArray(t)){n.push(`product.md: author ${e+1} must contain "name" and optional "url"`);continue}let r=t,i=Object.keys(r).filter(e=>e!==`name`&&e!==`url`);if(i.length&&n.push(`product.md: author ${e+1} has unknown field(s): ${i.join(`, `)}`),typeof r.name!=`string`){n.push(`product.md: author ${e+1} "name" must be a string`);continue}if(r.url!==void 0&&typeof r.url!=`string`){n.push(`product.md: author ${e+1} "url" must be a string`);continue}a.push({name:r.name,...r.url?{url:r.url}:{}})}d={id:B(t,`id`,n,`product.md`)||``,summary:B(t,`summary`,n,`product.md`),category:B(t,`category`,n,`product.md`),tags:V(t,`tags`,n,`product.md`),authors:a,license:B(t,`license`,n,`product.md`),limitations:Ye(t,`limitations`,n,`product.md`),doc:f(r),references:ot(t,n,`product.md`)}}else j(t)&&n.push(`product.md is missing`);let w={status:`draft`,method:[],sourceAreas:[],unmapped:[],limitations:[],rationale:``},ee=D(t,`coverage.md`);if(j(ee)){let e=P(ee,`utf8`),{data:t,body:r}=Je(e,n,`coverage.md`),i=f(r);H(t,[`status`,`method`,`sourceAreas`,`unmapped`,`limitations`],n,`coverage.md`),a(i.lead)&&n.push(`coverage.md: rationale must not contain an H1 or H2 heading`);for(let e of i.sections)n.push(`coverage.md: "## ${e.heading}" sections are not supported; keep the rationale in the lead paragraph`);w={status:B(t,`status`,n,`coverage.md`)||`draft`,method:Ye(t,`method`,n,`coverage.md`),sourceAreas:Ye(t,`sourceAreas`,n,`coverage.md`),unmapped:Ye(t,`unmapped`,n,`coverage.md`),limitations:Ye(t,`limitations`,n,`coverage.md`),rationale:i.lead}}else j(t)&&n.push(`coverage.md is missing`);j(D(t,`actors`))&&n.push(`actors/: there is no Actor resource type; move each file to entities/ and say how it acts with "kind" and "acts"`);let T=[],te=[],ne=[],re=(e,t,i)=>{for(let a of U(D(e,`screens`),`screen`,o,`${i}/screens`)){let{data:e,doc:i,references:o,directory:s,assets:c,assetMeta:l}=W(a,[`capabilities`,`entities`,`entryPoints`],n);ne.push({entities:V(e,`entities`,n,a.file),id:g(t,a.id),file:a.file,doc:i,references:o,directory:s,assets:c,assetMeta:l,containerId:t,capabilities:V(e,`capabilities`,n,a.file),entryPoints:Xe(e,n,a.file),information:r(y(i,`Information presented`)||``),actions:r(y(i,`Available actions`)||``),states:v(y(i,`View states`)||``,n,a.file),capabilityBoundary:y(i,`Capability boundary`)||``})}};for(let e of U(D(t,`interfaces`),`interface`,o,`interfaces`,[`experiences`,`screens`])){let{data:t,doc:r,references:i,directory:a,assets:s,assetMeta:c}=W(e,[`type`,`actors`,`entryPoints`,`screens`],n);T.push({id:e.id,file:e.file,doc:r,references:i,directory:a,assets:s,assetMeta:c,type:B(t,`type`,n,e.file)||``,actors:V(t,`actors`,n,e.file),entryPoints:Xe(t,n,e.file),capabilityBoundary:y(r,`Capability boundary`)||``,screens:V(t,`screens`,n,e.file)});let l=U(D(e.directory,`experiences`),`experience`,o,`interfaces/${e.id}/experiences`,[`screens`]);for(let t of l){let r=g(e.id,t.id),i=W(t,[`actors`,`access`,`entryPoints`,`screens`],n);te.push({id:r,file:t.file,doc:i.doc,references:i.references,directory:i.directory,assets:i.assets,assetMeta:i.assetMeta,actors:V(i.data,`actors`,n,t.file),interface:e.id,access:B(i.data,`access`,n,t.file)||``,entryPoints:Xe(i.data,n,t.file),capabilityBoundary:y(i.doc,`Capability boundary`)||``,screens:V(i.data,`screens`,n,t.file)}),re(t.directory,r,`interfaces/${e.id}/experiences/${t.id}`)}j(D(e.directory,`screens`))&&re(e.directory,e.id,`interfaces/${e.id}`)}let ie=U(D(t,`domains`),`domain`,o,`domains`).map(e=>{let{id:t,file:r}=e,{data:i,doc:a,references:o,directory:s,assets:c,assetMeta:l}=W(e,[`colorSlot`],n);return{id:t,file:r,doc:a,references:o,directory:s,assets:c,assetMeta:l,colorSlot:typeof i.colorSlot==`number`?i.colorSlot:void 0,boundary:y(a,`Boundary`)||``}}),E=U(D(t,`entities`),`entity`,o,`entities`).map(e=>{let{id:t,file:r}=e,{data:i,doc:a,references:o,directory:s,assets:c,assetMeta:l}=W(e,[`domain`,`kind`,`acts`,`relations`,`transitions`],n);i.transitions!==void 0&&n.push(`${r}: "transitions" is gone; a Step's "entities" entry says which state it moves the thing from and to`);let u=At(y(a,`Information kept`),n,r),d=v(y(a,`States`)||``,n,r,`States`,`entity state`),f=y(a,`States`)!==void 0,p=B(i,`kind`,n,r),m=B(i,`acts`,n,r);m!==void 0&&!lt.includes(m)&&n.push(`${r}: acts "${m}" must be external|internal`),p!==void 0&&!ct.includes(p)&&n.push(`${r}: kind "${p}" must be person|system`),m!==void 0&&p===void 0&&n.push(`${r}: an Entity that acts needs "kind": person|system`),m===void 0&&p!==void 0&&n.push(`${r}: "kind" is only valid together with "acts"`),!u.length&&!f&&m===void 0&&n.push(`${r}: an Entity needs "## Information kept", "## States", or "acts"`);let h=[],g=i.relations;if(g!=null)if(!Array.isArray(g))n.push(`${r}: "relations" must be a list`);else for(let[e,t]of g.entries()){let i=`${r}: relation ${e+1}`;if(typeof t!=`object`||!t||Array.isArray(t)){n.push(`${i} must be a mapping`);continue}let a=t;H(a,[`entity`,`verb`,`cardinality`],n,i);let o=B(a,`entity`,n,i)||``,s=B(a,`verb`,n,i)||``,c=B(a,`cardinality`,n,i)||``;if(o||n.push(`${i}: needs an "entity"`),s||n.push(`${i}: needs a "verb"`),c===`many-to-one`){n.push(`${i}: "cardinality" is many-to-one; declare this relation on "${o}", where it reads one-to-many`);continue}if(!mt.has(c)){n.push(`${i}: "cardinality" must be one-to-one, one-to-many, or many-to-many`);continue}h.push({entity:o,verb:s,cardinality:c})}return{id:t,file:r,doc:a,references:o,directory:s,assets:c,assetMeta:l,domain:B(i,`domain`,n,r),informationKept:u,kind:p,acts:m,relations:h,states:d}}),ae=[],oe=[];for(let e of U(D(t,`capabilities`),`capability`,o,`capabilities`,[`scenarios`])){let{data:t,doc:r,references:i,directory:a,assets:s,assetMeta:c}=W(e,[`domain`,`entities`,`availability`],n);t.entities!==void 0&&n.push(`${e.file}: "entities" is gone from a Capability; each Step's "entities" says what it changes`),ae.push({id:e.id,file:e.file,doc:r,references:i,directory:a,assets:s,assetMeta:c,domain:B(t,`domain`,n,e.file),availability:Ze(t,n,e.file)});for(let t of U(D(e.directory,`scenarios`),`capability-scenario`,o,`capabilities/${e.id}/scenarios`)){let r=W(t,[`kind`,`routes`,`steps`],n);oe.push({id:t.id,file:t.file,doc:r.doc,references:r.references,directory:r.directory,assets:r.assets,assetMeta:r.assetMeta,kind:B(r.data,`kind`,n,t.file)||``,capability:e.id,routes:yt(r.data,n,t.file),steps:bt(r.data,n,t.file,!1),...vt(r.doc,n,t.file)})}}let se=[],O=[];for(let e of U(D(t,`journeys`),`journey`,o,`journeys`,[`scenarios`])){let{data:t,doc:r,references:i,directory:a,assets:s,assetMeta:c}=W(e,[`actors`],n);se.push({id:e.id,file:e.file,doc:r,references:i,directory:a,assets:s,assetMeta:c,actors:V(t,`actors`,n,e.file),goal:y(r,`Goal`)||``,successCriterion:y(r,`Success criterion`)||``});for(let t of U(D(e.directory,`scenarios`),`journey-scenario`,o,`journeys/${e.id}/scenarios`)){let r=W(t,[`kind`,`result`,`routes`,`steps`],n);O.push({id:t.id,file:t.file,doc:r.doc,references:r.references,directory:r.directory,assets:r.assets,assetMeta:r.assetMeta,kind:B(r.data,`kind`,n,t.file)||``,journey:e.id,result:B(r.data,`result`,n,t.file)||``,routes:yt(r.data,n,t.file),steps:bt(r.data,n,t.file,!0),...vt(r.doc,n,t.file)})}}let k=U(D(t,`business-rules`),`business-rule`,o,`business-rules`).map(e=>{let{id:t,file:r}=e,{data:i,doc:a,references:o,directory:s,assets:c,assetMeta:l}=W(e,[`appliesTo`,`permits`],n);return{id:t,file:r,doc:a,references:o,directory:s,assets:c,assetMeta:l,appliesTo:Ct(i,n,r),permits:Ot(i,n,r),rationale:y(a,`Rationale`)||``}});return{root:t,config:s,product:d,scenarioKinds:l,coverage:w,interfaces:T,experiences:te,screens:ne,domains:ie,entities:E,capabilities:ae,capabilityScenarios:oe,businessRules:k,journeys:se,journeyScenarios:O,issues:n,notices:i}}function Mt(e){let t;try{t=Ke(e)}catch{t=void 0}if(j(D(e,`.businesslens`)))return t?{modelRoot:e,gitRoot:t}:{modelRoot:e};if(t&&j(D(t,`.businesslens`)))return{modelRoot:t,gitRoot:t};throw Error("No .businesslens/ Product Model found here or at the repository root — use `businesslens-map` for established code or `businesslens-ideate` for a new product")}function Nt(e){return e.code===`ENOENT`}function Pt(e){let t=M(e);if(t.isSymbolicLink())throw Error(`Refusing to use a symbolic link for generated BusinessLens output: ${e}`);if(!t.isDirectory())throw Error(`Generated BusinessLens output path is not a directory: ${e}`)}function Ft(e,...t){if(t.length<2||t.some(e=>!e||e===`.`||e===`..`||e.includes(`/`)||e.includes(`\\`)))throw Error(`Generated BusinessLens output requires safe repository-relative path segments.`);let n=F(O(e)),r=O(n,...t),i=se(n,r);if(!i||i===`..`||i.startsWith(`..${k}`)||ae(i))throw Error(`Generated BusinessLens output must stay inside the repository.`);let a=n;for(let e of t.slice(0,-1)){a=D(a,e);try{Pt(a)}catch(e){if(!Nt(e))throw e;try{N(a)}catch(e){if(e.code!==`EEXIST`)throw e}Pt(a)}}if(F(a)!==a)throw Error(`Generated BusinessLens output resolves outside its expected directory: ${a}`);try{let e=M(r);if(e.isSymbolicLink())throw Error(`Refusing to overwrite a symbolic link with generated BusinessLens output: ${r}`);if(!e.isFile())throw Error(`Generated BusinessLens output path is not a regular file: ${r}`)}catch(e){if(!Nt(e))throw e}return r}function It(e,t,n){let r=Ft(e,...t),i=he.O_NOFOLLOW??0,a;try{a=ve(r,he.O_WRONLY|he.O_CREAT|he.O_TRUNC|i,384)}catch(e){throw e.code===`ELOOP`?Error(`Refusing to overwrite a symbolic link with generated BusinessLens output: ${r}`):e}try{R(a,n,{encoding:`utf8`})}finally{me(a)}return r}let Lt;function Rt(){if(Lt)return Lt;let e=ie(De(import.meta.url));for(let t of[D(e,`../package.json`),D(e,`../../package.json`)])try{let e=JSON.parse(P(t,`utf8`));if(e.name===`businesslens`&&e.version)return Lt=e.version,Lt}catch{}return Lt=`0.0.0`,Lt}const zt=new Set([`public`,`authenticated`,`restricted`]),Bt=new Set([`complete`,`partial`,`draft`]),Vt=new Set([`achieved`,`not-achieved`]),Ht=new Set(e);function Ut(e,t){return e.size===t.size&&[...e].every(e=>t.has(e))}function Wt(e){return e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}function Gt(t,n){let r=[...t.issues],o=[...t.notices],d=new Set(n),f=(e,t,n)=>{t||r.push(`${e}: missing H1 title`),n||r.push(`${e}: missing lead paragraph (description)`)},p=(e,t,n,i=[])=>{let o=new Set(n.map(e=>e.toLowerCase())),s=new Set(i.map(e=>e.toLowerCase())),c=new Set;a(t.lead)&&r.push(`${e}: lead paragraph must not contain an H1 or H2 heading`);for(let n of t.sections){let t=n.heading.toLowerCase();o.has(t)&&(c.has(t)&&r.push(`${e}: duplicate "## ${n.heading}" section`),c.add(t)),s.has(t)&&r.push(`${e}: "## ${n.heading}" is not allowed on this resource type`),a(n.body)&&r.push(`${e}: "## ${n.heading}" content must not contain an H1 or H2 heading`)}},h=(e,t,n,i)=>{let a=y(t,n);if(a===void 0)return;let o=i===`ordered`?/^\s*\d+[.)]\s+\S.*$/:/^\s*[-*]\s+\S.*$/;a.split(`
7
+ `).some(e=>e.trim()&&!o.test(e))&&r.push(`${e}: "## ${n}" must contain only single-line ${i}-list items`)};t.product.id&&!c(t.product.id)&&r.push(`product.md: id must be lowercase kebab-case`),t.product.id.length>64&&r.push(`product.md: id must be at most 64 characters`),t.product.id||r.push(`product.md: missing id`),f(`product.md`,t.product.doc.title,t.product.doc.lead),p(`product.md`,t.product.doc,[`Intent`]),t.product.summary&&(/\r|\n/.test(t.product.summary)||t.product.summary.length>400)&&r.push(`product.md: summary must be a single line with at most 400 characters`),t.product.category&&!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(t.product.category)&&r.push(`product.md: category must be lowercase kebab-case`),t.product.category&&t.product.category.length>60&&r.push(`product.md: category must be at most 60 characters`),t.product.license&&!/^[A-Za-z0-9][A-Za-z0-9.+-]*$/.test(t.product.license)&&r.push(`product.md: license must be one SPDX license identifier`);for(let[e,n]of t.product.authors.entries())if((!n.name.trim()||n.name.length>120)&&r.push(`product.md: author ${e+1} name must contain 1–120 characters`),n.url)try{let e=new URL(n.url);if(![`http:`,`https:`].includes(e.protocol))throw Error(`unsupported protocol`)}catch{r.push(`product.md: author ${e+1} url must use HTTP(S)`)}Bt.has(t.coverage.status)||r.push(`coverage.md: status "${t.coverage.status}" must be complete|partial|draft`);let g=[...Object.entries(ft(t)),[`scenarioKinds`,t.scenarioKinds]],_=new Set([`interfaces`,`experiences`,`screens`]);for(let[e,t]of g){let n=_.has(e)?l:c;for(let i of t)n(i.id)||r.push(`${e}: id "${i.id}" must be lowercase kebab-case`)}let v=new Set(t.entities.map(e=>e.id)),b=new Map(t.entities.map(e=>[e.id,e])),x=new Set(t.entities.filter(e=>e.acts!==void 0).map(e=>e.id)),C=(e,t)=>{x.has(t)||(v.has(t)?r.push(`${e}: "${t}" does not act; an actor is an Entity with "acts" and "kind"`):r.push(`${e}: references missing entity "${t}"`))},w=new Set(t.interfaces.map(e=>e.id)),ee=new Map(t.interfaces.map(e=>[e.id,e])),T=new Map(t.experiences.map(e=>[e.id,e])),te=new Set(t.experiences.map(e=>e.interface)),ne=new Set([...t.interfaces.filter(e=>!te.has(e.id)).map(e=>e.id),...t.experiences.map(e=>e.id)]),re=new Set([...t.interfaces.map(e=>e.id),...t.experiences.map(e=>e.id),...t.screens.map(e=>e.id)]),ie=new Set(t.domains.map(e=>e.id)),E=new Set(t.capabilities.map(e=>e.id)),ae=new Set(t.journeys.map(e=>e.id)),D=new Set(t.capabilityScenarios.map(e=>e.id)),oe=new Set(t.journeyScenarios.map(e=>e.id)),se=new Set(t.scenarioKinds.map(e=>e.id)),O=(e,t)=>{let n=t.place;return n?l(n)?(re.has(n)||r.push(`${e}: Context references missing place "${n}"`),n):(r.push(`${e}: Context place "${n}" is not a valid Interface, Experience, or Screen id`),n):(r.push(`${e}: Context needs a non-empty place id`),n)},k=(e,n)=>{if(!n)return r.push(`${e}: availability Context needs a non-empty place id`),!1;if(!l(n))return r.push(`${e}: availability Context place "${n}" is not a valid Interface, Experience, or Screen id`),!1;if(ne.has(n))return!0;if(t.screens.some(e=>e.id===n))return r.push(`${e}: availability Context place "${n}" must name its containing Interface or Experience, not a Screen`),!1;let i=s(n);return w.has(i)?n===i?r.push(`${e}: interface "${i}" is divided into Experiences, so name one of them`):r.push(`${e}: availability Context references missing experience "${n}"`):r.push(`${e}: availability Context references missing interface "${i}"`),!1},ce=(e,t)=>{let n=new Set;for(let i of t){let t=i.place;n.has(t)&&r.push(`${e}: duplicate availability Context place "${t}"`),k(e,t)&&n.add(t)}return n},le=(e,t,n)=>{for(let i of t)n.has(i.type)||r.push(`${e}: entry point references undeclared interface "${i.type}"`)};for(let n of t.interfaces){f(n.file,n.doc.title,n.doc.lead),p(n.file,n.doc,[`Intent`,`Capability boundary`]),Ht.has(n.type)||r.push(`${n.file}: type "${n.type}" must be ${e.join(`|`)}`),n.actors.length||r.push(`${n.file}: needs at least one actor`);for(let e of n.actors)C(n.file,e);n.capabilityBoundary||r.push(`${n.file}: missing "## Capability boundary" section`);for(let e of n.entryPoints){if(e.type===n.type)continue;e.type===n.id?r.push(`${n.file}: entry point key "${e.type}" is this Interface's own id; use its type "${n.type}"`):w.has(e.type)||r.push(`${n.file}: entry point key "${e.type}" must be this Interface's type "${n.type}" or another Interface's id`)}}for(let e of t.experiences){f(e.file,e.doc.title,e.doc.lead),p(e.file,e.doc,[`Intent`,`Capability boundary`]),zt.has(e.access)||r.push(`${e.file}: access "${e.access}" must be public|authenticated|restricted`),e.actors.length||r.push(`${e.file}: needs at least one actor`);for(let t of e.actors)C(e.file,t);let t=ee.get(e.interface);if(t)for(let n of e.actors)t.actors.includes(n)||r.push(`${e.file}: actor "${n}" is not supported by interface "${e.interface}"`);e.capabilityBoundary||r.push(`${e.file}: missing "## Capability boundary" section`),le(e.file,e.entryPoints,new Set([e.interface]))}for(let e of t.interfaces){if(!te.has(e.id))continue;let n=new Set(t.experiences.filter(t=>t.interface===e.id).flatMap(e=>e.actors));for(let t of e.actors)n.has(t)||r.push(`${e.file}: actor "${t}" is not covered by any Experience declaring this interface`)}let ue=new Map;for(let e of t.capabilities){if(!e.domain)continue;ue.set(e.domain,(ue.get(e.domain)||0)+1)}for(let e of t.domains){f(e.file,e.doc.title,e.doc.lead),p(e.file,e.doc,[`Intent`,`Boundary`]),e.boundary.trim()?/\b(does not|doesn't|not own|never|excludes?|rather than|outside)\b/i.test(e.boundary)||r.push(`${e.file}: "## Boundary" must state what the Domain does not own, not only what it covers`):r.push(`${e.file}: a Domain needs a "## Boundary" section`);let t=ue.get(e.id)||0;t<2&&o.push(`${e.file}: names ${t} Capabilit${t===1?`y`:`ies`}; a Domain holding fewer than two is a folder, not a region`)}let de=new Map;for(let e of t.experiences){let t=de.get(e.interface)||[];t.push(e),de.set(e.interface,t)}for(let e of t.interfaces){let n=de.get(e.id)||[],i=[];for(let n of t.capabilities){let r=n.availability.some(t=>t.place===e.id||t.place.startsWith(`${e.id}::`));if(!r)continue;let a=new Set(e.actors.filter(e=>t.capabilityScenarios.some(t=>t.capability===n.id&&t.steps.some(t=>t.actor===e))));a.size&&i.push(a)}let a=new Map,o=e=>{let t=e;for(;a.get(t)!==void 0&&a.get(t)!==t;)t=a.get(t);return t};for(let e of i){let[t,...n]=[...e];if(!t)continue;a.has(t)||a.set(t,t);for(let e of n)a.has(e)||a.set(e,e),a.set(o(e),o(t))}let s=new Set([...a.keys()].map(o)).size,c=s>1,l=new Set(n.map(e=>e.access).filter(Boolean)),u=l.size>1||c,d=n.some(n=>{let r=n.id.split(`::`).pop();return t.experiences.some(t=>t.interface!==e.id&&t.id.split(`::`).pop()===r)});n.length&&!u&&!d&&r.push(`${e.file}: holds Experiences but serves one audience through one access mode, and none is a counterpart; use direct Interface availability`),!n.length&&c&&r.push(`${e.file}: serves Actor sets no available Capability bridges; these are Experiences, not one context`)}let fe=/(?:ing|tion|sion|ment|ance|ence|ity|ness)$/,pe=new Set(`add.answer.apply.approve.archive.assign.back.block.book.browse.build.cancel.change.check.choose.close.collect.compare.complete.compose.configure.confirm.connect.contribute.create.decide.decline.delete.deliver.discover.edit.enter.expire.explore.export.find.follow.gate.generate.grant.handle.import.install.invite.issue.join.keep.leave.link.lint.list.manage.map.mark.merge.move.name.open.order.organize.pause.pay.place.plan.preserve.publish.pull.read.receive.recover.refresh.refund.reject.remove.rename.reorder.reply.republish.report.request.reset.resolve.restore.resume.retry.return.review.revoke.run.save.schedule.search.select.send.serve.set.settle.share.ship.show.sign.start.stop.submit.subscribe.switch.synchronize.track.transfer.unfollow.unlist.update.upload.verify.view.withdraw.write`.split(`.`)),A=new Set;for(let e of[...t.entities,...t.domains,...t.interfaces,...t.experiences,...t.screens]){A.add(e.id);let t=e.id.split(`::`).pop();t&&A.add(t)}let me=e=>pe.has(e)&&!A.has(e),he=[...t.capabilities.map(e=>({file:e.file,id:e.id,kind:`Capability`})),...t.capabilityScenarios.map(e=>({file:e.file,id:e.id,kind:`Capability Scenario`})),...t.journeys.map(e=>({file:e.file,id:e.id,kind:`Journey`})),...t.journeyScenarios.map(e=>({file:e.file,id:e.id,kind:`Journey Scenario`}))];for(let e of he){let t=e.id.split(`-`),n=t[t.length-1]||``,r=t.some(me);fe.test(n)&&!r&&o.push(`${e.file}: ${e.kind} id "${e.id}" reads as a noun phrase; a behavioral id starts with a verb`)}for(let e of he){let t=e.id.split(`-`);if(t.length<2)continue;let n=t.slice(1).join(`-`);if(A.has(n))continue;let r=[...A].filter(e=>e.endsWith(`-${n}`)).sort((e,t)=>e.length-t.length)[0];r&&o.push(`${e.file}: ${e.kind} id "${e.id}" names "${n}" where this model declares "${r}"; use the declared name`)}let ge=[...t.entities.map(e=>({file:e.file,id:e.id,kind:`Entity`})),...t.domains.map(e=>({file:e.file,id:e.id,kind:`Domain`})),...t.businessRules.map(e=>({file:e.file,id:e.id,kind:`Business Rule`}))];for(let e of ge){let t=e.id.split(`-`);if(t.length<2)continue;let n=t[0]||``;me(n)&&o.push(`${e.file}: ${e.kind} id "${e.id}" opens with a verb; cross-cutting ids name what a thing is, not what is done`)}let j=[...t.capabilityScenarios,...t.journeyScenarios],M=new Set,N=new Map,_e=new Map;for(let e of j)for(let t of e.steps)for(let n of t.entities){if((n.effect??`changes`)===`reads`)continue;if(M.add(n.entity),n.to!==void 0&&N.set(n.entity,new Set([...N.get(n.entity)??[],n.to])),n.from!==void 0){let t=_e.get(n.entity)??new Map;t.has(n.from)||t.set(n.from,e.file),_e.set(n.entity,t)}}let ve=new Map;for(let e of t.screens)for(let t of e.entities)v.has(t)||r.push(`${e.file}: names missing entity "${t}"`),ve.set(t,[...ve.get(t)||[],e.id]);let P=new Set(t.businessRules.flatMap(e=>(e.permits??[]).flatMap(e=>[...e.configuredBy?[e.configuredBy]:[],...e.when.flatMap(e=>[...e.entity?[e.entity]:[],...typeof e.value==`object`&&e.value!==null?[e.value.configuredBy]:[]])]))),ye=new Set([...j.flatMap(e=>e.steps.flatMap(e=>e.actor?[e.actor]:[])),...t.interfaces.flatMap(e=>e.actors),...t.experiences.flatMap(e=>e.actors),...t.journeys.flatMap(e=>e.actors),...t.businessRules.flatMap(e=>(e.permits??[]).flatMap(e=>[...e.actors,...e.related.length?[e.related[e.related.length-1].entity]:[]]))]);for(let e of t.entities){f(e.file,e.doc.title,e.doc.lead),p(e.file,e.doc,[`Intent`,`Information kept`,`States`],[`Transitions`,`Relations`]),e.domain&&!ie.has(e.domain)&&r.push(`${e.file}: names missing domain "${e.domain}"`);let n=e.states[0]?.title,i=N.get(e.id)??new Set;for(let t of e.states.slice(1))i.has(t.title)||o.push(`${e.file}: no Step leaves it in "${t.title}"`);for(let[t,r]of _e.get(e.id)??[])t!==n&&!i.has(t)&&o.push(`${e.file}: ${r} moves it from "${t}", which no Step produces`);let a=new Set;for(let t of e.states)a.has(t.title)&&r.push(`${e.file}: duplicate state "${t.title}"`),a.add(t.title);let s=new Set;for(let n of e.relations){v.has(n.entity)||r.push(`${e.file}: relation names missing entity "${n.entity}"`);let i=`${n.entity}\0${n.verb}`;if(s.has(i)&&r.push(`${e.file}: duplicate relation "${n.verb} ${n.entity}"`),s.add(i),n.entity===e.id)continue;let a=t.entities.find(e=>e.id===n.entity);for(let t of a?.relations??[]){if(t.entity!==e.id)continue;o.push(`${e.file}: "${n.verb} ${n.entity}" faces "${t.verb} ${t.entity}" in ${a.file}; a relation is declared on one side only and now states both ends`)}}!M.has(e.id)&&!(ve.get(e.id)||[]).length&&!ye.has(e.id)&&!P.has(e.id)&&r.push(`${e.file}: no Step changes it, no Screen presents it, nothing names it as an actor, and no Rule reads it`)}let F=new Map;for(let e of t.capabilities)f(e.file,e.doc.title,e.doc.lead),p(e.file,e.doc,[`Intent`]),e.availability.length||r.push(`${e.file}: needs at least one availability Context`),e.domain&&!ie.has(e.domain)&&r.push(`${e.file}: references missing domain "${e.domain}"`),F.set(e.id,ce(e.file,e.availability));let I=e=>{let t=T.get(e);if(t)return new Set(t.actors);let n=ee.get(e);return n?new Set(n.actors):void 0},L=new Map(t.screens.map(e=>[e.id,e])),be=new Map;for(let e of t.screens){let t=be.get(e.containerId)||[];t.push(e),be.set(e.containerId,t)}let xe=e=>(de.get(e)||[]).length>0,R=e=>xe(e)?(de.get(e)||[]).map(e=>e.id):[e],Se=(e,t)=>R(t).every(t=>e.has(t)),Ce=(e,t)=>{if(!t){r.push(`${e}: Context needs a non-empty place id`);return}if(!l(t)){r.push(`${e}: Context place "${t}" is not a valid Interface, Experience, or Screen id`);return}let n=L.get(t);if(n)return{place:t,containerId:n.containerId,screen:n};let i=T.get(t);if(i)return(be.get(t)||[]).length&&r.push(`${e}: Experience "${t}" owns Screens, so the Context must name one of its Screens`),{place:t,containerId:t,screen:void 0};let a=ee.get(t);if(a)return te.has(t)?r.push(`${e}: Interface "${t}" is divided into Experiences, so the Context must name one of them, one of their Screens, or a Screen it shares`):(be.get(t)||[]).length&&r.push(`${e}: Interface "${t}" owns Screens, so the Context must name one of its Screens`),{place:t,containerId:t,screen:void 0};r.push(`${e}: Context references missing place "${t}"`)},we=(e,n,i)=>{e.routes.length||r.push(`${e.file}: needs at least one route`);let a=new Set,s=new Set,l=new Map(e.routes.map(e=>[e.id,[]]));for(let t of e.routes){let n=`${e.file}: route "${t.id}"`;t.id?c(t.id)||r.push(`${n}: id must be lowercase kebab-case`):r.push(`${e.file}: route id must not be empty`),a.has(t.id)&&r.push(`${n}: duplicate route id`),a.add(t.id);let i=t.name.trim();i||r.push(`${n}: needs a non-empty name`);let o=i.toLocaleLowerCase();s.has(o)&&r.push(`${n}: duplicate route name "${t.name}"`),s.add(o)}e.steps.length||r.push(`${e.file}: needs at least one step`);let u=new Set,d=new Set,f=new Set,p=[],m=e.steps[0]?.unattended===!0,h=new Map,g=new Map;for(let[i,s]of e.steps.entries()){let c=`${e.file}: step ${i+1}`;s.text.trim()||r.push(`${c}: needs non-empty text`),/\r|\n/.test(s.text)&&r.push(`${c}: text must be a single line`),[`actor`,`product`,`condition`].includes(s.kind)||r.push(`${c}: kind "${s.kind}" must be actor|product|condition`),s.kind===`actor`&&!s.actor&&r.push(`${c}: an actor Step needs one actor`),s.actor&&(f.add(s.actor),C(c,s.actor),m&&r.push(`${c}: an unattended Scenario names no actor; its permission is a Rule's "unattended" grant`)),!n&&!s.capability&&s.entities.some(e=>(e.effect??`changes`)!==`reads`)&&r.push(`${c}: a Journey Step that creates, changes or removes an Entity needs a "capability" to own the change`);for(let e of s.entities){let t=b.get(e.entity);if(!t){r.push(`${c}: references missing entity "${e.entity}"`);continue}let n=e.effect??`changes`,i=e.as?`${e.entity} (${e.as})`:e.entity,a=e.as?`aliased`:`bare`,o=g.get(e.entity);o&&o!==a&&r.push(`${c}: "${e.entity}" is ${o===`aliased`?`aliased`:`bare`} elsewhere in this Scenario; once an Entity is aliased, every mention of it is`),g.set(e.entity,a);let s=t.states.length>0;for(let[n,i]of[[`from`,e.from],[`to`,e.to]]){if(i===void 0)continue;s?t.states.some(e=>e.title===i)||r.push(`${c}: "${i}" is not a state of entity "${e.entity}"`):r.push(`${c}: "${n}: ${i}" names a state, and entity "${e.entity}" declares none`)}s&&n===`creates`&&e.to===void 0&&r.push(`${c}: creating "${i}" needs "to", the state it starts in`),s&&n===`removes`&&e.from===void 0&&r.push(`${c}: removing "${i}" needs "from", the state it ends in`);let l=`${e.entity}\0${e.as??``}`,u=h.get(l);e.from!==void 0&&u!==void 0&&u!==e.from&&r.push(`${c}: "${i}" was left in "${u}" by an earlier Step, not "${e.from}"; if these are different instances, give them aliases`),n===`removes`?h.delete(l):e.to!==void 0&&h.set(l,e.to)}let _=new Set(s.entities.map(e=>e.entity)),v=s.text.replace(/\bthe Product\b/gi,` `),y=e=>{let t=RegExp(`(?:^|[^a-z0-9])(${Wt(e)}(?:'s|s)?)(?=$|[^a-z0-9])`,`gi`);return[...v.matchAll(t)].map(e=>{let t=e.index+e[0].length-e[1].length;return[t,t+e[1].length]})},x=t.entities.filter(e=>e.doc.title&&(_.has(e.id)||e.id===s.actor)).flatMap(e=>y(e.doc.title));for(let e of t.entities){if(!e.doc.title||_.has(e.id)||e.id===s.actor)continue;let n=y(e.doc.title).some(([e,t])=>!x.some(([n,r])=>n<=e&&t<=r));if(!n)continue;let i=`${c}: text names "${e.doc.title}" and "entities" does not declare it`;t.coverage.status===`complete`?r.push(i):o.push(i)}let S=n||s.capability;if(S&&(p.push({capability:S,contextPlaces:new Set,containers:new Set}),E.has(S)||r.push(`${c}: references missing capability "${S}"`)),!s.contexts.length)continue;let w=new Set;for(let e of s.contexts){let t=`${c}: route "${e.routeId}"`;a.has(e.routeId)||r.push(`${t}: references undeclared route`),w.has(e.routeId)&&r.push(`${t}: duplicate Context`),w.add(e.routeId),l.get(e.routeId)?.push(e.place);let n=Ce(t,e.place);if(!n)continue;u.add(n.place),d.add(n.containerId);let i=S?p.at(-1):void 0;if(i?.contextPlaces.add(n.place),i?.containers.add(n.containerId),S){let e=F.get(S)||new Set;Se(e,n.containerId)||r.push(`${t}: Context place "${n.place}" is outside capability "${S}"`),n.screen&&!n.screen.capabilities.includes(S)&&r.push(`${t}: Screen "${n.screen.id}" does not expose capability "${S}"`)}if(s.actor){let e=I(n.containerId)||new Set;e.has(s.actor)||r.push(`${t}: Context place does not support actor "${s.actor}"`)}}Ut(w,a)||r.push(`${c}: contexts must assign every declared route or be omitted`)}let _=m;_&&e.steps[0]?.kind!==`condition`&&r.push(`${e.file}: an unattended trigger must be a condition Step`);for(let[t,n]of e.steps.entries())t>0&&n.unattended&&r.push(`${e.file}: step ${t+1}: "unattended" is valid only on the first Step`);!e.steps.some(e=>e.kind===`actor`)&&!_&&r.push(`${e.file}: needs at least one actor Step, or an unattended first condition Step`);for(let t of e.routes){let n=l.get(t.id)||[];n.length||r.push(`${e.file}: route "${t.id}" must have a Context on at least one Step`)}let v=new Map;for(let t of e.routes){let n=(l.get(t.id)||[]).join(`
8
+ `);if(!n)continue;let i=v.get(n);i?r.push(`${e.file}: route "${t.id}" repeats every Context place of route "${i}"`):v.set(n,t.id)}let y=new Set;if(!_)for(let t of d){let n=I(t)||new Set,i=[...f].filter(e=>n.has(e));i.length||r.push(`${e.file}: Context place "${t}" permits none of the Scenario Actors`);for(let e of i)y.add(e)}for(let t of f)x.has(t)&&!y.has(t)&&r.push(`${e.file}: actor "${t}" is not supported by any selected Context place`);if(i)for(let t of e.routes){let n=e.steps.find(e=>e.kind===`actor`&&e.contexts.some(e=>e.routeId===t.id));(!n?.actor||!i.has(n.actor))&&r.push(`${e.file}: route "${t.id}" must begin its contextualized Actor Steps with a Journey Actor`)}return{allContextPlaces:u,allContainers:d,scenarioActors:f,capabilitySteps:p}},Te=e=>{e.doc.title||r.push(`${e.file}: missing H1 title`),e.doc.lead&&r.push(`${e.file}: carries no lead paragraph; move that content into "## Trigger" or another explicit section`),p(e.file,e.doc,[`Intent`,`Trigger`,`Steps`,`Decision points`,`Outcome`,`Edge cases`],[`Goal`,`Success criterion`,`Steps`]),h(e.file,e.doc,`Edge cases`,`bullet`),(!e.kind||!se.has(e.kind))&&r.push(`${e.file}: kind "${e.kind}" is not defined in taxonomies.yaml`),e.trigger||r.push(`${e.file}: missing "## Trigger" section`),e.outcome||r.push(`${e.file}: missing "## Outcome" section`),y(e.doc,`Edge cases`)!==void 0&&!e.edgeCases.length&&r.push(`${e.file}: "## Edge cases" needs at least one bullet item when present`)},Ee=new Map;for(let e of[...t.capabilityScenarios,...t.journeyScenarios]){let t=Ee.get(e.id);t&&r.push(`${e.file}: scenario id "${e.id}" already used in ${t} (ids are global)`),Ee.set(e.id,e.file)}let De=new Map(t.capabilityScenarios.map(e=>[e.id,e])),Oe=new Map,ke=new Map;for(let e of t.capabilityScenarios){Te(e),e.capability?E.has(e.capability)||r.push(`${e.file}: references missing capability "${e.capability}"`):r.push(`${e.file}: needs one capability`);let{allContextPlaces:t,allContainers:n}=we(e,e.capability);Oe.set(e.id,t);let i=ke.get(e.capability)||new Set;for(let e of n)for(let t of R(e))i.add(t);ke.set(e.capability,i);let a=F.get(e.capability)||new Set;for(let t of n)Se(a,t)||r.push(`${e.file}: Context place "${t}" is outside capability "${e.capability}"`)}for(let e of t.capabilities){let n=ke.get(e.id)||new Set,i=F.get(e.id)||new Set;for(let a of i){if(n.has(a))continue;let i=`${e.file}: availability Context place "${a}" needs Capability Scenario coverage`;t.coverage.status===`complete`?r.push(i):o.push(i)}}let Ae=new Map(t.journeys.map(e=>[e.id,e]));for(let e of t.journeys){e.doc.title||r.push(`${e.file}: missing H1 title`),e.doc.lead&&r.push(`${e.file}: carries no lead paragraph; move that content into "## Goal"`),p(e.file,e.doc,[`Intent`,`Goal`,`Success criterion`],[`Trigger`,`Steps`,`Decision points`,`Outcome`,`Edge cases`]),e.actors.length||r.push(`${e.file}: needs at least one actor`);for(let t of e.actors)C(e.file,t);e.goal||r.push(`${e.file}: missing "## Goal" section`),e.successCriterion||r.push(`${e.file}: missing "## Success criterion" section`),t.journeyScenarios.some(t=>t.journey===e.id&&t.result===`achieved`)||r.push(`${e.file}: needs at least one achieved Journey Scenario`)}let je=new Map(t.journeyScenarios.map(e=>[e.id,e])),Me=new Map,Ne=new Map;for(let e of t.journeyScenarios){Te(e);let t=Ae.get(e.journey);t||r.push(`${e.file}: references missing journey "${e.journey}"`),Vt.has(e.result)||r.push(`${e.file}: result "${e.result}" must be achieved|not-achieved`);let n=new Set(t?.actors||[]),{scenarioActors:i,capabilitySteps:a}=we(e,void 0,n);t&&![...i].some(e=>n.has(e))&&r.push(`${e.file}: actor Steps must include at least one actor from journey "${e.journey}"`),a.length||r.push(`${e.file}: needs at least one Capability-bearing step`),Me.set(e.id,a),Ne.set(e.id,i),e.result===`achieved`&&new Set(a.map(e=>e.capability)).size<2&&r.push(`${e.file}: an achieved Journey Scenario needs at least two distinct Capabilities`)}for(let e of t.journeys){let n=new Set(t.journeyScenarios.filter(t=>t.journey===e.id&&t.result===`achieved`).flatMap(e=>[...Ne.get(e.id)||[]]));for(let t of e.actors)n.has(t)||r.push(`${e.file}: actor "${t}" needs an achieved Journey Scenario`)}for(let e of t.screens){f(e.file,e.doc.title,e.doc.lead),p(e.file,e.doc,[`Intent`,`Information presented`,`Available actions`,`View states`,`Capability boundary`]),h(e.file,e.doc,`Information presented`,`bullet`),h(e.file,e.doc,`Available actions`,`bullet`),R(e.containerId).every(e=>ne.has(e))||r.push(`${e.file}: containing place "${e.containerId}" must be an Interface or an Experience`),e.capabilities.length||r.push(`${e.file}: needs at least one capability`);for(let t of e.capabilities){if(!E.has(t)){r.push(`${e.file}: references missing capability "${t}"`);continue}let n=F.get(t)||new Set,i=R(e.containerId).filter(e=>!n.has(e));if(!i.length)continue;r.push(xe(e.containerId)?`${e.file}: capability "${t}" must be available in every Experience of "${e.containerId}", which shares this Screen; missing "${i.join(`", "`)}"`:`${e.file}: capability "${t}" is not available in containing place "${e.containerId}"`)}le(e.file,e.entryPoints,new Set([s(e.containerId)])),e.information.length||r.push(`${e.file}: "## Information presented" needs at least one bullet item`),e.capabilityBoundary||r.push(`${e.file}: missing "## Capability boundary" section`),y(e.doc,`Available actions`)!==void 0&&!e.actions.length&&r.push(`${e.file}: "## Available actions" needs at least one bullet item when present`),y(e.doc,`View states`)!==void 0&&!e.states.length&&r.push(`${e.file}: "## View states" needs at least one H3 state when present`);let t=new Set;for(let n of e.states){let i=n.title.toLowerCase();t.has(i)&&r.push(`${e.file}: duplicate view state "${n.title}"`),t.add(i)}for(let n of e.references){if(n.state===void 0)continue;t.has(n.state.toLowerCase())||r.push(`${e.file}: reference state "${n.state}" is not a view state of this Screen`)}}let Pe=(e,t,n)=>{let i=e;for(let[e,a]of t.entries()){let t=`${n}: related segment ${e+1}`;if(!v.has(a.entity)){r.push(`${t}: references missing entity "${a.entity}"`);return}if(a.entity===i){r.push(`${t}: "${a.verb}" walks "${i}" to itself, and a self-relation has no direction to follow`);return}let o=(b.get(i)?.relations??[]).filter(e=>e.verb===a.verb&&e.entity===a.entity),s=(b.get(a.entity)?.relations??[]).filter(e=>e.verb===a.verb&&e.entity===i),c=o.length+s.length;if(c===0){r.push(`${t}: no relation "${a.verb}" joins "${i}" and "${a.entity}" in either direction`);return}if(c>1){r.push(`${t}: "${a.verb}" joins "${i}" and "${a.entity}" more than once; give one of them another verb`);return}i=a.entity}return i};for(let e of t.businessRules){f(e.file,e.doc.title,e.doc.lead),p(e.file,e.doc,[`Intent`,`Rationale`]),e.appliesTo.length||r.push(`${e.file}: needs at least one appliesTo target`);let n=e.appliesTo.filter(e=>e.type!==`context`&&e.type!==`entity`),a=e.appliesTo.filter(e=>e.type===`entity`),s=n.some(e=>`contexts`in e&&Array.isArray(e.contexts)&&e.contexts.length>0),c=e.appliesTo.some(e=>e.type===`context`);if(n.length===1&&!s&&!c&&!a.length){let t=n[0],r=t&&`id`in t?t.id:`that resource`;o.push(`${e.file}: governs only "${r}"; a constraint true of one behavior belongs to it as a condition Step or Outcome, not a Business Rule`)}let l=new Set,u=new Set,d=new Set,m=[],h=[],g=[];for(let[n,a]of e.appliesTo.entries()){let o=`${e.file}: appliesTo item ${n+1}`;if(a.type===`context`){let e=O(o,a.context),t=`context\0${e}`;l.has(t)&&r.push(`${o}: duplicate Context target place "${e}"`);let n=g.find(t=>t!==e&&(i(t,e)||i(e,t)));n&&r.push(`${o}: Context target place "${e}" is redundant with "${n}"`),l.add(t),g.push(e);continue}if(a.type===`entity`){let e=b.get(a.id);a.id?e||r.push(`${o}: references missing entity "${a.id}"`):r.push(`${o}: needs a non-empty id`);let n=`entity\0${a.id}\0${a.effect??``}\0${a.from??``}\0${a.to??``}\0${a.facts.join(`,`)}`;if(l.has(n)&&r.push(`${o}: duplicate target "entity:${a.id}"`),l.add(n),e){for(let[t,n]of[[`from`,a.from],[`to`,a.to]])n!==void 0&&!e.states.some(e=>e.title===n)&&r.push(`${o}: "${t}: ${n}" is not a state of entity "${a.id}"`);for(let t of a.facts)e.informationKept.some(e=>e.name===t)||r.push(`${o}: "${t}" is not a fact of entity "${a.id}"`)}let s=t.screens.filter(e=>e.entities.includes(a.id)).map(e=>e.id),c=[];for(let[e,t]of a.contexts.entries()){let n=`${o}: Context ${e+1}`,l=O(n,t);c.includes(l)&&r.push(`${n}: duplicate Context place "${l}"`);let u=c.find(e=>e!==l&&(i(e,l)||i(l,e)));u&&r.push(`${n}: Context place "${l}" is redundant with "${u}"`),c.push(l),re.has(l)&&!s.some(e=>e===l||i(l,e))&&r.push(`${n}: Context place "${l}" presents entity "${a.id}" nowhere`)}continue}if(![`capability`,`capability-scenario`,`journey`,`journey-scenario`].includes(a.type)){r.push(`${o}: type "${a.type}" must be capability|capability-scenario|journey|journey-scenario|context|entity`);continue}a.id||r.push(`${o}: needs a non-empty id`);let s=`${a.type}\0${a.id}`;l.has(s)&&r.push(`${o}: duplicate target "${a.type}:${a.id}"`),l.add(s);let c=new Set;if(a.type===`capability`){E.has(a.id)||r.push(`${o}: references missing capability "${a.id}"`),u.add(a.id),c=new Set(F.get(a.id)||[]);for(let e of t.screens)c.has(e.containerId)&&e.capabilities.includes(a.id)&&c.add(e.id)}else if(a.type===`capability-scenario`)D.has(a.id)||r.push(`${o}: references missing Capability Scenario "${a.id}"`),m.push(a.id),c=Oe.get(a.id)||c;else if(a.type===`journey`){ae.has(a.id)||r.push(`${o}: references missing journey "${a.id}"`),d.add(a.id);for(let e of t.journeyScenarios.filter(e=>e.journey===a.id&&e.result===`achieved`))for(let t of Me.get(e.id)||[])for(let e of t.contextPlaces)c.add(e)}else{oe.has(a.id)||r.push(`${o}: references missing Journey Scenario "${a.id}"`),h.push(a.id);for(let e of Me.get(a.id)||[])for(let t of e.contextPlaces)c.add(t)}let f=[];for(let[e,t]of a.contexts.entries()){let n=`${o}: Context ${e+1}`,s=O(n,t);f.includes(s)&&r.push(`${n}: duplicate Context place "${s}"`);let l=f.find(e=>e!==s&&(i(e,s)||i(s,e)));l&&r.push(`${n}: Context place "${s}" is redundant with "${l}"`),f.push(s);let u=L.get(s),d=c.has(s)||[...c].some(e=>i(s,e))||u!==void 0&&Se(c,u.containerId);d||r.push(`${n}: Context place "${s}" is outside target "${a.type}:${a.id}"`)}}for(let t of m){let n=De.get(t)?.capability;n&&u.has(n)&&r.push(`${e.file}: target "capability-scenario:${t}" is redundant with capability target "${n}"`)}for(let t of h){let n=je.get(t)?.journey;n&&d.has(n)&&r.push(`${e.file}: target "journey-scenario:${t}" is redundant with journey target "${n}"`)}if(e.permits!==void 0){e.appliesTo.some(e=>e.type!==`entity`)&&r.push(`${e.file}: "permits" needs Entity targets only; an operation is an Entity effect, and who uses a surface is its Interface's "actors"`);let t=a.length===1?a[0]:void 0,n=t?b.get(t.id):void 0;for(let[i,o]of e.permits.entries()){let s=`${e.file}: grant ${i+1}`;for(let e of o.actors)C(s,e);if(o.configuredBy!==void 0&&!v.has(o.configuredBy)&&r.push(`${s}: "configuredBy" references missing entity "${o.configuredBy}"`),o.self)for(let e of a)v.has(e.id)&&!x.has(e.id)&&r.push(`${s}: "self" needs entity "${e.id}" to act`);if(o.related.length)if(!t)r.push(`${s}: "related" needs exactly one Entity target to start from`);else{let e=Pe(t.id,o.related,s);e!==void 0&&!x.has(e)&&r.push(`${s}: "related" ends on "${e}", which does not act`),e!==void 0&&o.actors.length&&!o.actors.includes(e)&&r.push(`${s}: "actors" excludes "${e}", where "related" ends; the grant can never be satisfied`)}for(let[e,i]of o.when.entries()){let a=`${s}: condition ${e+1}`;if(i.state!==void 0){if(!t){r.push(`${a}: a "state" condition needs exactly one Entity target`);continue}t.effect===`creates`&&r.push(`${a}: a "state" condition on a "creates" target; there is no instance yet`),n&&!n.states.some(e=>e.title===i.state)&&r.push(`${a}: "${i.state}" is not a state of entity "${t.id}"`);continue}let o=n,c=t?.id;if(i.entity!==void 0)o=b.get(i.entity),c=i.entity,o||r.push(`${a}: references missing entity "${i.entity}"`);else if(!t){r.push(`${a}: a "fact" without "entity" needs exactly one Entity target to read it on`);continue}o&&i.fact!==void 0&&!o.informationKept.some(e=>e.name===i.fact)&&r.push(`${a}: "${i.fact}" is not a fact of entity "${c}"`),typeof i.value==`object`&&i.value!==null&&!v.has(i.value.configuredBy)&&r.push(`${a}: "configuredBy" references missing entity "${i.value.configuredBy}"`)}}}}let Fe=t.businessRules.filter(e=>e.permits!==void 0&&e.appliesTo.length>0&&e.appliesTo.every(e=>e.type===`entity`)),Ie=e=>({entityId:e.id,effect:e.effect??null,from:e.from??null,to:e.to??null,facts:e.facts,contextPlaces:e.contexts.map(e=>e.place)}),Le=e=>({actorIds:e.actors,relatedActorId:e.related.at(-1)?.entity??null,self:e.self===!0,unattended:e.unattended===!0,stateConditions:e.when.flatMap(e=>e.state===void 0?[]:[e.state])}),Re=new Map(j.map(e=>[e.file,[...new Set(e.steps.flatMap(e=>e.contexts.map(e=>e.place)))]])),ze=(e,t,n,r,i)=>({label:e,actorId:t.actor??null,unattended:r,entityId:n.entity,alias:n.as??null,effect:n.effect??`changes`,from:n.from??null,to:n.to??null,contextPlaces:u(t.contexts.map(e=>e.place),Re.get(i)??[])}),Be=Fe.map(e=>({id:e.id,targets:e.appliesTo.filter(e=>e.type===`entity`).map(Ie),grants:(e.permits??[]).map(Le)})),z=(e,t,n,r,i=!1)=>m(Ie(e),ze(``,n,t,!1,r),i),Ve=new Map;for(let e of Fe){let t=e.appliesTo.map(e=>e.type===`entity`?[e.id,e.effect??``,e.from??``,e.to??``,e.facts.join(`,`),e.contexts.map(e=>e.place).join(`,`)].join(`|`):``).sort().join(`
9
+ `),n=Ve.get(t);n?o.push(`${e.file}: selects exactly what ${n} selects; grants meant as alternatives sit in one Rule, and apart they AND`):Ve.set(t,e.file)}for(let e of Fe)for(let[t,n]of e.appliesTo.entries()){if(n.type!==`entity`)continue;let r=`${e.file}: appliesTo item ${t+1}`,i=[],a=[];for(let e of j)for(let t of e.steps)for(let r of t.entities)z(n,r,t,e.file)&&i.push(r),z(n,r,t,e.file,!0)&&a.push(r);n.from!==void 0&&a.length&&a.every(e=>e.from===n.from)&&o.push(`${r}: "from: ${n.from}" is redundant; every Step it could select already leaves from it`);for(let[t,n]of(e.permits??[]).entries())for(let r of n.when){if(r.state===void 0)continue;i.length&&i.every(e=>e.from===r.state)&&o.push(`${e.file}: grant ${t+1}: "state: ${r.state}" is redundant; every selected Step already leaves from it`)}}r.push(...S({rules:Be,operations:j.flatMap(e=>{let t=e.steps[0]?.unattended===!0;return e.steps.flatMap((n,r)=>n.entities.map(i=>ze(`${e.file}: step ${r+1}`,n,i,t,e.file)))}),screens:t.screens.map(e=>({label:e.file,id:e.id,containerId:e.containerId,entityIds:e.entities,actorIds:[...I(e.containerId)??[]]}))})),t.interfaces.length===0&&r.push(`interfaces/: the model needs at least one interface`),t.coverage.status===`complete`&&t.capabilities.length===0&&r.push(`capabilities/: a complete model needs at least one capability`);let He=pt(t);for(let e of He){let n=new Set(e.assets),i=new Set(t.screens.find(t=>t.file===e.file)?.states.map(e=>e.title.toLowerCase())??[]),a=t.screens.some(t=>t.file===e.file);for(let t of e.assetMeta){if(n.has(t.file)||r.push(`${e.file}: asset "${t.file}" is not a file in this resource's expanded folder`),t.state===void 0)continue;a?i.has(t.state.toLowerCase())||r.push(`${e.file}: asset state "${t.state}" is not a view state of this Screen`):r.push(`${e.file}: asset "state" is only valid on a Screen`)}}let Ue=[{file:`product.md`,references:t.product.references},...He],We=new Set(t.screens.map(e=>e.file));for(let e of Ue){let t=new Set;for(let n of e.references){t.has(n.target)&&r.push(`${e.file}: duplicate reference target "${n.target}"`),t.add(n.target),n.state!==void 0&&!We.has(e.file)&&r.push(`${e.file}: reference "state" is only valid on a Screen`);let i=it(n);if(!i||d.has(i))continue;n.kind===`code`?r.push(`${e.file}: code reference path "${i}" is not a tracked file`):o.push(`${e.file}: reference target "${n.target}" does not exist in the repository`)}}return{ok:r.length===0,errors:r,warnings:o,counts:{interfaces:t.interfaces.length,experiences:t.experiences.length,screens:t.screens.length,domains:t.domains.length,entities:t.entities.length,capabilities:t.capabilities.length,capabilityScenarios:t.capabilityScenarios.length,journeys:t.journeys.length,journeyScenarios:t.journeyScenarios.length,businessRules:t.businessRules.length}}}function Kt(e,t){let n,r;try{({modelRoot:n,gitRoot:r}=Mt(e))}catch(e){let n=e.message;return t?console.log(JSON.stringify({ok:!1,errors:[n],warnings:[],counts:{}},null,2)):console.error(`error: ${n}`),1}let i=jt(n),a=Gt(i,r?qe(r):[]);if(t)console.log(JSON.stringify(a,null,2));else{for(let e of a.errors)console.error(`error: ${e}`);for(let e of a.warnings)console.warn(`warning: ${e}`);console.log(a.ok?`Product Model structure is sound.`:`Lint failed with ${a.errors.length} error(s).`)}return a.ok?0:1}const G=e=>[...e].sort((e,t)=>e.id.localeCompare(t.id)),K=e=>[...e].sort(),qt=e=>[...e].sort((e,t)=>e.place.localeCompare(t.place)).map(e=>({placeId:e.place})),Jt=/\.(png|jpe?g|gif|webp|avif|svg)$/i;function Yt(e,t){let n=new Map(e.assetMeta.map(e=>[e.file,e]));return e.assets.map(r=>{let i=n.get(r);return{kind:Jt.test(r)?`visual`:`doc`,role:r.startsWith(`implementation/`)?`implementation`:`intent`,target:se(t,D(e.directory,r)).split(k).join(`/`),...i?.title?{title:i.title}:{},...i?.state?{state:i.state}:{}}})}function q(e,t,n){return{intent:y(e.doc,`Intent`)||``,supportingSections:b(e.doc,[`Intent`,...t]),references:[...Yt(e,n),...e.references].map(e=>({kind:e.kind,role:e.role,target:e.target,...e.title?{title:e.title}:{},...e.state?{state:e.state}:{}}))}}function Xt(e,r,i=e.root){let a=new Map(e.capabilities.map(e=>[e.id,e])),o=new Map(e.journeys.map(t=>[t.id,e.journeyScenarios.filter(e=>e.journey===t.id)])),s=e=>K([...new Set(e.steps.flatMap(e=>e.actor?[e.actor]:[]))]),c=e=>e.routes.map(e=>({id:e.id,name:e.name})),l=(e,t)=>e.steps.map(n=>({text:n.text,kind:n.kind,actorId:n.actor??null,capabilityId:t??n.capability??null,entities:n.entities.map(e=>({entityId:e.entity,as:e.as??null,effect:e.effect??`changes`,from:e.from??null,to:e.to??null})),unattended:n.unattended===!0,contexts:e.routes.flatMap(e=>{let t=n.contexts.find(t=>t.routeId===e.id);return t?[{routeId:e.id,placeId:t.place}]:[]})})),u=(t,n)=>K((n===`capability`?e.capabilityScenarios:e.journeyScenarios).filter(e=>e.steps.some(e=>e.contexts.some(e=>e.place===t))).map(e=>e.id)),d={schemaVersion:n,id:e.product.id,title:e.product.doc.title,summary:e.product.summary||e.product.doc.lead,description:e.product.doc.lead,category:e.product.category??null,authors:e.product.authors,license:e.product.license??null,intent:y(e.product.doc,`Intent`)||``,supportingSections:b(e.product.doc,[`Intent`]),references:e.product.references.map(e=>({kind:e.kind,role:e.role,target:e.target,...e.title?{title:e.title}:{},...e.state?{state:e.state}:{}})),referenceProfile:`workspace`,tags:K(e.product.tags),generatedAt:r,generator:{name:`businesslens-cli`,version:Rt()},counts:{interfaces:e.interfaces.length,experiences:e.experiences.length,screens:e.screens.length,domains:e.domains.length,entities:e.entities.length,capabilities:e.capabilities.length,capabilityScenarios:e.capabilityScenarios.length,journeys:e.journeys.length,journeyScenarios:e.journeyScenarios.length,businessRules:e.businessRules.length},limitations:e.product.limitations,model:{taxonomies:{scenarioKinds:G(e.scenarioKinds).map(e=>({id:e.id,name:e.name,description:e.description,...e.colorSlot===void 0?{}:{colorSlot:e.colorSlot}}))},interfaces:G(e.interfaces).map(e=>({id:e.id,title:e.doc.title,description:e.doc.lead,type:e.type,actorIds:K(e.actors),entryPoints:e.entryPoints,capabilityBoundary:e.capabilityBoundary,...q(e,[`Capability boundary`],i)})),experiences:G(e.experiences).map(e=>({id:e.id,title:e.doc.title,description:e.doc.lead,actorIds:K(e.actors),interfaceIds:[e.interface],accessMode:e.access,entryPoints:e.entryPoints,capabilityBoundary:e.capabilityBoundary,...q(e,[`Capability boundary`],i)})),screens:G(e.screens).map(e=>({id:e.id,title:e.doc.title,description:e.doc.lead,capabilityIds:K(e.capabilities),entityIds:K(e.entities),capabilityScenarioIds:u(e.id,`capability`),journeyScenarioIds:u(e.id,`journey`),entryPoints:e.entryPoints,information:e.information,actions:e.actions,states:e.states,capabilityBoundary:e.capabilityBoundary,...q(e,[`Information presented`,`Available actions`,`View states`,`Capability boundary`],i)})),domains:G(e.domains).map(e=>({id:e.id,name:e.doc.title,description:e.doc.lead,boundary:e.boundary,...e.colorSlot===void 0?{}:{colorSlot:e.colorSlot},...q(e,[`Boundary`],i)})),entities:G(e.entities).map(e=>({id:e.id,title:e.doc.title,description:e.doc.lead,...e.domain?{domainId:e.domain}:{},kind:e.kind??null,acts:e.acts??null,informationKept:e.informationKept.map(e=>({name:e.name,description:e.description})),relations:e.relations.map(e=>({entityId:e.entity,verb:e.verb,cardinality:e.cardinality})),states:e.states.map(e=>({name:e.title,content:e.description})),...q(e,[`Information kept`,`States`],i)})),capabilities:G(e.capabilities).map(e=>({id:e.id,title:e.doc.title,description:e.doc.lead,...e.domain?{domainId:e.domain}:{},availability:qt(e.availability),...q(e,[],i)})),capabilityScenarios:G(e.capabilityScenarios).map(e=>({id:e.id,capabilityId:e.capability,title:e.doc.title,kindId:e.kind,actorIds:s(e),routes:c(e),trigger:e.trigger,steps:l(e,e.capability),decisionPoints:e.decisionPoints,outcome:e.outcome,edgeCases:e.edgeCases,...q(e,[`Trigger`,`Steps`,`Decision points`,`Outcome`,`Edge cases`],i)})),journeys:G(e.journeys).map(e=>{let t=o.get(e.id)||[],n=new Set(t.filter(e=>e.result===`achieved`).flatMap(e=>e.steps.flatMap(e=>e.capability?[e.capability]:[]))),r=new Set(t.filter(e=>e.result===`not-achieved`).flatMap(e=>e.steps.flatMap(e=>e.capability?[e.capability]:[]))),s=[...r].filter(e=>!n.has(e)),c=[...n].map(e=>a.get(e)?.domain).filter(e=>!!e);return{id:e.id,title:e.doc.title,goal:e.goal,successCriterion:e.successCriterion,actorIds:K(e.actors),capabilityIds:K([...n]),failureOnlyCapabilityIds:K(s),domainIds:K([...new Set(c)]),...q(e,[`Goal`,`Success criterion`],i)}}),journeyScenarios:G(e.journeyScenarios).map(e=>({id:e.id,journeyId:e.journey,title:e.doc.title,kindId:e.kind,actorIds:s(e),result:e.result,routes:c(e),steps:l(e),trigger:e.trigger,decisionPoints:e.decisionPoints,outcome:e.outcome,edgeCases:e.edgeCases,...q(e,[`Trigger`,`Decision points`,`Outcome`,`Edge cases`],i)})),businessRules:G(e.businessRules).map(e=>({id:e.id,title:e.doc.title,statement:e.doc.lead,rationale:e.rationale,appliesTo:e.appliesTo.map(e=>e.type===`context`?{type:`context`,context:{placeId:e.context.place}}:e.type===`entity`?{type:`entity`,entityId:e.id,effect:e.effect??null,from:e.from??null,to:e.to??null,facts:e.facts,contexts:e.contexts.map(e=>({placeId:e.place}))}:{type:e.type,id:e.id,contexts:e.contexts.map(e=>({placeId:e.place}))}),permits:e.permits===void 0?null:e.permits.map(e=>({actorIds:K(e.actors),related:e.related.map(e=>({verb:e.verb,entityId:e.entity})),self:e.self===!0,when:e.when.map(e=>({entityId:e.entity??null,fact:e.fact??null,state:e.state??null,operator:e.operator??null,value:e.value===void 0?null:typeof e.value==`object`?{configuredByEntityId:e.value.configuredBy}:e.value})),unattended:e.unattended===!0,configuredByEntityId:e.configuredBy??null})),...q(e,[`Rationale`],i)}))},coverage:{status:e.coverage.status,method:e.coverage.method,sourceAreas:e.coverage.sourceAreas,unmapped:e.coverage.unmapped,limitations:e.coverage.limitations,rationale:e.coverage.rationale}},f=t.parse(d),p=C(f);if(p.length)throw Error(`Report validation failed:\n- ${p.join(`
10
+ - `)}`);return f}function Zt({modelRoot:e,gitRoot:t}){let n=jt(e),r=t?qe(t):[],i=Gt(n,r);if(!i.ok)throw Error(`Lint failed:\n${i.errors.map(e=>`- ${e}`).join(`
11
+ `)}`);let a=new Date().toISOString().slice(0,10);return Xt(n,a,t??e)}function Qt(e){let t=Mt(e),r=h(Zt(t)),i=It(t.modelRoot,[`.businesslens`,`build`,`report.json`],`${JSON.stringify(r,null,2)}\n`);return It(t.modelRoot,[`.businesslens`,`cache`,`build.json`],`${JSON.stringify({builtAt:new Date().toISOString(),schemaVersion:n},null,2)}\n`),{report:r,outputFile:i}}function $t(e){try{let{outputFile:t}=Qt(e);return console.log(`Exported Blueprint to ${t}.`),0}catch(e){return console.error(e.message),1}}const en=`# Product Model
12
12
 
13
13
  This directory is a **BusinessLens Product Model**: what this product does and
14
14
  for whom. It is plain Markdown tracked in Git, and it is the source of truth for
@@ -35,34 +35,34 @@ intended product behavior.
35
35
  - Never edit \`cache/\`.
36
36
 
37
37
  Documentation: https://businesslens.io
38
- `;function nn(e){let t=O(e,`.businesslens`,`README.md`);return L(t,tn,{encoding:`utf8`}),t}const rn=8*1024*1024,an=[`Opened from a portable Product Report; source-repository navigation was intentionally removed.`,`Implementation alignment has not been verified in this repository.`];function on(e){if(/^https?:\/\//i.test(e))throw new R("`open` accepts local Product Report files. Use `businesslens blueprint pull <blueprint>` for catalog Blueprints.");let t=ie(e)?e:k(process.cwd(),e),n=M(t);if(n.isSymbolicLink()||!n.isFile())throw Error(`The report source must be a regular file, not a symbolic link.`);if(n.size>rn)throw Error(`The report exceeds the 8 MiB safety limit.`);return JSON.parse(P(t,`utf8`))}function q(e){return Object.keys(e).length?`---\n${De(e,{lineWidth:0}).trimEnd()}\n---\n\n`:``}function J(e){return e.map(e=>({kind:e.kind,role:e.role,target:e.target,...e.title?{title:e.title}:{}}))}function sn(e){return e.map(e=>({[e.type]:e.path}))}function cn(e){return e.map(e=>({place:e.placeId}))}function ln(e){return{place:e.placeId}}function un(e){return e.split(`::`)}function dn(e,t){let n=un(t);return n.length===3?O(e,`interfaces`,n[0],`experiences`,n[1],`screens`,`${n[2]}.md`):O(e,`interfaces`,n[0],`screens`,`${n[1]}.md`)}function Y(e,t,n,r){return r?O(e,t,`${n}.md`):O(e,`${t}.md`)}function X(e){return Object.fromEntries(Object.entries(e).filter(([,e])=>e==null||e===``?!1:!Array.isArray(e)||e.length>0))}function fn(e){return e.entities.map(e=>X({entity:e.entityId,as:e.as??void 0,effect:e.effect===`changes`?void 0:e.effect,from:e.from??void 0,to:e.to??void 0}))}function pn(e){return X({actors:e.actorIds,related:e.related.map(e=>({verb:e.verb,entity:e.entityId})),self:e.self?!0:void 0,when:e.when.map(e=>X({entity:e.entityId??void 0,fact:e.fact??void 0,state:e.state??void 0,...e.operator?{[e.operator]:typeof e.value==`object`&&e.value!==null?{configuredBy:e.value.configuredByEntityId}:e.value??void 0}:{}})),unattended:e.unattended?!0:void 0,configuredBy:e.configuredByEntityId??void 0})}function Z(e,t,n,r,i){let a=[`# ${e}`,t];n&&a.push(`## Intent\n\n${n}`);for(let e of r)e.content&&a.push(`## ${e.heading}\n\n${e.content}`);for(let e of i)a.push(`## ${e.heading}${e.content?`\n\n${e.content}`:``}`);return`${a.filter(Boolean).join(`
38
+ `;function tn(e){let t=D(e,`.businesslens`,`README.md`);return R(t,en,{encoding:`utf8`}),t}const nn=8*1024*1024,rn=[`Opened from a portable Product Report; source-repository navigation was intentionally removed.`,`Implementation alignment has not been verified in this repository.`];function an(e){if(/^https?:\/\//i.test(e))throw new z("`open` accepts local Product Report files. Use `businesslens blueprint pull <blueprint>` for catalog Blueprints.");let t=ae(e)?e:O(process.cwd(),e),n=M(t);if(n.isSymbolicLink()||!n.isFile())throw Error(`The report source must be a regular file, not a symbolic link.`);if(n.size>nn)throw Error(`The report exceeds the 8 MiB safety limit.`);return JSON.parse(P(t,`utf8`))}function J(e){return Object.keys(e).length?`---\n${Ee(e,{lineWidth:0}).trimEnd()}\n---\n\n`:``}function Y(e){return e.map(e=>({kind:e.kind,role:e.role,target:e.target,...e.title?{title:e.title}:{}}))}function on(e){return e.map(e=>({[e.type]:e.path}))}function sn(e){return e.map(e=>({place:e.placeId}))}function cn(e){return{place:e.placeId}}function ln(e){return e.split(`::`)}function un(e,t){let n=ln(t);return n.length===3?D(e,`interfaces`,n[0],`experiences`,n[1],`screens`,`${n[2]}.md`):D(e,`interfaces`,n[0],`screens`,`${n[1]}.md`)}function dn(e,t,n,r){return r?D(e,t,`${n}.md`):D(e,`${t}.md`)}function X(e){return Object.fromEntries(Object.entries(e).filter(([,e])=>e==null||e===``?!1:!Array.isArray(e)||e.length>0))}function fn(e){return e.entities.map(e=>X({entity:e.entityId,as:e.as??void 0,effect:e.effect===`changes`?void 0:e.effect,from:e.from??void 0,to:e.to??void 0}))}function pn(e){return X({actors:e.actorIds,related:e.related.map(e=>({verb:e.verb,entity:e.entityId})),self:e.self?!0:void 0,when:e.when.map(e=>X({entity:e.entityId??void 0,fact:e.fact??void 0,state:e.state??void 0,...e.operator?{[e.operator]:typeof e.value==`object`&&e.value!==null?{configuredBy:e.value.configuredByEntityId}:e.value??void 0}:{}})),unattended:e.unattended?!0:void 0,configuredBy:e.configuredByEntityId??void 0})}function Z(e,t,n,r,i){let a=[`# ${e}`,t];n&&a.push(`## Intent\n\n${n}`);for(let e of r)e.content&&a.push(`## ${e.heading}\n\n${e.content}`);for(let e of i)a.push(`## ${e.heading}${e.content?`\n\n${e.content}`:``}`);return`${a.filter(Boolean).join(`
39
39
 
40
- `)}\n`}function Q(e,t){N(k(e,`..`),{recursive:!0}),L(e,t,{encoding:`utf8`,flag:`wx`})}function mn(e,t){N(k(e,`..`),{recursive:!0}),L(e,t,{flag:`wx`})}function hn(e,t){let n=k(e,`.businesslens`);if(j(n)){let e=M(n);if(e.isSymbolicLink()||!e.isDirectory())throw Error(`Refusing to replace a non-directory or symbolic-link .businesslens target.`);if(ye(n).length>0){if(!t)throw Error(`.businesslens/ is not empty. Choose an empty target or pass --force to create a backup first.`);let e=`${n}.backup-${new Date().toISOString().replace(/[:.]/g,`-`)}`;F(n,e),console.log(`Backed up the existing product model to ${e}.`)}else xe(n)}return n}function gn(e,t,n){Q(O(e,`config.yaml`),De({schema:_t,sdd:{paths:[]}},{lineWidth:0})),Q(O(e,`.gitignore`),`build/
40
+ `)}\n`}function Q(e,t){N(O(e,`..`),{recursive:!0}),R(e,t,{encoding:`utf8`,flag:`wx`})}function mn(e,t){N(O(e,`..`),{recursive:!0}),R(e,t,{flag:`wx`})}function hn(e,t){let n=O(e,`.businesslens`);if(j(n)){let e=M(n);if(e.isSymbolicLink()||!e.isDirectory())throw Error(`Refusing to replace a non-directory or symbolic-link .businesslens target.`);if(ye(n).length>0){if(!t)throw Error(`.businesslens/ is not empty. Choose an empty target or pass --force to create a backup first.`);let e=`${n}.backup-${new Date().toISOString().replace(/[:.]/g,`-`)}`;I(n,e),console.log(`Backed up the existing product model to ${e}.`)}else be(n)}return n}function gn(e,t,n){Q(D(e,`config.yaml`),Ee({schema:gt,sdd:{paths:[]}},{lineWidth:0})),Q(D(e,`.gitignore`),`build/
41
41
  cache/
42
- `),Q(O(e,`taxonomies.yaml`),De({scenarioKinds:t.model.taxonomies.scenarioKinds},{lineWidth:0})),Q(n?O(e,`product`,`product.md`):O(e,`product.md`),q(X({id:t.id,summary:t.summary,category:t.category,tags:t.tags,authors:t.authors,license:t.license,limitations:t.limitations,references:J(t.references)}))+Z(t.title,t.description,t.intent,[],t.supportingSections)),Q(O(e,`coverage.md`),q({status:t.coverage.status,method:an,sourceAreas:[],unmapped:t.coverage.unmapped,limitations:[...t.coverage.limitations]})+Z(`Coverage`,t.coverage.rationale,``,[],[]));for(let n of t.model.interfaces){let r=t.model.experiences.some(e=>un(e.id)[0]===n.id)||t.model.screens.some(e=>un(e.id)[0]===n.id);Q(Y(O(e,`interfaces`),n.id,`interface`,r),q(X({type:n.type,actors:n.actorIds,entryPoints:sn(n.entryPoints),references:J(n.references)}))+Z(n.title,n.description,n.intent,[{heading:`Capability boundary`,content:n.capabilityBoundary}],n.supportingSections))}for(let n of t.model.domains)Q(Y(O(e,`domains`),n.id,`domain`,!1),q(X({colorSlot:n.colorSlot,references:J(n.references)}))+Z(n.name,n.description,n.intent,[{heading:`Boundary`,content:n.boundary}],n.supportingSections));for(let n of t.model.entities)Q(Y(O(e,`entities`),n.id,`entity`,!1),q(X({kind:n.kind??void 0,acts:n.acts??void 0,domain:n.domainId,relations:n.relations.length?n.relations.map(e=>({entity:e.entityId,verb:e.verb,cardinality:e.cardinality})):void 0,references:J(n.references)}))+Z(n.title,n.description,n.intent,[],[...n.informationKept.length?[{heading:`Information kept`,content:n.informationKept.map(e=>`- **${e.name}** — ${e.description}`).join(`
42
+ `),Q(D(e,`taxonomies.yaml`),Ee({scenarioKinds:t.model.taxonomies.scenarioKinds},{lineWidth:0})),Q(n?D(e,`product`,`product.md`):D(e,`product.md`),J(X({id:t.id,summary:t.summary,category:t.category,tags:t.tags,authors:t.authors,license:t.license,limitations:t.limitations,references:Y(t.references)}))+Z(t.title,t.description,t.intent,[],t.supportingSections)),Q(D(e,`coverage.md`),J({status:t.coverage.status,method:rn,sourceAreas:[],unmapped:t.coverage.unmapped,limitations:[...t.coverage.limitations]})+Z(`Coverage`,t.coverage.rationale,``,[],[]));for(let n of t.model.interfaces){let r=t.model.experiences.some(e=>ln(e.id)[0]===n.id)||t.model.screens.some(e=>ln(e.id)[0]===n.id);Q(dn(D(e,`interfaces`),n.id,`interface`,r),J(X({type:n.type,actors:n.actorIds,entryPoints:on(n.entryPoints),references:Y(n.references)}))+Z(n.title,n.description,n.intent,[{heading:`Capability boundary`,content:n.capabilityBoundary}],n.supportingSections))}for(let n of t.model.domains)Q(dn(D(e,`domains`),n.id,`domain`,!1),J(X({colorSlot:n.colorSlot,references:Y(n.references)}))+Z(n.name,n.description,n.intent,[{heading:`Boundary`,content:n.boundary}],n.supportingSections));for(let n of t.model.entities)Q(dn(D(e,`entities`),n.id,`entity`,!1),J(X({kind:n.kind??void 0,acts:n.acts??void 0,domain:n.domainId,relations:n.relations.length?n.relations.map(e=>({entity:e.entityId,verb:e.verb,cardinality:e.cardinality})):void 0,references:Y(n.references)}))+Z(n.title,n.description,n.intent,[],[...n.informationKept.length?[{heading:`Information kept`,content:n.informationKept.map(e=>`- **${e.name}** — ${e.description}`).join(`
43
43
  `)}]:[],...n.states.length?[{heading:`States`,content:n.states.map(e=>`### ${e.name}\n\n${e.content}`).join(`
44
44
 
45
- `)}]:[],...n.supportingSections]));for(let n of t.model.experiences){let[r,i]=un(n.id),a=t.model.screens.some(e=>{let t=un(e.id);return t.length===3&&t[0]===r&&t[1]===i});Q(Y(O(e,`interfaces`,r,`experiences`),i,`experience`,a),q(X({actors:n.actorIds,access:n.accessMode,entryPoints:sn(n.entryPoints),references:J(n.references)}))+Z(n.title,n.description,n.intent,[{heading:`Capability boundary`,content:n.capabilityBoundary}],n.supportingSections))}for(let n of t.model.screens){let t=n.states.map(e=>`### ${e.title}\n\n${e.description}`).join(`
45
+ `)}]:[],...n.supportingSections]));for(let n of t.model.experiences){let[r,i]=ln(n.id),a=t.model.screens.some(e=>{let t=ln(e.id);return t.length===3&&t[0]===r&&t[1]===i});Q(dn(D(e,`interfaces`,r,`experiences`),i,`experience`,a),J(X({actors:n.actorIds,access:n.accessMode,entryPoints:on(n.entryPoints),references:Y(n.references)}))+Z(n.title,n.description,n.intent,[{heading:`Capability boundary`,content:n.capabilityBoundary}],n.supportingSections))}for(let n of t.model.screens){let t=n.states.map(e=>`### ${e.title}\n\n${e.description}`).join(`
46
46
 
47
- `);Q(dn(e,n.id),q(X({capabilities:n.capabilityIds,entities:n.entityIds.length?n.entityIds:void 0,entryPoints:sn(n.entryPoints),references:J(n.references)}))+Z(n.title,n.description,n.intent,[{heading:`Information presented`,content:n.information.map(e=>`- ${e}`).join(`
47
+ `);Q(un(e,n.id),J(X({capabilities:n.capabilityIds,entities:n.entityIds.length?n.entityIds:void 0,entryPoints:on(n.entryPoints),references:Y(n.references)}))+Z(n.title,n.description,n.intent,[{heading:`Information presented`,content:n.information.map(e=>`- ${e}`).join(`
48
48
  `)},{heading:`Available actions`,content:n.actions.map(e=>`- ${e}`).join(`
49
- `)},{heading:`View states`,content:t},{heading:`Capability boundary`,content:n.capabilityBoundary}],n.supportingSections))}for(let n of t.model.capabilities){let r=t.model.capabilityScenarios.some(e=>e.capabilityId===n.id);Q(Y(O(e,`capabilities`),n.id,`capability`,r),q(X({domain:n.domainId,availability:cn(n.availability),references:J(n.references)}))+Z(n.title,n.description,n.intent,[],n.supportingSections))}for(let n of t.model.businessRules)Q(Y(O(e,`business-rules`),n.id,`business-rule`,!1),q({appliesTo:n.appliesTo.map(e=>e.type===`context`?{type:`context`,context:ln(e.context)}:e.type===`entity`?X({type:`entity`,id:e.entityId,effect:e.effect??void 0,from:e.from??void 0,to:e.to??void 0,facts:e.facts,contexts:e.contexts.map(ln)}):X({type:e.type,id:e.id,contexts:e.contexts.map(ln)})),...n.permits===null?{}:{permits:n.permits.map(pn)},...X({references:J(n.references)})})+Z(n.title,n.statement,n.intent,[{heading:`Rationale`,content:n.rationale}],n.supportingSections));let r=e=>{let t=e.decisionPoints.map(e=>`### ${e.title}\n\n${e.question}\n\n${e.branches.map(e=>`- ${e.condition} → ${e.outcome}`).join(`
49
+ `)},{heading:`View states`,content:t},{heading:`Capability boundary`,content:n.capabilityBoundary}],n.supportingSections))}for(let n of t.model.capabilities){let r=t.model.capabilityScenarios.some(e=>e.capabilityId===n.id);Q(dn(D(e,`capabilities`),n.id,`capability`,r),J(X({domain:n.domainId,availability:sn(n.availability),references:Y(n.references)}))+Z(n.title,n.description,n.intent,[],n.supportingSections))}for(let n of t.model.businessRules)Q(dn(D(e,`business-rules`),n.id,`business-rule`,!1),J({appliesTo:n.appliesTo.map(e=>e.type===`context`?{type:`context`,context:cn(e.context)}:e.type===`entity`?X({type:`entity`,id:e.entityId,effect:e.effect??void 0,from:e.from??void 0,to:e.to??void 0,facts:e.facts,contexts:e.contexts.map(cn)}):X({type:e.type,id:e.id,contexts:e.contexts.map(cn)})),...n.permits===null?{}:{permits:n.permits.map(pn)},...X({references:Y(n.references)})})+Z(n.title,n.statement,n.intent,[{heading:`Rationale`,content:n.rationale}],n.supportingSections));let r=e=>{let t=e.decisionPoints.map(e=>`### ${e.title}\n\n${e.question}\n\n${e.branches.map(e=>`- ${e.condition} → ${e.outcome}`).join(`
50
50
  `)}`).join(`
51
51
 
52
52
  `);return[{heading:`Trigger`,content:e.trigger},{heading:`Decision points`,content:t},{heading:`Outcome`,content:e.outcome},{heading:`Edge cases`,content:e.edgeCases.map(e=>`- ${e}`).join(`
53
- `)}]};for(let n of t.model.capabilityScenarios)Q(Y(O(e,`capabilities`,n.capabilityId,`scenarios`),n.id,`capability-scenario`,!1),q(X({kind:n.kindId,routes:Object.fromEntries(n.routes.map(e=>[e.id,e.name])),steps:n.steps.map(e=>({...X({text:e.text,kind:e.kind,actor:e.actorId??void 0,unattended:e.unattended?!0:void 0}),entities:fn(e),...X({contexts:e.contexts.length?Object.fromEntries(e.contexts.map(e=>[e.routeId,{place:e.placeId}])):void 0})})),references:J(n.references)}))+Z(n.title,``,n.intent,r(n),n.supportingSections));for(let n of t.model.journeys){let r=t.model.journeyScenarios.some(e=>e.journeyId===n.id);Q(Y(O(e,`journeys`),n.id,`journey`,r),q(X({actors:n.actorIds,references:J(n.references)}))+Z(n.title,``,n.intent,[{heading:`Goal`,content:n.goal},{heading:`Success criterion`,content:n.successCriterion}],n.supportingSections))}for(let n of t.model.journeyScenarios)Q(Y(O(e,`journeys`,n.journeyId,`scenarios`),n.id,`journey-scenario`,!1),q(X({kind:n.kindId,result:n.result,routes:Object.fromEntries(n.routes.map(e=>[e.id,e.name])),steps:n.steps.map(e=>({...X({text:e.text,kind:e.kind,actor:e.actorId??void 0,capability:e.capabilityId??void 0,unattended:e.unattended?!0:void 0}),entities:fn(e),...X({contexts:e.contexts.length?Object.fromEntries(e.contexts.map(e=>[e.routeId,{place:e.placeId}])):void 0})})),references:J(n.references)}))+Z(n.title,``,n.intent,r(n),n.supportingSections))}function _n(e,t,n,r={}){let i;try{let a=p(t),o=p(h(a)),s=E(k(e,`.businesslens`));N(s,{recursive:!0}),i=_e(O(s,`.businesslens-open-`));let c=O(i,`.businesslens`);if(gn(c,o,!!r.logo),r.logo){let e=T(r.logo);if(e.length)throw Error(`The Product logo is invalid: ${e.join(`; `)}`);mn(O(c,`product`,`logo.svg`),r.logo)}nn(i);let l=Kt(Mt(i),[]);if(!l.ok)throw Error(`The report cannot be expanded into a structurally sound Product Model:\n${l.errors.map(e=>`- ${e}`).join(`
54
- `)}`);let u=hn(e,n);return F(c,u),{report:o,root:u}}finally{i&&I(i,{recursive:!0,force:!0})}}async function vn(e,t,n){try{let r=_n(e,on(t),n);return console.log(`Opened Blueprint "${r.report.title}" into ${r.root}.`),0}catch(e){return console.error(e.message),e instanceof R?2:1}}function yn(e=process.env){let t=e.BUSINESSLENS_CONTRIBUTE_UPSTREAM?.trim();if(!t)return`businesslens/pdd`;if(!/^[\w.-]+\/[\w.-]+$/.test(t))throw Error(`BUSINESSLENS_CONTRIBUTE_UPSTREAM must be an "owner/repo" pair.`);return t}var bn=class extends R{};function xn(e,t){let n=pe(`gh`,e,{encoding:`utf8`,cwd:t,maxBuffer:32*1024*1024});if(n.status!==0)throw Error(`gh ${e.join(` `)} failed: ${(n.stderr||n.stdout||``).trim()}`);return n.stdout.trim()}function Sn(){let e=pe(`gh`,[`--version`],{encoding:`utf8`});if(e.status!==0)throw Error("The GitHub CLI (`gh`) is required to contribute a Blueprint.\nInstall it from https://cli.github.com, then run `gh auth login`.");let t=pe(`gh`,[`auth`,`status`],{encoding:`utf8`});if(t.status!==0)throw Error("`gh` is installed but not authenticated. Run `gh auth login` first.")}function Cn(){return xn([`api`,`user`,`--jq`,`.login`])}function wn(e){try{let t=xn([`repo`,`view`,e,`--json`,`owner`,`--jq`,`.owner.login`]);return Cn().toLowerCase()===t.toLowerCase()}catch{return!1}}const Tn=e=>new Promise(t=>{setTimeout(t,e)});function En(e,t,n){try{return xn([`pr`,`list`,`--repo`,e,`--head`,t,`--state`,`open`,`--json`,`url`,`--jq`,`.[0].url`],n)||void 0}catch{return}}async function Dn(e,t){for(let n=1;;n+=1)try{xn([`repo`,`clone`,e,t]);return}catch(e){if(n===3)throw e;await Tn(n*1500)}}async function On(e,t){let n;try{let r=yn();Sn();let{modelRoot:i,gitRoot:a}=Nt(e),o=Mt(i),s=Kt(o,a?Je(a):[]);if(!s.ok)throw Error(`The Product Model has lint errors:\n${s.errors.map(e=>`- ${e}`).join(`
55
- `)}`);let{report:c}=$t(i),l=h(c),u=x(l);if(u.length)throw Error(`The Product is missing public Blueprint metadata:\n${u.map(e=>`- ${e}`).join(`
56
- `)}`);let d=ct(i);if(!d)throw Error(`A public Blueprint requires .businesslens/product/logo.svg.`);let f=Ge(l.id);n=_e(O(Te(),`businesslens-contribute-`));let p=O(n,`model`);if(N(p,{recursive:!0}),_n(p,l,!1,{logo:d.bytes}),!t.yes){if(!process.stdin.isTTY)throw new bn(`Refusing to open a pull request without confirmation in a non-interactive session. Pass --yes.`);let e=Ce({input:process.stdin,output:process.stdout}),t=await e.question(`Open a public pull request adding Blueprint "${f}" to ${r}? [y/N] `);if(e.close(),!/^y(es)?$/i.test(t.trim()))return console.log(`Aborted.`),1}let m=O(n,`checkout`),g=r.split(`/`)[1],_;if(wn(r))console.log(`Cloning ${r}…`),await Dn(r,m);else{console.log(`Forking ${r}…`),xn([`repo`,`fork`,r,`--clone=false`,`--fork-name`,g],n),_=`${Cn()}/${g}`;try{xn([`repo`,`sync`,_,`--source`,r,`--branch`,`main`,`--force`],n)}catch(e){throw Error(`Could not bring ${_} up to date with ${r}:\n${e.message}\n\nContributing from a stale fork would open a pull request containing unrelated
57
- changes. Update the GitHub CLI, or delete ${_} and run this again.`)}console.log(`Cloning ${_}…`),await Dn(_,m)}let v=`blueprint/${f}`;de(`git`,[`-C`,m,`checkout`,`--quiet`,`-b`,v],{stdio:`pipe`});let y=O(m,`blueprints`,f);I(y,{recursive:!0,force:!0}),N(y,{recursive:!0}),ge(O(p,`.businesslens`),O(y,`.businesslens`),{recursive:!0}),I(O(y,`.businesslens`,`build`),{recursive:!0,force:!0}),I(O(y,`.businesslens`,`cache`),{recursive:!0,force:!0}),de(`git`,[`-C`,m,`add`,`-A`],{stdio:`pipe`}),de(`git`,[`-C`,m,`commit`,`-m`,`feat: add the ${f} Blueprint`],{stdio:`pipe`}),de(`git`,[`-C`,m,`push`,`--quiet`,`--force`,`origin`,`HEAD:refs/heads/${v}`],{stdio:`pipe`});let b=_?`${_.split(`/`)[0]}:${v}`:v,S=()=>{_&&console.log(`\nThis used your fork ${_}. GitHub needs it to keep the pull request open, so leave it in place until the Blueprint is merged.`)},C=En(r,b,m);if(C)return console.log(`\nUpdated ${C}`),S(),0;let w=xn([`pr`,`create`,`--repo`,r,`--base`,`main`,`--head`,b,`--title`,`Add the ${f} Blueprint`,`--body`,[`Adds \`blueprints/${f}\`.`,``,`- **Title:** ${l.title}`,`- **Summary:** ${l.summary}`,``,`Everything in this pull request was regenerated from a portable Product Report,`,`so it carries no implementation or repository-local references, no source paths,`,`and no reference to the repository`,"it came from, and is byte-identical to what `businesslens blueprint pull` produces."].join(`
58
- `)],m);return console.log(`\nOpened ${w}`),S(),0}catch(e){return console.error(e.message),e instanceof R?2:1}finally{n&&I(n,{recursive:!0,force:!0})}}const kn=(e,t)=>process.env[e]?.trim()||t,An=[{id:`claude`,name:`Claude Code`,projectRoot:`.claude`,projectSkillsDir:O(`.claude`,`skills`),globalHarnessDirs:e=>[kn(`CLAUDE_CONFIG_DIR`,O(e,`.claude`))],globalSkillsDir:e=>O(kn(`CLAUDE_CONFIG_DIR`,O(e,`.claude`)),`skills`)},{id:`codex`,name:`Codex CLI`,projectRoot:`.agents`,projectSkillsDir:O(`.agents`,`skills`),globalHarnessDirs:e=>[kn(`CODEX_HOME`,O(e,`.codex`))],globalSkillsDir:e=>O(kn(`CODEX_HOME`,O(e,`.codex`)),`skills`)},{id:`cursor`,name:`Cursor`,projectRoot:`.cursor`,projectSkillsDir:O(`.cursor`,`skills`),globalHarnessDirs:e=>[O(e,`.cursor`)],globalSkillsDir:e=>O(e,`.cursor`,`skills`)},{id:`gemini`,name:`Gemini CLI`,projectRoot:`.gemini`,projectSkillsDir:O(`.gemini`,`skills`),globalHarnessDirs:e=>[O(e,`.gemini`)],globalSkillsDir:e=>O(e,`.gemini`,`skills`)},{id:`github`,name:`GitHub Copilot`,projectRoot:`.github`,projectSkillsDir:O(`.github`,`skills`),globalHarnessDirs:e=>[O(e,`.copilot`),O(e,`.github`)],globalSkillsDir:e=>O(e,`.copilot`,`skills`)}],jn={agents:`codex`,claude:`claude`,"claude-code":`claude`,codex:`codex`,copilot:`github`,cursor:`cursor`,gemini:`gemini`,github:`github`};function Mn(e){return An.find(t=>t.id===e)}function Nn(e){if(e===void 0)return;let t=e.split(`,`).map(e=>e.trim().toLowerCase()).filter(Boolean);if(t.length===0)throw new R(`--providers requires at least one provider.`);let n=[],r=[];for(let e of t){let t=jn[e];t?n.includes(t)||n.push(t):r.push(e)}if(r.length>0)throw new R(`Unknown provider(s): ${r.join(`, `)}. Supported providers: ${An.map(e=>e.id).join(`, `)}.`);return n}function Pn(e,t=we()){let n=k(e),r=[];for(let e of An){let i=O(n,e.projectRoot);if(j(i)){r.push({provider:e,detectedAt:i,source:`project`});continue}let a=e.globalHarnessDirs(t).find(e=>j(e));a&&r.push({provider:e,detectedAt:a,source:`global`})}return r}function Fn(e,t,n,r=we()){return t===`project`?O(k(n),e.projectSkillsDir):e.globalSkillsDir(r)}function In(e,t=we()){let n=k(t),r=k(e);return r===n?`~`:r.startsWith(`${n}${A}`)?`~${r.slice(n.length)}`:r}const Ln=[`businesslens-map`,`businesslens-ideate`,`businesslens-verify`],Rn=`.businesslens-install.json`;function zn(){let e=E(Oe(import.meta.url));for(let t of[O(e,`../skills`),O(e,`../../skills`),O(e,`../../../skills`)])if(Ln.every(e=>j(O(t,e,`SKILL.md`))))return t}const Bn=/^[a-z0-9]+(?:-[a-z0-9]+)*$/;function Vn(e){return typeof e==`string`&&Bn.test(e)}function Hn(e,t){if(!Vn(t))return;let n=k(e,t);if(E(n)===k(e))return n}function Un(e){let t=O(e,Rn);if(j(t))try{let e=JSON.parse(P(t,`utf8`));if(e.schema===1&&e.package===`businesslens`&&typeof e.version==`string`&&Array.isArray(e.skills)&&e.skills.every(Vn))return e}catch{}}function Wn(e,t,n){for(let r of Ln){let i=O(e,r);if(!j(i))continue;let a=t?.skills.includes(r)??!1;if(!a&&!n)throw Error(`Refusing to overwrite ${i}: it is not marked as a BusinessLens-managed skill. Pass --force only if you own that directory.`)}}function Gn(e,t,n={}){for(let r of t){let t=Fn(r.provider,r.scope,e,n.home);if(!j(t))continue;Wn(t,Un(t),!!n.force)}}function Kn(e,t){let n=O(e,Rn),r=`${n}.${Pe()}.tmp`;L(r,`${JSON.stringify(t,null,2)}\n`),F(r,n)}function qn(e,t,n){let r=O(e,n),i=O(t,n),a=O(t,`.${n}.${Pe()}.tmp`),o=O(t,`.${n}.${Pe()}.backup`),s=!1;try{ge(r,a,{recursive:!0}),j(i)&&(F(i,o),s=!0),F(a,i),s&&I(o,{recursive:!0,force:!0})}catch(e){throw I(a,{recursive:!0,force:!0}),s&&!j(i)&&j(o)&&F(o,i),e}}function Jn(e,t,n,r={}){let i=zn();if(!i)throw Error(`Bundled BusinessLens skills were not found. Reinstall the businesslens package.`);let a=Fn(t.provider,t.scope,e,r.home);N(a,{recursive:!0});let o=Un(a);Wn(a,o,!!r.force);let s=(o?.skills??[]).filter(e=>!Ln.includes(e)).filter(e=>e.startsWith(`businesslens-`)).map(e=>Hn(a,e)).filter(e=>e!==void 0&&j(e));for(let e of Ln)qn(i,a,e);for(let e of s)j(e)&&I(e,{recursive:!0,force:!0});let c=new Date().toISOString();return Kn(a,{schema:1,package:`businesslens`,version:n,provider:t.provider.id,scope:t.scope,skills:[...Ln],installedAt:o?.installedAt??c,updatedAt:c}),{...t,skillsDir:a}}function Yn(e,t,n,r){let i=[],a=new Set;for(let o of n)for(let n of t){let t=Fn(n,o,e,r);if(a.has(t))continue;a.add(t);let s=Un(t);s&&i.push({provider:n,scope:o,skillsDir:t,manifest:s})}return i}function Xn(e){return!e.yes&&!!(process.stdin.isTTY&&process.stdout.isTTY)}function Zn(e){let t=e.scope?.trim().toLowerCase();if(t&&t!==`project`&&t!==`global`)throw new R(`--scope must be project or global.`);return t}function Qn(e){let t=Pn(e);if(console.log(),console.log(`◇ Detected harnesses`),t.length===0)console.log(` None detected — Claude Code and Codex are the recommended defaults.`);else{let e=Math.max(...t.map(e=>e.provider.name.length));for(let n of t)console.log(` ${n.provider.name.padEnd(e)} ${In(n.detectedAt)}`)}return console.log(),t}async function $n(e,t){let n=Nn(t.providers),r=Qn(e);if(n)return n;let i=r.map(e=>e.provider.id),a=i.length>0?i:[`claude`,`codex`];if(!Xn(t)){if(t.yes)return a;console.error(`Interactive installation requires a terminal. Pass --yes or --providers <list>.`);return}let o=await Me({message:`Install for detected harnesses only, or add more?`,options:[{value:`detected`,label:`Detected only`,hint:a.join(`, `)},{value:`customize`,label:`Customize...`}]});if(Ae(o)){ke(`Installation cancelled.`);return}if(o===`detected`)return a;let s=await je({message:`Select AI harnesses`,options:An.map(e=>({value:e.id,label:e.name})),initialValues:a,required:!0});if(Ae(s)){ke(`Installation cancelled.`);return}return s}async function er(e){let t=Zn(e);if(t)return t;if(!Xn(e)){if(e.yes)return`project`;console.error(`Interactive installation requires a terminal. Pass --scope project|global.`);return}let n=await Me({message:`Installation scope`,options:[{value:`project`,label:`Project`,hint:`Install in the current repository`},{value:`global`,label:`Global`,hint:`Install for this user`}]});if(Ae(n)){ke(`Installation cancelled.`);return}return n}async function tr(e,t={}){try{let n=await $n(e,t);if(!n)return 1;let r=await er(t);if(!r)return 1;let i=n.map(e=>({provider:Mn(e),scope:r}));Gn(e,i,{force:t.force});let a=process.stdout.isTTY?Ne():void 0;a?.start(`Installing BusinessLens skills...`);let o=n.map(n=>Jn(e,{provider:Mn(n),scope:r},zt(),{force:t.force}));a?.stop(`BusinessLens skills installed.`);let s=o.map(e=>In(E(e.skillsDir)));return console.log(`Installed BusinessLens into: ${s.join(`, `)} (${r}).`),console.log(`Installed skills: ${Ln.join(`, `)}.`),0}catch(e){return console.error(e.message),e instanceof R?2:1}}const nr=`https://businesslens.io`;function rr(e){let t=e.replace(/^\[(.*)\]$/,`$1`);return t===`localhost`||t===`::1`?!0:Fe(t)===4?t.split(`.`)[0]===`127`:!1}function ir(e){let t;try{t=new URL(e)}catch{throw new R(`"${e}" is not a valid catalog origin.`)}if(t.username||t.password||t.pathname!==`/`||t.search||t.hash)throw new R(`The catalog origin must be a bare origin, with no credentials, path, query string, or fragment.`);if(t.protocol===`https:`||t.protocol===`http:`&&rr(t.hostname))return t.origin;throw new R(`The catalog origin must use https, except on a loopback development host (localhost, 127.x.x.x, or ::1).`)}function ar(e,t=process.env){return ir(e??t.BUSINESSLENS_CATALOG_URL??nr)}const or=`application/vnd.businesslens.report+json`,sr=n.split(`.`)[0];function cr(e){let t=/;\s*version\s*=\s*"?(\d+)"?/i.exec(e);return t?.[1]??null}const lr=8*1024*1024;async function ur(e,t,n){if(!e.body)return new Uint8Array;let r=e.body.getReader(),i=[],a=0;for(;;){let{done:e,value:o}=await r.read();if(e)break;if(a+=o.byteLength,a>t)throw await r.cancel(),Error(n);i.push(o)}let o=new Uint8Array(a),s=0;for(let e of i)o.set(e,s),s+=e.byteLength;return o}function dr(e,t){let n=``;try{let e=JSON.parse(t);n=typeof e.message==`string`?e.message:typeof e.statusMessage==`string`?e.statusMessage:``}catch{}let r={404:`No such Blueprint in the catalog.`,410:`That Blueprint has been withdrawn from the catalog.`,503:`The catalog is temporarily unavailable. Try again shortly.`},i=r[e]||`Blueprint pull failed with status ${e}.`;return n&&!i.includes(n)?`${i} ${n}`:i}async function fr(e,t,n){try{let r=await e(`${t}/api/v1/blueprints/${n}/logo.svg`,{headers:{accept:`image/svg+xml`,"user-agent":`businesslens/${zt()}`},redirect:`manual`,signal:AbortSignal.timeout(15e3)});if(!r.ok||r.status>=300&&r.status<400)return;let i=Number(r.headers.get(`content-length`)||0);if(i>w)return;let a=await ur(r,w,`The Product logo exceeds the 256 KiB safety limit.`);return T(a).length?void 0:a}catch{return}}async function pr(e,t,n,r={}){let i,a;try{i=Ge(t),a=ar(n.catalog,r.env??process.env)}catch(e){return console.error(e.message),e instanceof R?2:1}let o=encodeURIComponent(i),s=`${a}/api/v1/blueprints/${o}/report.json`,c=r.fetch??globalThis.fetch,l,u;try{if(l=await c(s,{headers:{accept:`${or}; version=${sr}, application/json`,"user-agent":`businesslens/${zt()}`},redirect:`manual`,signal:AbortSignal.timeout(15e3)}),l.status>=300&&l.status<400)throw Error(`Refusing a redirected Blueprint report response.`);let e=Number(l.headers.get(`content-length`)||0);if(e>lr)throw Error(`The Blueprint report exceeds the 8 MiB safety limit.`);u=new TextDecoder().decode(await ur(l,lr,`The Blueprint report exceeds the 8 MiB safety limit.`))}catch(e){return console.error(e.message),1}if(!l.ok)return console.error(dr(l.status,u)),1;let d=l.headers.get(`content-type`)||``;if(!d.includes(or)&&!d.includes(`application/json`))return console.error(`The catalog returned an unexpected Blueprint report content type.`),1;let f=cr(d);if(f!==null&&f!==sr)return console.error(`The catalog serves Product Report version ${f}; this CLI reads version ${sr} only. Update businesslens, or ask the catalog for a current report.`),1;let p=l.headers.get(`x-businesslens-report-digest`),m=l.headers.get(`x-businesslens-blueprint`);if(!p||!/^[a-f0-9]{64}$/i.test(p))return console.error(`The catalog did not provide a valid Blueprint report digest.`),1;if(m!==i)return console.error(`The catalog returned a report for a different Blueprint.`),1;let h;try{h=JSON.parse(u)}catch{return console.error(`The catalog returned invalid Blueprint report JSON.`),1}if(te(h)!==p.toLowerCase())return console.error(`The Blueprint report digest does not match the catalog response.`),1;let g=await fr(c,a,o);try{let t=_n(e,h,n.force,g?{logo:g}:{});return console.log(`Pulled ${i} into ${t.root}.`),0}catch(e){return console.error(e.message),1}}function mr(e){let t=e.scope?.trim().toLowerCase();if(!t)return[`project`,`global`];if(t===`project`||t===`global`)return[t];throw new R(`--scope must be project or global.`)}async function hr(e,t={}){try{let n=Nn(t.providers),r=n?n.map(Mn):An,i=Yn(e,r,mr(t));if(i.length===0)return console.error("No managed BusinessLens skill installation found. Run `businesslens install` first."),1;let a=process.stdout.isTTY?Ne():void 0;a?.start(`Updating BusinessLens skills...`);let o=i.map(n=>Jn(e,{provider:n.provider,scope:n.scope},zt(),{force:t.force}));return a?.stop(`BusinessLens skills updated.`),console.log(`Updated BusinessLens in: ${o.map(e=>In(E(e.skillsDir))).join(`, `)}.`),0}catch(e){return console.error(e.message),e instanceof R?2:1}}function gr(e,t){let n=k(e),r=k(n,t);if(r.startsWith(`${n}${A}`))try{let e=n;for(let t of oe(n,r).split(A))if(e=k(e,t),M(e).isSymbolicLink())return;let t=2*1024*1024,i=M(r);if(!i.isFile()||i.size>t)return;let a=P(r);return a.byteLength>t||a.includes(0)||!Le(a)?void 0:a.toString(`utf8`)}catch{return}}const _r={mjs:`javascript`,cjs:`javascript`,mts:`typescript`,cts:`typescript`,h:`c`,hpp:`cpp`,tf:`hcl`,tfvars:`hcl`,env:`dotenv`,dockerfile:`dockerfile`,makefile:`makefile`,gemfile:`ruby`};function vr(e){let t=e.toLowerCase(),n=_r[t]??t;return Object.hasOwn(Re,n)||Object.hasOwn(ze,n)?n:`text`}const yr=e=>vr(D(e).slice(1)||re(e));async function br(e,t,n={}){let r=e.split(/\r\n|\n|\r/);n.sourceFile&&r.length>1&&r.at(-1)===``&&r.pop();let i=e.length<=25e4&&r.every(e=>e.length<=1e4)?vr(t):`text`,a;if(i!==`text`)try{a=(await Be(r.join(`
53
+ `)}]};for(let n of t.model.capabilityScenarios)Q(dn(D(e,`capabilities`,n.capabilityId,`scenarios`),n.id,`capability-scenario`,!1),J(X({kind:n.kindId,routes:Object.fromEntries(n.routes.map(e=>[e.id,e.name])),steps:n.steps.map(e=>({...X({text:e.text,kind:e.kind,actor:e.actorId??void 0,unattended:e.unattended?!0:void 0}),entities:fn(e),...X({contexts:e.contexts.length?Object.fromEntries(e.contexts.map(e=>[e.routeId,{place:e.placeId}])):void 0})})),references:Y(n.references)}))+Z(n.title,``,n.intent,r(n),n.supportingSections));for(let n of t.model.journeys){let r=t.model.journeyScenarios.some(e=>e.journeyId===n.id);Q(dn(D(e,`journeys`),n.id,`journey`,r),J(X({actors:n.actorIds,references:Y(n.references)}))+Z(n.title,``,n.intent,[{heading:`Goal`,content:n.goal},{heading:`Success criterion`,content:n.successCriterion}],n.supportingSections))}for(let n of t.model.journeyScenarios)Q(dn(D(e,`journeys`,n.journeyId,`scenarios`),n.id,`journey-scenario`,!1),J(X({kind:n.kindId,result:n.result,routes:Object.fromEntries(n.routes.map(e=>[e.id,e.name])),steps:n.steps.map(e=>({...X({text:e.text,kind:e.kind,actor:e.actorId??void 0,capability:e.capabilityId??void 0,unattended:e.unattended?!0:void 0}),entities:fn(e),...X({contexts:e.contexts.length?Object.fromEntries(e.contexts.map(e=>[e.routeId,{place:e.placeId}])):void 0})})),references:Y(n.references)}))+Z(n.title,``,n.intent,r(n),n.supportingSections))}function _n(e,t,n,r={}){let i;try{let a=p(t),o=p(h(a)),s=ie(O(e,`.businesslens`));N(s,{recursive:!0}),i=_e(D(s,`.businesslens-open-`));let c=D(i,`.businesslens`);if(gn(c,o,!!r.logo),r.logo){let e=T(r.logo);if(e.length)throw Error(`The Product logo is invalid: ${e.join(`; `)}`);mn(D(c,`product`,`logo.svg`),r.logo)}tn(i);let l=Gt(jt(i),[]);if(!l.ok)throw Error(`The report cannot be expanded into a structurally sound Product Model:\n${l.errors.map(e=>`- ${e}`).join(`
54
+ `)}`);let u=hn(e,n);return I(c,u),{report:o,root:u}}finally{i&&L(i,{recursive:!0,force:!0})}}async function vn(e,t,n){try{let r=_n(e,an(t),n);return console.log(`Opened Blueprint "${r.report.title}" into ${r.root}.`),0}catch(e){return console.error(e.message),e instanceof z?2:1}}function yn(e=process.env){let t=e.BUSINESSLENS_CONTRIBUTE_UPSTREAM?.trim();if(!t)return`businesslens/pdd`;if(!/^[\w.-]+\/[\w.-]+$/.test(t))throw Error(`BUSINESSLENS_CONTRIBUTE_UPSTREAM must be an "owner/repo" pair.`);return t}var bn=class extends z{};function xn(e,t){let n=A(`gh`,e,{encoding:`utf8`,cwd:t,maxBuffer:32*1024*1024});if(n.status!==0)throw Error(`gh ${e.join(` `)} failed: ${(n.stderr||n.stdout||``).trim()}`);return n.stdout.trim()}function Sn(){let e=A(`gh`,[`--version`],{encoding:`utf8`});if(e.status!==0)throw Error("The GitHub CLI (`gh`) is required to contribute a Blueprint.\nInstall it from https://cli.github.com, then run `gh auth login`.");let t=A(`gh`,[`auth`,`status`],{encoding:`utf8`});if(t.status!==0)throw Error("`gh` is installed but not authenticated. Run `gh auth login` first.")}function Cn(){return xn([`api`,`user`,`--jq`,`.login`])}function wn(e){try{let t=xn([`repo`,`view`,e,`--json`,`owner`,`--jq`,`.owner.login`]);return Cn().toLowerCase()===t.toLowerCase()}catch{return!1}}const Tn=e=>new Promise(t=>{setTimeout(t,e)});function En(e,t,n){try{return xn([`pr`,`list`,`--repo`,e,`--head`,t,`--state`,`open`,`--json`,`url`,`--jq`,`.[0].url`],n)||void 0}catch{return}}async function Dn(e,t){for(let n=1;;n+=1)try{xn([`repo`,`clone`,e,t]);return}catch(e){if(n===3)throw e;await Tn(n*1500)}}async function On(e,t){let n;try{let r=yn();Sn();let{modelRoot:i,gitRoot:a}=Mt(e),o=jt(i),s=Gt(o,a?qe(a):[]);if(!s.ok)throw Error(`The Product Model has lint errors:\n${s.errors.map(e=>`- ${e}`).join(`
55
+ `)}`);let{report:c}=Qt(i),l=h(c),u=x(l);if(u.length)throw Error(`The Product is missing public Blueprint metadata:\n${u.map(e=>`- ${e}`).join(`
56
+ `)}`);let d=st(i);if(!d)throw Error(`A public Blueprint requires .businesslens/product/logo.svg.`);let f=We(l.id);n=_e(D(we(),`businesslens-contribute-`));let p=D(n,`model`);if(N(p,{recursive:!0}),_n(p,l,!1,{logo:d.bytes}),!t.yes){if(!process.stdin.isTTY)throw new bn(`Refusing to open a pull request without confirmation in a non-interactive session. Pass --yes.`);let e=Se({input:process.stdin,output:process.stdout}),t=await e.question(`Open a public pull request adding Blueprint "${f}" to ${r}? [y/N] `);if(e.close(),!/^y(es)?$/i.test(t.trim()))return console.log(`Aborted.`),1}let m=D(n,`checkout`),g=r.split(`/`)[1],_;if(wn(r))console.log(`Cloning ${r}…`),await Dn(r,m);else{console.log(`Forking ${r}…`),xn([`repo`,`fork`,r,`--clone=false`,`--fork-name`,g],n),_=`${Cn()}/${g}`;try{xn([`repo`,`sync`,_,`--source`,r,`--branch`,`main`,`--force`],n)}catch(e){throw Error(`Could not bring ${_} up to date with ${r}:\n${e.message}\n\nContributing from a stale fork would open a pull request containing unrelated
57
+ changes. Update the GitHub CLI, or delete ${_} and run this again.`)}console.log(`Cloning ${_}…`),await Dn(_,m)}let v=`blueprint/${f}`;fe(`git`,[`-C`,m,`checkout`,`--quiet`,`-b`,v],{stdio:`pipe`});let y=D(m,`blueprints`,f);L(y,{recursive:!0,force:!0}),N(y,{recursive:!0}),ge(D(p,`.businesslens`),D(y,`.businesslens`),{recursive:!0}),L(D(y,`.businesslens`,`build`),{recursive:!0,force:!0}),L(D(y,`.businesslens`,`cache`),{recursive:!0,force:!0}),fe(`git`,[`-C`,m,`add`,`-A`],{stdio:`pipe`}),fe(`git`,[`-C`,m,`commit`,`-m`,`feat: add the ${f} Blueprint`],{stdio:`pipe`}),fe(`git`,[`-C`,m,`push`,`--quiet`,`--force`,`origin`,`HEAD:refs/heads/${v}`],{stdio:`pipe`});let b=_?`${_.split(`/`)[0]}:${v}`:v,S=()=>{_&&console.log(`\nThis used your fork ${_}. GitHub needs it to keep the pull request open, so leave it in place until the Blueprint is merged.`)},C=En(r,b,m);if(C)return console.log(`\nUpdated ${C}`),S(),0;let w=xn([`pr`,`create`,`--repo`,r,`--base`,`main`,`--head`,b,`--title`,`Add the ${f} Blueprint`,`--body`,[`Adds \`blueprints/${f}\`.`,``,`- **Title:** ${l.title}`,`- **Summary:** ${l.summary}`,``,`Everything in this pull request was regenerated from a portable Product Report,`,`so it carries no implementation or repository-local references, no source paths,`,`and no reference to the repository`,"it came from, and is byte-identical to what `businesslens blueprint pull` produces."].join(`
58
+ `)],m);return console.log(`\nOpened ${w}`),S(),0}catch(e){return console.error(e.message),e instanceof z?2:1}finally{n&&L(n,{recursive:!0,force:!0})}}const kn=(e,t)=>process.env[e]?.trim()||t,An=[{id:`claude`,name:`Claude Code`,projectRoot:`.claude`,projectSkillsDir:D(`.claude`,`skills`),globalHarnessDirs:e=>[kn(`CLAUDE_CONFIG_DIR`,D(e,`.claude`))],globalSkillsDir:e=>D(kn(`CLAUDE_CONFIG_DIR`,D(e,`.claude`)),`skills`)},{id:`codex`,name:`Codex CLI`,projectRoot:`.agents`,projectSkillsDir:D(`.agents`,`skills`),globalHarnessDirs:e=>[kn(`CODEX_HOME`,D(e,`.codex`))],globalSkillsDir:e=>D(kn(`CODEX_HOME`,D(e,`.codex`)),`skills`)},{id:`cursor`,name:`Cursor`,projectRoot:`.cursor`,projectSkillsDir:D(`.cursor`,`skills`),globalHarnessDirs:e=>[D(e,`.cursor`)],globalSkillsDir:e=>D(e,`.cursor`,`skills`)},{id:`gemini`,name:`Gemini CLI`,projectRoot:`.gemini`,projectSkillsDir:D(`.gemini`,`skills`),globalHarnessDirs:e=>[D(e,`.gemini`)],globalSkillsDir:e=>D(e,`.gemini`,`skills`)},{id:`github`,name:`GitHub Copilot`,projectRoot:`.github`,projectSkillsDir:D(`.github`,`skills`),globalHarnessDirs:e=>[D(e,`.copilot`),D(e,`.github`)],globalSkillsDir:e=>D(e,`.copilot`,`skills`)}],jn={agents:`codex`,claude:`claude`,"claude-code":`claude`,codex:`codex`,copilot:`github`,cursor:`cursor`,gemini:`gemini`,github:`github`};function Mn(e){return An.find(t=>t.id===e)}function Nn(e){if(e===void 0)return;let t=e.split(`,`).map(e=>e.trim().toLowerCase()).filter(Boolean);if(t.length===0)throw new z(`--providers requires at least one provider.`);let n=[],r=[];for(let e of t){let t=jn[e];t?n.includes(t)||n.push(t):r.push(e)}if(r.length>0)throw new z(`Unknown provider(s): ${r.join(`, `)}. Supported providers: ${An.map(e=>e.id).join(`, `)}.`);return n}function Pn(e,t=Ce()){let n=O(e),r=[];for(let e of An){let i=D(n,e.projectRoot);if(j(i)){r.push({provider:e,detectedAt:i,source:`project`});continue}let a=e.globalHarnessDirs(t).find(e=>j(e));a&&r.push({provider:e,detectedAt:a,source:`global`})}return r}function Fn(e,t,n,r=Ce()){return t===`project`?D(O(n),e.projectSkillsDir):e.globalSkillsDir(r)}function In(e,t=Ce()){let n=O(t),r=O(e);return r===n?`~`:r.startsWith(`${n}${k}`)?`~${r.slice(n.length)}`:r}const Ln=[`businesslens-map`,`businesslens-ideate`,`businesslens-verify`],Rn=`.businesslens-install.json`;function zn(){let e=ie(De(import.meta.url));for(let t of[D(e,`../skills`),D(e,`../../skills`),D(e,`../../../skills`)])if(Ln.every(e=>j(D(t,e,`SKILL.md`))))return t}const Bn=/^[a-z0-9]+(?:-[a-z0-9]+)*$/;function Vn(e){return typeof e==`string`&&Bn.test(e)}function Hn(e,t){if(!Vn(t))return;let n=O(e,t);if(ie(n)===O(e))return n}function Un(e){let t=D(e,Rn);if(j(t))try{let e=JSON.parse(P(t,`utf8`));if(e.schema===1&&e.package===`businesslens`&&typeof e.version==`string`&&Array.isArray(e.skills)&&e.skills.every(Vn))return e}catch{}}function Wn(e,t,n){for(let r of Ln){let i=D(e,r);if(!j(i))continue;let a=t?.skills.includes(r)??!1;if(!a&&!n)throw Error(`Refusing to overwrite ${i}: it is not marked as a BusinessLens-managed skill. Pass --force only if you own that directory.`)}}function Gn(e,t,n={}){for(let r of t){let t=Fn(r.provider,r.scope,e,n.home);if(!j(t))continue;Wn(t,Un(t),!!n.force)}}function Kn(e,t){let n=D(e,Rn),r=`${n}.${Ne()}.tmp`;R(r,`${JSON.stringify(t,null,2)}\n`),I(r,n)}function qn(e,t,n){let r=D(e,n),i=D(t,n),a=D(t,`.${n}.${Ne()}.tmp`),o=D(t,`.${n}.${Ne()}.backup`),s=!1;try{ge(r,a,{recursive:!0}),j(i)&&(I(i,o),s=!0),I(a,i),s&&L(o,{recursive:!0,force:!0})}catch(e){throw L(a,{recursive:!0,force:!0}),s&&!j(i)&&j(o)&&I(o,i),e}}function Jn(e,t,n,r={}){let i=zn();if(!i)throw Error(`Bundled BusinessLens skills were not found. Reinstall the businesslens package.`);let a=Fn(t.provider,t.scope,e,r.home);N(a,{recursive:!0});let o=Un(a);Wn(a,o,!!r.force);let s=(o?.skills??[]).filter(e=>!Ln.includes(e)).filter(e=>e.startsWith(`businesslens-`)).map(e=>Hn(a,e)).filter(e=>e!==void 0&&j(e));for(let e of Ln)qn(i,a,e);for(let e of s)j(e)&&L(e,{recursive:!0,force:!0});let c=new Date().toISOString();return Kn(a,{schema:1,package:`businesslens`,version:n,provider:t.provider.id,scope:t.scope,skills:[...Ln],installedAt:o?.installedAt??c,updatedAt:c}),{...t,skillsDir:a}}function Yn(e,t,n,r){let i=[],a=new Set;for(let o of n)for(let n of t){let t=Fn(n,o,e,r);if(a.has(t))continue;a.add(t);let s=Un(t);s&&i.push({provider:n,scope:o,skillsDir:t,manifest:s})}return i}function Xn(e){return!e.yes&&!!(process.stdin.isTTY&&process.stdout.isTTY)}function Zn(e){let t=e.scope?.trim().toLowerCase();if(t&&t!==`project`&&t!==`global`)throw new z(`--scope must be project or global.`);return t}function Qn(e){let t=Pn(e);if(console.log(),console.log(`◇ Detected harnesses`),t.length===0)console.log(` None detected — Claude Code and Codex are the recommended defaults.`);else{let e=Math.max(...t.map(e=>e.provider.name.length));for(let n of t)console.log(` ${n.provider.name.padEnd(e)} ${In(n.detectedAt)}`)}return console.log(),t}async function $n(e,t){let n=Nn(t.providers),r=Qn(e);if(n)return n;let i=r.map(e=>e.provider.id),a=i.length>0?i:[`claude`,`codex`];if(!Xn(t)){if(t.yes)return a;console.error(`Interactive installation requires a terminal. Pass --yes or --providers <list>.`);return}let o=await je({message:`Install for detected harnesses only, or add more?`,options:[{value:`detected`,label:`Detected only`,hint:a.join(`, `)},{value:`customize`,label:`Customize...`}]});if(ke(o)){Oe(`Installation cancelled.`);return}if(o===`detected`)return a;let s=await Ae({message:`Select AI harnesses`,options:An.map(e=>({value:e.id,label:e.name})),initialValues:a,required:!0});if(ke(s)){Oe(`Installation cancelled.`);return}return s}async function er(e){let t=Zn(e);if(t)return t;if(!Xn(e)){if(e.yes)return`project`;console.error(`Interactive installation requires a terminal. Pass --scope project|global.`);return}let n=await je({message:`Installation scope`,options:[{value:`project`,label:`Project`,hint:`Install in the current repository`},{value:`global`,label:`Global`,hint:`Install for this user`}]});if(ke(n)){Oe(`Installation cancelled.`);return}return n}async function tr(e,t={}){try{let n=await $n(e,t);if(!n)return 1;let r=await er(t);if(!r)return 1;let i=n.map(e=>({provider:Mn(e),scope:r}));Gn(e,i,{force:t.force});let a=process.stdout.isTTY?Me():void 0;a?.start(`Installing BusinessLens skills...`);let o=n.map(n=>Jn(e,{provider:Mn(n),scope:r},Rt(),{force:t.force}));a?.stop(`BusinessLens skills installed.`);let s=o.map(e=>In(ie(e.skillsDir)));return console.log(`Installed BusinessLens into: ${s.join(`, `)} (${r}).`),console.log(`Installed skills: ${Ln.join(`, `)}.`),0}catch(e){return console.error(e.message),e instanceof z?2:1}}const nr=`https://businesslens.io`;function rr(e){let t=e.replace(/^\[(.*)\]$/,`$1`);return t===`localhost`||t===`::1`?!0:Pe(t)===4?t.split(`.`)[0]===`127`:!1}function ir(e){let t;try{t=new URL(e)}catch{throw new z(`"${e}" is not a valid catalog origin.`)}if(t.username||t.password||t.pathname!==`/`||t.search||t.hash)throw new z(`The catalog origin must be a bare origin, with no credentials, path, query string, or fragment.`);if(t.protocol===`https:`||t.protocol===`http:`&&rr(t.hostname))return t.origin;throw new z(`The catalog origin must use https, except on a loopback development host (localhost, 127.x.x.x, or ::1).`)}function ar(e,t=process.env){return ir(e??t.BUSINESSLENS_CATALOG_URL??nr)}const or=`application/vnd.businesslens.report+json`,sr=n.split(`.`)[0];function cr(e){let t=/;\s*version\s*=\s*"?(\d+)"?/i.exec(e);return t?.[1]??null}const lr=8*1024*1024;async function ur(e,t,n){if(!e.body)return new Uint8Array;let r=e.body.getReader(),i=[],a=0;for(;;){let{done:e,value:o}=await r.read();if(e)break;if(a+=o.byteLength,a>t)throw await r.cancel(),Error(n);i.push(o)}let o=new Uint8Array(a),s=0;for(let e of i)o.set(e,s),s+=e.byteLength;return o}function dr(e,t){let n=``;try{let e=JSON.parse(t);n=typeof e.message==`string`?e.message:typeof e.statusMessage==`string`?e.statusMessage:``}catch{}let r={404:`No such Blueprint in the catalog.`,410:`That Blueprint has been withdrawn from the catalog.`,503:`The catalog is temporarily unavailable. Try again shortly.`},i=r[e]||`Blueprint pull failed with status ${e}.`;return n&&!i.includes(n)?`${i} ${n}`:i}async function fr(e,t,n){try{let r=await e(`${t}/api/v1/blueprints/${n}/logo.svg`,{headers:{accept:`image/svg+xml`,"user-agent":`businesslens/${Rt()}`},redirect:`manual`,signal:AbortSignal.timeout(15e3)});if(!r.ok||r.status>=300&&r.status<400)return;let i=Number(r.headers.get(`content-length`)||0);if(i>w)return;let a=await ur(r,w,`The Product logo exceeds the 256 KiB safety limit.`);return T(a).length?void 0:a}catch{return}}async function pr(e,t,n,r={}){let i,a;try{i=We(t),a=ar(n.catalog,r.env??process.env)}catch(e){return console.error(e.message),e instanceof z?2:1}let o=encodeURIComponent(i),s=`${a}/api/v1/blueprints/${o}/report.json`,c=r.fetch??globalThis.fetch,l,u;try{if(l=await c(s,{headers:{accept:`${or}; version=${sr}, application/json`,"user-agent":`businesslens/${Rt()}`},redirect:`manual`,signal:AbortSignal.timeout(15e3)}),l.status>=300&&l.status<400)throw Error(`Refusing a redirected Blueprint report response.`);let e=Number(l.headers.get(`content-length`)||0);if(e>lr)throw Error(`The Blueprint report exceeds the 8 MiB safety limit.`);u=new TextDecoder().decode(await ur(l,lr,`The Blueprint report exceeds the 8 MiB safety limit.`))}catch(e){return console.error(e.message),1}if(!l.ok)return console.error(dr(l.status,u)),1;let d=l.headers.get(`content-type`)||``;if(!d.includes(or)&&!d.includes(`application/json`))return console.error(`The catalog returned an unexpected Blueprint report content type.`),1;let f=cr(d);if(f!==null&&f!==sr)return console.error(`The catalog serves Product Report version ${f}; this CLI reads version ${sr} only. Update businesslens, or ask the catalog for a current report.`),1;let p=l.headers.get(`x-businesslens-report-digest`),m=l.headers.get(`x-businesslens-blueprint`);if(!p||!/^[a-f0-9]{64}$/i.test(p))return console.error(`The catalog did not provide a valid Blueprint report digest.`),1;if(m!==i)return console.error(`The catalog returned a report for a different Blueprint.`),1;let h;try{h=JSON.parse(u)}catch{return console.error(`The catalog returned invalid Blueprint report JSON.`),1}if(te(h)!==p.toLowerCase())return console.error(`The Blueprint report digest does not match the catalog response.`),1;let g=await fr(c,a,o);try{let t=_n(e,h,n.force,g?{logo:g}:{});return console.log(`Pulled ${i} into ${t.root}.`),0}catch(e){return console.error(e.message),1}}function mr(e){let t=e.scope?.trim().toLowerCase();if(!t)return[`project`,`global`];if(t===`project`||t===`global`)return[t];throw new z(`--scope must be project or global.`)}async function hr(e,t={}){try{let n=Nn(t.providers),r=n?n.map(Mn):An,i=Yn(e,r,mr(t));if(i.length===0)return console.error("No managed BusinessLens skill installation found. Run `businesslens install` first."),1;let a=process.stdout.isTTY?Me():void 0;a?.start(`Updating BusinessLens skills...`);let o=i.map(n=>Jn(e,{provider:n.provider,scope:n.scope},Rt(),{force:t.force}));return a?.stop(`BusinessLens skills updated.`),console.log(`Updated BusinessLens in: ${o.map(e=>In(ie(e.skillsDir))).join(`, `)}.`),0}catch(e){return console.error(e.message),e instanceof z?2:1}}const gr=/^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$/;function _r(e,t){let n=t.replace(/\.git$/i,``);if(!gr.test(e)||!gr.test(n))throw new z(`"${e}/${t}" is not a valid GitHub repository name.`);return{owner:e,name:n}}function vr(e){if(!/^[1-9]\d*$/.test(e))throw new z(`"${e}" is not a valid pull request number.`);return Number(e)}function yr(e){let t=e.trim(),n=()=>new z(`"${e}" is not a GitHub repository. Use owner/repo, a github.com URL, or git@github.com:owner/repo.`),r=t.match(/^git@github\.com:([^/]+)\/([^/]+?)\/?$/);if(r){let{owner:e,name:t}=_r(r[1],r[2]);return{owner:e,name:t,cloneUrl:`git@github.com:${e}/${t}.git`}}let i=t.match(/^([^/@:]+)\/([^/@:]+)$/);if(i){let{owner:e,name:t}=_r(i[1],i[2]);return{owner:e,name:t,cloneUrl:`https://github.com/${e}/${t}.git`}}let a;try{a=new URL(/^[a-z]+:\/\//i.test(t)?t:`https://${t}`)}catch{throw n()}if(a.hostname.toLowerCase()!==`github.com`&&a.hostname.toLowerCase()!==`www.github.com`||a.protocol!==`https:`&&a.protocol!==`http:`||a.username||a.password)throw n();let o=a.pathname.split(`/`).filter(Boolean).map(e=>decodeURIComponent(e));if(o.length<2)throw n();let{owner:s,name:c}=_r(o[0],o[1]),l={owner:s,name:c,cloneUrl:`https://github.com/${s}/${c}.git`},u=o.slice(2);if(u.length===0)return l;if(u[0]===`tree`&&u.length>=2)return{...l,ref:{kind:`branch`,name:u.slice(1).join(`/`)}};if(u[0]===`pull`&&u.length===2)return{...l,ref:{kind:`pull`,number:vr(u[1])}};throw new z(`"${e}" is not a repository, branch, or pull request URL. Use https://github.com/owner/repo, .../tree/<branch>, or .../pull/<number>.`)}function br(e){return e?e.kind===`pull`?`pull request #${e.number}`:`branch ${e.name}`:`default branch`}function xr(e){return e?e.kind===`pull`?`refs/pull/${e.number}/head`:e.name:`HEAD`}function Sr(e,t){let n=F(_e(D(we(),`businesslens-view-`))),r=!1,i=()=>{r||(r=!0,L(n,{recursive:!0,force:!0}))};try{Ge(n,`init`,`--quiet`),Ge(n,`remote`,`add`,`origin`,e.cloneUrl);try{Ge(n,`fetch`,`--quiet`,`--depth`,`1`,`--no-tags`,`origin`,xr(t))}catch(n){throw Error(`Could not fetch ${br(t)} of ${e.owner}/${e.name}:\n${n.message}`)}return Ge(n,`checkout`,`--quiet`,`--detach`,`FETCH_HEAD`),{root:n,commit:Ge(n,`rev-parse`,`HEAD`),dispose:i}}catch(e){throw i(),e}}function Cr(e,t){let n=O(e),r=O(n,t);if(r.startsWith(`${n}${k}`))try{let e=n;for(let t of se(n,r).split(k))if(e=O(e,t),M(e).isSymbolicLink())return;let t=2*1024*1024,i=M(r);if(!i.isFile()||i.size>t)return;let a=P(r);return a.byteLength>t||a.includes(0)||!Ie(a)?void 0:a.toString(`utf8`)}catch{return}}const wr={mjs:`javascript`,cjs:`javascript`,mts:`typescript`,cts:`typescript`,h:`c`,hpp:`cpp`,tf:`hcl`,tfvars:`hcl`,env:`dotenv`,dockerfile:`dockerfile`,makefile:`makefile`,gemfile:`ruby`};function Tr(e){let t=e.toLowerCase(),n=wr[t]??t;return Object.hasOwn(Le,n)||Object.hasOwn(Re,n)?n:`text`}const Er=e=>Tr(E(e).slice(1)||re(e));async function Dr(e,t,n={}){let r=e.split(/\r\n|\n|\r/);n.sourceFile&&r.length>1&&r.at(-1)===``&&r.pop();let i=e.length<=25e4&&r.every(e=>e.length<=1e4)?Tr(t):`text`,a;if(i!==`text`)try{a=(await ze(r.join(`
59
59
  `),{lang:i,themes:{light:`github-light`,dark:`github-dark`},defaultColor:!1})).tokens}catch{}let o=r.map((e,t)=>{let i=t+1,o=n.first!==void 0&&i>=n.first&&i<=n.last,s=a?.[t]?.map(e=>[`span`,{style:e.htmlStyle??{}},e.content])??[e];return n.sourceFile&&(i===(n.first??1)&&s.unshift([`span`,{id:`reference`}]),s.unshift([`a`,{class:`blr-line-number`,href:`#L`+i,"aria-label":`Line `+i},String(i)])),[`span`,{class:`line`+(o?` highlight`:``),...n.sourceFile?{id:`L`+i}:{}},...s,...t<r.length-1?[`
60
- `]:[]]});return[`pre`,{class:`shiki`+(n.sourceFile?` blr-source-code`:``),language:t,code:e,...n.filename?{filename:n.filename}:{}},[`code`,{},...o]]}function xr(e,t){if(e?.referenceProfile!==`workspace`)return;let n=[...e.references,...Object.values(_(e.model)).flatMap(e=>e.flatMap(e=>e.references))];return n.find(e=>e.kind===`code`&&e.target===t)}function Sr(e,t){if(t.startLine!==void 0){let n=t.endLine??t.startLine,r=n===t.startLine?`Line ${n}.`:`Lines ${t.startLine}–${n}.`;return t.startLine<1||t.startLine>e.length?{note:`${r} The requested line is outside this file.`}:{first:t.startLine,last:Math.min(n,e.length),note:n>e.length?`${r} The range extends beyond this file.`:r}}if(!t.symbol)return{note:``};let n=t.symbol.split(/[.:#]+/).filter(Boolean).at(-1)??t.symbol;for(let r of[...new Set([t.symbol,n])]){let n=r.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`),i=RegExp(`(^|[^\\w$])${n}($|[^\\w$])`),a=e.findIndex(e=>i.test(e));if(a!==-1)return{first:a+1,last:a+1,note:`First text match for ${t.symbol}.`}}return{note:`No text match for ${t.symbol} in this file.`}}async function Cr(e,t,n){let r=xr(e,n),i=r&&d(r.target,[],`reference`),a=t&&i?gr(t,i.path):void 0;if(!r||!i||a===void 0)return{status:404,data:{message:`This code reference is unavailable in the current local report.`}};let o=a.split(/\r\n|\n|\r/);o.length>1&&o.at(-1)===``&&o.pop();let s=Sr(o,i);return{status:200,data:{kind:`code`,path:i.path,details:String(o.length)+(o.length===1?` line`:` lines`)+(s.note?` · `+s.note:``),document:{nodes:[await br(a,yr(i.path),{sourceFile:!0,...s})],frontmatter:{},meta:{}}}}}const wr=e=>`/_businesslens/file/`+e.split(`/`).map(encodeURIComponent).join(`/`),Tr=/\.(?:avif|gif|jpe?g|png|svg|webp)$/i,Er=/\.(?:avif|gif|jpe?g|md|pdf|png|svg|txt|webp)$/i;function Dr(e,t,n=!1){if(/^[\s]*\/\//.test(e)&&(e=`https:`+e),/^https?:\/\//i.test(e)||!n&&/^mailto:/i.test(e)||!n&&e.startsWith(`#`))return e;if(/^[a-z][a-z\d+.-]*:/i.test(e)||/[\\\u0000-\u0020]/.test(e))return;let r=e.search(/[?#]/),i=r<0?``:e.slice(r),a;try{a=decodeURIComponent(r<0?e:e.slice(0,r))}catch{return}if(/[\\\u0000-\u001f]/.test(a))return;let o=a.startsWith(`/`)?ae.normalize(a.slice(1)):ae.join(ae.dirname(t),a);if(!(o===`..`||o.startsWith(`../`)||o.startsWith(`/`))&&(n?Tr:Er).test(o))return wr(o)+i}const Or=Ve({registerDefaultPlugins:!1,autoUnwrap:!1,autoClose:!1,linkify:!0}),kr=new Set(`p.h1.h2.h3.h4.h5.h6.strong.em.s.del.ul.ol.li.blockquote.hr.br.a.img.code.pre.table.thead.tbody.tr.th.td`.split(`.`)),Ar=e=>e.map(e=>typeof e==`string`?e:Ar(e.slice(2))).join(``);async function jr(e,t){let n=[];for(let r of e){if(typeof r==`string`){n.push(r);continue}let[e,i,...a]=r;if(!e)continue;if(!kr.has(e)){n.push(...await jr(a,t));continue}if(e===`pre`){n.push(await br(Ar(a),String(i.language??`text`),{filename:typeof i.filename==`string`?i.filename:void 0}));continue}let o={};if(/^h[1-6]$/.test(e)&&typeof i.id==`string`&&(o.id=i.id),e===`ol`&&Number.isInteger(Number(i.start))&&(o.start=Number(i.start)),(e===`th`||e===`td`)&&/^(?:text-align:\s*)?(?:left|right|center);?$/.test(String(i.style))&&(o.style=i.style),e===`a`||e===`img`){let r=Dr(String(i[e===`a`?`href`:`src`]??``),t,e===`img`);if(!r){n.push(e===`img`?String(i.alt||Ar(a)||`Image`)+` (image unavailable)`:Ar(a));continue}o[e===`a`?`href`:`src`]=r,typeof i.title==`string`&&(o.title=i.title),e===`img`?(o.alt=String(i.alt??Ar(a)),o.loading=`lazy`):/^(?:https?:|mailto:)/i.test(r)&&(o.target=`_blank`,o.rel=`noopener noreferrer`,o[`data-external`]=``,o[`aria-description`]=`Opens in a new tab`)}n.push([e,o,...await jr(a,t)])}return n}async function Mr(e,t){let n=/\.md$/i.test(t)?gr(e,t):void 0;if(n===void 0)return{status:404,data:{message:`This document is unavailable in the local repository.`}};let r=n.replace(/^\uFEFF/,``).replace(/\r\n?/g,`
60
+ `]:[]]});return[`pre`,{class:`shiki`+(n.sourceFile?` blr-source-code`:``),language:t,code:e,...n.filename?{filename:n.filename}:{}},[`code`,{},...o]]}function Or(e,t){if(e?.referenceProfile!==`workspace`)return;let n=[...e.references,...Object.values(_(e.model)).flatMap(e=>e.flatMap(e=>e.references))];return n.find(e=>e.kind===`code`&&e.target===t)}function kr(e,t){if(t.startLine!==void 0){let n=t.endLine??t.startLine,r=n===t.startLine?`Line ${n}.`:`Lines ${t.startLine}–${n}.`;return t.startLine<1||t.startLine>e.length?{note:`${r} The requested line is outside this file.`}:{first:t.startLine,last:Math.min(n,e.length),note:n>e.length?`${r} The range extends beyond this file.`:r}}if(!t.symbol)return{note:``};let n=t.symbol.split(/[.:#]+/).filter(Boolean).at(-1)??t.symbol;for(let r of[...new Set([t.symbol,n])]){let n=r.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`),i=RegExp(`(^|[^\\w$])${n}($|[^\\w$])`),a=e.findIndex(e=>i.test(e));if(a!==-1)return{first:a+1,last:a+1,note:`First text match for ${t.symbol}.`}}return{note:`No text match for ${t.symbol} in this file.`}}async function Ar(e,t,n){let r=Or(e,n),i=r&&d(r.target,[],`reference`),a=t&&i?Cr(t,i.path):void 0;if(!r||!i||a===void 0)return{status:404,data:{message:`This code reference is unavailable in the current local report.`}};let o=a.split(/\r\n|\n|\r/);o.length>1&&o.at(-1)===``&&o.pop();let s=kr(o,i);return{status:200,data:{kind:`code`,path:i.path,details:String(o.length)+(o.length===1?` line`:` lines`)+(s.note?` · `+s.note:``),document:{nodes:[await Dr(a,Er(i.path),{sourceFile:!0,...s})],frontmatter:{},meta:{}}}}}const jr=e=>`/_businesslens/file/`+e.split(`/`).map(encodeURIComponent).join(`/`),Mr=/\.(?:avif|gif|jpe?g|png|svg|webp)$/i,Nr=/\.(?:avif|gif|jpe?g|md|pdf|png|svg|txt|webp)$/i;function Pr(e,t,n=!1){if(/^[\s]*\/\//.test(e)&&(e=`https:`+e),/^https?:\/\//i.test(e)||!n&&/^mailto:/i.test(e)||!n&&e.startsWith(`#`))return e;if(/^[a-z][a-z\d+.-]*:/i.test(e)||/[\\\u0000-\u0020]/.test(e))return;let r=e.search(/[?#]/),i=r<0?``:e.slice(r),a;try{a=decodeURIComponent(r<0?e:e.slice(0,r))}catch{return}if(/[\\\u0000-\u001f]/.test(a))return;let o=a.startsWith(`/`)?oe.normalize(a.slice(1)):oe.join(oe.dirname(t),a);if(!(o===`..`||o.startsWith(`../`)||o.startsWith(`/`))&&(n?Mr:Nr).test(o))return jr(o)+i}const Fr=Be({registerDefaultPlugins:!1,autoUnwrap:!1,autoClose:!1,linkify:!0}),Ir=new Set(`p.h1.h2.h3.h4.h5.h6.strong.em.s.del.ul.ol.li.blockquote.hr.br.a.img.code.pre.table.thead.tbody.tr.th.td`.split(`.`)),Lr=e=>e.map(e=>typeof e==`string`?e:Lr(e.slice(2))).join(``);async function Rr(e,t){let n=[];for(let r of e){if(typeof r==`string`){n.push(r);continue}let[e,i,...a]=r;if(!e)continue;if(!Ir.has(e)){n.push(...await Rr(a,t));continue}if(e===`pre`){n.push(await Dr(Lr(a),String(i.language??`text`),{filename:typeof i.filename==`string`?i.filename:void 0}));continue}let o={};if(/^h[1-6]$/.test(e)&&typeof i.id==`string`&&(o.id=i.id),e===`ol`&&Number.isInteger(Number(i.start))&&(o.start=Number(i.start)),(e===`th`||e===`td`)&&/^(?:text-align:\s*)?(?:left|right|center);?$/.test(String(i.style))&&(o.style=i.style),e===`a`||e===`img`){let r=Pr(String(i[e===`a`?`href`:`src`]??``),t,e===`img`);if(!r){n.push(e===`img`?String(i.alt||Lr(a)||`Image`)+` (image unavailable)`:Lr(a));continue}o[e===`a`?`href`:`src`]=r,typeof i.title==`string`&&(o.title=i.title),e===`img`?(o.alt=String(i.alt??Lr(a)),o.loading=`lazy`):/^(?:https?:|mailto:)/i.test(r)&&(o.target=`_blank`,o.rel=`noopener noreferrer`,o[`data-external`]=``,o[`aria-description`]=`Opens in a new tab`)}n.push([e,o,...await Rr(a,t)])}return n}async function zr(e,t){let n=/\.md$/i.test(t)?Cr(e,t):void 0;if(n===void 0)return{status:404,data:{message:`This document is unavailable in the local repository.`}};let r=n.replace(/^\uFEFF/,``).replace(/\r\n?/g,`
61
61
  `),i=r.startsWith(`---
62
- `)?/^---\n([\s\S]*?)^(?:---|\.\.\.)[ \t]*(?:\n|$)/m.exec(r):null,a=i?r.slice(i[0].length):r,o=await Or(a);return{status:200,data:{kind:`markdown`,path:t,metadata:i?.[1]??null,document:{nodes:await jr(o.nodes,t),frontmatter:{},meta:{}}}}}const Nr=`127.0.0.1`,Pr=`/_businesslens/report.json`,Fr=`/_businesslens/events`,Ir=`/_businesslens/health`,Lr=`/_businesslens/logo.svg`,Rr=`/_businesslens/file/`,zr=`/_businesslens/code`,Br=Oe(new URL(`./viewer/`,import.meta.url)),Vr=k(ne(import.meta.url).resolve(`businesslens/package.json`),`../layers/nuxt/theme/public/brand`),Hr=25*1024*1024,Ur={".css":`text/css; charset=utf-8`,".html":`text/html; charset=utf-8`,".ico":`image/x-icon`,".js":`text/javascript; charset=utf-8`,".json":`application/json; charset=utf-8`,".map":`application/json; charset=utf-8`,".png":`image/png`,".svg":`image/svg+xml`,".webmanifest":`application/manifest+json; charset=utf-8`,".woff":`font/woff`,".woff2":`font/woff2`},Wr={".avif":`image/avif`,".gif":`image/gif`,".jpeg":`image/jpeg`,".jpg":`image/jpeg`,".md":`text/markdown; charset=utf-8`,".pdf":`application/pdf`,".png":`image/png`,".svg":`image/svg+xml; charset=utf-8`,".txt":`text/plain; charset=utf-8`,".webp":`image/webp`};var Gr=class{report;serialized;error;revision=0;timer;watcher;listeners=new Set;constructor(e){this.options=e,e.initialReport?this.accept(e.initialReport,!1):this.refresh(!1),e.watchRoot&&(this.watcher=Se(e.watchRoot,{recursive:!0},(t,n)=>{if(!this.isModelSource(n))return;let r=this.isLogoSource(n);this.timer&&clearTimeout(this.timer),this.timer=setTimeout(()=>this.refresh(!0,r),e.debounceMs??180)}),this.watcher.on(`error`,e=>this.reject(`File watching failed: ${e.message}`)))}snapshot(){return{report:this.report,error:this.error,revision:this.revision}}subscribe(e){return this.listeners.add(e),this.error&&e({type:`compile-error`,revision:this.revision,message:this.error}),()=>this.listeners.delete(e)}refresh(e=!0,t=!1){try{this.accept(this.options.compile(),e,t)}catch(t){this.reject(t.message,e)}}close(){this.timer&&clearTimeout(this.timer),this.watcher?.close(),this.listeners.clear()}isModelSource(e){if(e===null)return!0;let t=e.toString().replaceAll(`\\`,`/`),n=t.split(`/`)[0];return n===`build`||n===`cache`?!1:this.options.watchRoot&&t===re(this.options.watchRoot)?!0:/\.(?:md|ya?ml|svg)$/i.test(t)}isLogoSource(e){if(e===null)return!0;let t=e.toString().replaceAll(`\\`,`/`);return t===`logo.svg`||t.endsWith(`/logo.svg`)||!!(this.options.watchRoot&&t===re(this.options.watchRoot))}accept(e,t,n=!1){let r=JSON.stringify(e),i=this.error!==void 0,a=r!==this.serialized;this.report=e,this.serialized=r,this.error=void 0,!(!t||!a&&!i&&!n)&&(this.revision+=1,this.emit({type:`report`,revision:this.revision}))}reject(e,t=!0){e!==this.error&&(this.error=e,t&&(this.revision+=1,this.emit({type:`compile-error`,revision:this.revision,message:e})))}emit(e){for(let t of this.listeners)t(e)}};function Kr(e){e.setHeader(`cache-control`,`no-store`),e.setHeader(`content-security-policy`,`default-src 'none'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; font-src 'self'; img-src 'self' data: https: http:; frame-src 'self'; manifest-src 'self'; connect-src 'self'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'`),e.setHeader(`cross-origin-resource-policy`,`same-origin`),e.setHeader(`permissions-policy`,`camera=(), microphone=(), geolocation=()`),e.setHeader(`referrer-policy`,`no-referrer`),e.setHeader(`x-content-type-options`,`nosniff`),e.setHeader(`x-frame-options`,`DENY`)}function $(e,t,n,r){let i=`${JSON.stringify(n)}\n`;e.statusCode=t,e.setHeader(`content-type`,`application/json; charset=utf-8`),e.setHeader(`content-length`,Buffer.byteLength(i)),e.end(r?void 0:i)}function qr(e,t,n){if(e.setHeader(`content-security-policy`,`default-src 'none'; script-src 'none'; style-src 'none'; img-src 'none'; connect-src 'none'; sandbox`),!t){$(e,404,{message:`This Product Model has no logo.`},n);return}try{let r=M(t);if(!r.isFile()||r.isSymbolicLink())throw Error(`The Product logo is not a regular file.`);if(r.size>w)throw Error(`The Product logo is too large.`);let i=P(t),a=T(i);if(a.length)throw Error(a.join(`; `));e.statusCode=200,e.setHeader(`content-type`,`image/svg+xml; charset=utf-8`),e.setHeader(`content-length`,i.byteLength),e.end(n?void 0:i)}catch(t){let r=t.code===`ENOENT`?`This Product Model has no logo.`:`The Product logo is invalid: ${t.message}`;$(e,t.code===`ENOENT`?404:422,{message:r},n)}}function Jr(e,t){let n=e.headers.host?.toLowerCase();return n===`${Nr}:${t}`||n===`localhost:${t}`}function Yr(e,t){let n;try{n=decodeURIComponent(t)}catch{return}let r=n===`/`?`index.html`:n.replace(/^\/+/,``),i=k(e),a=k(i,r);if(!(a!==i&&!a.startsWith(`${i}${A}`))){try{let e=M(a);if(!e.isFile()||e.isSymbolicLink())return}catch{return}return a}}function Xr(e,t){let n;try{n=decodeURIComponent(t.slice(Rr.length))}catch{return}if(!n||n.startsWith(`/`))return;let r=k(e),i=k(r,n);if(i.startsWith(`${r}${A}`)&&D(i).toLowerCase()in Wr){try{let e=r;for(let t of oe(r,i).split(A))if(e=k(e,t),M(e).isSymbolicLink())return;let t=M(i);if(!t.isFile()||t.isSymbolicLink()||t.size>Hr)return}catch{return}return i}}function Zr(e,t,n){let r=D(t).toLowerCase(),i=P(t);if(r===`.svg`){let t=T(i);if(t.length){$(e,422,{message:`This SVG is not inert: ${t.join(`; `)}`},n);return}}e.statusCode=200,r===`.pdf`&&(e.setHeader(`x-frame-options`,`SAMEORIGIN`),e.setHeader(`content-security-policy`,`default-src 'none'; script-src 'none'; frame-ancestors 'self'`)),e.setHeader(`content-type`,Wr[r]??`application/octet-stream`),e.setHeader(`content-length`,i.byteLength),e.end(n?void 0:i)}function Qr(e,t,n,r,i){if(e.headers.accept?.includes(`text/html`)&&!e.headers.accept.includes(`application/json`)){t.statusCode=302,t.setHeader(`location`,`/?f=`+encodeURIComponent(n.pathname+n.search+(n.pathname===zr?`#reference`:``))),t.end();return}i().then(e=>{t.destroyed||$(t,e.status,e.data,r)}).catch(()=>{t.destroyed||$(t,500,{message:`This reference could not be rendered.`},r)})}function $r(e,t,n,r){let i=e.viewerRoot??Br;return(a,o)=>{Kr(o);let s=a.method===`HEAD`;if(a.method!==`GET`&&!s){o.setHeader(`allow`,`GET, HEAD`),$(o,405,{message:`Method not allowed.`},!1);return}if(!Jr(a,r)){$(o,403,{message:`The local viewer accepts loopback requests only.`},s);return}let c=new URL(a.url??`/`,`http://${Nr}:${r}`),l=c.pathname;if(l===Ir){$(o,200,{ok:!0},s);return}if(l===Pr){let e=t.snapshot();e.report?(o.setHeader(`x-businesslens-report-state`,e.error?`stale`:`ready`),$(o,200,e.report,s)):$(o,422,{message:e.error},s);return}if(l===Lr){qr(o,e.logoFile,s);return}if(l===zr){Qr(a,o,c,s,()=>Cr(t.snapshot().report,e.assetRoot,c.searchParams.get(`target`)??``));return}if(l.startsWith(Rr)){let t=e.assetRoot&&Xr(e.assetRoot,l);t?D(t).toLowerCase()===`.md`&&c.searchParams.get(`raw`)!==`1`?Qr(a,o,c,s,()=>Mr(e.assetRoot,oe(e.assetRoot,t).split(A).join(`/`))):Zr(o,t,s):$(o,404,{message:`Not found.`},s);return}if(l===Fr){if(s){o.statusCode=200,o.setHeader(`content-type`,`text/event-stream; charset=utf-8`),o.end();return}o.statusCode=200,o.setHeader(`content-type`,`text/event-stream; charset=utf-8`),o.setHeader(`connection`,`keep-alive`),o.flushHeaders(),o.write(`: connected
62
+ `)?/^---\n([\s\S]*?)^(?:---|\.\.\.)[ \t]*(?:\n|$)/m.exec(r):null,a=i?r.slice(i[0].length):r,o=await Fr(a);return{status:200,data:{kind:`markdown`,path:t,metadata:i?.[1]??null,document:{nodes:await Rr(o.nodes,t),frontmatter:{},meta:{}}}}}const Br=`127.0.0.1`,Vr=`/_businesslens/report.json`,Hr=`/_businesslens/events`,Ur=`/_businesslens/health`,Wr=`/_businesslens/logo.svg`,Gr=`/_businesslens/file/`,Kr=`/_businesslens/code`,qr=De(new URL(`./viewer/`,import.meta.url)),Jr=O(ne(import.meta.url).resolve(`businesslens/package.json`),`../layers/nuxt/theme/public/brand`),Yr=25*1024*1024,Xr={".css":`text/css; charset=utf-8`,".html":`text/html; charset=utf-8`,".ico":`image/x-icon`,".js":`text/javascript; charset=utf-8`,".json":`application/json; charset=utf-8`,".map":`application/json; charset=utf-8`,".png":`image/png`,".svg":`image/svg+xml`,".webmanifest":`application/manifest+json; charset=utf-8`,".woff":`font/woff`,".woff2":`font/woff2`},Zr={".avif":`image/avif`,".gif":`image/gif`,".jpeg":`image/jpeg`,".jpg":`image/jpeg`,".md":`text/markdown; charset=utf-8`,".pdf":`application/pdf`,".png":`image/png`,".svg":`image/svg+xml; charset=utf-8`,".txt":`text/plain; charset=utf-8`,".webp":`image/webp`};var Qr=class{report;serialized;error;revision=0;timer;watcher;listeners=new Set;constructor(e){this.options=e,e.initialReport?this.accept(e.initialReport,!1):this.refresh(!1),e.watchRoot&&(this.watcher=xe(e.watchRoot,{recursive:!0},(t,n)=>{if(!this.isModelSource(n))return;let r=this.isLogoSource(n);this.timer&&clearTimeout(this.timer),this.timer=setTimeout(()=>this.refresh(!0,r),e.debounceMs??180)}),this.watcher.on(`error`,e=>this.reject(`File watching failed: ${e.message}`)))}snapshot(){return{report:this.report,error:this.error,revision:this.revision}}subscribe(e){return this.listeners.add(e),this.error&&e({type:`compile-error`,revision:this.revision,message:this.error}),()=>this.listeners.delete(e)}refresh(e=!0,t=!1){try{this.accept(this.options.compile(),e,t)}catch(t){this.reject(t.message,e)}}close(){this.timer&&clearTimeout(this.timer),this.watcher?.close(),this.listeners.clear()}isModelSource(e){if(e===null)return!0;let t=e.toString().replaceAll(`\\`,`/`),n=t.split(`/`)[0];return n===`build`||n===`cache`?!1:this.options.watchRoot&&t===re(this.options.watchRoot)?!0:/\.(?:md|ya?ml|svg)$/i.test(t)}isLogoSource(e){if(e===null)return!0;let t=e.toString().replaceAll(`\\`,`/`);return t===`logo.svg`||t.endsWith(`/logo.svg`)||!!(this.options.watchRoot&&t===re(this.options.watchRoot))}accept(e,t,n=!1){let r=JSON.stringify(e),i=this.error!==void 0,a=r!==this.serialized;this.report=e,this.serialized=r,this.error=void 0,!(!t||!a&&!i&&!n)&&(this.revision+=1,this.emit({type:`report`,revision:this.revision}))}reject(e,t=!0){e!==this.error&&(this.error=e,t&&(this.revision+=1,this.emit({type:`compile-error`,revision:this.revision,message:e})))}emit(e){for(let t of this.listeners)t(e)}};function $r(e){e.setHeader(`cache-control`,`no-store`),e.setHeader(`content-security-policy`,`default-src 'none'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; font-src 'self'; img-src 'self' data: https: http:; frame-src 'self'; manifest-src 'self'; connect-src 'self'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'`),e.setHeader(`cross-origin-resource-policy`,`same-origin`),e.setHeader(`permissions-policy`,`camera=(), microphone=(), geolocation=()`),e.setHeader(`referrer-policy`,`no-referrer`),e.setHeader(`x-content-type-options`,`nosniff`),e.setHeader(`x-frame-options`,`DENY`)}function $(e,t,n,r){let i=`${JSON.stringify(n)}\n`;e.statusCode=t,e.setHeader(`content-type`,`application/json; charset=utf-8`),e.setHeader(`content-length`,Buffer.byteLength(i)),e.end(r?void 0:i)}function ei(e,t,n){if(e.setHeader(`content-security-policy`,`default-src 'none'; script-src 'none'; style-src 'none'; img-src 'none'; connect-src 'none'; sandbox`),!t){$(e,404,{message:`This Product Model has no logo.`},n);return}try{let r=M(t);if(!r.isFile()||r.isSymbolicLink())throw Error(`The Product logo is not a regular file.`);if(r.size>w)throw Error(`The Product logo is too large.`);let i=P(t),a=T(i);if(a.length)throw Error(a.join(`; `));e.statusCode=200,e.setHeader(`content-type`,`image/svg+xml; charset=utf-8`),e.setHeader(`content-length`,i.byteLength),e.end(n?void 0:i)}catch(t){let r=t.code===`ENOENT`?`This Product Model has no logo.`:`The Product logo is invalid: ${t.message}`;$(e,t.code===`ENOENT`?404:422,{message:r},n)}}function ti(e,t){let n=e.headers.host?.toLowerCase();return n===`${Br}:${t}`||n===`localhost:${t}`}function ni(e,t){let n;try{n=decodeURIComponent(t)}catch{return}let r=n===`/`?`index.html`:n.replace(/^\/+/,``),i=O(e),a=O(i,r);if(!(a!==i&&!a.startsWith(`${i}${k}`))){try{let e=M(a);if(!e.isFile()||e.isSymbolicLink())return}catch{return}return a}}function ri(e,t){let n;try{n=decodeURIComponent(t.slice(Gr.length))}catch{return}if(!n||n.startsWith(`/`))return;let r=O(e),i=O(r,n);if(i.startsWith(`${r}${k}`)&&E(i).toLowerCase()in Zr){try{let e=r;for(let t of se(r,i).split(k))if(e=O(e,t),M(e).isSymbolicLink())return;let t=M(i);if(!t.isFile()||t.isSymbolicLink()||t.size>Yr)return}catch{return}return i}}function ii(e,t,n){let r=E(t).toLowerCase(),i=P(t);if(r===`.svg`){let t=T(i);if(t.length){$(e,422,{message:`This SVG is not inert: ${t.join(`; `)}`},n);return}}e.statusCode=200,r===`.pdf`&&(e.setHeader(`x-frame-options`,`SAMEORIGIN`),e.setHeader(`content-security-policy`,`default-src 'none'; script-src 'none'; frame-ancestors 'self'`)),e.setHeader(`content-type`,Zr[r]??`application/octet-stream`),e.setHeader(`content-length`,i.byteLength),e.end(n?void 0:i)}function ai(e,t,n,r,i){if(e.headers.accept?.includes(`text/html`)&&!e.headers.accept.includes(`application/json`)){t.statusCode=302,t.setHeader(`location`,`/?f=`+encodeURIComponent(n.pathname+n.search+(n.pathname===Kr?`#reference`:``))),t.end();return}i().then(e=>{t.destroyed||$(t,e.status,e.data,r)}).catch(()=>{t.destroyed||$(t,500,{message:`This reference could not be rendered.`},r)})}function oi(e,t,n,r){let i=e.viewerRoot??qr;return(a,o)=>{$r(o);let s=a.method===`HEAD`;if(a.method!==`GET`&&!s){o.setHeader(`allow`,`GET, HEAD`),$(o,405,{message:`Method not allowed.`},!1);return}if(!ti(a,r)){$(o,403,{message:`The local viewer accepts loopback requests only.`},s);return}let c=new URL(a.url??`/`,`http://${Br}:${r}`),l=c.pathname;if(l===Ur){$(o,200,{ok:!0},s);return}if(l===Vr){let e=t.snapshot();e.report?(o.setHeader(`x-businesslens-report-state`,e.error?`stale`:`ready`),$(o,200,e.report,s)):$(o,422,{message:e.error},s);return}if(l===Wr){ei(o,e.logoFile,s);return}if(l===Kr){ai(a,o,c,s,()=>Ar(t.snapshot().report,e.assetRoot,c.searchParams.get(`target`)??``));return}if(l.startsWith(Gr)){let t=e.assetRoot&&ri(e.assetRoot,l);t?E(t).toLowerCase()===`.md`&&c.searchParams.get(`raw`)!==`1`?ai(a,o,c,s,()=>zr(e.assetRoot,se(e.assetRoot,t).split(k).join(`/`))):ii(o,t,s):$(o,404,{message:`Not found.`},s);return}if(l===Hr){if(s){o.statusCode=200,o.setHeader(`content-type`,`text/event-stream; charset=utf-8`),o.end();return}o.statusCode=200,o.setHeader(`content-type`,`text/event-stream; charset=utf-8`),o.setHeader(`connection`,`keep-alive`),o.flushHeaders(),o.write(`: connected
63
63
 
64
- `),n.add(o);let e=t.subscribe(e=>{o.write(`event: ${e.type}\ndata: ${JSON.stringify(e)}\n\n`)});a.once(`close`,()=>{e(),n.delete(o)});return}let u=Yr(i,l)??(l.startsWith(`/brand/`)?Yr(Vr,l.slice(7)):void 0);if(!u){$(o,404,{message:`Not found.`},s);return}let d=P(u);o.statusCode=200,o.setHeader(`content-type`,Ur[D(u)]??`application/octet-stream`),o.setHeader(`content-length`,d.byteLength),o.end(s?void 0:d)}}async function ei(e){let t=e.port??0,n=new Gr(e),r=new Set;return await new Promise((i,a)=>{let o=Ie(),s=e=>{n.close(),a(e)};o.once(`error`,s),o.listen(t,Nr,()=>{o.removeListener(`error`,s);let t=o.address();if(!t||typeof t==`string`){n.close(),o.close(),a(Error(`The local viewer did not receive a TCP port.`));return}let c=t.port;o.on(`request`,$r(e,n,r,c)),i({server:o,port:c,url:`http://${Nr}:${c}`,refresh:()=>n.refresh(),close:()=>new Promise((e,t)=>{n.close();for(let e of r)e.end();r.clear(),o.close(n=>n?t(n):e())})})})})}function ti(e){let t=process.platform===`darwin`?{file:`open`,args:[e]}:process.platform===`win32`?{file:`cmd`,args:[`/c`,`start`,``,e]}:{file:`xdg-open`,args:[e]},n=fe(t.file,t.args,{detached:!0,stdio:`ignore`});n.on(`error`,()=>{}),n.unref()}async function ni(e,t){try{let n=Nt(e),r=Qt(n),i=await ei({port:t.port,initialReport:r,compile:()=>Qt(n),watchRoot:O(n.modelRoot,`.businesslens`),logoFile:O(n.modelRoot,`.businesslens`,`product`,`logo.svg`),assetRoot:n.gitRoot});return console.log(`Viewing the local Product Model at ${i.url}`),console.log(`Press Ctrl+C to stop.`),t.open&&ti(i.url),await new Promise(e=>{let t=!1,n=()=>{t||(t=!0,i.close().then(()=>e(0),t=>{console.error(t.message),e(1)}))};process.once(`SIGINT`,n),process.once(`SIGTERM`,n)})}catch(e){return console.error(e.message),1}}function ri(e){let{cwd:t}=e.optsWithGlobals();return k(process.cwd(),t||`.`)}function ii(e){let t=e.trim().toLowerCase();if(t!==`project`&&t!==`global`)throw new ue(`expected "project" or "global"`);return t}function ai(e){if(!/^\d+$/.test(e))throw new ue(`expected an integer from 1 to 65535`);let t=Number(e);if(t<1||t>65535)throw new ue(`expected an integer from 1 to 65535`);return t}function oi(e){let t=e.endsWith(`
64
+ `),n.add(o);let e=t.subscribe(e=>{o.write(`event: ${e.type}\ndata: ${JSON.stringify(e)}\n\n`)});a.once(`close`,()=>{e(),n.delete(o)});return}let u=ni(i,l)??(l.startsWith(`/brand/`)?ni(Jr,l.slice(7)):void 0);if(!u){$(o,404,{message:`Not found.`},s);return}let d=P(u);o.statusCode=200,o.setHeader(`content-type`,Xr[E(u)]??`application/octet-stream`),o.setHeader(`content-length`,d.byteLength),o.end(s?void 0:d)}}async function si(e){let t=e.port??0,n=new Qr(e),r=new Set;return await new Promise((i,a)=>{let o=Fe(),s=e=>{n.close(),a(e)};o.once(`error`,s),o.listen(t,Br,()=>{o.removeListener(`error`,s);let t=o.address();if(!t||typeof t==`string`){n.close(),o.close(),a(Error(`The local viewer did not receive a TCP port.`));return}let c=t.port;o.on(`request`,oi(e,n,r,c)),i({server:o,port:c,url:`http://${Br}:${c}`,refresh:()=>n.refresh(),close:()=>new Promise((e,t)=>{n.close();for(let e of r)e.end();r.clear(),o.close(n=>n?t(n):e())})})})})}function ci(e){let t=process.platform===`darwin`?{file:`open`,args:[e]}:process.platform===`win32`?{file:`cmd`,args:[`/c`,`start`,``,e]}:{file:`xdg-open`,args:[e]},n=pe(t.file,t.args,{detached:!0,stdio:`ignore`});n.on(`error`,()=>{}),n.unref()}function li(e,t){let n=[e.branch!==void 0&&`--branch`,e.pr!==void 0&&`--pr`].filter(Boolean);if(n.length>1)throw new z(`Pass either --branch or --pr, not both.`);if(t&&n.length)throw new z(`The repository URL already names a ${br(t)}; drop ${n[0]}.`);if(e.branch!==void 0){let t=e.branch.trim();if(!t)throw new z(`--branch needs a branch or tag name.`);return{kind:`branch`,name:t}}return e.pr===void 0?t:{kind:`pull`,number:e.pr}}function ui(e,t){if(t.repository===void 0){if(t.branch!==void 0||t.pr!==void 0)throw new z(`--branch and --pr apply to a GitHub repository. Pass one: businesslens view owner/repo --pr 12`);return{resolved:Mt(e),subject:`the local Product Model`}}if(t.explicitCwd)throw new z(`--cwd selects a local model. A GitHub repository is viewed from its own root.`);let n=yr(t.repository),r=li(t,n.ref),i=`${n.owner}/${n.name}`;console.log(`Fetching ${br(r)} of ${i}…`);let a=Sr(n,r);try{return{resolved:Mt(a.root),snapshot:a,subject:`${i} (${br(r)}, commit ${a.commit.slice(0,7)})`}}catch(e){throw a.dispose(),e}}async function di(e,t){let n;try{n=ui(e,t);let{resolved:r,snapshot:i}=n,a=Zt(r),o=await si({port:t.port,initialReport:a,compile:()=>Zt(r),watchRoot:i?void 0:D(r.modelRoot,`.businesslens`),logoFile:D(r.modelRoot,`.businesslens`,`product`,`logo.svg`),assetRoot:r.gitRoot});return console.log(`Viewing ${n.subject} at ${o.url}`),console.log(`Press Ctrl+C to stop.`),t.open&&ci(o.url),await new Promise(e=>{let t=!1,n=()=>{t||(t=!0,o.close().then(()=>e(0),t=>{console.error(t.message),e(1)}).finally(()=>i?.dispose()))};process.once(`SIGINT`,n),process.once(`SIGTERM`,n)})}catch(e){return n?.snapshot?.dispose(),console.error(e.message),e instanceof z?2:1}}function fi(e){let{cwd:t}=e.optsWithGlobals();return O(process.cwd(),t||`.`)}function pi(e){let t=e.trim().toLowerCase();if(t!==`project`&&t!==`global`)throw new de(`expected "project" or "global"`);return t}function mi(e){if(!/^\d+$/.test(e))throw new de(`expected an integer from 1 to 65535`);let t=Number(e);if(t<1||t>65535)throw new de(`expected an integer from 1 to 65535`);return t}function hi(e){if(!/^[1-9]\d*$/.test(e))throw new de(`expected a positive pull request number`);return Number(e)}function gi(e){let t=e.endsWith(`
65
65
  `)?`
66
66
  `:``,n=e.trimEnd().split(/\n{2,}/),r=n.findIndex(e=>e.startsWith(`Commands:`)),i=n.findIndex(e=>e.startsWith(`Options:`));if(r<0||i<0||r<i)return e;let[a]=n.splice(r,1);return n.splice(i,0,a),n.join(`
67
67
 
68
- `)+t}function si(e){let t=new se().name(`businesslens`).description(`Product-Driven Development for coding agents`).usage(`<command> [options]`).option(`-c, --cwd <path>`,`Run from another directory`).version(zt(),`-V, --version`,`Show the CLI version`).helpOption(`-h, --help`,`Show help for command`).helpCommand(`help [command]`,`Show help for command`).showSuggestionAfterError().configureHelp({showGlobalOptions:!0,formatHelp(e,t){let n=le.prototype.formatHelp.call(t,e,t);return e.parent?n:oi(n)}}).exitOverride();t.command(`install`).summary(`Install BusinessLens skills`).description(`Install BusinessLens skills for detected AI harnesses.`).option(`--providers <list>`,`Comma-separated providers: claude,codex,cursor,gemini,github`).option(`--scope <scope>`,`Installation scope: project or global`,ii).option(`--yes`,`Accept detected providers and default to project scope`).option(`--force`,`Replace an unmarked colliding BusinessLens skill directory`).action(async(t,n)=>{e(await tr(ri(n),t))}),t.command(`update`).summary(`Update managed skill installations`).description(`Update BusinessLens-managed skill installations.`).option(`--providers <list>`,`Limit discovery to: claude,codex,cursor,gemini,github`).option(`--scope <scope>`,`Installation scope: project or global`,ii).option(`--force`,`Replace an unmarked collision inside a managed installation`).action(async(t,n)=>{e(await hr(ri(n),t))}),t.command(`lint`).summary(`Lint a Product Model`).description(`Lint the current .businesslens/ Product Model.`).option(`--json`,`Write the lint result as JSON`).action((t,n)=>{e(qt(ri(n),!!t.json))}),t.command(`view`).summary(`View a Product Model locally`).description(`View the current Product Model on localhost.`).option(`--no-open`,`Do not open the default browser`).option(`--port <port>`,`Port to listen on`,ai).action(async(t,n)=>{e(await ni(ri(n),t))});let n=t.command(`blueprint`).summary(`Move Product Models between repositories`).description(`Export, open, pull, or contribute portable Product Model Blueprints.`).usage(`<command> [options]`).helpOption(`-h, --help`,`Show help for command`).helpCommand(`help [command]`,`Show help for command`).argument(`[command]`).action((e,t,n)=>{e&&n.error(`error: unknown command '${e}' for 'businesslens blueprint'`),n.outputHelp()});return n.command(`export`).summary(`Export a Blueprint`).description(`Compile .businesslens/ into a portable Product Report.`).action((t,n)=>{e(en(ri(n)))}),n.command(`open <report>`).summary(`Open a local Blueprint`).description(`Expand a local Product Report into .businesslens/.`).option(`--force`,`Back up and replace a non-empty .businesslens/ directory`).action(async(t,n,r)=>{e(await vn(ri(r),t,!!n.force))}),n.command(`pull <name>`).summary(`Pull a catalog Blueprint`).description(`Pull a Blueprint from a catalog into .businesslens/.`).option(`--catalog <origin>`,`Catalog origin (default: BUSINESSLENS_CATALOG_URL or https://businesslens.io)`).option(`--force`,`Back up and replace a non-empty .businesslens/ directory`).action(async(t,n,r)=>{e(await pr(ri(r),t,{catalog:n.catalog,force:!!n.force}))}),n.command(`contribute`).summary(`Contribute a Blueprint`).description(`Propose the current Product Model for the Blueprint catalog.`).option(`--yes`,`Skip the confirmation prompt`).action(async(t,n)=>{e(await On(ri(n),{yes:!!t.yes}))}),t}async function ci(e=process.argv){let t=0,n=si(e=>{t=e});if(e.length===2)return n.outputHelp(),0;try{return await n.parseAsync(e),t}catch(e){return e instanceof ce?e.exitCode===0||e.code===`commander.help`?0:2:(console.error(`error: ${e.message}`),1)}}ci().then(e=>{process.exitCode=e},e=>{console.error(`error: ${e.message}`),process.exitCode=1});
68
+ `)+t}function _i(e){let t=new ce().name(`businesslens`).description(`Product-Driven Development for coding agents`).usage(`<command> [options]`).option(`-c, --cwd <path>`,`Run from another directory`).version(Rt(),`-V, --version`,`Show the CLI version`).helpOption(`-h, --help`,`Show help for command`).helpCommand(`help [command]`,`Show help for command`).showSuggestionAfterError().configureHelp({showGlobalOptions:!0,formatHelp(e,t){let n=ue.prototype.formatHelp.call(t,e,t);return e.parent?n:gi(n)}}).exitOverride();t.command(`install`).summary(`Install BusinessLens skills`).description(`Install BusinessLens skills for detected AI harnesses.`).option(`--providers <list>`,`Comma-separated providers: claude,codex,cursor,gemini,github`).option(`--scope <scope>`,`Installation scope: project or global`,pi).option(`--yes`,`Accept detected providers and default to project scope`).option(`--force`,`Replace an unmarked colliding BusinessLens skill directory`).action(async(t,n)=>{e(await tr(fi(n),t))}),t.command(`update`).summary(`Update managed skill installations`).description(`Update BusinessLens-managed skill installations.`).option(`--providers <list>`,`Limit discovery to: claude,codex,cursor,gemini,github`).option(`--scope <scope>`,`Installation scope: project or global`,pi).option(`--force`,`Replace an unmarked collision inside a managed installation`).action(async(t,n)=>{e(await hr(fi(n),t))}),t.command(`lint`).summary(`Lint a Product Model`).description(`Lint the current .businesslens/ Product Model.`).option(`--json`,`Write the lint result as JSON`).action((t,n)=>{e(Kt(fi(n),!!t.json))}),t.command(`view [repository]`).summary(`View a Product Model locally`).description(`View the current Product Model, or a GitHub repository's, on localhost.`).option(`--no-open`,`Do not open the default browser`).option(`--port <port>`,`Port to listen on`,mi).option(`--branch <name>`,`Branch or tag of the GitHub repository`).option(`--pr <number>`,`Pull request of the GitHub repository to view`,hi).action(async(t,n,r)=>{let{cwd:i}=r.optsWithGlobals();e(await di(fi(r),{...n,repository:t,explicitCwd:i!==void 0}))});let n=t.command(`blueprint`).summary(`Move Product Models between repositories`).description(`Export, open, pull, or contribute portable Product Model Blueprints.`).usage(`<command> [options]`).helpOption(`-h, --help`,`Show help for command`).helpCommand(`help [command]`,`Show help for command`).argument(`[command]`).action((e,t,n)=>{e&&n.error(`error: unknown command '${e}' for 'businesslens blueprint'`),n.outputHelp()});return n.command(`export`).summary(`Export a Blueprint`).description(`Compile .businesslens/ into a portable Product Report.`).action((t,n)=>{e($t(fi(n)))}),n.command(`open <report>`).summary(`Open a local Blueprint`).description(`Expand a local Product Report into .businesslens/.`).option(`--force`,`Back up and replace a non-empty .businesslens/ directory`).action(async(t,n,r)=>{e(await vn(fi(r),t,!!n.force))}),n.command(`pull <name>`).summary(`Pull a catalog Blueprint`).description(`Pull a Blueprint from a catalog into .businesslens/.`).option(`--catalog <origin>`,`Catalog origin (default: BUSINESSLENS_CATALOG_URL or https://businesslens.io)`).option(`--force`,`Back up and replace a non-empty .businesslens/ directory`).action(async(t,n,r)=>{e(await pr(fi(r),t,{catalog:n.catalog,force:!!n.force}))}),n.command(`contribute`).summary(`Contribute a Blueprint`).description(`Propose the current Product Model for the Blueprint catalog.`).option(`--yes`,`Skip the confirmation prompt`).action(async(t,n)=>{e(await On(fi(n),{yes:!!t.yes}))}),t}async function vi(e=process.argv){let t=0,n=_i(e=>{t=e});if(e.length===2)return n.outputHelp(),0;try{return await n.parseAsync(e),t}catch(e){return e instanceof le?e.exitCode===0||e.code===`commander.help`?0:2:(console.error(`error: ${e.message}`),1)}}vi().then(e=>{process.exitCode=e},e=>{console.error(`error: ${e.message}`),process.exitCode=1});
@@ -1 +1 @@
1
- <!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><link rel="stylesheet" href="/_nuxt/entry.hGHEpN7d.css" crossorigin><link rel="modulepreload" as="script" crossorigin href="/_nuxt/C6TYTbB-.js"><script type="module" src="/_nuxt/C6TYTbB-.js" crossorigin></script><link rel="prefetch" as="style" crossorigin href="/_nuxt/error-404.VJcZ3YVw.css"><link rel="prefetch" as="script" crossorigin href="/_nuxt/DMuErDnz.js"><link rel="prefetch" as="style" crossorigin href="/_nuxt/error-500.BwxPsW7a.css"><link rel="prefetch" as="script" crossorigin href="/_nuxt/BFRp6mHu.js"><script>"use strict";(()=>{const o=window,e=document.documentElement,c=["dark","light"],s=getStorageValue("localStorage","nuxt-color-mode")||"light";let r=s==="system"?f():s;const l=e.getAttribute("data-color-mode-forced");l&&(r=l),i(r),o["__NUXT_COLOR_MODE__"]={preference:s,value:r,getColorScheme:f,addColorScheme:i,removeColorScheme:d};function i(t){const a=""+t+"",n="";e.classList?e.classList.add(a):e.className+=" "+a,n&&e.setAttribute("data-"+n,t)}function d(t){const a=""+t+"",n="";e.classList?e.classList.remove(a):e.className=e.className.replace(new RegExp(a,"g"),""),n&&e.removeAttribute("data-"+n)}function u(t){return o.matchMedia("(prefers-color-scheme"+t+")")}function f(){if(o.matchMedia&&u("").media!=="not all"){for(const t of c)if(u(":"+t).matches)return t}return"light"}})();function getStorageValue(o,e){switch(o){case"localStorage":try{return window.localStorage.getItem(e)}catch{return null}case"sessionStorage":try{return window.sessionStorage.getItem(e)}catch{return null}case"cookie":try{return getCookie(e)}catch{return null}default:return null}}function getCookie(o){const c=("; "+window.document.cookie).split("; "+o+"=");if(c.length===2){const s=c.pop();return s?s.split(";").shift():null}}</script></head><body><div id="__nuxt" class="isolate"></div><div id="teleports"></div><script>window.__NUXT__={};window.__NUXT__.config={public:{pddVersion:"0.19.0"},app:{baseURL:"/",buildId:"d2255909-0bae-47f6-96ad-a333650a8e90",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script><script type="application/json" data-nuxt-data="nuxt-app" data-ssr="false" id="__NUXT_DATA__">[{"prerenderedAt":1,"serverRendered":2},1789673457577,false]</script></body></html>
1
+ <!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><link rel="stylesheet" href="/_nuxt/entry.hGHEpN7d.css" crossorigin><link rel="modulepreload" as="script" crossorigin href="/_nuxt/C6TYTbB-.js"><script type="module" src="/_nuxt/C6TYTbB-.js" crossorigin></script><link rel="prefetch" as="style" crossorigin href="/_nuxt/error-404.VJcZ3YVw.css"><link rel="prefetch" as="script" crossorigin href="/_nuxt/DMuErDnz.js"><link rel="prefetch" as="style" crossorigin href="/_nuxt/error-500.BwxPsW7a.css"><link rel="prefetch" as="script" crossorigin href="/_nuxt/BFRp6mHu.js"><script>"use strict";(()=>{const o=window,e=document.documentElement,c=["dark","light"],s=getStorageValue("localStorage","nuxt-color-mode")||"light";let r=s==="system"?f():s;const l=e.getAttribute("data-color-mode-forced");l&&(r=l),i(r),o["__NUXT_COLOR_MODE__"]={preference:s,value:r,getColorScheme:f,addColorScheme:i,removeColorScheme:d};function i(t){const a=""+t+"",n="";e.classList?e.classList.add(a):e.className+=" "+a,n&&e.setAttribute("data-"+n,t)}function d(t){const a=""+t+"",n="";e.classList?e.classList.remove(a):e.className=e.className.replace(new RegExp(a,"g"),""),n&&e.removeAttribute("data-"+n)}function u(t){return o.matchMedia("(prefers-color-scheme"+t+")")}function f(){if(o.matchMedia&&u("").media!=="not all"){for(const t of c)if(u(":"+t).matches)return t}return"light"}})();function getStorageValue(o,e){switch(o){case"localStorage":try{return window.localStorage.getItem(e)}catch{return null}case"sessionStorage":try{return window.sessionStorage.getItem(e)}catch{return null}case"cookie":try{return getCookie(e)}catch{return null}default:return null}}function getCookie(o){const c=("; "+window.document.cookie).split("; "+o+"=");if(c.length===2){const s=c.pop();return s?s.split(";").shift():null}}</script></head><body><div id="__nuxt" class="isolate"></div><div id="teleports"></div><script>window.__NUXT__={};window.__NUXT__.config={public:{pddVersion:"0.20.0"},app:{baseURL:"/",buildId:"d2b92f2f-1f0b-435e-9ef9-537b7d452693",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script><script type="application/json" data-nuxt-data="nuxt-app" data-ssr="false" id="__NUXT_DATA__">[{"prerenderedAt":1,"serverRendered":2},1789739705047,false]</script></body></html>
@@ -1 +1 @@
1
- <!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><link rel="stylesheet" href="/_nuxt/entry.hGHEpN7d.css" crossorigin><link rel="modulepreload" as="script" crossorigin href="/_nuxt/C6TYTbB-.js"><script type="module" src="/_nuxt/C6TYTbB-.js" crossorigin></script><link rel="prefetch" as="style" crossorigin href="/_nuxt/error-404.VJcZ3YVw.css"><link rel="prefetch" as="script" crossorigin href="/_nuxt/DMuErDnz.js"><link rel="prefetch" as="style" crossorigin href="/_nuxt/error-500.BwxPsW7a.css"><link rel="prefetch" as="script" crossorigin href="/_nuxt/BFRp6mHu.js"><script>"use strict";(()=>{const o=window,e=document.documentElement,c=["dark","light"],s=getStorageValue("localStorage","nuxt-color-mode")||"light";let r=s==="system"?f():s;const l=e.getAttribute("data-color-mode-forced");l&&(r=l),i(r),o["__NUXT_COLOR_MODE__"]={preference:s,value:r,getColorScheme:f,addColorScheme:i,removeColorScheme:d};function i(t){const a=""+t+"",n="";e.classList?e.classList.add(a):e.className+=" "+a,n&&e.setAttribute("data-"+n,t)}function d(t){const a=""+t+"",n="";e.classList?e.classList.remove(a):e.className=e.className.replace(new RegExp(a,"g"),""),n&&e.removeAttribute("data-"+n)}function u(t){return o.matchMedia("(prefers-color-scheme"+t+")")}function f(){if(o.matchMedia&&u("").media!=="not all"){for(const t of c)if(u(":"+t).matches)return t}return"light"}})();function getStorageValue(o,e){switch(o){case"localStorage":try{return window.localStorage.getItem(e)}catch{return null}case"sessionStorage":try{return window.sessionStorage.getItem(e)}catch{return null}case"cookie":try{return getCookie(e)}catch{return null}default:return null}}function getCookie(o){const c=("; "+window.document.cookie).split("; "+o+"=");if(c.length===2){const s=c.pop();return s?s.split(";").shift():null}}</script></head><body><div id="__nuxt" class="isolate"></div><div id="teleports"></div><script>window.__NUXT__={};window.__NUXT__.config={public:{pddVersion:"0.19.0"},app:{baseURL:"/",buildId:"d2255909-0bae-47f6-96ad-a333650a8e90",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script><script type="application/json" data-nuxt-data="nuxt-app" data-ssr="false" id="__NUXT_DATA__">[{"prerenderedAt":1,"serverRendered":2},1789673457577,false]</script></body></html>
1
+ <!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><link rel="stylesheet" href="/_nuxt/entry.hGHEpN7d.css" crossorigin><link rel="modulepreload" as="script" crossorigin href="/_nuxt/C6TYTbB-.js"><script type="module" src="/_nuxt/C6TYTbB-.js" crossorigin></script><link rel="prefetch" as="style" crossorigin href="/_nuxt/error-404.VJcZ3YVw.css"><link rel="prefetch" as="script" crossorigin href="/_nuxt/DMuErDnz.js"><link rel="prefetch" as="style" crossorigin href="/_nuxt/error-500.BwxPsW7a.css"><link rel="prefetch" as="script" crossorigin href="/_nuxt/BFRp6mHu.js"><script>"use strict";(()=>{const o=window,e=document.documentElement,c=["dark","light"],s=getStorageValue("localStorage","nuxt-color-mode")||"light";let r=s==="system"?f():s;const l=e.getAttribute("data-color-mode-forced");l&&(r=l),i(r),o["__NUXT_COLOR_MODE__"]={preference:s,value:r,getColorScheme:f,addColorScheme:i,removeColorScheme:d};function i(t){const a=""+t+"",n="";e.classList?e.classList.add(a):e.className+=" "+a,n&&e.setAttribute("data-"+n,t)}function d(t){const a=""+t+"",n="";e.classList?e.classList.remove(a):e.className=e.className.replace(new RegExp(a,"g"),""),n&&e.removeAttribute("data-"+n)}function u(t){return o.matchMedia("(prefers-color-scheme"+t+")")}function f(){if(o.matchMedia&&u("").media!=="not all"){for(const t of c)if(u(":"+t).matches)return t}return"light"}})();function getStorageValue(o,e){switch(o){case"localStorage":try{return window.localStorage.getItem(e)}catch{return null}case"sessionStorage":try{return window.sessionStorage.getItem(e)}catch{return null}case"cookie":try{return getCookie(e)}catch{return null}default:return null}}function getCookie(o){const c=("; "+window.document.cookie).split("; "+o+"=");if(c.length===2){const s=c.pop();return s?s.split(";").shift():null}}</script></head><body><div id="__nuxt" class="isolate"></div><div id="teleports"></div><script>window.__NUXT__={};window.__NUXT__.config={public:{pddVersion:"0.20.0"},app:{baseURL:"/",buildId:"d2b92f2f-1f0b-435e-9ef9-537b7d452693",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script><script type="application/json" data-nuxt-data="nuxt-app" data-ssr="false" id="__NUXT_DATA__">[{"prerenderedAt":1,"serverRendered":2},1789739705047,false]</script></body></html>
@@ -1 +1 @@
1
- {"id":"d2255909-0bae-47f6-96ad-a333650a8e90","timestamp":1789673409172}
1
+ {"id":"d2b92f2f-1f0b-435e-9ef9-537b7d452693","timestamp":1789739674165}
@@ -0,0 +1 @@
1
+ {"id":"d2b92f2f-1f0b-435e-9ef9-537b7d452693","timestamp":1789739674165,"prerendered":[]}
@@ -1 +1 @@
1
- <!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><link rel="stylesheet" href="/_nuxt/entry.hGHEpN7d.css" crossorigin><link rel="modulepreload" as="script" crossorigin href="/_nuxt/C6TYTbB-.js"><script type="module" src="/_nuxt/C6TYTbB-.js" crossorigin></script><link rel="prefetch" as="style" crossorigin href="/_nuxt/error-404.VJcZ3YVw.css"><link rel="prefetch" as="script" crossorigin href="/_nuxt/DMuErDnz.js"><link rel="prefetch" as="style" crossorigin href="/_nuxt/error-500.BwxPsW7a.css"><link rel="prefetch" as="script" crossorigin href="/_nuxt/BFRp6mHu.js"><script>"use strict";(()=>{const o=window,e=document.documentElement,c=["dark","light"],s=getStorageValue("localStorage","nuxt-color-mode")||"light";let r=s==="system"?f():s;const l=e.getAttribute("data-color-mode-forced");l&&(r=l),i(r),o["__NUXT_COLOR_MODE__"]={preference:s,value:r,getColorScheme:f,addColorScheme:i,removeColorScheme:d};function i(t){const a=""+t+"",n="";e.classList?e.classList.add(a):e.className+=" "+a,n&&e.setAttribute("data-"+n,t)}function d(t){const a=""+t+"",n="";e.classList?e.classList.remove(a):e.className=e.className.replace(new RegExp(a,"g"),""),n&&e.removeAttribute("data-"+n)}function u(t){return o.matchMedia("(prefers-color-scheme"+t+")")}function f(){if(o.matchMedia&&u("").media!=="not all"){for(const t of c)if(u(":"+t).matches)return t}return"light"}})();function getStorageValue(o,e){switch(o){case"localStorage":try{return window.localStorage.getItem(e)}catch{return null}case"sessionStorage":try{return window.sessionStorage.getItem(e)}catch{return null}case"cookie":try{return getCookie(e)}catch{return null}default:return null}}function getCookie(o){const c=("; "+window.document.cookie).split("; "+o+"=");if(c.length===2){const s=c.pop();return s?s.split(";").shift():null}}</script></head><body><div id="__nuxt" class="isolate"></div><div id="teleports"></div><script>window.__NUXT__={};window.__NUXT__.config={public:{pddVersion:"0.19.0"},app:{baseURL:"/",buildId:"d2255909-0bae-47f6-96ad-a333650a8e90",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script><script type="application/json" data-nuxt-data="nuxt-app" data-ssr="false" id="__NUXT_DATA__">[{"prerenderedAt":1,"serverRendered":2},1789673457577,false]</script></body></html>
1
+ <!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><link rel="stylesheet" href="/_nuxt/entry.hGHEpN7d.css" crossorigin><link rel="modulepreload" as="script" crossorigin href="/_nuxt/C6TYTbB-.js"><script type="module" src="/_nuxt/C6TYTbB-.js" crossorigin></script><link rel="prefetch" as="style" crossorigin href="/_nuxt/error-404.VJcZ3YVw.css"><link rel="prefetch" as="script" crossorigin href="/_nuxt/DMuErDnz.js"><link rel="prefetch" as="style" crossorigin href="/_nuxt/error-500.BwxPsW7a.css"><link rel="prefetch" as="script" crossorigin href="/_nuxt/BFRp6mHu.js"><script>"use strict";(()=>{const o=window,e=document.documentElement,c=["dark","light"],s=getStorageValue("localStorage","nuxt-color-mode")||"light";let r=s==="system"?f():s;const l=e.getAttribute("data-color-mode-forced");l&&(r=l),i(r),o["__NUXT_COLOR_MODE__"]={preference:s,value:r,getColorScheme:f,addColorScheme:i,removeColorScheme:d};function i(t){const a=""+t+"",n="";e.classList?e.classList.add(a):e.className+=" "+a,n&&e.setAttribute("data-"+n,t)}function d(t){const a=""+t+"",n="";e.classList?e.classList.remove(a):e.className=e.className.replace(new RegExp(a,"g"),""),n&&e.removeAttribute("data-"+n)}function u(t){return o.matchMedia("(prefers-color-scheme"+t+")")}function f(){if(o.matchMedia&&u("").media!=="not all"){for(const t of c)if(u(":"+t).matches)return t}return"light"}})();function getStorageValue(o,e){switch(o){case"localStorage":try{return window.localStorage.getItem(e)}catch{return null}case"sessionStorage":try{return window.sessionStorage.getItem(e)}catch{return null}case"cookie":try{return getCookie(e)}catch{return null}default:return null}}function getCookie(o){const c=("; "+window.document.cookie).split("; "+o+"=");if(c.length===2){const s=c.pop();return s?s.split(";").shift():null}}</script></head><body><div id="__nuxt" class="isolate"></div><div id="teleports"></div><script>window.__NUXT__={};window.__NUXT__.config={public:{pddVersion:"0.20.0"},app:{baseURL:"/",buildId:"d2b92f2f-1f0b-435e-9ef9-537b7d452693",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script><script type="application/json" data-nuxt-data="nuxt-app" data-ssr="false" id="__NUXT_DATA__">[{"prerenderedAt":1,"serverRendered":2},1789739705046,false]</script></body></html>
package/docs/cli-view.md CHANGED
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: view
3
- description: Open the current Product Model as a private local report that stays updated while you edit.
3
+ description: Open a Product Model as a private local report, from your checkout or from a GitHub repository.
4
4
  section: open-source
5
5
  group: CLI
6
6
  order: 28
@@ -10,32 +10,56 @@ order: 28
10
10
 
11
11
  ```bash
12
12
  npx businesslens view
13
+ npx businesslens view owner/repo --pr 12
13
14
  ```
14
15
 
15
- `view` opens the current Product Model as a read-only report on localhost. It
16
- does not alter the model, write `.businesslens/build/report.json`, or send
17
- report data to BusinessLens.
16
+ `view` opens a Product Model as a read-only report on localhost. It does not
17
+ alter the model, write `.businesslens/build/report.json`, or send report data
18
+ to BusinessLens. The model must pass structural lint before the viewer opens.
18
19
 
19
- The model must pass structural lint before the viewer opens. While it remains
20
- open, valid changes appear automatically. If an edit introduces a lint error,
21
- the browser keeps the last valid report visible and recovers after the error is
22
- fixed.
20
+ The server listens only on `127.0.0.1` and stops when the command exits.
23
21
 
24
22
  ## Options
25
23
 
26
24
  | Option | Effect |
27
25
  | --- | --- |
26
+ | `[repository]` | View a GitHub repository instead of the working directory |
27
+ | `--branch <name>` | Branch or tag of that repository |
28
+ | `--pr <number>` | Pull request of that repository |
28
29
  | `--no-open` | Print the URL without launching the default browser |
29
30
  | `--port <port>` | Listen on a specific port from 1 through 65535 |
30
- | `-c, --cwd <path>` | Start model lookup from another directory |
31
+ | `-c, --cwd <path>` | Start local model lookup from another directory |
31
32
 
32
- By default, `view` starts from the current directory. If that directory directly
33
- contains `.businesslens/`, that model is used; otherwise BusinessLens checks the
34
- Git repository root. See
35
- [Choosing the Product Model](./cli.md#choosing-the-product-model) to select a
36
- nested Blueprint, another repository, or a standalone model with `--cwd`.
33
+ ## Your checkout
37
34
 
38
- The server listens only on `127.0.0.1` and stops when the command exits.
35
+ Without a repository, `view` uses the model in the current directory, or at
36
+ the Git repository root. Valid edits appear automatically while the viewer is
37
+ open; a lint error keeps the last valid report on screen until it is fixed.
38
+ See [Choosing the Product Model](./cli.md#choosing-the-product-model) for
39
+ `--cwd`.
40
+
41
+ ## A GitHub repository
42
+
43
+ Pass a repository as `owner/repo`, a `https://github.com/owner/repo` URL, or
44
+ `git@github.com:owner/repo.git` to fetch over SSH. Only GitHub is supported.
45
+
46
+ ```bash
47
+ npx businesslens view acme/checkout # default branch
48
+ npx businesslens view acme/checkout --branch release/2.4
49
+ npx businesslens view acme/checkout --pr 12
50
+ npx businesslens view https://github.com/acme/checkout/pull/12
51
+ ```
52
+
53
+ A `.../tree/<branch>` or `.../pull/<number>` URL selects the revision by
54
+ itself; do not combine it with `--branch` or `--pr`.
55
+
56
+ A pull request shows its head as GitHub holds it, so a pull request opened from
57
+ a fork shows the fork's content without naming the fork.
58
+
59
+ The revision is fetched shallowly into a temporary directory and deleted when
60
+ the command exits. Nothing from the repository runs. Private repositories use
61
+ your own Git credentials. The report does not refresh; run the command again
62
+ for a newer revision. `--cwd` does not apply.
39
63
 
40
64
  Use [`blueprint export`](./cli-export.md) when you need to write a source-free,
41
65
  portable Product Report.
package/docs/cli.md CHANGED
@@ -19,7 +19,7 @@ npx businesslens <command> [options]
19
19
  | [`install`](./cli-install.md) | Install map, ideate, and verify |
20
20
  | [`update`](./cli-update.md) | Refresh marked skill installations |
21
21
  | [`lint`](./cli-lint.md) | Check Product Model structure without semantic claims |
22
- | [`view`](./cli-view.md) | Render the current Product Model on localhost without exporting it |
22
+ | [`view`](./cli-view.md) | Render a Product Model on localhost without exporting it, local or from GitHub |
23
23
  | [`blueprint export`](./cli-export.md) | Compile a portable Product Report (a Blueprint) |
24
24
  | [`blueprint open`](./cli-open.md) | Expand a local Blueprint into `.businesslens/` |
25
25
  | [`blueprint pull`](./cli-pull.md) | Pull a catalog Blueprint by name |
@@ -55,6 +55,10 @@ npx businesslens view --cwd ./blueprints/example-product
55
55
  npx businesslens lint --cwd ../fixture-shop --json
56
56
  ```
57
57
 
58
+ `view` can instead take a GitHub repository, branch, or pull request; the model
59
+ is then looked up from that snapshot's root, and `--cwd` does not apply. See
60
+ [`view`](./cli-view.md#a-github-repository).
61
+
58
62
  For `blueprint open` and `blueprint pull`, `--cwd` is instead the exact target
59
63
  directory where `.businesslens/` will be created. For `install` and `update`, it
60
64
  is the project used for harness detection and project-scoped skill installation.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "businesslens",
3
- "version": "0.19.0",
3
+ "version": "0.20.0",
4
4
  "description": "Product-Driven Development for coding agents: a git-tracked Product Model in .businesslens/, verified against implementation.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1 +0,0 @@
1
- {"id":"d2255909-0bae-47f6-96ad-a333650a8e90","timestamp":1789673409172,"prerendered":[]}