@paniolo/scan 0.0.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.
Files changed (3) hide show
  1. package/README.md +52 -0
  2. package/dist/cli.js +81 -0
  3. package/package.json +41 -0
package/README.md ADDED
@@ -0,0 +1,52 @@
1
+ # paniolo-scan
2
+
3
+ > Scan a repository for AI harness performance — how well your repo is set up for
4
+ > coding agents across Copilot, Cursor, Codex, Antigravity, Claude Code, and Gemini.
5
+
6
+ **100% diagnostic.** Reports findings only — no scaffolding, autofix, or file writes.
7
+
8
+ ```bash
9
+ npx @paniolo/scan
10
+ ```
11
+
12
+ ## Quick start
13
+
14
+ ```bash
15
+ # Scan the current directory
16
+ npx @paniolo/scan
17
+
18
+ # JSON for CI
19
+ npx @paniolo/scan --format json
20
+
21
+ # Filter by harness
22
+ npx @paniolo/scan --harness cursor,codex
23
+
24
+ # Scan another repo path
25
+ npx @paniolo/scan ../songshare-effect
26
+ ```
27
+
28
+ ## Documentation
29
+
30
+ - [Overview](./docs/overview.md) — problem, scope, supported harnesses
31
+ - [Meta-harness model](./docs/meta-harness.md) — what “good” means, evaluation dimensions, calibration
32
+ - [Harness analysis](./docs/harness-analysis.md) — sharing vs unique guidance, per-harness scores
33
+ - [Full-spectrum roadmap](./docs/full-spectrum-intelligence-layer.md) — implementation path for intelligence-layer scanning
34
+ - [Architecture](./docs/architecture.md) — pipeline, rule model, roadmap
35
+ - [Rules catalog](./docs/rules-catalog.md) — built-in rules by dimension and status
36
+ - [CI integration](./docs/ci.md) — exit codes and GitHub Actions
37
+ - [npm publishing steps](./docs/npm-publishing-steps.md) — release runbook for publishing `npx @paniolo/scan`
38
+ - [npm packaging and privacy](./docs/npm-packaging-privacy.md) — publishing `npx @paniolo/scan` without publishing `src/`
39
+ - [Diagrams](./docs/diagrams.md) — Mermaid conventions for docs
40
+
41
+ ## Development
42
+
43
+ ```bash
44
+ npm install
45
+ npm run build
46
+ npm test
47
+ npm start
48
+ ```
49
+
50
+ ## License
51
+
52
+ MIT
package/dist/cli.js ADDED
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env node
2
+ import es from"node:path";function K(e){process.stdout.write(`${JSON.stringify(e,null,2)}
3
+ `)}import{readFile as be}from"node:fs/promises";import xe from"node:path";var H=["copilot","cursor","codex","antigravity","claude","gemini"],he=new Set(["shared","skills","agents"]);function ge(e,n){return e.harness!==void 0?n.includes(e.harness)?[e.harness]:[]:he.has(e.layer)?n:e.path.startsWith(".cursor/")?n.includes("cursor")?["cursor"]:[]:e.path.startsWith(".codex/")?n.includes("codex")?["codex"]:[]:e.path.startsWith(".agent/")?n.includes("antigravity")?["antigravity"]:[]:e.path==="AGENTS.md"||e.path.startsWith("docs/ai/")?n:[]}function me(e,n){return n.length>=2?!0:he.has(e.layer)||e.path==="AGENTS.md"}import{access as on}from"node:fs/promises";import an from"node:path";var w=75,Y=300,T=["CLAUDE.md","GEMINI.md",".github/copilot-instructions.md"];function f(e,n){return e.files.some(t=>t.path===n)}function d(e,n){return e.fileContents.get(n)??null}function b(e,n){return e.files.find(s=>s.path===n)?.lines??null}function ye(e){return/AGENTS\.md/.test(e)||/docs\/ai\/rules\.md/.test(e)}function B(e){return e.match(/^---\r?\n([\s\S]*?)\r?\n---/)?.[1]??null}function R(e,n){return new RegExp(`^${n}:`,"m").test(e)}async function v(e,n){try{return await on(an.join(e,n)),!0}catch{return!1}}function G(e,n){return e.files.filter(t=>t.path.startsWith(n)).map(t=>t.path)}function g(e){return e.files.some(n=>n.path.startsWith("skills/")&&n.path.endsWith("/SKILL.md"))}function $(e){return/available-skills\.md/.test(e)||/skills\/\*\/SKILL\.md/.test(e)||/npm run qmd/.test(e)||/skills\//.test(e)&&/available-skills/.test(e)}function j(e){return/agent routing/i.test(e)&&/\|/.test(e)&&/agents\/.*\.agent\.md/.test(e)}var ln=50,cn={copilot:".github/copilot-instructions.md",claude:"CLAUDE.md",gemini:"GEMINI.md"};function E(e,n,t){let s=e.settings?.[n];if(typeof s=="object"&&s!==null&&!Array.isArray(s)&&Object.keys(s).some(a=>a.startsWith(t)))return!0;if(e.settingsRaw===null)return!1;let r=n.replaceAll(".","\\.");return new RegExp(`"${r}"\\s*:\\s*\\{[^}]*"${t}[^"]*"`).test(e.settingsRaw)}function ke(e){return e.files.some(n=>n.path.startsWith("agents/")&&n.path.endsWith(".agent.md"))}function O(e,n){let t=b(e,n);return t===null?!1:t<=ln}function C(e){return cn[e]??null}function Se(e,n){let t=d(e,n);return t===null?!1:/AGENTS\.md/.test(t)||/docs\/ai\/rules\.md/.test(t)}function X(e){return e.files.some(n=>n.path.startsWith(".agent/workflows/"))}function J(e,n){if(!g(e))return!1;if(f(e,"docs/ai/available-skills.md"))return!0;let t=d(e,"AGENTS.md");if(t!==null&&$(t))return!0;let s=C(n);if(s===null)return!1;let r=d(e,s);return r!==null&&/skills\//.test(r)}function we(e){let n=d(e,"CLAUDE.md");return n!==null&&j(n)}var Ce={sharedSkillsDiscoverable:"Shared skills discoverable",customAgents:"Custom agents",lifecycleHooks:"Lifecycle hooks",thinAdapter:"Thin adapter",workflows:"Workflows / playbooks"},_=["sharedSkillsDiscoverable","customAgents","lifecycleHooks","thinAdapter","workflows"],un="Harness not detected";async function dn(e){try{return await be(xe.join(e,".claude/settings.json"),"utf8")}catch{return null}}async function pn(e){try{return await be(xe.join(e,".codex/hooks.json"),"utf8"),!0}catch{return!1}}function l(e,n){return{status:e,detail:n}}function fn(e,n){if(!g(n.context))return l("no","No skills/ tree");switch(e){case"copilot":case"cursor":return E(n.vscode,"chat.agentSkillsLocations","skills")?l("yes","skills/ wired in chat.agentSkillsLocations"):l("partial","skills/ present but not wired in VS Code settings");case"codex":return J(n.context,"codex")?l("partial","skills/ with prose discovery via AGENTS.md or skill index"):l("partial","skills/ present; no documented discovery workflow");case"claude":case"gemini":return J(n.context,e)?l("partial","Prose discovery via skill index or adapter pointers"):l("no","skills/ without index or adapter pointers");case"antigravity":return X(n.context)?l("yes","skills/ with workflow playbooks"):l("partial","skills/ present without workflow playbooks");default:return l("no","Unknown harness")}}function hn(e,n){let t=ke(n.context);switch(e){case"copilot":case"cursor":return E(n.vscode,"chat.agentFilesLocations","agents")&&t?l("yes","agents/ wired in chat.agentFilesLocations"):t?l("partial","agents/ present but not wired in VS Code settings"):l("no","No agents/ tree or VS Code agent locations");case"codex":return n.context.files.some(r=>r.path.startsWith(".codex/agents/"))&&t?l("yes",".codex/agents wrappers with shared agents/"):t?l("partial","agents/ present without .codex/agents wrappers"):l("no","No custom agents configured");case"claude":return we(n.context)?l("yes","Agent routing table in CLAUDE.md"):t?l("partial","agents/ present without routing table in CLAUDE.md"):l("no","No agent routing or agents/ tree");case"gemini":{if(!t)return l("no","No agents/ tree");let s=C("gemini"),r=s===null?null:n.context.fileContents.get(s)??null;return r!==null&&/agents\//.test(r)?l("partial","agents/ referenced from GEMINI.md"):l("partial","agents/ present; document routing in GEMINI.md")}case"antigravity":return l("na","Uses workflow playbooks instead of custom agents");default:return l("no","Unknown harness")}}function gn(e,n){let t=n.vscode.hooksDirExists,s=n.vscode.settings?.["chat.useCustomAgentHooks"]===!0;switch(e){case"copilot":case"cursor":return t&&s?l("yes",".github/hooks/ with chat.useCustomAgentHooks"):t||s?l("partial","Hooks partially configured in VS Code workspace"):l("no","No lifecycle hooks configured");case"codex":return n.codexHooksExists?l("yes",".codex/hooks.json present"):t?l("partial","Shared .github/hooks/ only; no .codex/hooks.json"):l("no","No Codex hooks configured");case"claude":return n.claudeSettingsRaw!==null&&/"hooks"\s*:/.test(n.claudeSettingsRaw)?l("yes","Hooks in .claude/settings.json"):t?l("partial","Shared .github/hooks/ only; no .claude hooks"):l("no","No Claude hooks configured");case"gemini":return t&&s?l("partial","Shared VS Code hooks only; no Gemini-native hooks"):l("no","No lifecycle hooks for Gemini");case"antigravity":return l("na","No hook surface for Antigravity workflows");default:return l("no","Unknown harness")}}function mn(e,n){switch(e){case"copilot":{let t=C("copilot");return t===null||!n.context.files.some(s=>s.path===t)?l("no","Missing .github/copilot-instructions.md"):O(n.context,t)?l("yes","Copilot instructions within line budget"):l("partial","Copilot instructions exceed thin adapter budget")}case"cursor":{let t=n.context.files.some(r=>r.path.startsWith(".cursor/rules/")),s=E(n.vscode,"chat.agentSkillsLocations","skills")&&E(n.vscode,"chat.agentFilesLocations","agents");return t||s?l("yes","Cursor rules or VS Code agent settings configured"):l("partial","Cursor detected without rules or agent settings")}case"codex":return n.context.files.some(s=>s.path.startsWith(".codex/"))?n.context.files.some(s=>s.path==="AGENTS.md")?l("yes",".codex/ with shared AGENTS.md entry"):l("partial",".codex/ without root AGENTS.md"):l("no","No .codex/ config surface");case"claude":{let t=C("claude");if(t===null||!n.context.files.some(i=>i.path===t))return l("no","Missing CLAUDE.md");let s=O(n.context,t),r=Se(n.context,t);return s&&r?l("yes","Thin CLAUDE.md referencing shared layers"):s?l("partial","CLAUDE.md thin but missing shared layer pointers"):l("partial","CLAUDE.md exceeds thin adapter budget")}case"gemini":{let t=C("gemini");return t===null||!n.context.files.some(s=>s.path===t)?l("no","Missing GEMINI.md"):O(n.context,t)?l("yes","Thin GEMINI.md adapter"):l("partial","GEMINI.md exceeds thin adapter budget")}case"antigravity":return l("partial","Workflows only; no classic root adapter");default:return l("no","Unknown harness")}}function yn(e,n){return e!=="antigravity"?l("na","Workflow playbooks are Antigravity-specific"):X(n.context)?l("yes",".agent/workflows/ playbooks present"):l("no","No .agent/workflows/ playbooks")}function kn(e,n,t){switch(e){case"sharedSkillsDiscoverable":return fn(n,t);case"customAgents":return hn(n,t);case"lifecycleHooks":return gn(n,t);case"thinAdapter":return mn(n,t);case"workflows":return yn(n,t);default:return l("no","Unknown capability")}}async function Q(e,n){let t=H.filter(i=>e.harnesses.includes(i)),s={context:e,vscode:n,claudeSettingsRaw:await dn(e.repoRoot),codexHooksExists:await pn(e.repoRoot)},r={};for(let i of _){let o={};for(let a of H){if(!e.harnesses.includes(a)){o[a]=l("na",un);continue}o[a]=kn(i,a,s)}r[i]=o}return{capabilities:_,harnesses:t,matrix:r}}function Ae(e){switch(e){case"yes":return"\u2713";case"partial":return"~";case"no":return"\u2717";case"na":return"\u2014"}}var He=["error","warn","info"],Sn={error:"ERROR",warn:"WARN",info:"INFO"},wn={layering:"Layering",sharing:"Sharing",discoverability:"Discoverability",harnessWiring:"Harness wiring",maintainability:"Maintainability",guardrails:"Guardrails"},bn=["layering","sharing","discoverability","harnessWiring","maintainability","guardrails"],xn=["copilot","cursor","codex","antigravity","claude","gemini"];function Cn(e){let n=Sn[e.severity],t=e.file===null?"":` ${e.file}${e.line===null?"":`:${String(e.line)}`}`,s=e.harnesses.length===0?"":`
4
+ Harnesses: ${e.harnesses.join(", ")}`,r=e.hint===void 0?"":`
5
+ Hint: ${e.hint}`;return`[${n}] ${e.ruleId}
6
+ ${e.message}${t?`
7
+ ${t}`:""}${s}${r}`}function An(e){process.stdout.write(`## Guidance sharing
8
+
9
+ `),process.stdout.write(`Shared (canonical / multi-harness): ${String(e.shared.lines)} lines (${String(e.shared.percentLines)}%) across ${String(e.shared.files)} file(s)
10
+ `),process.stdout.write(`Harness-specific: ${String(e.unique.totals.lines)} lines (${String(e.unique.totals.percentLines)}%) across ${String(e.unique.totals.files)} file(s)
11
+ `);let n=xn.filter(t=>e.unique.byHarness[t].lines>0);if(n.length>0){process.stdout.write(`
12
+ Per-harness unique guidance:
13
+ `);for(let t of n){let s=e.unique.byHarness[t];process.stdout.write(` ${t}: ${String(s.lines)} lines (${String(s.files)} file(s))
14
+ `)}}process.stdout.write(`
15
+ `)}function Hn(e){process.stdout.write(`## Meta-harness dimensions
16
+
17
+ `);for(let n of bn){let t=e.dimensions[n],s=wn[n],r=t.applicableRules===0?" (no rules yet)":` (${String(t.passedRules)}/${String(t.applicableRules)} rules)`;process.stdout.write(`${s}: ${String(t.score)}/100 (${t.grade})${r}
18
+ `)}process.stdout.write(`
19
+ `)}function Rn(e){if(process.stdout.write(`## Harness gap matrix
20
+
21
+ `),e.harnesses.length===0){process.stdout.write(`No harnesses detected \u2014 add tool adapters to compare capabilities.
22
+
23
+ `);return}let n=28,t=Math.max(...e.harnesses.map(r=>r.length),6)+2,s=`${"Capability".padEnd(n)}${e.harnesses.map(r=>r.padEnd(t)).join("")}`;process.stdout.write(`${s}
24
+ `);for(let r of _){let i=Ce[r].padEnd(n),o=e.harnesses.map(a=>{let c=e.matrix[r][a].status;return Ae(c).padEnd(t)}).join("");process.stdout.write(`${i}${o}
25
+ `)}process.stdout.write(`
26
+ Legend: \u2713 yes ~ partial \u2717 no \u2014 not applicable
27
+
28
+ `)}function vn(e){process.stdout.write(`## Harness optimization
29
+
30
+ `);let n=e.filter(s=>s.detected);if(n.length===0){process.stdout.write(`No harnesses detected \u2014 add tool adapters to evaluate optimization.
31
+
32
+ `);return}for(let s of n){let r=s.score===null?"n/a":`${String(s.score)}/100 (${s.grade})`;process.stdout.write(`${s.harness}: ${r}
33
+ `),s.loadProfile!==null&&process.stdout.write(` Load profile: ${String(s.loadProfile.sharedLines)} shared + ${String(s.loadProfile.uniqueLines)} unique lines (${String(s.loadProfile.sharedPercent)}% shared)
34
+ `);let i=s.checks.filter(a=>!a.passed),o=s.checks.length-i.length;process.stdout.write(` Checks: ${String(o)}/${String(s.checks.length)} passed`),s.findingsCount.error+s.findingsCount.warn>0&&process.stdout.write(`; findings: ${String(s.findingsCount.error)} error, ${String(s.findingsCount.warn)} warn`),process.stdout.write(`
35
+ `);for(let a of i.slice(0,5))process.stdout.write(` \u2717 ${a.label}
36
+ `);i.length>5&&process.stdout.write(` \u2026 and ${String(i.length-5)} more
37
+ `),process.stdout.write(`
38
+ `)}let t=e.filter(s=>!s.detected);t.length>0&&process.stdout.write(`Not detected: ${t.map(s=>s.harness).join(", ")}
39
+
40
+ `)}function Z(e){if(process.stdout.write(`paniolo-scan \u2014 AI harness diagnostic
41
+
42
+ `),process.stdout.write(`Root: ${e.root}
43
+ `),process.stdout.write(`Harnesses: ${e.harnesses.length===0?"(none detected)":e.harnesses.join(", ")}
44
+ `),process.stdout.write(`Guidance files: ${String(e.inventory.fileCount)}
45
+ `),process.stdout.write(`Findings: ${String(e.summary.error)} error(s), ${String(e.summary.warn)} warn(s), ${String(e.summary.info)} info
46
+
47
+ `),An(e.sharing),Hn(e.metaHarness),Rn(e.harnessGapMatrix),vn(e.harnessOptimization),process.stdout.write(`## Findings
48
+
49
+ `),e.findings.length===0){process.stdout.write(`No findings.
50
+ `);return}let n=[...e.findings].sort((t,s)=>{let r=He.indexOf(t.severity)-He.indexOf(s.severity);return r!==0?r:t.ruleId.localeCompare(s.ruleId)});for(let t of n)process.stdout.write(`${Cn(t)}
51
+
52
+ `)}import Jt from"node:path";function En(){return{copilot:{files:0,lines:0,bytes:0},cursor:{files:0,lines:0,bytes:0},codex:{files:0,lines:0,bytes:0},antigravity:{files:0,lines:0,bytes:0},claude:{files:0,lines:0,bytes:0},gemini:{files:0,lines:0,bytes:0}}}function W(e,n){return n===0?0:Math.round(e/n*1e3)/10}function ee(e,n){let t={files:0,lines:0,bytes:0},s=En(),r={files:0,lines:0,bytes:0},i={files:0,lines:0,bytes:0},o=[];for(let a of e){let c=ge(a,n);if(c.length===0)continue;i.files+=1,i.lines+=a.lines,i.bytes+=a.bytes;let u=me(a,c),y=u?"shared":"unique";if(o.push({path:a.path,lines:a.lines,scope:y,harnesses:c}),u)t.files+=1,t.lines+=a.lines,t.bytes+=a.bytes;else{let p=c[0];p!==void 0&&(s[p].files+=1,s[p].lines+=a.lines,s[p].bytes+=a.bytes),r.files+=1,r.lines+=a.lines,r.bytes+=a.bytes}}return{totals:i,shared:{...t,percentLines:W(t.lines,i.lines)},unique:{byHarness:s,totals:{...r,percentLines:W(r.lines,i.lines)}},files:o}}function Re(e,n){let t={};for(let s of n){let r=e.unique.byHarness[s].lines,i=e.shared.lines,o=i+r;t[s]={sharedLines:i,uniqueLines:r,totalLines:o,sharedPercent:W(i,o),uniquePercent:W(r,o)}}return t}import{access as Fn,readFile as Nn}from"node:fs/promises";import h from"node:path";function L(e){try{let n=JSON.parse(e);if(typeof n=="object"&&n!==null&&!Array.isArray(n))return n}catch{}return Ln(e)}function Ln(e){let n={},t=/"([^"]+)"\s*:\s*(true|false|null|\d+(?:\.\d+)?|"[^"]*")/g;for(let s of e.matchAll(t)){let r=s[1],i=s[2];r===void 0||i===void 0||(n[r]=In(i))}return Object.keys(n).length===0?null:n}function In(e){if(e==="true")return!0;if(e==="false")return!1;if(e==="null")return null;if(e.startsWith('"')&&e.endsWith('"'))return e.slice(1,-1);let n=Number(e);return Number.isNaN(n)?e:n}async function m(e){try{return await Fn(e),!0}catch{return!1}}async function Dn(e){try{let n=await Nn(e,"utf8");return L(n)}catch{return null}}function Pn(e){return e===null?!1:e["chat.agentSkillsLocations"]!==void 0||e["chat.agentFilesLocations"]!==void 0||e["chat.useCustomAgentHooks"]===!0}async function ne(e){let n=new Set;await m(h.join(e,".github/copilot-instructions.md"))&&n.add("copilot"),await m(h.join(e,".github/hooks"))&&n.add("copilot");let t=await Dn(h.join(e,".vscode/settings.json"));return Pn(t)&&(n.add("cursor"),n.add("copilot")),await m(h.join(e,".cursor"))&&n.add("cursor"),await m(h.join(e,".codex/config.toml"))&&n.add("codex"),await m(h.join(e,".codex/hooks.json"))&&n.add("codex"),await m(h.join(e,".codex/agents"))&&n.add("codex"),await m(h.join(e,".agent/workflows"))&&n.add("antigravity"),await m(h.join(e,".agent/README.md"))&&n.add("antigravity"),await m(h.join(e,"CLAUDE.md"))&&n.add("claude"),await m(h.join(e,".claude/settings.json"))&&n.add("claude"),await m(h.join(e,"GEMINI.md"))&&n.add("gemini"),[...n].sort()}import{access as Mn}from"node:fs/promises";import I from"node:path";var Tn=[{min:85,grade:"excellent"},{min:70,grade:"good"},{min:50,grade:"fair"},{min:0,grade:"poor"}],te={error:12,warn:5,info:1},Gn=40;async function F(e){try{return await Mn(e),!0}catch{return!1}}function k(e,n){return e.files.some(t=>t.path===n)}function $n(e,n){return e.fileContents.get(n)??null}function jn(e,n){return e.files.find(s=>s.path===n)?.lines??null}function On(e){return/AGENTS\.md/.test(e)||/docs\/ai\/rules\.md/.test(e)}function A(e){return e.files.some(n=>n.path.startsWith("skills/"))}function U(e){return e.files.some(n=>n.path.startsWith("agents/"))}function ve(e,n){return e.settings?.[n]===!0}function N(e,n,t){let s=e.settings?.[n];if(typeof s=="object"&&s!==null&&!Array.isArray(s)&&Object.keys(s).some(a=>a.startsWith(t)))return!0;if(e.settingsRaw===null)return!1;let r=n.replaceAll(".","\\.");return new RegExp(`"${r}"\\s*:\\s*\\{[^}]*"${t}[^"]*"`).test(e.settingsRaw)}function se(e,n){let t=jn(e,n);return t===null?!1:t<=w}function re(e,n){let t=$n(e,n);return t===null?!1:On(t)}var _n={copilot:[{id:"copilot-instructions",label:"Copilot instructions file present",weight:15,run:({context:e})=>k(e,".github/copilot-instructions.md")},{id:"copilot-references-shared",label:"Copilot instructions reference shared layers",weight:15,run:({context:e})=>re(e,".github/copilot-instructions.md")},{id:"copilot-adapter-thin",label:"Copilot instructions stay thin",weight:10,run:({context:e})=>se(e,".github/copilot-instructions.md")},{id:"copilot-skills-location",label:"chat.agentSkillsLocations points at skills/",weight:15,run:({vscode:e})=>N(e,"chat.agentSkillsLocations","skills")},{id:"copilot-agents-location",label:"chat.agentFilesLocations points at agents/",weight:15,run:({vscode:e})=>N(e,"chat.agentFilesLocations","agents")},{id:"copilot-hooks-enabled",label:"Custom agent hooks enabled when .github/hooks/ exists",weight:15,run:({vscode:e})=>!e.hooksDirExists||ve(e,"chat.useCustomAgentHooks")},{id:"copilot-shared-skills",label:"Shared skills/ tree present",weight:8,run:({context:e})=>A(e)},{id:"copilot-shared-agents",label:"Shared agents/ tree present",weight:7,run:({context:e})=>U(e)}],cursor:[{id:"cursor-rules-or-settings",label:"Cursor rules or VS Code agent settings present",weight:20,run:({context:e,vscode:n})=>e.files.some(t=>t.path.startsWith(".cursor/rules/"))||N(n,"chat.agentSkillsLocations","skills")},{id:"cursor-skills-location",label:"chat.agentSkillsLocations points at skills/",weight:20,run:({vscode:e})=>N(e,"chat.agentSkillsLocations","skills")},{id:"cursor-agents-location",label:"chat.agentFilesLocations points at agents/",weight:20,run:({vscode:e})=>N(e,"chat.agentFilesLocations","agents")},{id:"cursor-hooks-enabled",label:"Custom agent hooks enabled when .github/hooks/ exists",weight:15,run:({vscode:e})=>!e.hooksDirExists||ve(e,"chat.useCustomAgentHooks")},{id:"cursor-shared-skills",label:"Shared skills/ tree present",weight:13,run:({context:e})=>A(e)},{id:"cursor-shared-agents",label:"Shared agents/ tree present",weight:12,run:({context:e})=>U(e)}],codex:[{id:"codex-config-surface",label:"Codex config surface present (.codex/ or nested AGENTS.md)",weight:25,run:async({context:e})=>e.files.some(n=>n.path.startsWith(".codex/"))?!0:await F(I.join(e.repoRoot,"api/AGENTS.md"))||await F(I.join(e.repoRoot,"react/AGENTS.md"))||await F(I.join(e.repoRoot,"shared/AGENTS.md"))},{id:"codex-hooks",label:"Codex hooks.json present",weight:20,run:async({context:e})=>F(I.join(e.repoRoot,".codex/hooks.json"))},{id:"codex-agents-wrappers",label:"Codex agent wrappers present",weight:20,run:async({context:e})=>F(I.join(e.repoRoot,".codex/agents"))},{id:"codex-shared-entry",label:"Root AGENTS.md present",weight:20,run:({context:e})=>k(e,"AGENTS.md")},{id:"codex-shared-skills",label:"Shared skills/ tree present",weight:8,run:({context:e})=>A(e)},{id:"codex-shared-rules",label:"Canonical rules doc present",weight:7,run:({context:e})=>k(e,"docs/ai/rules.md")}],antigravity:[{id:"antigravity-workflows",label:"Workflow playbooks present (.agent/workflows/)",weight:35,run:({context:e})=>e.files.some(n=>n.path.startsWith(".agent/workflows/"))},{id:"antigravity-readme",label:".agent/README.md documents workflow layout",weight:15,run:({context:e})=>k(e,".agent/README.md")},{id:"antigravity-shared-entry",label:"Root AGENTS.md present",weight:20,run:({context:e})=>k(e,"AGENTS.md")},{id:"antigravity-shared-skills",label:"Shared skills/ tree present",weight:15,run:({context:e})=>A(e)},{id:"antigravity-ai-system-doc",label:"AI system layout doc present",weight:15,run:({context:e})=>k(e,"docs/ai/ai-system.md")}],claude:[{id:"claude-adapter",label:"CLAUDE.md adapter present",weight:25,run:({context:e})=>k(e,"CLAUDE.md")},{id:"claude-references-shared",label:"CLAUDE.md references shared layers",weight:25,run:({context:e})=>re(e,"CLAUDE.md")},{id:"claude-adapter-thin",label:"CLAUDE.md stays thin",weight:20,run:({context:e})=>se(e,"CLAUDE.md")},{id:"claude-shared-skills",label:"Shared skills/ tree present",weight:15,run:({context:e})=>A(e)},{id:"claude-skills-index",label:"Skill index doc for prose discovery",weight:8,run:({context:e})=>k(e,"docs/ai/available-skills.md")},{id:"claude-shared-agents",label:"Shared agents/ tree present",weight:7,run:({context:e})=>U(e)}],gemini:[{id:"gemini-adapter",label:"GEMINI.md adapter present",weight:30,run:({context:e})=>k(e,"GEMINI.md")},{id:"gemini-references-shared",label:"GEMINI.md references shared layers",weight:25,run:({context:e})=>re(e,"GEMINI.md")},{id:"gemini-adapter-thin",label:"GEMINI.md stays thin",weight:20,run:({context:e})=>se(e,"GEMINI.md")},{id:"gemini-shared-skills",label:"Shared skills/ tree present",weight:15,run:({context:e})=>A(e)},{id:"gemini-shared-agents",label:"Shared agents/ tree present",weight:10,run:({context:e})=>U(e)}]};function Wn(e){for(let{min:n,grade:t}of Tn)if(e>=n)return t;return"poor"}function Un(e,n){let t={error:0,warn:0,info:0};for(let s of e)s.harnesses.includes(n)&&(s.severity==="error"?t.error+=1:s.severity==="warn"?t.warn+=1:t.info+=1);return t}function Vn(e){let n=e.error*te.error+e.warn*te.warn+e.info*te.info;return Math.min(n,Gn)}async function qn(e,n){let t=_n[e],s=[];for(let r of t){let i=await r.run(n);s.push({id:r.id,label:r.label,passed:i,weight:r.weight})}return s}function zn(e){let n=e.reduce((s,r)=>s+r.weight,0);if(n===0)return 0;let t=e.filter(s=>s.passed).reduce((s,r)=>s+r.weight,0);return Math.round(t/n*100)}async function Kn(e,n,t,s,r,i){if(!n.harnesses.includes(e))return{harness:e,detected:!1,score:null,grade:"not-detected",checks:[],findingsCount:{error:0,warn:0,info:0},loadProfile:null};let c=await qn(e,{context:n,vscode:t,harness:e}),u=Un(s,e),y=zn(c),p=Vn(u),M=Math.max(0,Math.min(100,y-p));return{harness:e,detected:!0,score:M,grade:Wn(M),checks:c,findingsCount:u,loadProfile:i[e]??null}}async function ie(e,n,t,s){let r=Re(s,e.harnesses),i=[];for(let o of H){let a=await Kn(o,e,n,t,s,r);i.push(a)}return i}var Yn=[{min:85,grade:"excellent"},{min:70,grade:"good"},{min:50,grade:"fair"},{min:0,grade:"poor"}];function D(e){for(let{min:n,grade:t}of Yn)if(e>=n)return t;return"poor"}function Ee(e,n){return n===0?0:e>=80?95:e>=65?82:e>=50?68:e>=30?52:35}import{readFile as Bn,stat as Xn}from"node:fs/promises";import Le from"node:path";async function x(e){let n=Le.join(e,".github/hooks"),t=!1;try{t=(await Xn(n)).isDirectory()}catch{t=!1}let s=Le.join(e,".vscode/settings.json"),r=null,i=null;try{let o=await Bn(s,"utf8");i=o,r=L(o)}catch{r=null,i=null}return{settings:r,settingsRaw:i,hooksDirExists:t}}var oe="docs/ai/rules.md",V=3,Jn=80;function ae(e){return e.trim().replace(/\s+/g," ")}function Qn(e){let n=ae(e);return!(n.length<20||/^#{1,6}\s/.test(n)||/^[-*]\s/.test(n)&&n.length<40||/AGENTS\.md/.test(n)||/docs\/ai\/rules\.md/.test(n))}function Ie(e){return e.map(n=>ae(n)).filter(n=>n.length>0).join(`
53
+ `)}function Zn(e,n){let t=e.split(`
54
+ `),s=Ie(n.split(`
55
+ `)),r=[],i=new Set;for(let o=0;o<=t.length-V;o+=1){if(i.has(o))continue;let a=t.slice(o,o+V);if(!a.every(u=>Qn(u)))continue;let c=Ie(a);if(!(c.length<Jn)&&s.includes(c)){r.push({startLine:o+1,lineCount:V,sample:ae(a[0]??"")}),i.add(o);for(let u=1;u<V;u+=1)i.add(o+u)}}return r}function Fe(e){let n=d(e,oe);if(n===null)return[];let t=[];for(let s of T){let r=d(e,s);if(r===null)continue;let i=Zn(r,n);for(let o of i)t.push({ruleId:"adapter-content-duplication",severity:"warn",message:`${s} duplicates ${String(o.lineCount)} lines from ${oe} near line ${String(o.startLine)}.`,file:s,line:o.startLine,harnesses:[],hint:`Replace duplicated rules with a pointer to ${oe}.`})}return t}import{readFile as et}from"node:fs/promises";import nt from"node:path";var Ne="docs/ai/available-skills.md";async function tt(e){try{let n=await et(nt.join(e,"package.json"),"utf8");return JSON.parse(n).scripts??null}catch{return null}}function st(e){return e===null?!1:Object.entries(e).some(([n,t])=>n.toLowerCase().includes("qmd")||t.toLowerCase().includes("qmd"))}function De(e){return g(e)?f(e,Ne)?[]:[{ruleId:"skills-index",severity:"info",message:`${Ne} is missing; add a skill slug index when using skills/.`,file:null,line:null,harnesses:[],hint:"List each skills/<slug>/ folder so agents can pick a minimal skill set."}]:[]}function Pe(e){if(!e.harnesses.includes("claude")||!f(e,"CLAUDE.md"))return[];let n=d(e,"CLAUDE.md");return n!==null&&j(n)?[]:[{ruleId:"claude-agent-routing",severity:"info",message:"CLAUDE.md should include an Agent Routing table pointing at agents/*.agent.md.",file:"CLAUDE.md",line:null,harnesses:["claude"],hint:"Add a markdown table mapping task types to shared agents/*.agent.md files."}]}function Me(e){if(!g(e)||!f(e,"AGENTS.md"))return[];let n=d(e,"AGENTS.md");return n!==null&&$(n)?[]:[{ruleId:"agents-md-mentions-skills",severity:"info",message:"AGENTS.md should point at docs/ai/available-skills.md or the qmd search workflow.",file:"AGENTS.md",line:null,harnesses:[],hint:"Document how agents discover skills (index doc or npm run qmd -- search)."}]}async function Te(e){let n=g(e),t=e.files.filter(r=>r.path.startsWith("docs/ai/")).length;if(!n&&t<2)return[];let s=await tt(e.repoRoot);return st(s)?[]:[{ruleId:"qmd-script-present",severity:"info",message:'package.json is missing a "qmd" (or equivalent) script for skill/doc search.',file:"package.json",line:null,harnesses:[],hint:'Add something like "qmd": "bun run ./scripts/qmd/qmd.bun.ts" and document usage in AGENTS.md.'}]}var Ge=/!?\[([^\]]*)\]\(([^)]+)\)/g;function q(e){let n=[],t=Ge.exec(e);for(;t!==null;)t[0].startsWith("!")||n.push({label:t[1]??"",href:(t[2]??"").trim()}),t=Ge.exec(e);return n}var rt=new Set(["url","path","href","link","..."]);function $e(e){return e.length===0||rt.has(e.toLowerCase())||e.startsWith("http://")||e.startsWith("https://")||e.startsWith("mailto:")||e.startsWith("tel:")?!0:(e.split("#")[0]??"").length===0}function je(e,n){let t=(n.split("#")[0]??"").trim();if(t.length===0)return null;let s=t;try{s=decodeURIComponent(t)}catch{s=t}if(s.startsWith("/"))return s.slice(1).replaceAll("\\","/");let r=e.includes("/")?e.slice(0,e.lastIndexOf("/")):"",i=r.length===0?s:`${r}/${s}`;return it(i)}function it(e){let n=e.replaceAll("\\","/").split("/"),t=[];for(let s of n)if(!(s===""||s===".")){if(s===".."){t.pop();continue}t.push(s)}return t.join("/")}function z(e,n){let t=e.split(`
56
+ `);for(let s=0;s<t.length;s+=1)if(t[s]?.includes(n))return s+1;return null}var ot=[{id:"github-skills-shadow",pattern:/\.github\/skills\//,message:"use skills/ instead of the removed .github/skills/ path"},{id:"github-agents-shadow",pattern:/\.github\/agents\//,message:"use agents/ instead of the removed .github/agents/ path"},{id:"github-hooks-scripts",pattern:/\.github\/hooks\/scripts\//,message:"use agents/scripts/ instead of .github/hooks/scripts/"},{id:"cursor-skills-shadow",pattern:/\.cursor\/skills\//,message:"use skills/ instead of .cursor/skills/"},{id:"legacy-agent-docs",pattern:/\.agent\/(rules|codebase-map|troubleshooting)\.md/,message:"move legacy .agent docs to docs/ai/ equivalents"}];function at(e){return/do not|don't|not create|instead of|removed|never use|avoid|legacy|not use|no longer|there is no|\(not `|not `\.cursor\/skills|use \/skills\/|use \/agents\//i.test(e)}function lt(e,n){let t=e.split(`
57
+ `);for(let s=0;s<t.length;s+=1){let r=t[s]??"";if(!at(r)&&(n.lastIndex=0,n.test(r)))return s+1}return null}function ct(e,n){return n.lastIndex=0,n.test(e)}function Oe(e){let n=[];for(let t of e.files){if(!t.path.endsWith(".md")&&!t.path.endsWith(".mdc"))continue;let s=d(e,t.path);if(s!==null)for(let r of ot){let i=lt(s,r.pattern);if(i!==null){n.push({ruleId:"forbidden-legacy-paths",severity:"warn",message:`${t.path} references a legacy guidance path (${r.id}).`,file:t.path,line:i,harnesses:[],hint:r.message});continue}for(let o of q(s))ct(o.href,r.pattern)&&n.push({ruleId:"forbidden-legacy-paths",severity:"warn",message:`${t.path} links to a legacy guidance path (${r.id}).`,file:t.path,line:z(s,o.href),harnesses:[],hint:r.message})}}return n}var ut=["docs/","skills/","agents/",".cursor/",".github/",".codex/",".agent/",".claude/"],dt=new Set(["AGENTS.md","CLAUDE.md","GEMINI.md","README.md",".github/copilot-instructions.md"]);function _e(e){let n=e.replaceAll("\\","/").replace(/\/+$/,"");return dt.has(n)?!0:ut.some(t=>n===t.slice(0,-1)||n.startsWith(t))}var pt=["",".md","/README.md","/index.md"];async function We(e,n){for(let t of pt){let s=`${n}${t}`.replaceAll("\\","/");if(await v(e,s))return!0}return!1}var ft=8;function ht(e){return/my-doc\.md|example\.spec\.|\/example\//i.test(e)}function gt(e){return e.endsWith("/")?!0:e.endsWith(".md")||e.endsWith(".mdc")}async function Ue(e){let n=[];for(let t of e.files){if(!t.path.endsWith(".md")&&!t.path.endsWith(".mdc"))continue;let s=d(e,t.path);if(s===null)continue;let r=0;for(let i of q(s)){if($e(i.href))continue;let o=je(t.path,i.href);if(!(o===null||!_e(o)||!gt(o)||ht(o)||await We(e.repoRoot,o))&&(n.push({ruleId:"guidance-links-resolve",severity:"warn",message:`Broken link in ${t.path}: (${i.href}) does not resolve.`,file:t.path,line:z(s,i.href),harnesses:[],hint:"Fix the path or add the missing guidance file."}),r+=1,r>=ft))break}}return n}function Ve(e,n,t,s){let r=n?.[t];if(typeof r=="object"&&r!==null&&!Array.isArray(r)&&Object.keys(r).some(c=>c.startsWith(s)))return!0;if(e===null)return!1;let i=t.replaceAll(".","\\.");return new RegExp(`"${i}"\\s*:\\s*\\{[^}]*"${s}[^"]*"`).test(e)}async function qe(e){let n=await x(e.repoRoot),t=[];return Ve(n.settingsRaw,n.settings,"chat.agentSkillsLocations","skills")||t.push({ruleId:"vscode-skills-location",severity:"warn",message:"chat.agentSkillsLocations should point at skills/ in .vscode/settings.json.",file:".vscode/settings.json",line:null,harnesses:["cursor","copilot"],hint:'Set "chat.agentSkillsLocations": { "skills/": true }.'}),Ve(n.settingsRaw,n.settings,"chat.agentFilesLocations","agents")||t.push({ruleId:"vscode-agents-location",severity:"warn",message:"chat.agentFilesLocations should point at agents/ in .vscode/settings.json.",file:".vscode/settings.json",line:null,harnesses:["cursor","copilot"],hint:'Set "chat.agentFilesLocations": { "agents/": true }.'}),t}function ze(e,n){return e.length===0?!0:e.some(t=>n.includes(t))}function le(e,n,t,s){return{ruleId:e,severity:"warn",message:`${n} has ${String(t)} lines; keep root adapters thin (max ${String(w)}).`,file:n,line:null,harnesses:[s],hint:"Move detailed guidance to docs/ai/ and skills/; keep the adapter as a pointer."}}function mt(e){let n=[];for(let t of G(e,"skills/")){if(!t.endsWith("/SKILL.md")&&!t.endsWith("SKILL.md"))continue;let s=d(e,t);if(s===null)continue;let r=B(s);if(r===null){n.push({ruleId:"skill-frontmatter",severity:"error",message:`${t} is missing YAML frontmatter.`,file:t,line:null,harnesses:[]});continue}R(r,"name")||n.push({ruleId:"skill-frontmatter",severity:"error",message:`${t} is missing a name field in frontmatter.`,file:t,line:null,harnesses:[]}),R(r,"description")||n.push({ruleId:"skill-frontmatter",severity:"error",message:`${t} is missing a description field in frontmatter.`,file:t,line:null,harnesses:[]})}return n}function yt(e){let n=[];for(let t of G(e,"skills/")){if(!t.endsWith("SKILL.md"))continue;let s=b(e,t);s!==null&&s>Y&&n.push({ruleId:"skill-line-count",severity:"warn",message:`${t} has ${String(s)} lines; keep skills under ${String(Y)}.`,file:t,line:null,harnesses:[],hint:"Move detail to docs/; keep SKILL.md as a pointer."})}return n}function kt(e){let n=[];for(let t of G(e,"agents/")){if(!t.endsWith(".agent.md"))continue;let s=d(e,t);if(s===null)continue;let r=B(s);if(r===null){n.push({ruleId:"agent-frontmatter",severity:"error",message:`${t} is missing YAML frontmatter.`,file:t,line:null,harnesses:[]});continue}R(r,"name")||n.push({ruleId:"agent-frontmatter",severity:"error",message:`${t} is missing a name field in frontmatter.`,file:t,line:null,harnesses:[]}),R(r,"description")||n.push({ruleId:"agent-frontmatter",severity:"error",message:`${t} is missing a description field in frontmatter.`,file:t,line:null,harnesses:[]})}return n}function St(e){let n=[];for(let t of T){if(!f(e,t))continue;let s=d(e,t);s===null||ye(s)||n.push({ruleId:"adapter-points-to-shared",severity:"warn",message:`${t} should reference AGENTS.md and docs/ai/rules.md.`,file:t,line:null,harnesses:[],hint:"Keep adapters thin; point at shared layers instead of duplicating rules."})}return n}async function wt(e){if(!e.files.some(r=>r.path.startsWith("skills/")))return[];let t=[],s=[".github/skills",".cursor/skills"];for(let r of s)await v(e.repoRoot,r)&&t.push({ruleId:"no-duplicate-skill-trees",severity:"error",message:`${r}/ duplicates root skills/; use skills/ only.`,file:r,line:null,harnesses:[],hint:"Remove the shadow tree and keep canonical skills/ at the repo root."});return t}async function bt(e){return e.files.some(t=>t.path.startsWith("agents/"))?await v(e.repoRoot,".github/agents")?[{ruleId:"no-duplicate-agent-trees",severity:"error",message:".github/agents/ duplicates root agents/; use agents/ only.",file:".github/agents",line:null,harnesses:[],hint:"Remove .github/agents/ and keep canonical agents/ at the repo root."}]:[]:[]}async function xt(e){let n=await x(e.repoRoot);return n.hooksDirExists?n.settings?.["chat.useCustomAgentHooks"]===!0?[]:[{ruleId:"vscode-custom-hooks",severity:"warn",message:".github/hooks/ exists but chat.useCustomAgentHooks is not true in .vscode/settings.json.",file:".vscode/settings.json",line:null,harnesses:["cursor","copilot"],hint:'Set "chat.useCustomAgentHooks": true so Cursor and Copilot load workspace hooks.'}]:[]}var Ct={id:"shared-agents-md",title:"Shared AGENTS.md entry point",severity:"warn",category:"structure",dimension:"layering",harnesses:[],check(e){return f(e,"AGENTS.md")?[]:[{ruleId:"shared-agents-md",severity:"warn",message:"AGENTS.md is missing.",file:null,line:null,harnesses:[],hint:"Add AGENTS.md as the repo-wide AI entry point with workflow, safety, and pointers to canonical docs."}]}},At={id:"shared-rules-doc",title:"Canonical rules doc",severity:"warn",category:"structure",dimension:"layering",harnesses:[],check(e){return f(e,"docs/ai/rules.md")?[]:[{ruleId:"shared-rules-doc",severity:"warn",message:"docs/ai/rules.md is missing.",file:null,line:null,harnesses:[],hint:"Add docs/ai/rules.md as the canonical coding-rules reference."}]}},Ht={id:"adapter-thin-claude",title:"Thin CLAUDE.md adapter",severity:"warn",category:"adapter",dimension:"layering",harnesses:["claude"],check(e){let n=b(e,"CLAUDE.md");return n===null||n<=w?[]:[le("adapter-thin-claude","CLAUDE.md",n,"claude")]}},Rt={id:"adapter-thin-gemini",title:"Thin GEMINI.md adapter",severity:"warn",category:"adapter",dimension:"layering",harnesses:["gemini"],check(e){let n=b(e,"GEMINI.md");return n===null||n<=w?[]:[le("adapter-thin-gemini","GEMINI.md",n,"gemini")]}},vt={id:"adapter-thin-copilot",title:"Thin Copilot instructions",severity:"warn",category:"adapter",dimension:"layering",harnesses:["copilot"],check(e){let n=".github/copilot-instructions.md",t=b(e,n);return t===null||t<=w?[]:[le("adapter-thin-copilot",n,t,"copilot")]}},Et={id:"adapter-points-to-shared",title:"Adapters reference shared layers",severity:"warn",category:"adapter",dimension:"layering",harnesses:[],check:St},Lt={id:"skill-frontmatter",title:"Skill frontmatter",severity:"error",category:"skills",dimension:"maintainability",harnesses:[],check:mt},It={id:"skill-line-count",title:"Skill line budget",severity:"warn",category:"skills",dimension:"maintainability",harnesses:[],check:yt},Ft={id:"agent-frontmatter",title:"Agent frontmatter",severity:"error",category:"agents",dimension:"maintainability",harnesses:[],check:kt},Nt={id:"skills-index",title:"Skill slug index",severity:"info",category:"discoverability",dimension:"discoverability",harnesses:[],check:De},Dt={id:"claude-agent-routing",title:"Claude agent routing table",severity:"info",category:"discoverability",dimension:"discoverability",harnesses:["claude"],check:Pe},Pt={id:"agents-md-mentions-skills",title:"AGENTS.md skill discovery",severity:"info",category:"discoverability",dimension:"discoverability",harnesses:[],check:Me},Ke=[Ct,At,Ht,Rt,vt,Et,Lt,It,Ft,Nt,Dt,Pt];function Mt(e,n,t){return ze(e.harnesses,n.harnesses)?t===null||t.length===0||e.harnesses.length===0?!0:e.harnesses.some(s=>t.includes(s)):!1}function Tt(e,n,t){return ze(t,e.harnesses)?n===null||n.length===0?!0:t.some(s=>n.includes(s)):!1}async function Ye(e,n){let t=[];for(let a of Ke)Mt(a,e,n)&&t.push(...a.check(e));if(Tt(e,n,["cursor","copilot"])){let a=await qe(e);t.push(...a);let c=await xt(e);t.push(...c)}let s=await wt(e);t.push(...s);let r=await bt(e);t.push(...r);let i=await Te(e);t.push(...i);let o=await Ue(e);return t.push(...o),t.push(...Fe(e)),t.push(...Oe(e)),t}function Be(e,n){switch(e){case"skills-index":case"agents-md-mentions-skills":return g(n);case"qmd-script-present":return Gt(n);case"claude-agent-routing":return n.harnesses.includes("claude")&&f(n,"CLAUDE.md");default:return!0}}function Gt(e){return g(e)?!0:e.files.filter(t=>t.path.startsWith("docs/ai/")).length>=2}var ce={"shared-agents-md":"layering","shared-rules-doc":"layering","adapter-thin-claude":"layering","adapter-thin-gemini":"layering","adapter-thin-copilot":"layering","adapter-points-to-shared":"layering","skill-frontmatter":"maintainability","skill-line-count":"maintainability","agent-frontmatter":"maintainability","no-duplicate-skill-trees":"maintainability","no-duplicate-agent-trees":"maintainability","vscode-skills-location":"harnessWiring","vscode-agents-location":"harnessWiring","vscode-custom-hooks":"guardrails","skills-index":"discoverability","claude-agent-routing":"discoverability","agents-md-mentions-skills":"discoverability","qmd-script-present":"discoverability","guidance-links-resolve":"maintainability","adapter-content-duplication":"sharing","forbidden-legacy-paths":"maintainability"};function Xe(e){return ce[e]??null}var $t=["layering","sharing","discoverability","harnessWiring","maintainability","guardrails"],ue={error:15,warn:6,info:2},jt=35;function Ot(e){let n=e.filter(s=>s.detected&&s.score!==null);if(n.length===0)return 0;let t=n.reduce((s,r)=>s+(r.score??0),0);return Math.round(t/n.length)}function _t(e){return Object.entries(ce).filter(([,n])=>n===e).map(([n])=>n)}function Qe(e,n){return e.filter(t=>Xe(t.ruleId)===n)}function Wt(e){let n=0;for(let t of e)t.severity==="error"?n+=ue.error:t.severity==="warn"?n+=ue.warn:n+=ue.info;return Math.min(n,jt)}function Je(e,n,t){let s=_t(e).filter(c=>Be(c,t)),r=s.length,i=new Set(Qe(n,e).map(c=>c.ruleId)),o=s.filter(c=>!i.has(c)).length,a=r===0?0:Math.round(o/r*100);return{score:a,grade:D(a),passedRules:o,applicableRules:r}}function de(e,n,t,s){let r={};for(let i of $t){if(i==="sharing"){let o=Ee(n.shared.percentLines,n.totals.lines),a=Wt(Qe(e,i)),c=Math.max(0,Math.min(100,o-a));r.sharing={score:c,grade:D(c),passedRules:n.shared.percentLines>=65?1:0,applicableRules:n.totals.lines>0?1:0};continue}if(i==="harnessWiring"){let o=Je(i,e,s),a=Ot(t),c=a===0?o.score:Math.round((o.score+a)/2),u=Math.max(0,Math.min(100,c));r.harnessWiring={score:u,grade:D(u),passedRules:o.passedRules,applicableRules:o.applicableRules};continue}r[i]=Je(i,e,s)}return{profile:"meta-harness",dimensions:r}}import{readFile as Ze,readdir as Ut,stat as en}from"node:fs/promises";import P from"node:path";var Vt=[".md",".mdc"],qt=["AGENTS.md","docs/ai/rules.md","docs/ai/ai-system.md","docs/ai/available-skills.md","docs/ai/hooks.md"],zt=[{path:"CLAUDE.md",harness:"claude"},{path:"GEMINI.md",harness:"gemini"},{path:".github/copilot-instructions.md",harness:"copilot"}],Kt=[{dir:"skills",layer:"skills"},{dir:"agents",layer:"agents"},{dir:".cursor/rules",layer:"adapter",harness:"cursor"},{dir:".agent/workflows",layer:"workflows",harness:"antigravity"},{dir:".codex",layer:"adapter",harness:"codex"},{dir:"docs/ai",layer:"shared"}];function Yt(e){return Vt.some(n=>e.endsWith(n))}function Bt(e){return e.length===0?0:e.split(`
58
+ `).length}async function nn(e){let n=[],t=[];try{t=await Ut(e)}catch{return n}for(let s of t){let r=P.join(e,s),i=await en(r);i.isDirectory()?n.push(...await nn(r)):i.isFile()&&Yt(r)&&n.push(r)}return n}async function Xt(e,n,t,s){let r=P.join(e,n);try{let i=await en(r);if(!i.isFile())return null;let o=await Ze(r,"utf8"),a={path:n,layer:t,lines:Bt(o),bytes:i.size};return s!==void 0&&(a.harness=s),a}catch{return null}}async function pe(e){let n=[],t=new Map,s=new Set;async function r(i,o,a){let c=i.replaceAll("\\","/");if(s.has(c))return;let u=await Xt(e,c,o,a);if(u===null)return;s.add(c),n.push(u);let y=P.join(e,c);try{let p=await Ze(y,"utf8");t.set(c,p)}catch{}}for(let i of qt)await r(i,"shared");for(let i of zt)await r(i.path,"adapter",i.harness);for(let{dir:i,layer:o,harness:a}of Kt){let c=P.join(e,i),u=await nn(c);for(let y of u){let p=P.relative(e,y).replaceAll("\\","/");await r(p,o,a)}}return n.sort((i,o)=>i.path.localeCompare(o.path)),{files:n,contents:t}}var Qt="0.0.0",tn={error:3,warn:2,info:1};function Zt(e){let n=0,t=0,s=0;for(let r of e)r.severity==="error"?n+=1:r.severity==="warn"?t+=1:s+=1;return{error:n,warn:t,info:s}}async function fe(e){let n=Jt.resolve(e.repoRoot),t=await ne(n),{files:s,contents:r}=await pe(n),i={repoRoot:n,harnesses:t,files:s,fileContents:r},o=await Ye(i,e.harnessFilter),a=Zt(o),c=ee(s,t),u=await x(n),y=await ie(i,u,o,c),p=de(o,c,y,i),M=await Q(i,u);return{version:Qt,root:n,harnesses:t,inventory:{fileCount:s.length,files:s},sharing:c,metaHarness:p,harnessGapMatrix:M,harnessOptimization:y,findings:o,summary:a}}function sn(e,n){let t=tn[n];return e.some(s=>tn[s.severity]>=t)}var rn=0,ns=1,S=2,ts=["copilot","cursor","codex","antigravity","claude","gemini"];function ss(){process.stdout.write(`paniolo-scan \u2014 AI harness diagnostic (read-only)
59
+
60
+ Usage:
61
+ paniolo-scan [options] [path]
62
+
63
+ Options:
64
+ --harness <list> Comma-separated harness filter (copilot,cursor,codex,antigravity,claude,gemini)
65
+ --format <type> Output format: terminal (default) | json
66
+ --fail-on <level> Exit 1 when findings at or above level: error (default) | warn | info
67
+ -h, --help Show help
68
+
69
+ Examples:
70
+ npx @paniolo/scan
71
+ npx @paniolo/scan --format json
72
+ npx @paniolo/scan --harness cursor,codex --fail-on warn
73
+ `)}function rs(e){let n=e.split(",").map(s=>s.trim()).filter(s=>s.length>0);if(n.length===0)return null;let t=n.filter(s=>!ts.includes(s));return t.length>0?(process.stderr.write(`error: unknown harness(es): ${t.join(", ")}
74
+ `),process.exit(S),[]):n}function is(e){return e==="error"||e==="warn"||e==="info"?e:(process.stderr.write(`error: invalid --fail-on value: ${e}
75
+ `),process.exit(S),"error")}function os(e){return e==="terminal"||e==="json"?e:(process.stderr.write(`error: invalid --format value: ${e}
76
+ `),process.exit(S),"terminal")}function as(e){let n=process.cwd(),t=null,s="terminal",r="error",i=!1;for(let o=0;o<e.length;o+=1){let a=e[o];if(a!==void 0){if(a==="-h"||a==="--help"){i=!0;continue}if(a==="--harness"){let c=e[o+1];c===void 0&&(process.stderr.write(`error: --harness requires a value
77
+ `),process.exit(S)),t=rs(c),o+=1;continue}if(a==="--format"){let c=e[o+1];c===void 0&&(process.stderr.write(`error: --format requires a value
78
+ `),process.exit(S)),s=os(c),o+=1;continue}if(a==="--fail-on"){let c=e[o+1];c===void 0&&(process.stderr.write(`error: --fail-on requires a value
79
+ `),process.exit(S)),r=is(c),o+=1;continue}a.startsWith("-")&&(process.stderr.write(`error: unknown option: ${a}
80
+ `),process.exit(S)),n=es.resolve(a)}}return{repoRoot:n,harnessFilter:t,format:s,failOn:r,help:i}}async function ls(){let e=as(process.argv.slice(2));e.help&&(ss(),process.exit(rn));try{let n=await fe({repoRoot:e.repoRoot,harnessFilter:e.harnessFilter});e.format==="json"?K(n):Z(n),sn(n.findings,e.failOn)&&process.exit(ns),process.exit(rn)}catch(n){let t=n instanceof Error?n.message:String(n);process.stderr.write(`error: ${t}
81
+ `),process.exit(S)}}await ls();
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@paniolo/scan",
3
+ "version": "0.0.0",
4
+ "description": "Scan a repository for AI coding agent harness best practices (diagnostic only)",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "engines": {
8
+ "node": ">=20"
9
+ },
10
+ "bin": {
11
+ "paniolo-scan": "dist/cli.js"
12
+ },
13
+ "files": [
14
+ "dist/**/*.js",
15
+ "README.md"
16
+ ],
17
+ "scripts": {
18
+ "build": "npm run typecheck && node scripts/build.mjs",
19
+ "pack:dry-run": "npm pack --dry-run",
20
+ "prepack": "npm run build",
21
+ "qmd": "node scripts/qmd.mjs",
22
+ "start": "node dist/cli.js",
23
+ "test": "vitest run",
24
+ "typecheck": "tsc --noEmit"
25
+ },
26
+ "keywords": [
27
+ "ai",
28
+ "agents",
29
+ "cursor",
30
+ "copilot",
31
+ "codex",
32
+ "harness",
33
+ "lint"
34
+ ],
35
+ "devDependencies": {
36
+ "@types/node": "^22.19.20",
37
+ "esbuild": "^0.27.7",
38
+ "typescript": "^5.8.3",
39
+ "vitest": "^3.2.4"
40
+ }
41
+ }