@pharn-dev/pharn 0.3.1 → 0.3.2
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/dist/index.js +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{realpathSync}from"node:fs";import{fileURLToPath}from"node:url";import minimist from"minimist";import{intro,log as log4,note as note2,spinner as spinner2}from"@clack/prompts";import pc from"picocolors";import{createRequire}from"node:module";var require2=createRequire(import.meta.url),PHARN_VERSION=require2("../package.json").version;var LOGO=["\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2557","\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551","\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551","\u2588\u2588\u2554\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551","\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551","\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D"];function showBanner(){console.log();for(let line of LOGO)console.log(` ${pc.cyan(pc.bold(line))}`);console.log(),console.log(` ${pc.dim(`Audit-grade methodology for Claude Code \xB7 v${PHARN_VERSION}`)}`),console.log()}import{confirm,isCancel,log}from"@clack/prompts";function cancelAndExit(message="Cancelled. Nothing was changed."){log.info(message),process.exit(0)}async function confirmWarning(warning,prompt,initialValue){log.warn(warning);let result=await confirm({message:prompt,initialValue});return isCancel(result)&&cancelAndExit(),result===!0}var REPO="pharn-dev/pharn-oss",REPO_BRANCH="main",REPO_URL="github.com/pharn-dev/pharn-oss",FIRST_FEATURE_COMMAND="/pharn-spec",GRILLERS_DIR="pharn-pipeline/grillers",LENSES_DIR="pharn-review",SKILLS_VERSION_FILE="SKILLS_VERSION",TRUSTED_DOCS=["CONSTITUTION.md","ARCHITECTURE.md","THREAT-MODEL.md","LIMITS.md"],CONTRACTS_DIR="pharn-contracts",FLOOR_DIR=".dev/floor",PHARN_GRILLERS_DIR="pharn/pharn-pipeline/grillers",PHARN_LENSES_DIR="pharn/pharn-review",PHARN_CONTRACTS_DIR="pharn/pharn-contracts",PHARN_FLOOR_DIR="pharn/floor",PHARN_TRUSTED_DOCS=["pharn/CONSTITUTION.md","pharn/ARCHITECTURE.md"],CLAUDE_COMMANDS_DIR=".claude/commands",CLAUDE_HOOKS_DIR=".claude/hooks",CLAUDE_SETTINGS_FILE=".claude/settings.json",PRODUCT_COMMAND_PREFIX="pharn-",DEV_COMMAND_PREFIX="pharn-dev-";import{existsSync,readFileSync,readdirSync}from"node:fs";import{join,resolve}from"node:path";var SSR_FRAMEWORKS=new Set(["next","nuxt","@remix-run/react","@remix-run/node","@sveltejs/kit","astro","@angular/ssr"]),BACKEND_FRAMEWORKS=new Set(["express","fastify","@nestjs/core","koa","hono","@hapi/hapi","prisma","@prisma/client","drizzle-orm"]),CLIENT_UI=new Set(["react","vue","svelte","@angular/core","solid-js","preact"]),ARCHETYPE_ORDER=["ssr","backend","spa","lib"],hasAny=(names,allowlist)=>{for(let name of allowlist)if(names.has(name))return!0;return!1};function packageSignals(pkg){let names=new Set([...Object.keys(pkg.dependencies??{}),...Object.keys(pkg.devDependencies??{})]);return{ssr:hasAny(names,SSR_FRAMEWORKS),backend:hasAny(names,BACKEND_FRAMEWORKS),clientUi:hasAny(names,CLIENT_UI)}}var SQL_HOST_DIRS=new Set(["migrations","db","database","prisma","drizzle","sql"]),NON_UI_DIRS=new Set(["test","tests","__tests__","__mocks__","spec","e2e","emails","email"]),TEST_FIXTURE_RE=/\.(test|spec)\.(tsx|jsx)$/;function classifyEntry(name,isDir,segments){let lower=name.toLowerCase(),parent=segments[segments.length-1],underDbLocation=segments.some(s=>SQL_HOST_DIRS.has(s));if(isDir){let apiBackend=lower==="api"&&(segments.length===0||parent==="pages"||parent==="app"),migrationsBackend=lower==="migrations"&&(segments.length===0||underDbLocation);return{ssr:!1,backend:apiBackend||migrationsBackend,clientUi:!1}}let isRouteHandler=lower==="route.ts"||lower==="route.tsx"||lower==="route.js"||lower==="route.mjs";return{ssr:lower.startsWith("next.config."),backend:isRouteHandler&&segments.includes("app")||lower.endsWith(".sql")&&underDbLocation,clientUi:(lower.endsWith(".tsx")||lower.endsWith(".jsx"))&&!TEST_FIXTURE_RE.test(lower)&&!segments.some(s=>NON_UI_DIRS.has(s))}}function mergeSignals(a,b){return{ssr:a.ssr||b.ssr,backend:a.backend||b.backend,clientUi:a.clientUi||b.clientUi}}function archetypesFromSignals(sig){let hasSpa=sig.clientUi&&!sig.ssr,found=new Set;return sig.ssr&&found.add("ssr"),sig.backend&&found.add("backend"),hasSpa&&found.add("spa"),found.size===0&&found.add("lib"),ARCHETYPE_ORDER.filter(a=>found.has(a))}var SKIP_DIRS=new Set(["node_modules",".git","dist","build"]),MAX_DEPTH=24,MAX_ENTRIES=5e4;function scanFileTreeSignals(root){let acc={ssr:!1,backend:!1,clientUi:!1},budget=MAX_ENTRIES,allFound=()=>acc.ssr&&acc.backend&&acc.clientUi,readEntries=dir=>{try{return readdirSync(dir,{withFileTypes:!0})}catch{return[]}},walk=(dir,depth,segments)=>{if(depth>MAX_DEPTH||budget<=0||allFound())return;let entries=readEntries(dir).sort((a,b)=>a.name<b.name?-1:a.name>b.name?1:0);for(let entry of entries){if(budget<=0||allFound())return;let name=entry.name;if(entry.isSymbolicLink())continue;let isDir=entry.isDirectory();isDir&&SKIP_DIRS.has(name.toLowerCase())||!isDir&&name.toLowerCase().startsWith(".env")||!isDir&&!entry.isFile()||(budget-=1,acc=mergeSignals(acc,classifyEntry(name,isDir,segments)),isDir&&walk(join(dir,name),depth+1,[...segments,name.toLowerCase()]))}};return walk(root,0,[]),acc}function readPackageSignals(cwd){let empty=packageSignals({}),pkgPath=resolve(cwd,"package.json");if(!existsSync(pkgPath))return{pkgSig:empty,packageJsonFound:!1};try{let parsed=JSON.parse(readFileSync(pkgPath,"utf8"));return typeof parsed!="object"||parsed===null||Array.isArray(parsed)?{pkgSig:empty,packageJsonFound:!1}:{pkgSig:packageSignals(parsed),packageJsonFound:!0}}catch{return{pkgSig:empty,packageJsonFound:!1}}}function detectArchetypesFromProject(cwd){let{pkgSig,packageJsonFound}=readPackageSignals(cwd),fileSig=scanFileTreeSignals(cwd),merged=mergeSignals(pkgSig,fileSig);return{archetypes:archetypesFromSignals(merged),packageJsonFound}}import{existsSync as existsSync3,readFileSync as readFileSync2,readdirSync as readdirSync2}from"node:fs";import{existsSync as existsSync2}from"node:fs";import{resolve as resolve2,sep}from"node:path";var ManifestValidationError=class extends Error{constructor(message){super(message),this.name="ManifestValidationError"}};var VERSION_RE=/^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/;var CAPABILITY_NAME_RE=/^[a-z0-9]+(-[a-z0-9]+)*$/,COPY_FILENAME_RE=/^[a-z0-9]+(-[a-z0-9]+)*\.(md|cjs|mjs|json)$/,ROLE_VALUES=["griller","lens"],APPLIES_TOKEN_VALUES=["universal","ssr","backend","spa","lib"],CONTROL_CHARS_RE=/[\x00-\x1f\x7f-\x9f]/;function assertRole(value,label){if(typeof value!="string")throw new ManifestValidationError(`${label} role must be a string`);let match=ROLE_VALUES.find(r=>r===value);if(!match)throw new ManifestValidationError(`${label} has invalid role ${JSON.stringify(value)} (expected one of ${ROLE_VALUES.join(", ")})`);return match}function assertAppliesToken(value,label){let match=APPLIES_TOKEN_VALUES.find(t=>t===value);if(!match)throw new ManifestValidationError(`${label} has invalid applies value ${JSON.stringify(value)} (expected one of ${APPLIES_TOKEN_VALUES.join(", ")})`);return match}function assertSafeString(value,label,pattern){if(typeof value!="string")throw new ManifestValidationError(`${label} must be a string`);if(CONTROL_CHARS_RE.test(value))throw new ManifestValidationError(`${label} contains control characters`);if(!pattern.test(value))throw new ManifestValidationError(`${label} has invalid format: ${JSON.stringify(value)}`);return value}function assertNoDotDot(value,label){if(value.includes(".."))throw new ManifestValidationError(`${label} must not contain '..'`)}function isPlainObject(v){return typeof v=="object"&&v!==null&&!Array.isArray(v)}function safeJoin(base,rel){let target=resolve2(base,rel),root=resolve2(base);if(target!==root&&!target.startsWith(root+sep))throw new ManifestValidationError(`Refusing path escape: ${rel} resolves outside ${base}`);return target}function detectLayout(rootDir){return existsSync2(safeJoin(rootDir,PHARN_CONTRACTS_DIR))?"pharn":"flat"}function layoutPaths(layout){return layout==="pharn"?{layout,grillers:PHARN_GRILLERS_DIR,lenses:PHARN_LENSES_DIR,contracts:PHARN_CONTRACTS_DIR,floor:PHARN_FLOOR_DIR,docs:PHARN_TRUSTED_DOCS}:{layout,grillers:GRILLERS_DIR,lenses:LENSES_DIR,contracts:CONTRACTS_DIR,floor:FLOOR_DIR,docs:TRUSTED_DOCS}}function configLayout(config){return config.layout==="pharn"?"pharn":"flat"}function parseCapabilityIndex(repoDir){let capabilities=[],paths=layoutPaths(detectLayout(repoDir)),subtrees=[{dir:paths.grillers,role:"griller"},{dir:paths.lenses,role:"lens"}];for(let subtree of subtrees){let subtreeDir=safeJoin(repoDir,subtree.dir);if(!existsSync3(subtreeDir))throw new ManifestValidationError(`Capability subtree "${subtree.dir}" is missing in the fetched repo.`);let names=readdirSync2(subtreeDir,{withFileTypes:!0}).filter(e=>e.isDirectory()&&!e.isSymbolicLink()).map(e=>e.name).sort((a,b)=>a<b?-1:a>b?1:0);for(let name of names){assertSafeString(name,`capability "${name}"`,CAPABILITY_NAME_RE),assertNoDotDot(name,`capability "${name}"`);let capFile=safeJoin(subtreeDir,`${name}/${name}.md`);if(!existsSync3(capFile))throw new ManifestValidationError(`Capability "${name}" in ${subtree.dir} is missing its markdown ${name}/${name}.md.`);let frontmatter=extractFrontmatter(readFileSync2(capFile,"utf8"),name),role=assertRole(readField(frontmatter,"role",name),name);if(role!==subtree.role)throw new ManifestValidationError(`Capability "${name}" declares role "${role}" but lives under ${subtree.dir} (expected "${subtree.role}").`);let applies=parseApplies(readField(frontmatter,"applies",name),name);capabilities.push({name,role,applies})}}return{capabilities}}function extractFrontmatter(content,name){let match=/^---\n([\s\S]*?)\n---/.exec(content);if(!match)throw new ManifestValidationError(`Capability "${name}" is missing a "---"-fenced frontmatter block.`);return match[1]}function readField(frontmatter,field,name){let match=new RegExp(`^${field}:[ \\t]*(.+?)[ \\t]*$`,"m").exec(frontmatter);if(!match)throw new ManifestValidationError(`Capability "${name}" is missing the "${field}" frontmatter field.`);return stripQuotes(match[1])}function stripQuotes(value){let trimmed=value.trim();return trimmed.startsWith('"')&&trimmed.endsWith('"')||trimmed.startsWith("'")&&trimmed.endsWith("'")?trimmed.slice(1,-1):trimmed}function parseApplies(raw,name){if(!/^\[.*\]$/.test(raw))throw new ManifestValidationError(`Capability "${name}" has a malformed "applies" value ${JSON.stringify(raw)} (expected a "[...]" array).`);let tokens=raw.slice(1,-1).split(",").map(t=>stripQuotes(t)).filter(t=>t.length>0);if(tokens.length===0)throw new ManifestValidationError(`Capability "${name}" has an empty "applies" array \u2014 every capability must declare its applicability.`);let validated=tokens.map(t=>assertAppliesToken(t,`capability "${name}"`)),universal=validated.includes("universal");if(universal&&validated.length>1)throw new ManifestValidationError(`Capability "${name}" mixes "universal" with archetypes in "applies" \u2014 use exactly ["universal"] or archetypes only.`);return universal?"universal":validated}function resolveCapabilities(archetypes,index){let detected=new Set(archetypes),selected=[],skipped=[];for(let entry of index.capabilities){if(entry.applies==="universal"){selected.push({name:entry.name,role:entry.role,matched:"universal"});continue}let matched=entry.applies.filter(a=>detected.has(a));matched.length>0?selected.push({name:entry.name,role:entry.role,matched}):skipped.push({name:entry.name,role:entry.role,reason:`applies to [${entry.applies.join(", ")}]; detected [${archetypes.join(", ")}]`})}return{selected,skipped}}import{mkdtempSync,rmSync}from"node:fs";import{tmpdir}from"node:os";import{join as join2}from"node:path";import degit from"degit";var API="https://api.github.com",FETCH_TIMEOUT_MS=8e3;async function fetchRepo(){let sha=await fetchCommitSha(),ref=sha??REPO_BRANCH,dir=mkdtempSync(join2(tmpdir(),"pharn-"));try{await degit(`${REPO}#${ref}`,{force:!0,cache:!1}).clone(dir)}catch(err){throw rmSync(dir,{recursive:!0,force:!0}),err}return{dir,sha,cleanup:()=>rmSync(dir,{recursive:!0,force:!0})}}async function fetchCommitSha(){let url=`${API}/repos/${REPO}/commits/${REPO_BRANCH}`,controller=new AbortController,timer=setTimeout(()=>controller.abort(),FETCH_TIMEOUT_MS);try{let res=await fetch(url,{redirect:"error",signal:controller.signal,headers:{Accept:"application/vnd.github+json","X-GitHub-Api-Version":"2022-11-28"}});if(!res.ok)return null;let body=await res.json();return typeof body.sha=="string"?body.sha:null}catch{return null}finally{clearTimeout(timer)}}import{existsSync as existsSync4}from"node:fs";import{resolve as resolve3}from"node:path";import{cancel}from"@clack/prompts";function runGitPrereq(){let cwd=process.cwd();existsSync4(resolve3(cwd,".git"))||fail(`\u2717 git not found.
|
|
2
|
+
import{realpathSync}from"node:fs";import{fileURLToPath}from"node:url";import minimist from"minimist";import{intro,log as log4,note as note2,spinner as spinner2}from"@clack/prompts";import pc from"picocolors";import{createRequire}from"node:module";var require2=createRequire(import.meta.url),PHARN_VERSION=require2("../package.json").version;var LOGO=["\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2557","\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551","\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551","\u2588\u2588\u2554\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551","\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551","\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D"];function showBanner(){console.log();for(let line of LOGO)console.log(` ${pc.cyan(pc.bold(line))}`);console.log(),console.log(` ${pc.dim(`Audit-grade methodology for Claude Code \xB7 v${PHARN_VERSION}`)}`),console.log()}import{confirm,isCancel,log}from"@clack/prompts";function cancelAndExit(message="Cancelled. Nothing was changed."){log.info(message),process.exit(0)}async function confirmWarning(warning,prompt,initialValue){log.warn(warning);let result=await confirm({message:prompt,initialValue});return isCancel(result)&&cancelAndExit(),result===!0}var REPO="pharn-dev/pharn-oss",REPO_BRANCH="main",REPO_URL="github.com/pharn-dev/pharn-oss",FIRST_FEATURE_COMMAND="/pharn-spec",GRILLERS_DIR="pharn-pipeline/grillers",LENSES_DIR="pharn-review",SKILLS_VERSION_FILE="SKILLS_VERSION",TRUSTED_DOCS=["CONSTITUTION.md","ARCHITECTURE.md","THREAT-MODEL.md","LIMITS.md"],CONTRACTS_DIR="pharn-contracts",FLOOR_DIR=".dev/floor",PHARN_GRILLERS_DIR="pharn/pharn-pipeline/grillers",PHARN_LENSES_DIR="pharn/pharn-review",PHARN_CONTRACTS_DIR="pharn/pharn-contracts",PHARN_FLOOR_DIR="pharn/floor",PHARN_TRUSTED_DOCS=["pharn/CONSTITUTION.md","pharn/ARCHITECTURE.md"],CLAUDE_COMMANDS_DIR=".claude/commands",CLAUDE_HOOKS_DIR=".claude/hooks",CLAUDE_SETTINGS_FILE=".claude/settings.json",PRODUCT_COMMAND_PREFIX="pharn-",DEV_COMMAND_PREFIX="pharn-dev-";import{existsSync,readFileSync,readdirSync}from"node:fs";import{join,resolve}from"node:path";var SSR_FRAMEWORKS=new Set(["next","nuxt","@remix-run/react","@remix-run/node","@sveltejs/kit","astro","@angular/ssr"]),BACKEND_FRAMEWORKS=new Set(["express","fastify","@nestjs/core","koa","hono","@hapi/hapi","prisma","@prisma/client","drizzle-orm"]),CLIENT_UI=new Set(["react","vue","svelte","@angular/core","solid-js","preact"]),ARCHETYPE_ORDER=["ssr","backend","spa","lib"],hasAny=(names,allowlist)=>{for(let name of allowlist)if(names.has(name))return!0;return!1};function packageSignals(pkg){let names=new Set([...Object.keys(pkg.dependencies??{}),...Object.keys(pkg.devDependencies??{})]);return{ssr:hasAny(names,SSR_FRAMEWORKS),backend:hasAny(names,BACKEND_FRAMEWORKS),clientUi:hasAny(names,CLIENT_UI)}}var SQL_HOST_DIRS=new Set(["migrations","db","database","prisma","drizzle","sql"]),NON_UI_DIRS=new Set(["test","tests","__tests__","__mocks__","spec","e2e","emails","email"]),TEST_FIXTURE_RE=/\.(test|spec)\.(tsx|jsx)$/;function classifyEntry(name,isDir,segments){let lower=name.toLowerCase(),parent=segments[segments.length-1],underDbLocation=segments.some(s=>SQL_HOST_DIRS.has(s));if(isDir){let apiBackend=lower==="api"&&(segments.length===0||parent==="pages"||parent==="app"),migrationsBackend=lower==="migrations"&&(segments.length===0||underDbLocation);return{ssr:!1,backend:apiBackend||migrationsBackend,clientUi:!1}}let isRouteHandler=lower==="route.ts"||lower==="route.tsx"||lower==="route.js"||lower==="route.mjs";return{ssr:lower.startsWith("next.config."),backend:isRouteHandler&&segments.includes("app")||lower.endsWith(".sql")&&underDbLocation,clientUi:(lower.endsWith(".tsx")||lower.endsWith(".jsx"))&&!TEST_FIXTURE_RE.test(lower)&&!segments.some(s=>NON_UI_DIRS.has(s))}}function mergeSignals(a,b){return{ssr:a.ssr||b.ssr,backend:a.backend||b.backend,clientUi:a.clientUi||b.clientUi}}function archetypesFromSignals(sig){let hasSpa=sig.clientUi&&!sig.ssr,found=new Set;return sig.ssr&&found.add("ssr"),sig.backend&&found.add("backend"),hasSpa&&found.add("spa"),found.size===0&&found.add("lib"),ARCHETYPE_ORDER.filter(a=>found.has(a))}var SKIP_DIRS=new Set(["node_modules",".git","dist","build"]),MAX_DEPTH=24,MAX_ENTRIES=5e4;function scanFileTreeSignals(root){let acc={ssr:!1,backend:!1,clientUi:!1},budget=MAX_ENTRIES,allFound=()=>acc.ssr&&acc.backend&&acc.clientUi,readEntries=dir=>{try{return readdirSync(dir,{withFileTypes:!0})}catch{return[]}},walk=(dir,depth,segments)=>{if(depth>MAX_DEPTH||budget<=0||allFound())return;let entries=readEntries(dir).sort((a,b)=>a.name<b.name?-1:a.name>b.name?1:0);for(let entry of entries){if(budget<=0||allFound())return;let name=entry.name;if(entry.isSymbolicLink())continue;let isDir=entry.isDirectory();isDir&&SKIP_DIRS.has(name.toLowerCase())||!isDir&&name.toLowerCase().startsWith(".env")||!isDir&&!entry.isFile()||(budget-=1,acc=mergeSignals(acc,classifyEntry(name,isDir,segments)),isDir&&walk(join(dir,name),depth+1,[...segments,name.toLowerCase()]))}};return walk(root,0,[]),acc}function readPackageSignals(cwd){let empty=packageSignals({}),pkgPath=resolve(cwd,"package.json");if(!existsSync(pkgPath))return{pkgSig:empty,packageJsonFound:!1};try{let parsed=JSON.parse(readFileSync(pkgPath,"utf8"));return typeof parsed!="object"||parsed===null||Array.isArray(parsed)?{pkgSig:empty,packageJsonFound:!1}:{pkgSig:packageSignals(parsed),packageJsonFound:!0}}catch{return{pkgSig:empty,packageJsonFound:!1}}}function detectArchetypesFromProject(cwd){let{pkgSig,packageJsonFound}=readPackageSignals(cwd),fileSig=scanFileTreeSignals(cwd),merged=mergeSignals(pkgSig,fileSig);return{archetypes:archetypesFromSignals(merged),packageJsonFound}}import{existsSync as existsSync3,readFileSync as readFileSync2,readdirSync as readdirSync2}from"node:fs";import{existsSync as existsSync2}from"node:fs";import{resolve as resolve2,sep}from"node:path";var ManifestValidationError=class extends Error{constructor(message){super(message),this.name="ManifestValidationError"}};var VERSION_RE=/^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/;var CAPABILITY_NAME_RE=/^[a-z0-9]+(-[a-z0-9]+)*$/,COPY_FILENAME_RE=/^[a-z0-9]+(-[a-z0-9]+)*\.(md|cjs|mjs|json)$/,COMMIT_RE=/^[0-9a-f]{40}$/,ROLE_VALUES=["griller","lens"],APPLIES_TOKEN_VALUES=["universal","ssr","backend","spa","lib"],CONTROL_CHARS_RE=/[\x00-\x1f\x7f-\x9f]/;function assertRole(value,label){if(typeof value!="string")throw new ManifestValidationError(`${label} role must be a string`);let match=ROLE_VALUES.find(r=>r===value);if(!match)throw new ManifestValidationError(`${label} has invalid role ${JSON.stringify(value)} (expected one of ${ROLE_VALUES.join(", ")})`);return match}function assertAppliesToken(value,label){let match=APPLIES_TOKEN_VALUES.find(t=>t===value);if(!match)throw new ManifestValidationError(`${label} has invalid applies value ${JSON.stringify(value)} (expected one of ${APPLIES_TOKEN_VALUES.join(", ")})`);return match}function assertSafeString(value,label,pattern){if(typeof value!="string")throw new ManifestValidationError(`${label} must be a string`);if(CONTROL_CHARS_RE.test(value))throw new ManifestValidationError(`${label} contains control characters`);if(!pattern.test(value))throw new ManifestValidationError(`${label} has invalid format: ${JSON.stringify(value)}`);return value}function assertNoDotDot(value,label){if(value.includes(".."))throw new ManifestValidationError(`${label} must not contain '..'`)}function isPlainObject(v){return typeof v=="object"&&v!==null&&!Array.isArray(v)}function safeJoin(base,rel){let target=resolve2(base,rel),root=resolve2(base);if(target!==root&&!target.startsWith(root+sep))throw new ManifestValidationError(`Refusing path escape: ${rel} resolves outside ${base}`);return target}function detectLayout(rootDir){return existsSync2(safeJoin(rootDir,PHARN_CONTRACTS_DIR))?"pharn":"flat"}function layoutPaths(layout){return layout==="pharn"?{layout,grillers:PHARN_GRILLERS_DIR,lenses:PHARN_LENSES_DIR,contracts:PHARN_CONTRACTS_DIR,floor:PHARN_FLOOR_DIR,docs:PHARN_TRUSTED_DOCS}:{layout,grillers:GRILLERS_DIR,lenses:LENSES_DIR,contracts:CONTRACTS_DIR,floor:FLOOR_DIR,docs:TRUSTED_DOCS}}function configLayout(config){return config.layout==="pharn"?"pharn":"flat"}function parseCapabilityIndex(repoDir){let capabilities=[],paths=layoutPaths(detectLayout(repoDir)),subtrees=[{dir:paths.grillers,role:"griller"},{dir:paths.lenses,role:"lens"}];for(let subtree of subtrees){let subtreeDir=safeJoin(repoDir,subtree.dir);if(!existsSync3(subtreeDir))throw new ManifestValidationError(`Capability subtree "${subtree.dir}" is missing in the fetched repo.`);let names=readdirSync2(subtreeDir,{withFileTypes:!0}).filter(e=>e.isDirectory()&&!e.isSymbolicLink()).map(e=>e.name).sort((a,b)=>a<b?-1:a>b?1:0);for(let name of names){assertSafeString(name,`capability "${name}"`,CAPABILITY_NAME_RE),assertNoDotDot(name,`capability "${name}"`);let capFile=safeJoin(subtreeDir,`${name}/${name}.md`);if(!existsSync3(capFile))throw new ManifestValidationError(`Capability "${name}" in ${subtree.dir} is missing its markdown ${name}/${name}.md.`);let frontmatter=extractFrontmatter(readFileSync2(capFile,"utf8"),name),role=assertRole(readField(frontmatter,"role",name),name);if(role!==subtree.role)throw new ManifestValidationError(`Capability "${name}" declares role "${role}" but lives under ${subtree.dir} (expected "${subtree.role}").`);let applies=parseApplies(readField(frontmatter,"applies",name),name);capabilities.push({name,role,applies})}}return{capabilities}}function extractFrontmatter(content,name){let match=/^---\n([\s\S]*?)\n---/.exec(content);if(!match)throw new ManifestValidationError(`Capability "${name}" is missing a "---"-fenced frontmatter block.`);return match[1]}function readField(frontmatter,field,name){let match=new RegExp(`^${field}:[ \\t]*(.+?)[ \\t]*$`,"m").exec(frontmatter);if(!match)throw new ManifestValidationError(`Capability "${name}" is missing the "${field}" frontmatter field.`);return stripQuotes(match[1])}function stripQuotes(value){let trimmed=value.trim();return trimmed.startsWith('"')&&trimmed.endsWith('"')||trimmed.startsWith("'")&&trimmed.endsWith("'")?trimmed.slice(1,-1):trimmed}function parseApplies(raw,name){if(!/^\[.*\]$/.test(raw))throw new ManifestValidationError(`Capability "${name}" has a malformed "applies" value ${JSON.stringify(raw)} (expected a "[...]" array).`);let tokens=raw.slice(1,-1).split(",").map(t=>stripQuotes(t)).filter(t=>t.length>0);if(tokens.length===0)throw new ManifestValidationError(`Capability "${name}" has an empty "applies" array \u2014 every capability must declare its applicability.`);let validated=tokens.map(t=>assertAppliesToken(t,`capability "${name}"`)),universal=validated.includes("universal");if(universal&&validated.length>1)throw new ManifestValidationError(`Capability "${name}" mixes "universal" with archetypes in "applies" \u2014 use exactly ["universal"] or archetypes only.`);return universal?"universal":validated}function resolveCapabilities(archetypes,index){let detected=new Set(archetypes),selected=[],skipped=[];for(let entry of index.capabilities){if(entry.applies==="universal"){selected.push({name:entry.name,role:entry.role,matched:"universal"});continue}let matched=entry.applies.filter(a=>detected.has(a));matched.length>0?selected.push({name:entry.name,role:entry.role,matched}):skipped.push({name:entry.name,role:entry.role,reason:`applies to [${entry.applies.join(", ")}]; detected [${archetypes.join(", ")}]`})}return{selected,skipped}}import{mkdtempSync,rmSync}from"node:fs";import{tmpdir}from"node:os";import{join as join2}from"node:path";import degit from"degit";var API="https://api.github.com",FETCH_TIMEOUT_MS=8e3;async function fetchRepo(){let rawSha=await fetchCommitSha(),sha=rawSha===null?null:assertSafeString(rawSha,"commit SHA",COMMIT_RE),ref=sha??REPO_BRANCH,dir=mkdtempSync(join2(tmpdir(),"pharn-"));try{await degit(`${REPO}#${ref}`,{force:!0,cache:!1}).clone(dir)}catch(err){throw rmSync(dir,{recursive:!0,force:!0}),err}return{dir,sha,cleanup:()=>rmSync(dir,{recursive:!0,force:!0})}}async function fetchCommitSha(){let url=`${API}/repos/${REPO}/commits/${REPO_BRANCH}`,controller=new AbortController,timer=setTimeout(()=>controller.abort(),FETCH_TIMEOUT_MS);try{let res=await fetch(url,{redirect:"error",signal:controller.signal,headers:{Accept:"application/vnd.github+json","X-GitHub-Api-Version":"2022-11-28"}});if(!res.ok)return null;let body=await res.json();return typeof body.sha=="string"?body.sha:null}catch{return null}finally{clearTimeout(timer)}}import{existsSync as existsSync4}from"node:fs";import{resolve as resolve3}from"node:path";import{cancel}from"@clack/prompts";function runGitPrereq(){let cwd=process.cwd();existsSync4(resolve3(cwd,".git"))||fail(`\u2717 git not found.
|
|
3
3
|
Run: git init && git add -A && git commit -m 'init'
|
|
4
4
|
Then re-run: npx @pharn-dev/pharn init`)}function fail(message){cancel(message),process.exit(1)}import{existsSync as existsSync5,readdirSync as readdirSync3,statSync}from"node:fs";import{join as join3,resolve as resolve4,sep as sep2}from"node:path";var PHARN_CONFIG_FILE="pharn.config.json";function*walkFiles(dir,prefix=""){for(let entry of readdirSync3(dir,{withFileTypes:!0})){if(entry.isSymbolicLink())continue;let rel=prefix?`${prefix}/${entry.name}`:entry.name;entry.isDirectory()?yield*walkFiles(resolve4(dir,entry.name),rel):yield rel}}function toPosix(rel){return rel.split(sep2).join("/").replace(/\/+$/,"")}function collectExpectedInstallPaths(params){let{repoDir,capabilities,layout}=params,paths=layoutPaths(layout),expected=new Map,add=(rel,repoPath)=>{expected.set(toPosix(rel),repoPath)},addDir=(relDir,keep)=>{let from=safeJoin(repoDir,relDir);if(!(!existsSync5(from)||!statSync(from).isDirectory()))for(let rel of walkFiles(from))keep&&!keep(rel)||add(join3(relDir,rel),resolve4(from,rel))};for(let cap of capabilities){let subtree=cap.role==="griller"?paths.grillers:paths.lenses;addDir(`${subtree}/${cap.name}`)}addDir(CLAUDE_COMMANDS_DIR,rel=>!rel.includes("/")&&rel.endsWith(".md")&&rel.startsWith(PRODUCT_COMMAND_PREFIX)&&!rel.startsWith(DEV_COMMAND_PREFIX)),addDir(CLAUDE_HOOKS_DIR,rel=>!rel.includes("/")&&rel.endsWith(".cjs")&&!rel.endsWith(".test.cjs"));for(let doc of paths.docs){let from=safeJoin(repoDir,doc);existsSync5(from)&&add(doc,from)}return addDir(paths.contracts),addDir(paths.floor,rel=>!/\.test\.(mjs|cjs)$/.test(rel)),expected}function conflictingWriteTargets(params){let{repoDir,projectRoot,capabilities,layout}=params,expected=collectExpectedInstallPaths({repoDir,capabilities,layout}),candidates=new Set(expected.keys());candidates.add(PHARN_CONFIG_FILE);let conflicts=[];for(let rel of candidates)existsSync5(safeJoin(projectRoot,rel))&&conflicts.push(rel);return conflicts.sort()}var MAX_LISTED=10;async function confirmWriteTargets(repoDir,cwd,selection){let conflicts=conflictingWriteTargets({repoDir,projectRoot:cwd,capabilities:selection.selected,layout:detectLayout(repoDir)});if(conflicts.length===0)return!0;let shown=conflicts.slice(0,MAX_LISTED),more=conflicts.length-shown.length,list=shown.map(p=>` \u2022 ${p}`).join(`
|
|
5
5
|
`),tail=more>0?`
|