@paniolo/scan 0.0.0 → 0.0.1
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/cli.js +34 -34
- package/package.json +24 -14
package/dist/cli.js
CHANGED
|
@@ -1,61 +1,61 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
3
|
-
`)}import{readFile as
|
|
4
|
-
Harnesses: ${e.harnesses.join(", ")}`,
|
|
2
|
+
import ds from"node:path";function B(e){process.stdout.write(`${JSON.stringify(e,null,2)}
|
|
3
|
+
`)}import{readFile as Ce}from"node:fs/promises";import Ae from"node:path";var E=["copilot","cursor","codex","antigravity","claude","gemini"],me=new Set(["shared","skills","agents"]);function ye(e,n){return e.harness!==void 0?n.includes(e.harness)?[e.harness]:[]:me.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 ke(e,n){return n.length>=2?!0:me.has(e.layer)||e.path==="AGENTS.md"}import{access as cn}from"node:fs/promises";import un from"node:path";var b=75,J=300,$=["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 x(e,n){return e.files.find(s=>s.path===n)?.lines??null}function Se(e){return e.includes("AGENTS.md")||e.includes("docs/ai/rules.md")}function Q(e){return e.match(/^---\r?\n([\s\S]*?)\r?\n---/)?.[1]??null}function L(e,n){return new RegExp(`^${n}:`,"m").test(e)}async function I(e,n){try{return await cn(un.join(e,n)),!0}catch{return!1}}function j(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 O(e){return e.includes("available-skills.md")||e.includes("skills/*/SKILL.md")||e.includes("npm run qmd")||e.includes("skills/")&&e.includes("available-skills")}function _(e){return/agent routing/i.test(e)&&e.includes("|")&&/agents\/.*\.agent\.md/.test(e)}function m(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}var dn=50,pn={copilot:".github/copilot-instructions.md",claude:"CLAUDE.md",gemini:"GEMINI.md"};function F(e,n,t){let s=e.settings?.[n];if(m(s)&&Object.keys(s).some(a=>a.startsWith(t)))return!0;if(e.settingsRaw===null)return!1;let i=n.replaceAll(".",String.raw`\.`);return new RegExp(`"${i}"\\s*:\\s*\\{[^}]*"${t}[^"]*"`).test(e.settingsRaw)}function we(e){return e.files.some(n=>n.path.startsWith("agents/")&&n.path.endsWith(".agent.md"))}function W(e,n){let t=x(e,n);return t===null?!1:t<=dn}function A(e){return pn[e]??null}function be(e,n){let t=d(e,n);return t===null?!1:t.includes("AGENTS.md")||t.includes("docs/ai/rules.md")}function Z(e){return e.files.some(n=>n.path.startsWith(".agent/workflows/"))}function ee(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&&O(t))return!0;let s=A(n);if(s===null)return!1;let i=d(e,s);return i!==null&&i.includes("skills/")}function xe(e){let n=d(e,"CLAUDE.md");return n!==null&&_(n)}var Re={sharedSkillsDiscoverable:"Shared skills discoverable",customAgents:"Custom agents",lifecycleHooks:"Lifecycle hooks",thinAdapter:"Thin adapter",workflows:"Workflows / playbooks"},U=["sharedSkillsDiscoverable","customAgents","lifecycleHooks","thinAdapter","workflows"],fn="Harness not detected";async function hn(e){try{return await Ce(Ae.join(e,".claude/settings.json"),"utf8")}catch{return null}}async function gn(e){try{return await Ce(Ae.join(e,".codex/hooks.json"),"utf8"),!0}catch{return!1}}function l(e,n){return{status:e,detail:n}}function mn(){function e(){return{copilot:l("na",""),cursor:l("na",""),codex:l("na",""),antigravity:l("na",""),claude:l("na",""),gemini:l("na","")}}return{sharedSkillsDiscoverable:e(),customAgents:e(),lifecycleHooks:e(),thinAdapter:e(),workflows:e()}}function yn(e,n){if(!g(n.context))return l("no","No skills/ tree");switch(e){case"copilot":case"cursor":return F(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 ee(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 ee(n.context,e)?l("partial","Prose discovery via skill index or adapter pointers"):l("no","skills/ without index or adapter pointers");case"antigravity":return Z(n.context)?l("yes","skills/ with workflow playbooks"):l("partial","skills/ present without workflow playbooks");default:return l("no","Unknown harness")}}function kn(e,n){let t=we(n.context);switch(e){case"copilot":case"cursor":return F(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(i=>i.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 xe(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=A("gemini"),i=s===null?null:n.context.fileContents.get(s)??null;return i!==null&&i.includes("agents/")?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 Sn(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 wn(e,n){switch(e){case"copilot":{let t=A("copilot");return t===null||!n.context.files.some(s=>s.path===t)?l("no","Missing .github/copilot-instructions.md"):W(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(i=>i.path.startsWith(".cursor/rules/")),s=F(n.vscode,"chat.agentSkillsLocations","skills")&&F(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=A("claude");if(t===null||!n.context.files.some(r=>r.path===t))return l("no","Missing CLAUDE.md");let s=W(n.context,t),i=be(n.context,t);return s&&i?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=A("gemini");return t===null||!n.context.files.some(s=>s.path===t)?l("no","Missing GEMINI.md"):W(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 bn(e,n){return e!=="antigravity"?l("na","Workflow playbooks are Antigravity-specific"):Z(n.context)?l("yes",".agent/workflows/ playbooks present"):l("no","No .agent/workflows/ playbooks")}function xn(e,n,t){switch(e){case"sharedSkillsDiscoverable":return yn(n,t);case"customAgents":return kn(n,t);case"lifecycleHooks":return Sn(n,t);case"thinAdapter":return wn(n,t);case"workflows":return bn(n,t);default:return l("no","Unknown capability")}}async function Cn(e,n){let t=E.filter(r=>e.harnesses.includes(r)),s={context:e,vscode:n,claudeSettingsRaw:await hn(e.repoRoot),codexHooksExists:await gn(e.repoRoot)},i=mn();for(let r of U){let o=i[r];for(let a of E){if(!e.harnesses.includes(a)){o[a]=l("na",fn);continue}o[a]=xn(r,a,s)}}return{capabilities:U,harnesses:t,matrix:i}}function ve(e){switch(e){case"yes":return"\u2713";case"partial":return"~";case"no":return"\u2717";case"na":return"\u2014"}}var He=Cn;var Ee=["error","warn","info"],An={error:"ERROR",warn:"WARN",info:"INFO"},Rn={layering:"Layering",sharing:"Sharing",discoverability:"Discoverability",harnessWiring:"Harness wiring",maintainability:"Maintainability",guardrails:"Guardrails"},vn=["layering","sharing","discoverability","harnessWiring","maintainability","guardrails"],Hn=["copilot","cursor","codex","antigravity","claude","gemini"];function En(e){let n=An[e.severity],t=e.file===null?"":` ${e.file}${e.line===null?"":`:${String(e.line)}`}`,s=e.harnesses.length===0?"":`
|
|
4
|
+
Harnesses: ${e.harnesses.join(", ")}`,i=e.hint===void 0?"":`
|
|
5
5
|
Hint: ${e.hint}`;return`[${n}] ${e.ruleId}
|
|
6
6
|
${e.message}${t?`
|
|
7
|
-
${t}`:""}${s}${
|
|
7
|
+
${t}`:""}${s}${i}`}function Ln(e){process.stdout.write(`## Guidance sharing
|
|
8
8
|
|
|
9
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
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=
|
|
11
|
+
`);let n=Hn.filter(t=>e.unique.byHarness[t].lines>0);if(n.length>0){process.stdout.write(`
|
|
12
12
|
Per-harness unique guidance:
|
|
13
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
14
|
`)}}process.stdout.write(`
|
|
15
|
-
`)}function
|
|
15
|
+
`)}function In(e){process.stdout.write(`## Meta-harness dimensions
|
|
16
16
|
|
|
17
|
-
`);for(let n of
|
|
17
|
+
`);for(let n of vn){let t=e.dimensions[n],s=Rn[n],i=t.applicableRules===0?" (no rules yet)":` (${String(t.passedRules)}/${String(t.applicableRules)} rules)`;process.stdout.write(`${s}: ${String(t.score)}/100 (${t.grade})${i}
|
|
18
18
|
`)}process.stdout.write(`
|
|
19
|
-
`)}function
|
|
19
|
+
`)}function Fn(e){if(process.stdout.write(`## Harness gap matrix
|
|
20
20
|
|
|
21
21
|
`),e.harnesses.length===0){process.stdout.write(`No harnesses detected \u2014 add tool adapters to compare capabilities.
|
|
22
22
|
|
|
23
|
-
`);return}let n=28,t=Math.max(...e.harnesses.map(
|
|
24
|
-
`);for(let
|
|
23
|
+
`);return}let n=28,t=Math.max(...e.harnesses.map(i=>i.length),6)+2,s=`${"Capability".padEnd(n)}${e.harnesses.map(i=>i.padEnd(t)).join("")}`;process.stdout.write(`${s}
|
|
24
|
+
`);for(let i of U){let r=Re[i].padEnd(n),o=e.harnesses.map(a=>{let{status:c}=e.matrix[i][a];return ve(c).padEnd(t)}).join("");process.stdout.write(`${r}${o}
|
|
25
25
|
`)}process.stdout.write(`
|
|
26
26
|
Legend: \u2713 yes ~ partial \u2717 no \u2014 not applicable
|
|
27
27
|
|
|
28
|
-
`)}function
|
|
28
|
+
`)}function Dn(e){process.stdout.write(`## Harness optimization
|
|
29
29
|
|
|
30
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
31
|
|
|
32
|
-
`);return}for(let s of n){let
|
|
32
|
+
`);return}for(let s of n){let i=s.score===null?"n/a":`${String(s.score)}/100 (${s.grade})`;process.stdout.write(`${s.harness}: ${i}
|
|
33
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
|
|
35
|
-
`);for(let a of
|
|
36
|
-
`);
|
|
34
|
+
`);let r=s.checks.filter(a=>!a.passed),o=s.checks.length-r.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 r.slice(0,5))process.stdout.write(` \u2717 ${a.label}
|
|
36
|
+
`);r.length>5&&process.stdout.write(` \u2026 and ${String(r.length-5)} more
|
|
37
37
|
`),process.stdout.write(`
|
|
38
38
|
`)}let t=e.filter(s=>!s.detected);t.length>0&&process.stdout.write(`Not detected: ${t.map(s=>s.harness).join(", ")}
|
|
39
39
|
|
|
40
|
-
`)}function
|
|
40
|
+
`)}function ne(e){if(process.stdout.write(`paniolo-scan \u2014 AI harness diagnostic
|
|
41
41
|
|
|
42
42
|
`),process.stdout.write(`Root: ${e.root}
|
|
43
43
|
`),process.stdout.write(`Harnesses: ${e.harnesses.length===0?"(none detected)":e.harnesses.join(", ")}
|
|
44
44
|
`),process.stdout.write(`Guidance files: ${String(e.inventory.fileCount)}
|
|
45
45
|
`),process.stdout.write(`Findings: ${String(e.summary.error)} error(s), ${String(e.summary.warn)} warn(s), ${String(e.summary.info)} info
|
|
46
46
|
|
|
47
|
-
`),
|
|
47
|
+
`),Ln(e.sharing),In(e.metaHarness),Fn(e.harnessGapMatrix),Dn(e.harnessOptimization),process.stdout.write(`## Findings
|
|
48
48
|
|
|
49
49
|
`),e.findings.length===0){process.stdout.write(`No findings.
|
|
50
|
-
`);return}let n=[...e.findings].sort((t,s)=>{let
|
|
50
|
+
`);return}let n=[...e.findings].sort((t,s)=>{let i=Ee.indexOf(t.severity)-Ee.indexOf(s.severity);return i!==0?i:t.ruleId.localeCompare(s.ruleId)});for(let t of n)process.stdout.write(`${En(t)}
|
|
51
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
|
|
54
|
-
`),s=
|
|
55
|
-
`)),
|
|
56
|
-
`);for(let s=0;s<t.length;s+=1)
|
|
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
|
|
52
|
+
`)}import ls from"node:path";function Nn(){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 q(e,n){return n===0?0:Math.round(e/n*1e3)/10}function te(e,n){let t={files:0,lines:0,bytes:0},s=Nn(),i={files:0,lines:0,bytes:0},r={files:0,lines:0,bytes:0},o=[];for(let a of e){let c=ye(a,n);if(c.length===0)continue;r.files+=1,r.lines+=a.lines,r.bytes+=a.bytes;let u=ke(a,c),k=u?"shared":"unique";if(o.push({path:a.path,lines:a.lines,scope:k,harnesses:c}),u)t.files+=1,t.lines+=a.lines,t.bytes+=a.bytes;else{let[p]=c;p!==void 0&&(s[p].files+=1,s[p].lines+=a.lines,s[p].bytes+=a.bytes),i.files+=1,i.lines+=a.lines,i.bytes+=a.bytes}}return{totals:r,shared:{...t,percentLines:q(t.lines,r.lines)},unique:{byHarness:s,totals:{...i,percentLines:q(i.lines,r.lines)}},files:o}}function Le(e,n){let t={sharedLines:0,uniqueLines:0,totalLines:0,sharedPercent:0,uniquePercent:0},s={copilot:{...t},cursor:{...t},codex:{...t},antigravity:{...t},claude:{...t},gemini:{...t}};for(let i of n){let r=e.unique.byHarness[i].lines,o=e.shared.lines,a=o+r;s[i]={sharedLines:o,uniqueLines:r,totalLines:a,sharedPercent:q(o,a),uniquePercent:q(r,a)}}return s}import{access as Tn,readFile as Gn}from"node:fs/promises";import h from"node:path";function D(e){try{let n=JSON.parse(e);if(m(n))return n}catch{}return Pn(e)}function Pn(e){let n={},t=/"([^"]+)"\s*:\s*(true|false|null|\d+(?:\.\d+)?|"[^"]*")/g;for(let[,s,i]of e.matchAll(t))s===void 0||i===void 0||(n[s]=Mn(i));return Object.keys(n).length===0?null:n}function Mn(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 y(e){try{return await Tn(e),!0}catch{return!1}}async function $n(e){try{let n=await Gn(e,"utf8");return D(n)}catch{return null}}function jn(e){return e===null?!1:e["chat.agentSkillsLocations"]!==void 0||e["chat.agentFilesLocations"]!==void 0||e["chat.useCustomAgentHooks"]===!0}async function se(e){let n=new Set;await y(h.join(e,".github/copilot-instructions.md"))&&n.add("copilot"),await y(h.join(e,".github/hooks"))&&n.add("copilot");let t=await $n(h.join(e,".vscode/settings.json"));return jn(t)&&(n.add("cursor"),n.add("copilot")),await y(h.join(e,".cursor"))&&n.add("cursor"),await y(h.join(e,".codex/config.toml"))&&n.add("codex"),await y(h.join(e,".codex/hooks.json"))&&n.add("codex"),await y(h.join(e,".codex/agents"))&&n.add("codex"),await y(h.join(e,".agent/workflows"))&&n.add("antigravity"),await y(h.join(e,".agent/README.md"))&&n.add("antigravity"),await y(h.join(e,"CLAUDE.md"))&&n.add("claude"),await y(h.join(e,".claude/settings.json"))&&n.add("claude"),await y(h.join(e,"GEMINI.md"))&&n.add("gemini"),[...n].sort()}import{access as On}from"node:fs/promises";import N from"node:path";var _n=[{min:85,grade:"excellent"},{min:70,grade:"good"},{min:50,grade:"fair"},{min:0,grade:"poor"}],ie={error:12,warn:5,info:1},Wn=40;async function P(e){try{return await On(e),!0}catch{return!1}}function S(e,n){return e.files.some(t=>t.path===n)}function Fe(e,n){return e.fileContents.get(n)??null}function Un(e,n){return e.files.find(s=>s.path===n)?.lines??null}function qn(e){return e.includes("AGENTS.md")||e.includes("docs/ai/rules.md")}function R(e){return e.files.some(n=>n.path.startsWith("skills/"))}function V(e){return e.files.some(n=>n.path.startsWith("agents/"))}function Ie(e,n){return e.settings?.[n]===!0}function M(e,n,t){let s=e.settings?.[n];if(m(s)&&Object.keys(s).some(a=>a.startsWith(t)))return!0;if(e.settingsRaw===null)return!1;let i=n.replaceAll(".",String.raw`\.`);return new RegExp(`"${i}"\\s*:\\s*\\{[^}]*"${t}[^"]*"`).test(e.settingsRaw)}function re(e,n){let t=Un(e,n);return t===null?!1:t<=b}function oe(e,n){let t=Fe(e,n);return t===null?!1:qn(t)}function Vn(e){let n=Fe(e,".agent/README.md");return n===null?!1:/\.agent\/workflows\/|workflows\//i.test(n)&&/(workflow|playbook|slash command|mission|turbo)/i.test(n)}var zn={copilot:[{id:"copilot-instructions",label:"Copilot instructions file present",weight:15,run:({context:e})=>S(e,".github/copilot-instructions.md")},{id:"copilot-references-shared",label:"Copilot instructions reference shared layers",weight:15,run:({context:e})=>oe(e,".github/copilot-instructions.md")},{id:"copilot-adapter-thin",label:"Copilot instructions stay thin",weight:10,run:({context:e})=>re(e,".github/copilot-instructions.md")},{id:"copilot-skills-location",label:"chat.agentSkillsLocations points at skills/",weight:15,run:({vscode:e})=>M(e,"chat.agentSkillsLocations","skills")},{id:"copilot-agents-location",label:"chat.agentFilesLocations points at agents/",weight:15,run:({vscode:e})=>M(e,"chat.agentFilesLocations","agents")},{id:"copilot-hooks-enabled",label:"Custom agent hooks enabled when .github/hooks/ exists",weight:15,run:({vscode:e})=>!e.hooksDirExists||Ie(e,"chat.useCustomAgentHooks")},{id:"copilot-shared-skills",label:"Shared skills/ tree present",weight:8,run:({context:e})=>R(e)},{id:"copilot-shared-agents",label:"Shared agents/ tree present",weight:7,run:({context:e})=>V(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/"))||M(n,"chat.agentSkillsLocations","skills")},{id:"cursor-skills-location",label:"chat.agentSkillsLocations points at skills/",weight:20,run:({vscode:e})=>M(e,"chat.agentSkillsLocations","skills")},{id:"cursor-agents-location",label:"chat.agentFilesLocations points at agents/",weight:20,run:({vscode:e})=>M(e,"chat.agentFilesLocations","agents")},{id:"cursor-hooks-enabled",label:"Custom agent hooks enabled when .github/hooks/ exists",weight:15,run:({vscode:e})=>!e.hooksDirExists||Ie(e,"chat.useCustomAgentHooks")},{id:"cursor-shared-skills",label:"Shared skills/ tree present",weight:13,run:({context:e})=>R(e)},{id:"cursor-shared-agents",label:"Shared agents/ tree present",weight:12,run:({context:e})=>V(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 P(N.join(e.repoRoot,"api/AGENTS.md"))||await P(N.join(e.repoRoot,"react/AGENTS.md"))||await P(N.join(e.repoRoot,"shared/AGENTS.md"))},{id:"codex-hooks",label:"Codex hooks.json present",weight:20,run:({context:e})=>P(N.join(e.repoRoot,".codex/hooks.json"))},{id:"codex-agents-wrappers",label:"Codex agent wrappers present",weight:20,run:({context:e})=>P(N.join(e.repoRoot,".codex/agents"))},{id:"codex-shared-entry",label:"Root AGENTS.md present",weight:20,run:({context:e})=>S(e,"AGENTS.md")},{id:"codex-shared-skills",label:"Shared skills/ tree present",weight:8,run:({context:e})=>R(e)},{id:"codex-shared-rules",label:"Canonical rules doc present",weight:7,run:({context:e})=>S(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})=>Vn(e)},{id:"antigravity-shared-entry",label:"Root AGENTS.md present",weight:20,run:({context:e})=>S(e,"AGENTS.md")},{id:"antigravity-shared-skills",label:"Shared skills/ tree present",weight:15,run:({context:e})=>R(e)},{id:"antigravity-ai-system-doc",label:"AI system layout doc present",weight:15,run:({context:e})=>S(e,"docs/ai/ai-system.md")}],claude:[{id:"claude-adapter",label:"CLAUDE.md adapter present",weight:25,run:({context:e})=>S(e,"CLAUDE.md")},{id:"claude-references-shared",label:"CLAUDE.md references shared layers",weight:25,run:({context:e})=>oe(e,"CLAUDE.md")},{id:"claude-adapter-thin",label:"CLAUDE.md stays thin",weight:20,run:({context:e})=>re(e,"CLAUDE.md")},{id:"claude-shared-skills",label:"Shared skills/ tree present",weight:15,run:({context:e})=>R(e)},{id:"claude-skills-index",label:"Skill index doc for prose discovery",weight:8,run:({context:e})=>S(e,"docs/ai/available-skills.md")},{id:"claude-shared-agents",label:"Shared agents/ tree present",weight:7,run:({context:e})=>V(e)}],gemini:[{id:"gemini-adapter",label:"GEMINI.md adapter present",weight:30,run:({context:e})=>S(e,"GEMINI.md")},{id:"gemini-references-shared",label:"GEMINI.md references shared layers",weight:25,run:({context:e})=>oe(e,"GEMINI.md")},{id:"gemini-adapter-thin",label:"GEMINI.md stays thin",weight:20,run:({context:e})=>re(e,"GEMINI.md")},{id:"gemini-shared-skills",label:"Shared skills/ tree present",weight:15,run:({context:e})=>R(e)},{id:"gemini-shared-agents",label:"Shared agents/ tree present",weight:10,run:({context:e})=>V(e)}]};function Kn(e){for(let{min:n,grade:t}of _n)if(e>=n)return t;return"poor"}function Yn(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 Xn(e){let n=e.error*ie.error+e.warn*ie.warn+e.info*ie.info;return Math.min(n,Wn)}async function Bn(e,n){let t=zn[e],s=[];for(let i of t){let r=await i.run(n);s.push({id:i.id,label:i.label,passed:r,weight:i.weight})}return s}function Jn(e){let n=e.reduce((s,i)=>s+i.weight,0);if(n===0)return 0;let t=e.filter(s=>s.passed).reduce((s,i)=>s+i.weight,0);return Math.round(t/n*100)}async function Qn(e,n,t,s,i,r){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 Bn(e,{context:n,vscode:t,harness:e}),u=Yn(s,e),k=Jn(c),p=Xn(u),G=Math.max(0,Math.min(100,k-p));return{harness:e,detected:!0,score:G,grade:Kn(G),checks:c,findingsCount:u,loadProfile:r[e]??null}}async function ae(e,n,t,s){let i=Le(s,e.harnesses),r=[];for(let o of E){let a=await Qn(o,e,n,t,s,i);r.push(a)}return r}import{readFile as Zn,stat as et}from"node:fs/promises";import De from"node:path";async function C(e){let n=De.join(e,".github/hooks"),t=!1;try{t=(await et(n)).isDirectory()}catch{t=!1}let s=De.join(e,".vscode/settings.json"),i=null,r=null;try{let o=await Zn(s,"utf8");r=o,i=D(o)}catch{i=null,r=null}return{settings:i,settingsRaw:r,hooksDirExists:t}}var le="docs/ai/rules.md",z=3,nt=80;function ce(e){return e.trim().replaceAll(/\s+/g," ")}function tt(e){let n=ce(e);return!(n.length<20||/^#{1,6}\s/.test(n)||/^[-*]\s/.test(n)&&n.length<40||n.includes("AGENTS.md")||n.includes("docs/ai/rules.md"))}function Ne(e){return e.map(n=>ce(n)).filter(n=>n.length>0).join(`
|
|
53
|
+
`)}function st(e,n){let t=e.split(`
|
|
54
|
+
`),s=Ne(n.split(`
|
|
55
|
+
`)),i=[],r=new Set;for(let o=0;o<=t.length-z;o+=1){if(r.has(o))continue;let a=t.slice(o,o+z);if(!a.every(u=>tt(u)))continue;let c=Ne(a);if(!(c.length<nt)&&s.includes(c)){i.push({startLine:o+1,lineCount:z,sample:ce(a[0]??"")}),r.add(o);for(let u=1;u<z;u+=1)r.add(o+u)}}return i}function Pe(e){let n=d(e,le);if(n===null)return[];let t=[];for(let s of $){let i=d(e,s);if(i===null)continue;let r=st(i,n);for(let o of r)t.push({ruleId:"adapter-content-duplication",severity:"warn",message:`${s} duplicates ${String(o.lineCount)} lines from ${le} near line ${String(o.startLine)}.`,file:s,line:o.startLine,harnesses:[],hint:`Replace duplicated rules with a pointer to ${le}.`})}return t}import{readFile as ot}from"node:fs/promises";import at from"node:path";var Me=/!?\[([^\]]*)\]\(([^)]+)\)/g;function v(e){let n=[],t=Me.exec(e);for(;t!==null;){let[s]=t;s.startsWith("!")||n.push({label:t[1]??"",href:(t[2]??"").trim()}),t=Me.exec(e)}return n}var it=new Set(["url","path","href","link","..."]);function K(e){return e.length===0||it.has(e.toLowerCase())||e.startsWith("http://")||e.startsWith("https://")||e.startsWith("mailto:")||e.startsWith("tel:")?!0:(e.split("#")[0]??"").length===0}function Y(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 i=e.includes("/")?e.slice(0,e.lastIndexOf("/")):"",r=i.length===0?s:`${i}/${s}`;return rt(r)}function rt(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 X(e,n){let t=e.split(`
|
|
56
|
+
`);for(let s=0;s<t.length;s+=1){let i=t[s];if(i!==void 0&&i.includes(n))return s+1}return null}var Te="docs/ai/available-skills.md";async function lt(e){try{let n=await ot(at.join(e,"package.json"),"utf8"),t=JSON.parse(n);if(m(t)&&"scripts"in t){let{scripts:s}=t;if(m(s))return s}return null}catch{return null}}function ct(e){return e===null?!1:Object.entries(e).some(([n,t])=>n.toLowerCase().includes("qmd")||typeof t=="string"&&t.toLowerCase().includes("qmd"))}function Ge(e){return g(e)?f(e,Te)?[]:[{ruleId:"skills-index",severity:"info",message:`${Te} 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 $e(e){if(!e.harnesses.includes("claude")||!f(e,"CLAUDE.md"))return[];let n=d(e,"CLAUDE.md");return n!==null&&_(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 je(e){if(!g(e)||!f(e,"AGENTS.md"))return[];let n=d(e,"AGENTS.md");return n!==null&&O(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)."}]}var ut=50;function dt(e){return!e.endsWith(".md")&&!e.endsWith(".mdc")?!1:e.startsWith("docs/")||e.includes("/docs/")}function pt(e,n){for(let t of v(n)){if(K(t.href))continue;let s=Y(e,t.href);if(s!==null&&dt(s))return!0}return!1}function Oe(e){let n=[];for(let t of e.files){if(!t.path.startsWith("skills/")||!t.path.endsWith("SKILL.md")||(t.lines??0)<=ut)continue;let s=d(e,t.path);s===null||pt(t.path,s)||n.push({ruleId:"skill-doc-deep-links",severity:"info",message:`${t.path} does not deep-link into a durable doc under docs/.`,file:t.path,line:null,harnesses:[],hint:"Pair the skill with a docs/ reference and link it (for example a **Details:** link to a docs/ page) instead of inlining all detail."})}return n}async function _e(e){let n=g(e),t=e.files.filter(i=>i.path.startsWith("docs/ai/")).length;if(!n&&t<2)return[];let s=await lt(e.repoRoot);return ct(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 ft=[{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 ht(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 gt(e,n){let t=e.split(`
|
|
57
|
+
`);for(let s=0;s<t.length;s+=1){let i=t[s]??"";if(!ht(i)&&(n.lastIndex=0,n.test(i)))return s+1}return null}function mt(e,n){return n.lastIndex=0,n.test(e)}function We(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 i of ft){let r=gt(s,i.pattern);if(r!==null){n.push({ruleId:"forbidden-legacy-paths",severity:"warn",message:`${t.path} references a legacy guidance path (${i.id}).`,file:t.path,line:r,harnesses:[],hint:i.message});continue}for(let o of v(s))mt(o.href,i.pattern)&&n.push({ruleId:"forbidden-legacy-paths",severity:"warn",message:`${t.path} links to a legacy guidance path (${i.id}).`,file:t.path,line:X(s,o.href),harnesses:[],hint:i.message})}}return n}var yt=["docs/","skills/","agents/",".cursor/",".github/",".codex/",".agent/",".claude/"],kt=new Set(["AGENTS.md","CLAUDE.md","GEMINI.md","README.md",".github/copilot-instructions.md"]);function Ue(e){let n=e.replaceAll("\\","/").replace(/\/+$/,"");return kt.has(n)?!0:yt.some(t=>n===t.slice(0,-1)||n.startsWith(t))}var St=["",".md","/README.md","/index.md"];async function qe(e,n){for(let t of St){let s=`${n}${t}`.replaceAll("\\","/");if(await I(e,s))return!0}return!1}var wt=8;function bt(e){return/my-doc\.md|example\.spec\.|\/example\//i.test(e)}function xt(e){return e.endsWith("/")?!0:e.endsWith(".md")||e.endsWith(".mdc")}async function Ve(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 i=0;for(let r of v(s)){if(K(r.href))continue;let o=Y(t.path,r.href);if(!(o===null||!Ue(o)||!xt(o)||bt(o)||await qe(e.repoRoot,o))&&(n.push({ruleId:"guidance-links-resolve",severity:"warn",message:`Broken link in ${t.path}: (${r.href}) does not resolve.`,file:t.path,line:X(s,r.href),harnesses:[],hint:"Fix the path or add the missing guidance file."}),i+=1,i>=wt))break}}return n}function ze(e,n,t,s){let i=n?.[t];if(m(i)&&Object.keys(i).some(c=>c.startsWith(s)))return!0;if(e===null)return!1;let r=t.replaceAll(".",String.raw`\.`);return new RegExp(`"${r}"\\s*:\\s*\\{[^}]*"${s}[^"]*"`).test(e)}async function Ke(e){let n=await C(e.repoRoot),t=[];return ze(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 }.'}),ze(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 Ye(e,n){return e.length===0?!0:e.some(t=>n.includes(t))}function ue(e,n,t,s){return{ruleId:e,severity:"warn",message:`${n} has ${String(t)} lines; keep root adapters thin (max ${String(b)}).`,file:n,line:null,harnesses:[s],hint:"Move detailed guidance to docs/ai/ and skills/; keep the adapter as a pointer."}}function Ct(e){let n=[];for(let t of j(e,"skills/")){if(!t.endsWith("/SKILL.md")&&!t.endsWith("SKILL.md"))continue;let s=d(e,t);if(s===null)continue;let i=Q(s);if(i===null){n.push({ruleId:"skill-frontmatter",severity:"error",message:`${t} is missing YAML frontmatter.`,file:t,line:null,harnesses:[]});continue}L(i,"name")||n.push({ruleId:"skill-frontmatter",severity:"error",message:`${t} is missing a name field in frontmatter.`,file:t,line:null,harnesses:[]}),L(i,"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 At(e){let n=[];for(let t of j(e,"skills/")){if(!t.endsWith("SKILL.md"))continue;let s=x(e,t);s!==null&&s>J&&n.push({ruleId:"skill-line-count",severity:"warn",message:`${t} has ${String(s)} lines; keep skills under ${String(J)}.`,file:t,line:null,harnesses:[],hint:"Move detail to docs/; keep SKILL.md as a pointer."})}return n}function Rt(e){let n=[];for(let t of j(e,"agents/")){if(!t.endsWith(".agent.md"))continue;let s=d(e,t);if(s===null)continue;let i=Q(s);if(i===null){n.push({ruleId:"agent-frontmatter",severity:"error",message:`${t} is missing YAML frontmatter.`,file:t,line:null,harnesses:[]});continue}L(i,"name")||n.push({ruleId:"agent-frontmatter",severity:"error",message:`${t} is missing a name field in frontmatter.`,file:t,line:null,harnesses:[]}),L(i,"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 vt(e){let n=[];for(let t of $){if(!f(e,t))continue;let s=d(e,t);s===null||Se(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 Ht(e){if(!e.files.some(i=>i.path.startsWith("skills/")))return[];let t=[],s=[".github/skills",".cursor/skills"];for(let i of s)await I(e.repoRoot,i)&&t.push({ruleId:"no-duplicate-skill-trees",severity:"error",message:`${i}/ duplicates root skills/; use skills/ only.`,file:i,line:null,harnesses:[],hint:"Remove the shadow tree and keep canonical skills/ at the repo root."});return t}async function Et(e){return e.files.some(t=>t.path.startsWith("agents/"))?await I(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 Lt(e){let n=await C(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 It={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."}]}},Ft={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."}]}},Dt={id:"adapter-thin-claude",title:"Thin CLAUDE.md adapter",severity:"warn",category:"adapter",dimension:"layering",harnesses:["claude"],check(e){let n=x(e,"CLAUDE.md");return n===null||n<=b?[]:[ue("adapter-thin-claude","CLAUDE.md",n,"claude")]}},Nt={id:"adapter-thin-gemini",title:"Thin GEMINI.md adapter",severity:"warn",category:"adapter",dimension:"layering",harnesses:["gemini"],check(e){let n=x(e,"GEMINI.md");return n===null||n<=b?[]:[ue("adapter-thin-gemini","GEMINI.md",n,"gemini")]}},Pt={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=x(e,n);return t===null||t<=b?[]:[ue("adapter-thin-copilot",n,t,"copilot")]}},Mt={id:"adapter-points-to-shared",title:"Adapters reference shared layers",severity:"warn",category:"adapter",dimension:"layering",harnesses:[],check:vt},Tt={id:"skill-frontmatter",title:"Skill frontmatter",severity:"error",category:"skills",dimension:"maintainability",harnesses:[],check:Ct},Gt={id:"skill-line-count",title:"Skill line budget",severity:"warn",category:"skills",dimension:"maintainability",harnesses:[],check:At},$t={id:"agent-frontmatter",title:"Agent frontmatter",severity:"error",category:"agents",dimension:"maintainability",harnesses:[],check:Rt},jt={id:"skills-index",title:"Skill slug index",severity:"info",category:"discoverability",dimension:"discoverability",harnesses:[],check:Ge},Ot={id:"claude-agent-routing",title:"Claude agent routing table",severity:"info",category:"discoverability",dimension:"discoverability",harnesses:["claude"],check:$e},_t={id:"agents-md-mentions-skills",title:"AGENTS.md skill discovery",severity:"info",category:"discoverability",dimension:"discoverability",harnesses:[],check:je},Wt={id:"skill-doc-deep-links",title:"Skills deep-link into docs",severity:"info",category:"discoverability",dimension:"discoverability",harnesses:[],check:Oe},Xe=[It,Ft,Dt,Nt,Pt,Mt,Tt,Gt,$t,jt,Ot,_t,Wt];function Ut(e,n,t){return Ye(e.harnesses,n.harnesses)?t===null||t.length===0||e.harnesses.length===0?!0:e.harnesses.some(s=>t.includes(s)):!1}function qt(e,n,t){return Ye(t,e.harnesses)?n===null||n.length===0?!0:t.some(s=>n.includes(s)):!1}async function Be(e,n){let t=[];for(let a of Xe)Ut(a,e,n)&&t.push(...a.check(e));if(qt(e,n,["cursor","copilot"])){let a=await Ke(e);t.push(...a);let c=await Lt(e);t.push(...c)}let s=await Ht(e);t.push(...s);let i=await Et(e);t.push(...i);let r=await _e(e);t.push(...r);let o=await Ve(e);return t.push(...o),t.push(...Pe(e)),t.push(...We(e)),t}var Vt=new Set(["skills-index","agents-md-mentions-skills","skill-doc-deep-links"]);function Je(e,n){return Vt.has(e)?g(n):e==="qmd-script-present"?zt(n):e==="claude-agent-routing"?n.harnesses.includes("claude")&&f(n,"CLAUDE.md"):!0}function zt(e){return g(e)?!0:e.files.filter(t=>t.path.startsWith("docs/ai/")).length>=2}var de={"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","skill-doc-deep-links":"discoverability","qmd-script-present":"discoverability","guidance-links-resolve":"maintainability","adapter-content-duplication":"sharing","forbidden-legacy-paths":"maintainability"};function Qe(e){return de[e]??null}var Kt=[{min:85,grade:"excellent"},{min:70,grade:"good"},{min:50,grade:"fair"},{min:0,grade:"poor"}];function H(e){for(let{min:n,grade:t}of Kt)if(e>=n)return t;return"poor"}function Ze(e,n){return n===0?0:e>=80?95:e>=65?82:e>=50?68:e>=30?52:35}var Yt=["layering","sharing","discoverability","harnessWiring","maintainability","guardrails"],pe={error:15,warn:6,info:2},Xt=35;function Bt(e){let n=e.filter(s=>s.detected&&s.score!==null);if(n.length===0)return 0;let t=n.reduce((s,i)=>s+(i.score??0),0);return Math.round(t/n.length)}function Jt(e){return Object.entries(de).filter(([,n])=>n===e).map(([n])=>n)}function nn(e,n){return e.filter(t=>Qe(t.ruleId)===n)}function Qt(e){let n=0;for(let t of e)t.severity==="error"?n+=pe.error:t.severity==="warn"?n+=pe.warn:n+=pe.info;return Math.min(n,Xt)}function en(e,n,t){let s=Jt(e).filter(c=>Je(c,t)),i=s.length,r=new Set(nn(n,e).map(c=>c.ruleId)),o=s.filter(c=>!r.has(c)).length,a=i===0?0:Math.round(o/i*100);return{score:a,grade:H(a),passedRules:o,applicableRules:i}}function Zt(){let e={score:0,grade:H(0),passedRules:0,applicableRules:0};return{layering:{...e},sharing:{...e},discoverability:{...e},harnessWiring:{...e},maintainability:{...e},guardrails:{...e}}}function fe(e,n,t,s){let i=Zt();for(let r of Yt){if(r==="sharing"){let o=Ze(n.shared.percentLines,n.totals.lines),a=Qt(nn(e,r)),c=Math.max(0,Math.min(100,o-a));i.sharing={score:c,grade:H(c),passedRules:n.shared.percentLines>=65?1:0,applicableRules:n.totals.lines>0?1:0};continue}if(r==="harnessWiring"){let o=en(r,e,s),a=Bt(t),c=a===0?o.score:Math.round((o.score+a)/2),u=Math.max(0,Math.min(100,c));i.harnessWiring={score:u,grade:H(u),passedRules:o.passedRules,applicableRules:o.applicableRules};continue}i[r]=en(r,e,s)}return{profile:"meta-harness",dimensions:i}}import{readFile as tn,readdir as es,stat as sn}from"node:fs/promises";import T from"node:path";var ns=[".md",".mdc"],ts=["AGENTS.md","docs/ai/rules.md","docs/ai/ai-system.md","docs/ai/available-skills.md","docs/ai/hooks.md"],ss=[{path:"CLAUDE.md",harness:"claude"},{path:"GEMINI.md",harness:"gemini"},{path:".github/copilot-instructions.md",harness:"copilot"},{path:".agent/README.md",harness:"antigravity"}],is=[{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 rs(e){return ns.some(n=>e.endsWith(n))}function os(e){return e.length===0?0:e.split(`
|
|
58
|
+
`).length}async function rn(e){let n=[],t=[];try{t=await es(e)}catch{return n}for(let s of t){let i=T.join(e,s),r=await sn(i);r.isDirectory()?n.push(...await rn(i)):r.isFile()&&rs(i)&&n.push(i)}return n}async function as(e){let{repoRoot:n,relativePath:t,layer:s,harness:i}=e,r=T.join(n,t);try{let o=await sn(r);if(!o.isFile())return null;let a=await tn(r,"utf8"),c={path:t,layer:s,lines:os(a),bytes:o.size};return i!==void 0&&(c.harness=i),c}catch{return null}}async function he(e){let n=[],t=new Map,s=new Set;async function i(r,o,a){let c=r.replaceAll("\\","/");if(s.has(c))return;let u=await as({repoRoot:e,relativePath:c,layer:o,harness:a});if(u===null)return;s.add(c),n.push(u);let k=T.join(e,c);try{let p=await tn(k,"utf8");t.set(c,p)}catch{}}for(let r of ts)await i(r,"shared");for(let r of ss)await i(r.path,"adapter",r.harness);for(let{dir:r,layer:o,harness:a}of is){let c=T.join(e,r),u=await rn(c);for(let k of u){let p=T.relative(e,k).replaceAll("\\","/");await i(p,o,a)}}return n.sort((r,o)=>r.path.localeCompare(o.path)),{files:n,contents:t}}var cs="0.0.1",on={error:3,warn:2,info:1};function us(e){let n=0,t=0,s=0;for(let i of e)i.severity==="error"?n+=1:i.severity==="warn"?t+=1:s+=1;return{error:n,warn:t,info:s}}async function ge(e){let n=ls.resolve(e.repoRoot),t=await se(n),{files:s,contents:i}=await he(n),r={repoRoot:n,harnesses:t,files:s,fileContents:i},o=await Be(r,e.harnessFilter),a=us(o),c=te(s,t),u=await C(n),k=await ae(r,u,o,c),p=fe(o,c,k,r),G=await He(r,u);return{version:cs,root:n,harnesses:t,inventory:{fileCount:s.length,files:s},sharing:c,metaHarness:p,harnessGapMatrix:G,harnessOptimization:k,findings:o,summary:a}}function an(e,n){let t=on[n];return e.some(s=>on[s.severity]>=t)}var ln=0,ps=1,w=2;function fs(){process.stdout.write(`paniolo-scan \u2014 AI harness diagnostic (read-only)
|
|
59
59
|
|
|
60
60
|
Usage:
|
|
61
61
|
paniolo-scan [options] [path]
|
|
@@ -70,12 +70,12 @@ Examples:
|
|
|
70
70
|
npx @paniolo/scan
|
|
71
71
|
npx @paniolo/scan --format json
|
|
72
72
|
npx @paniolo/scan --harness cursor,codex --fail-on warn
|
|
73
|
-
`)}function
|
|
74
|
-
`),process.exit(
|
|
75
|
-
`),process.exit(
|
|
76
|
-
`),process.exit(
|
|
77
|
-
`),process.exit(
|
|
78
|
-
`),process.exit(
|
|
79
|
-
`),process.exit(
|
|
80
|
-
`),process.exit(
|
|
81
|
-
`),process.exit(
|
|
73
|
+
`)}function hs(e){let n=e.split(",").map(i=>i.trim()).filter(i=>i.length>0);if(n.length===0)return null;let t=[],s=[];for(let i of n)i==="copilot"||i==="cursor"||i==="codex"||i==="antigravity"||i==="claude"||i==="gemini"?t.push(i):s.push(i);return s.length>0?(process.stderr.write(`error: unknown harness(es): ${s.join(", ")}
|
|
74
|
+
`),process.exit(w),[]):t}function gs(e){return e==="error"||e==="warn"||e==="info"?e:(process.stderr.write(`error: invalid --fail-on value: ${e}
|
|
75
|
+
`),process.exit(w),"error")}function ms(e){return e==="terminal"||e==="json"?e:(process.stderr.write(`error: invalid --format value: ${e}
|
|
76
|
+
`),process.exit(w),"terminal")}function ys(e){let n=process.cwd(),t=null,s="terminal",i="error",r=!1;for(let o=0;o<e.length;o+=1){let a=e[o];if(a!==void 0){if(a==="-h"||a==="--help"){r=!0;continue}if(a==="--harness"){let c=e[o+1];c===void 0&&(process.stderr.write(`error: --harness requires a value
|
|
77
|
+
`),process.exit(w)),t=hs(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(w)),s=ms(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(w)),i=gs(c),o+=1;continue}a.startsWith("-")&&(process.stderr.write(`error: unknown option: ${a}
|
|
80
|
+
`),process.exit(w)),n=ds.resolve(a)}}return{repoRoot:n,harnessFilter:t,format:s,failOn:i,help:r}}async function ks(){let e=ys(process.argv.slice(2));e.help&&(fs(),process.exit(ln));try{let n=await ge({repoRoot:e.repoRoot,harnessFilter:e.harnessFilter});e.format==="json"?B(n):ne(n),an(n.findings,e.failOn)&&process.exit(ps),process.exit(ln)}catch(n){let t=n instanceof Error?n.message:String(n);process.stderr.write(`error: ${t}
|
|
81
|
+
`),process.exit(w)}}await ks();
|
package/package.json
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@paniolo/scan",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.1",
|
|
4
4
|
"description": "Scan a repository for AI coding agent harness best practices (diagnostic only)",
|
|
5
|
-
"
|
|
5
|
+
"keywords": [
|
|
6
|
+
"agents",
|
|
7
|
+
"ai",
|
|
8
|
+
"codex",
|
|
9
|
+
"copilot",
|
|
10
|
+
"cursor",
|
|
11
|
+
"harness",
|
|
12
|
+
"lint"
|
|
13
|
+
],
|
|
6
14
|
"license": "MIT",
|
|
7
|
-
"engines": {
|
|
8
|
-
"node": ">=20"
|
|
9
|
-
},
|
|
10
15
|
"bin": {
|
|
11
16
|
"paniolo-scan": "dist/cli.js"
|
|
12
17
|
},
|
|
@@ -14,28 +19,33 @@
|
|
|
14
19
|
"dist/**/*.js",
|
|
15
20
|
"README.md"
|
|
16
21
|
],
|
|
22
|
+
"type": "module",
|
|
17
23
|
"scripts": {
|
|
18
24
|
"build": "npm run typecheck && node scripts/build.mjs",
|
|
25
|
+
"format": "oxfmt .",
|
|
26
|
+
"format:check": "oxfmt --check .",
|
|
27
|
+
"format:list-different": "oxfmt --list-different .",
|
|
28
|
+
"lint": "npm run typecheck && oxlint --config .oxlintrc.json --type-aware src vitest.config.ts && oxlint --config .oxlintrc.json --type-aware scripts",
|
|
29
|
+
"lint:fix": "oxlint --config .oxlintrc.json --type-aware --fix src vitest.config.ts && oxlint --config .oxlintrc.json --type-aware --fix scripts",
|
|
19
30
|
"pack:dry-run": "npm pack --dry-run",
|
|
20
31
|
"prepack": "npm run build",
|
|
21
32
|
"qmd": "node scripts/qmd.mjs",
|
|
33
|
+
"scan:self": "node dist/cli.js . --fail-on warn",
|
|
34
|
+
"check:ai-system": "npm run build && npm run scan:self",
|
|
22
35
|
"start": "node dist/cli.js",
|
|
23
36
|
"test": "vitest run",
|
|
24
37
|
"typecheck": "tsc --noEmit"
|
|
25
38
|
},
|
|
26
|
-
"keywords": [
|
|
27
|
-
"ai",
|
|
28
|
-
"agents",
|
|
29
|
-
"cursor",
|
|
30
|
-
"copilot",
|
|
31
|
-
"codex",
|
|
32
|
-
"harness",
|
|
33
|
-
"lint"
|
|
34
|
-
],
|
|
35
39
|
"devDependencies": {
|
|
36
40
|
"@types/node": "^22.19.20",
|
|
37
41
|
"esbuild": "^0.27.7",
|
|
42
|
+
"oxfmt": "^0.36.0",
|
|
43
|
+
"oxlint": "^1.68.0",
|
|
44
|
+
"oxlint-tsgolint": "^0.23.0",
|
|
38
45
|
"typescript": "^5.8.3",
|
|
39
46
|
"vitest": "^3.2.4"
|
|
47
|
+
},
|
|
48
|
+
"engines": {
|
|
49
|
+
"node": ">=20"
|
|
40
50
|
}
|
|
41
51
|
}
|