@zhama/tego-plugin-sdk 1.37.2 → 1.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent/guides/host-api-versions.md +3 -0
- package/agent/reference/plugin-sdk-api.md +1 -0
- package/dist/access.d.cts +1 -0
- package/dist/access.d.ts +1 -0
- package/dist/{chunk-EIVA5BR6.js → chunk-2KXEYXM2.js} +1 -1
- package/dist/{chunk-I5Z3ECC4.js → chunk-44LPR67H.js} +1 -1
- package/dist/chunk-66LDWN4T.cjs +1 -0
- package/dist/{chunk-HZ4YWRYX.cjs → chunk-6EKQMOYT.cjs} +2 -2
- package/dist/{chunk-SKNPNPJP.cjs → chunk-H6GXDFLP.cjs} +1 -1
- package/dist/{chunk-6HNG5OZI.cjs → chunk-IOZFHJ4F.cjs} +1 -1
- package/dist/chunk-JV2G2Z33.js +1 -0
- package/dist/{chunk-DRHBXEPK.cjs → chunk-QQOUNXLC.cjs} +7 -7
- package/dist/{chunk-IQNSBHBI.js → chunk-UZBWS5AR.js} +1 -1
- package/dist/{chunk-56QOZQTY.js → chunk-XOXUS4VI.js} +1 -1
- package/dist/decision-card-IF2c4wvM.d.cts +161 -0
- package/dist/decision-card-IF2c4wvM.d.ts +161 -0
- package/dist/dev.cjs +3 -3
- package/dist/dev.js +1 -1
- package/dist/doctor.cjs +6 -6
- package/dist/doctor.js +13 -13
- package/dist/hono.d.cts +1 -0
- package/dist/hono.d.ts +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +14 -1
- package/dist/index.d.ts +14 -1
- package/dist/index.js +1 -1
- package/dist/manifest.cjs +1 -1
- package/dist/manifest.js +1 -1
- package/dist/pack.cjs +1 -1
- package/dist/pack.js +1 -1
- package/dist/publish.cjs +1 -1
- package/dist/publish.js +1 -1
- package/dist/scaffold.cjs +2 -2
- package/dist/scaffold.js +1 -1
- package/dist/server-runtime.cjs +1 -1
- package/dist/server-runtime.d.cts +1 -0
- package/dist/server-runtime.d.ts +1 -0
- package/dist/server-runtime.js +1 -1
- package/dist/server.cjs +1 -1
- package/dist/server.d.cts +25 -0
- package/dist/server.d.ts +25 -0
- package/dist/server.js +1 -1
- package/dist/testing.d.cts +1 -0
- package/dist/testing.d.ts +1 -0
- package/dist/vite.cjs +1 -1
- package/dist/vite.js +1 -1
- package/package.json +1 -1
- /package/dist/{chunk-QKSPPMVN.js → chunk-CDJQ7OPA.js} +0 -0
- /package/dist/{chunk-BN6TGHSB.cjs → chunk-SR5V7ZC2.cjs} +0 -0
package/dist/doctor.cjs
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var C=Object.defineProperty;var f=(e,t)=>C(e,"name",{value:t,configurable:!0});var chunkH6GXDFLP_cjs=require("./chunk-H6GXDFLP.cjs"),chunk45Y4CR4R_cjs=require("./chunk-45Y4CR4R.cjs"),chunk2P6UEAPV_cjs=require("./chunk-2P6UEAPV.cjs"),chunkN4SXZZPY_cjs=require("./chunk-N4SXZZPY.cjs"),chunkIOZFHJ4F_cjs=require("./chunk-IOZFHJ4F.cjs");require("./chunk-AJY7ZHXK.cjs"),require("./chunk-OP64Y4IN.cjs");var child_process=require("child_process"),promises=require("fs/promises"),path=require("path"),crypto=require("crypto"),COLOR_ALLOW_MARKER="ui-colors-allow",PALETTE="slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose",UTILITY="bg|text|border(?:-[trblxyse])?|ring(?:-offset)?|from|via|to|fill|stroke|outline|divide|placeholder|caret|accent|decoration|shadow",COLOR_PATTERNS=[new RegExp(`(?<![\\w-])(?:${UTILITY})-(?:(?:${PALETTE})-(?:50|[1-9]00|950)|white|black)(?:\\/\\d{1,3})?(?![\\w-])`,"g"),/-\[(?:#[0-9a-fA-F]{3,8}|(?:rgba?|hsla?)\(\s*\d+)/g,/(?<![a-zA-Z0-9[-])(?:rgba?|hsla?)\(\s*\d+/g],QUOTED_HEX=/['"`]#[0-9a-fA-F]{3,8}['"`]/g,CSS_HEX=/(?<![\w&-])#[0-9a-fA-F]{3,8}(?![\w-])/g,DARK_VARIANT=/(?<![\w-])dark:(?=\S)/g,DARK_SELECTOR=/\.dark(?![\w-])/g;function stripComments(e){return e.replace(/(^|[\s{(,;])\/\*[\s\S]*?\*\//g,(t,s)=>s+t.slice(s.length).replace(/[^\n]/g," ")).replace(/(^|\s)\/\/.*$/gm,(t,s)=>s)}f(stripComments,"stripComments");function allowedLines(e){const t=new Set;return e.forEach((s,n)=>{s.includes(COLOR_ALLOW_MARKER)&&(t.add(n),/^\s*(?:\/\/|\/\*|\*|\{\/\*)/.test(s)&&t.add(n+1))}),t}f(allowedLines,"allowedLines");function findColorIssues(e,t){const s=t===".css",n=allowedLines(e.split(`
|
|
2
2
|
`)),l=stripComments(e).split(`
|
|
3
3
|
`),r=[...COLOR_PATTERNS,s?CSS_HEX:QUOTED_HEX],a=s?[DARK_VARIANT,DARK_SELECTOR]:[DARK_VARIANT],o=[];return l.forEach((p,i)=>{if(n.has(i))return;const h=[];for(const[d,c]of[["color",r],["dark",a]])for(const u of c)for(const g of p.matchAll(u))h.push({kind:d,match:g[0],at:g.index??0});h.sort((d,c)=>d.kind.localeCompare(c.kind)||d.at-c.at).forEach(({kind:d,match:c})=>o.push({line:i+1,kind:d,match:c}))}),o}f(findColorIssues,"findColorIssues");function findingFingerprint(e,t,s){return crypto.createHash("sha1").update(`${e}\0${t??""}\0${s.replace(/\s+/g," ").trim()}`).digest("hex").slice(0,16)}f(findingFingerprint,"findingFingerprint");var rule=f((e,t,s,n)=>({id:e,severity:t,guide:s,title:n}),"rule"),DOCTOR_RULES=[rule("TP-MAN-001","error","manifest.md","tego.plugin.json is valid"),rule("TP-MAN-002","error","host-api-versions.md","hostApiRange admits every host capability the code uses"),rule("TP-PKG-001","error","manifest.md","SDK packages come from npm aliases or workspace links, never a plain @tego/* name"),rule("TP-PKG-002","warning","testing.md","package.json has the lint, type-check, test and build gates"),rule("TP-PKG-003","error","manifest.md","No imports of @tego/shared or host app code; only published packages"),rule("TP-CLAW-002","warning","claw.md","Each claw skill's frontmatter name matches its folder"),rule("TP-DESK-001","error","desktop-web.md","No eval or new Function (the desktop CSP refuses them)"),rule("TP-DESK-002","error","desktop-web.md","No addresses built from location.origin (tauri://localhost on desktop)"),rule("TP-DESK-003","error","desktop-web.md","External links open with ctx.openExternal, not window.open"),rule("TP-DESK-004","error","desktop-web.md","Files are saved with ctx.download, not <a download> or a.download"),rule("TP-DESK-005","warning","desktop-web.md","No streamed responses or EventSource from the frontend (the desktop proxy buffers; use ctx.events)"),rule("TP-DESK-006","warning","desktop-web.md","Clipboard writes go through ctx.copyText"),rule("TP-WEB-001","error","desktop-web.md","The frontend calls no third-party URL (the web CSP blocks it); call the backend, which declares egress"),rule("TP-WEB-002","error","desktop-web.md","No remote or injected scripts; everything ships in the bundle"),rule("TP-WEB-003","error","frontend.md","The frontend calls its backend through ctx.api (createApiClient), never a raw fetch of a path"),rule("TP-WEB-004","error","desktop-web.md","No cookie-based auth (document.cookie, hono/cookie); ctx.api carries the credentials"),rule("TP-UI-001","error","ui.md","Colors come from token classes: no hex, rgb(), palette classes or dark: overrides"),rule("TP-UI-002","error","ui.md","No direct @radix-ui/* imports; use @tego/ui"),rule("TP-UI-003","error","ui.md","The plugin's root element carries its scope class (.<pluginId>) when it compiles its own Tailwind"),rule("TP-UI-004","error","frontend.md","The frontend has its own error boundary"),rule("TP-UI-005","warning","ui.md","Interface copy stays within budget: explanations \u2264 24 characters, any string \u2264 40 (zh-CN)"),rule("TP-I18N-001","error","i18n.md","All ten locales, with the same keys as zh-CN, and every nav label defined"),rule("TP-I18N-002","error","i18n.md","Every locale value keeps the {{placeholders}} of zh-CN and is not empty"),rule("TP-I18N-003","error","i18n.md","Every literal t('\u2026') key exists in zh-CN"),rule("TP-I18N-004","warning","i18n.md","Current terminology: \u6570\u5B57\u5458\u5DE5 / Employee, never \u6570\u5B57\u5206\u8EAB / digital avatar"),rule("TP-I18N-005","error","i18n.md","tegoAccess.* strings are merged by pack; the plugin locales do not copy them"),rule("TP-BE-001","error","backend.md","Identity comes from the verified ctx, never from the raw x-tego-plugin-* headers"),rule("TP-BE-002","error","access.md","No host role names in business code; ask the access engine"),rule("TP-BE-003","error","migrations.md","Migrations are <ordinal>_<name>.sql with unique ordinals"),rule("TP-BE-004","error","migrations.md","Committed migrations are never edited or deleted"),rule("TP-BE-005","warning","backend.md","Backend errors are logged through host.log, not console"),rule("TP-BE-006","error","backend.md","Configuration comes from host.config, never process.env"),rule("TP-BE-007","warning","backend.md","The backend app turns every error into JSON with pluginErrorHandler"),rule("TP-ACC-001","error","access.md","With an access catalog, a test asserts every route has a policy (assertRouteCoverage)"),rule("TP-ACC-002","error","access.md","With an access catalog, start() reconciles it (reconcile(host))"),rule("TP-TEST-001","warning","testing.md","The plugin has tests"),rule("TP-TEST-002","warning","testing.md","Each test file starts with a Business Cases list"),rule("TP-TPL-001","error","agent-templates.md","A template whose content changed raises its version (existing employees only see updates by version)"),rule("TP-TPL-002","warning","agent-templates.md","usageGuide.summary is at most 30 characters and quickPrompts lists 3\u20134 prompts")],BY_ID=new Map(DOCTOR_RULES.map(e=>[e.id,e]));function doctorRule(e){return BY_ID.get(e)}f(doctorRule,"doctorRule");var GUIDE_SCOPES={"manifest.md":["tego.plugin.json","package.json"],"host-api-versions.md":["tego.plugin.json"],"desktop-web.md":["<frontend>/src/**"],"frontend.md":["<frontend>/src/**"],"ui.md":["<frontend>/src/**/*.tsx","<frontend>/src/**/*.css"],"i18n.md":["<frontend>/i18n/**"],"backend.md":["<backend>/src/**"],"storage.md":["<backend>/src/**/storage*","<backend>/src/**/upload*","<backend>/src/**/files*"],"migrations.md":["<backend>/migrations/**","<backend>/src/**/schema*.ts","<backend>/drizzle.config.ts"],"access.md":["shared/access.json","<backend>/src/access*.ts","<backend>/src/**/access/**"],"claw.md":["<claw>/**"],"testing.md":["**/*.test.ts"],"agent-templates.md":["templates/**"]};function ruleCatalog(e={}){const t=f(n=>n.replace("<frontend>",e.frontend??"frontend").replace("<backend>",e.backend??"backend").replace("<claw>",e.claw??"claw"),"resolve2"),s=GUIDE_SCOPES;return{catalogVersion:1,rules:DOCTOR_RULES.map(n=>({...n,appliesTo:(s[n.guide]??[]).map(t),enforcedBy:"doctor"})),guides:Object.entries(s).map(([n,l])=>({file:n,appliesTo:l.map(t)}))}}f(ruleCatalog,"ruleCatalog");var DEFAULT_CHECK_STEPS=["doctor","lint","type-check","test"],LOCKFILES=[["pnpm-lock.yaml","pnpm"],["package-lock.json","npm"],["yarn.lock","yarn"],["bun.lockb","bun"],["bun.lock","bun"]],MAX_DETAIL=4e3,MAX_STEP_FINDINGS=50;async function detectPackageManager(e){let t=path.resolve(e);for(;;){for(const[n,l]of LOCKFILES)if(await promises.access(path.join(t,n)).then(()=>!0,()=>!1))return l;const s=path.dirname(t);if(s===t)return"npm";t=s}}f(detectPackageManager,"detectPackageManager");function run(e,t,s,n){return new Promise(l=>{const r=child_process.spawn(e,t,{cwd:s,signal:n,shell:process.platform==="win32",env:{...process.env,FORCE_COLOR:"0",NO_COLOR:"1",CI:"1"}});let a="";r.stdout.on("data",o=>a+=String(o)),r.stderr.on("data",o=>a+=String(o)),r.on("error",o=>l({code:null,output:`${a}
|
|
4
4
|
${o.message}`})),r.on("close",o=>l({code:o,output:a}))})}f(run,"run");function stripAnsi(e){return e.replace(/\u001b\[[0-9;]*m/g,"")}f(stripAnsi,"stripAnsi");function toRelative(e,t){const s=path.isAbsolute(t)?t:path.resolve(e,t),n=path.relative(e,s);return(n.startsWith("..")?t:n).split(path.sep).join("/")}f(toRelative,"toRelative");var TS_PAREN=/^(.+?)\((\d+),\d+\):\s+error\s+(TS\d+):\s+(.*)$/,TS_COLON=/^(.+?):(\d+):\d+\s+-\s+error\s+(TS\d+):\s+(.*)$/;function parseTypeErrors(e,t){const s=[];for(const n of stripAnsi(e).split(`
|
|
5
5
|
`)){const l=n.trim();if(l.startsWith(">"))continue;const r=TS_PAREN.exec(l)??TS_COLON.exec(l);if(!r)continue;const a=toRelative(t,r[1]);if(s.push({source:"type-check",severity:"error",rule:r[3],file:a,line:Number(r[2]),message:r[4],fingerprint:findingFingerprint(r[3],a,r[4])}),s.length>=MAX_STEP_FINDINGS)break}return s}f(parseTypeErrors,"parseTypeErrors");function parseEslintJson(e,t){const s=e.indexOf("[{"),n=e.lastIndexOf("}]");if(s<0||n<s)return e.trim().startsWith("[]")?[]:void 0;let l;try{l=JSON.parse(e.slice(s,n+2))}catch{return}const r=[];for(const a of l){const o=toRelative(t,a.filePath);for(const p of a.messages){const i=p.ruleId??"eslint";if(r.push({source:"lint",severity:p.severity>=2?"error":"warning",rule:i,file:o,...p.line?{line:p.line}:{},message:p.message,fingerprint:findingFingerprint(i,o,p.message)}),r.length>=MAX_STEP_FINDINGS)return r}}return r}f(parseEslintJson,"parseEslintJson");function parseTestFailures(e,t){const s=[],n=new Set;for(const l of stripAnsi(e).split(`
|
|
6
6
|
`)){const r=/^\s*(?:FAIL|×|✗)\s+(\S+\.(?:test|spec)\.[cm]?[jt]sx?)\s*(?:>\s*(.*))?$/.exec(l);if(!r)continue;const a=toRelative(t,r[1]),o=r[2]?.trim().replace(/^$/,"a test failed")??"a test failed",p=`${a}\0${o}`;if(!n.has(p)&&(n.add(p),s.push({source:"test",severity:"error",rule:"test",file:a,message:o,fingerprint:findingFingerprint("test",a,o)}),s.length>=MAX_STEP_FINDINGS))break}return s}f(parseTestFailures,"parseTestFailures");function tail(e){const t=stripAnsi(e).trim();return t.length>MAX_DETAIL?`\u2026${t.slice(-MAX_DETAIL)}`:t}f(tail,"tail");async function runChecks(e){const t=path.resolve(e.root),s=e.steps??DEFAULT_CHECK_STEPS,l=JSON.parse(await promises.readFile(path.join(t,"package.json"),"utf8").catch(()=>"{}")).scripts??{},r=await detectPackageManager(t),a=[],o=[];for(const p of s){const i=Date.now(),h=f((g,w)=>{a.push({step:p,status:g,durationMs:Date.now()-i,...w?{detail:w}:{}})},"done");if(e.signal?.aborted)break;if(p==="doctor"){const g=await runDoctor(e.files?{root:t,files:e.files}:{root:t});for(const w of g.findings)o.push({source:"doctor",...w});h(g.errors>0?"failed":"passed");continue}const d=l[p];if(!d){h("skipped",`package.json has no "${p}" script`);continue}if(p==="lint"&&/^\s*eslint\b/.test(d)){const g=e.files?.length?e.files.filter(b=>/\.[cm]?[jt]sx?$/.test(b)):["."];if(g.length===0){h("skipped","no lintable files among the given ones");continue}const k=await run(r,[...r==="npm"?["exec","--","eslint"]:["exec","eslint"],"--format","json","--no-warn-ignored",...g],t,e.signal),m=parseEslintJson(k.output,t);if(m){o.push(...m),h(m.some(b=>b.severity==="error")?"failed":"passed");continue}if(k.code===0){h("passed");continue}o.push({source:"lint",severity:"error",rule:"lint",message:tail(k.output),fingerprint:findingFingerprint("lint",void 0,"lint failed")}),h("failed",tail(k.output));continue}const c=await run(r,["run",p],t,e.signal);if(c.code===0){h("passed");continue}const u=p==="type-check"?parseTypeErrors(c.output,t):p==="test"?parseTestFailures(c.output,t):[];o.push(...u.length>0?u:[{source:p,severity:"error",rule:p,message:tail(c.output),fingerprint:findingFingerprint(p,void 0,`${p} failed`)}]),h("failed",tail(c.output))}return{passed:!a.some(p=>p.status==="failed"),steps:a,findings:o}}f(runChecks,"runChecks");function formatCheckReport(e){const t=e.steps.map(s=>`${s.status==="passed"?"\u2713":s.status==="failed"?"\u2716":"\u2013"} ${s.step}${s.status==="skipped"&&s.detail?` (${s.detail})`:""}`);for(const s of e.findings){const n=s.file?`${s.file}${s.line?`:${s.line}`:""} `:"";t.push(` ${s.severity==="error"?"\u2716":"\u26A0"} [${s.source}] ${s.rule??""} ${n}${s.message.split(`
|
|
7
7
|
`)[0]}`)}return t.push(e.passed?"\u2705 check: passed":"\u2716 check: failed"),t.join(`
|
|
8
|
-
`)}f(formatCheckReport,"formatCheckReport");var VERSIONED_CAPABILITIES=[{pattern:/\.openExternal\s*\(/,since:"1.37.0",name:"ctx.openExternal"},{pattern:/\.copyText\s*\(/,since:"1.37.0",name:"ctx.copyText"},{pattern:/\b(?:ctx|host|context)\.download\s*\(/,since:"1.37.0",name:"ctx.download"},{pattern:/\b(?:ctx|host|context)\.platform\b/,since:"1.37.0",name:"ctx.platform"},{pattern:/\b(?:ctx|context)\.events\b/,since:"1.3.0",name:"ctx.events"},{pattern:/\bhost\.directory\b/,since:"1.5.0",name:"host.directory"},{pattern:/\b(?:ctx|context)\.ai\b/,since:"1.9.0",name:"ctx.ai"},{pattern:/\.hostUrl\s*\(/,since:"1.12.0",name:"ctx.hostUrl"},{pattern:/\.uploadPath\s*\(/,since:"1.12.0",name:"storage.uploadPath"},{pattern:/\.collapseSidebar\s*\(/,since:"1.14.0",name:"ctx.layout.collapseSidebar"},{pattern:/\.managedPath\s*\(/,since:"1.15.0",name:"storage.managedPath"},{pattern:/\bstorage\.copy\s*\(/,since:"1.19.0",name:"storage.copy"},{pattern:/\b(?:ctx|context)\.office\b/,since:"1.21.0",name:"ctx.office"},{pattern:/\bhost\.tenantId\b/,since:"1.22.0",name:"host.tenantId"},{pattern:/\bproactive\.emit\s*\(/,since:"1.26.0",name:"proactive.emit"},{pattern:/\bmail\.send\s*\(/,since:"1.29.0",name:"mail.send"},{pattern:/\battention\.request\s*\(/,since:"1.34.0",name:"attention.request"}],SOURCE_EXTENSIONS=new Set([".ts",".tsx",".mts",".cts",".js",".jsx",".mjs",".css"]),SKIP_DIRS=new Set(["node_modules","dist","build",".tego-dev",".turbo",".drizzle","__fixtures__","coverage"]),TEST_FILE=/\.(?:test|spec)\.[cm]?[jt]sx?$|(?:^|[\\/])test[\\/]|(?:^|[\\/])test-[\w-]+\.[cm]?[jt]s$|-double\.[cm]?[jt]s$|\.d\.ts$/,IGNORE_MARKER=/tego-doctor-ignore\s+([A-Z0-9-]+(?:\s*,\s*[A-Z0-9-]+)*)/,PLACEHOLDER=/\{\{\s*([\w.-]+)\s*\}\}/g,REQUIRED_SCRIPTS=["lint","type-check","test","build"];function toPosix(e){return e.split(path.sep).join("/")}f(toPosix,"toPosix");async function walk(e,t,s){let n;try{n=await promises.readdir(path.join(e,t),{withFileTypes:!0})}catch{return}for(const l of n){const r=t?`${t}/${l.name}`:l.name;l.isDirectory()?!SKIP_DIRS.has(l.name)&&!l.name.startsWith(".")&&await walk(e,r,s):SOURCE_EXTENSIONS.has(path.extname(l.name))&&s.push(r)}}f(walk,"walk");function folderReader(e){return{async list(t){try{return(await promises.readdir(path.join(e,t),{withFileTypes:!0})).filter(n=>n.isFile()).map(n=>n.name)}catch{return[]}},readText:f(t=>promises.readFile(path.join(e,t),"utf8").catch(()=>null),"readText")}}f(folderReader,"folderReader");function runGit(e,t){return new Promise(s=>{child_process.execFile("git",["-C",e,...t],{maxBuffer:8*1024*1024},(n,l)=>s(n?null:l))})}f(runGit,"runGit");function ignoredLines(e){const t=new Map;return e.split(`
|
|
8
|
+
`)}f(formatCheckReport,"formatCheckReport");var VERSIONED_CAPABILITIES=[{pattern:/\.openExternal\s*\(/,since:"1.37.0",name:"ctx.openExternal"},{pattern:/\.copyText\s*\(/,since:"1.37.0",name:"ctx.copyText"},{pattern:/\b(?:ctx|host|context)\.download\s*\(/,since:"1.37.0",name:"ctx.download"},{pattern:/\b(?:ctx|host|context)\.platform\b/,since:"1.37.0",name:"ctx.platform"},{pattern:/\b(?:ctx|context)\.events\b/,since:"1.3.0",name:"ctx.events"},{pattern:/\bhost\.directory\b/,since:"1.5.0",name:"host.directory"},{pattern:/\b(?:ctx|context)\.ai\b/,since:"1.9.0",name:"ctx.ai"},{pattern:/\.hostUrl\s*\(/,since:"1.12.0",name:"ctx.hostUrl"},{pattern:/\.uploadPath\s*\(/,since:"1.12.0",name:"storage.uploadPath"},{pattern:/\.collapseSidebar\s*\(/,since:"1.14.0",name:"ctx.layout.collapseSidebar"},{pattern:/\.managedPath\s*\(/,since:"1.15.0",name:"storage.managedPath"},{pattern:/\bstorage\.copy\s*\(/,since:"1.19.0",name:"storage.copy"},{pattern:/\b(?:ctx|context)\.office\b/,since:"1.21.0",name:"ctx.office"},{pattern:/\bhost\.tenantId\b/,since:"1.22.0",name:"host.tenantId"},{pattern:/\bproactive\.emit\s*\(/,since:"1.26.0",name:"proactive.emit"},{pattern:/\bmail\.send\s*\(/,since:"1.29.0",name:"mail.send"},{pattern:/\battention\.request\s*\(/,since:"1.34.0",name:"attention.request"},{pattern:/\bDecisionCardSurface\b/,since:"1.38.0",name:"DecisionCardSurface"},{pattern:/\bsources\.latestUserMessage\s*\(/,since:"1.39.0",name:"sources.latestUserMessage"}],SOURCE_EXTENSIONS=new Set([".ts",".tsx",".mts",".cts",".js",".jsx",".mjs",".css"]),SKIP_DIRS=new Set(["node_modules","dist","build",".tego-dev",".turbo",".drizzle","__fixtures__","coverage"]),TEST_FILE=/\.(?:test|spec)\.[cm]?[jt]sx?$|(?:^|[\\/])test[\\/]|(?:^|[\\/])test-[\w-]+\.[cm]?[jt]s$|-double\.[cm]?[jt]s$|\.d\.ts$/,IGNORE_MARKER=/tego-doctor-ignore\s+([A-Z0-9-]+(?:\s*,\s*[A-Z0-9-]+)*)/,PLACEHOLDER=/\{\{\s*([\w.-]+)\s*\}\}/g,REQUIRED_SCRIPTS=["lint","type-check","test","build"];function toPosix(e){return e.split(path.sep).join("/")}f(toPosix,"toPosix");async function walk(e,t,s){let n;try{n=await promises.readdir(path.join(e,t),{withFileTypes:!0})}catch{return}for(const l of n){const r=t?`${t}/${l.name}`:l.name;l.isDirectory()?!SKIP_DIRS.has(l.name)&&!l.name.startsWith(".")&&await walk(e,r,s):SOURCE_EXTENSIONS.has(path.extname(l.name))&&s.push(r)}}f(walk,"walk");function folderReader(e){return{async list(t){try{return(await promises.readdir(path.join(e,t),{withFileTypes:!0})).filter(n=>n.isFile()).map(n=>n.name)}catch{return[]}},readText:f(t=>promises.readFile(path.join(e,t),"utf8").catch(()=>null),"readText")}}f(folderReader,"folderReader");function runGit(e,t){return new Promise(s=>{child_process.execFile("git",["-C",e,...t],{maxBuffer:8*1024*1024},(n,l)=>s(n?null:l))})}f(runGit,"runGit");function ignoredLines(e){const t=new Map;return e.split(`
|
|
9
9
|
`).forEach((s,n)=>{const l=IGNORE_MARKER.exec(s);if(!l?.[1])return;const r=new Set(l[1].split(",").map(o=>o.trim())),a=f(o=>t.set(o,new Set([...t.get(o)??[],...r])),"add");a(n),/^\s*(?:\/\/|\/\*|\*|\{\/\*)/.test(s)&&a(n+1)}),t}f(ignoredLines,"ignoredLines");var Findings=class{static{f(this,"Findings")}list=[];add(e,t,s={}){const{severity:n,guide:l}=doctorRule(e),{evidence:r,...a}=s;this.list.push({rule:e,severity:n,message:t,guide:l,...a,fingerprint:findingFingerprint(e,a.file,r??t)})}};function eachMatch(e,t,s,n){const l=ignoredLines(e.text);e.code.split(`
|
|
10
|
-
`).forEach((r,a)=>{if(l.get(a)?.has(t))return;const o=s.exec(r);o&&n(a+1,o[0],r.trim())})}f(eachMatch,"eachMatch");function partOf(e,t){const s=f(n=>!!n&&(e===n||e.startsWith(`${n}/`)),"inside");return s(t.frontend?.dir)||s(t.publicFrontend?.dir)?"frontend":s(t.backend?.dir)?"backend":s(t.claw?.dir)?"claw":s("shared")?"shared":"other"}f(partOf,"partOf");async function readJson(e){try{return JSON.parse(await promises.readFile(e,"utf8"))}catch{return}}f(readJson,"readJson");function checkPackageJson(e,t){if(!t)return;for(const l of["dependencies","devDependencies","peerDependencies"]){const r=t[l];if(!(!r||typeof r!="object"))for(const[a,o]of Object.entries(r))!a.startsWith("@tego/")||typeof o!="string"||/^(?:npm:@zhama\/tego-|workspace:|file:|link:|portal:)/.test(o)||e.add("TP-PKG-001",`${l}.${a} is "${o}": on npm @tego/* belongs to another project. Install it as "npm:@zhama/tego-${a.slice(6)}@^<sdk version>".`,{file:"package.json"})}const s=t.scripts??{},n=REQUIRED_SCRIPTS.filter(l=>typeof s[l]!="string");n.length>0&&e.add("TP-PKG-002",`package.json has no ${n.map(l=>`"${l}"`).join(", ")} script; the platform's gates are lint, type-check, test and build.`,{file:"package.json"})}f(checkPackageJson,"checkPackageJson");async function checkLocales(e,t,s,n,l){const r=n.frontend;if(!r)return;const{bundles:a,errors:o}=await chunk45Y4CR4R_cjs.verifyFrontendLocales(folderReader(t),r);for(const m of o)e.add("TP-I18N-001",m,{file:r.i18nDirPath});const p=a.get("zh-CN");if(!p)return;const i=f((m,b="",
|
|
11
|
-
`).forEach((
|
|
10
|
+
`).forEach((r,a)=>{if(l.get(a)?.has(t))return;const o=s.exec(r);o&&n(a+1,o[0],r.trim())})}f(eachMatch,"eachMatch");function partOf(e,t){const s=f(n=>!!n&&(e===n||e.startsWith(`${n}/`)),"inside");return s(t.frontend?.dir)||s(t.publicFrontend?.dir)?"frontend":s(t.backend?.dir)?"backend":s(t.claw?.dir)?"claw":s("shared")?"shared":"other"}f(partOf,"partOf");async function readJson(e){try{return JSON.parse(await promises.readFile(e,"utf8"))}catch{return}}f(readJson,"readJson");function checkPackageJson(e,t){if(!t)return;for(const l of["dependencies","devDependencies","peerDependencies"]){const r=t[l];if(!(!r||typeof r!="object"))for(const[a,o]of Object.entries(r))!a.startsWith("@tego/")||typeof o!="string"||/^(?:npm:@zhama\/tego-|workspace:|file:|link:|portal:)/.test(o)||e.add("TP-PKG-001",`${l}.${a} is "${o}": on npm @tego/* belongs to another project. Install it as "npm:@zhama/tego-${a.slice(6)}@^<sdk version>".`,{file:"package.json"})}const s=t.scripts??{},n=REQUIRED_SCRIPTS.filter(l=>typeof s[l]!="string");n.length>0&&e.add("TP-PKG-002",`package.json has no ${n.map(l=>`"${l}"`).join(", ")} script; the platform's gates are lint, type-check, test and build.`,{file:"package.json"})}f(checkPackageJson,"checkPackageJson");async function checkLocales(e,t,s,n,l){const r=n.frontend;if(!r)return;const{bundles:a,errors:o}=await chunk45Y4CR4R_cjs.verifyFrontendLocales(folderReader(t),r);for(const m of o)e.add("TP-I18N-001",m,{file:r.i18nDirPath});const p=a.get("zh-CN");if(!p)return;const i=f((m,b="",T=new Map)=>{if(typeof m!="object"||m===null||Array.isArray(m))return T.set(b,m),T;for(const[v,P]of Object.entries(m))i(P,b?`${b}.${v}`:v,T);return T},"leaves"),h=f(m=>new Set(typeof m=="string"?[...m.matchAll(PLACEHOLDER)].map(b=>b[1]):[]),"placeholders"),d=i(p),c=`${r.i18nDirPath}/zh-CN.json`;[...d.keys()].some(m=>m==="tegoAccess"||m.startsWith("tegoAccess."))&&e.add("TP-I18N-005","zh-CN.json contains tegoAccess.* strings; pack merges the access UI strings, remove the copy from every locale.",{file:c,evidence:"tegoAccess"});let u=0;for(const[m,b]of d){if(typeof b!="string"||u>=10)continue;const T=m.slice(m.lastIndexOf(".")+1);if(MODEL_TEXT_KEY.test(T))continue;const v=EXPLANATION_KEY.test(T)?EXPLANATION_LIMIT:ANY_STRING_LIMIT,P=copyLength(b);P>v&&(u++,e.add("TP-UI-005",`"${m}" is ${P} characters (budget ${v}); shorten it, or move the explanation into an InfoTip.`,{file:c,evidence:m}))}for(const[m,b]of a){const T=`${r.i18nDirPath}/${m}.json`;let v=0;for(const[P,y]of i(b)){if(v>=10)break;if(typeof y=="string"&&y.trim()===""){e.add("TP-I18N-002",`"${P}" is empty in ${m}.json.`,{file:T}),v++;continue}if(m==="zh-CN"||!d.has(P))continue;const E=h(d.get(P)),x=h(y),S=[...E].filter($=>!x.has($)),A=[...x].filter($=>!E.has($));(S.length||A.length)&&(e.add("TP-I18N-002",`"${P}" in ${m}.json has placeholders {${[...x].join(", ")}} but zh-CN has {${[...E].join(", ")}}.`,{file:T}),v++)}for(const[P,y]of i(b))if(typeof y=="string"&&/数字分身|digital avatar/i.test(y)){e.add("TP-I18N-004",`"${P}" in ${m}.json says "${/数字分身/.test(y)?"\u6570\u5B57\u5206\u8EAB":"digital avatar"}"; the platform calls them \u6570\u5B57\u5458\u5DE5 / Employee ({{avatarTerm}}).`,{file:T});break}}const g=chunk45Y4CR4R_cjs.flattenLocaleKeys(p),w=new Set(Object.keys(p)),k=f(m=>g.has(m)||["_one","_other","_zero","_few","_many","_two"].some(b=>g.has(m+b))||[...g].some(b=>b.startsWith(`${m}.`)),"exists");for(const m of l){if(m.part!=="frontend"||m.test)continue;const b=ignoredLines(m.text);m.code.split(`
|
|
11
|
+
`).forEach((T,v)=>{if(!b.get(v)?.has("TP-I18N-003"))for(const P of T.matchAll(/\bt\(\s*['"]([A-Za-z0-9_.-]+)['"]/g)){const y=P[1],E=y.split(".")[0];!y.includes(".")||!w.has(E)||k(y)||e.add("TP-I18N-003",`t('${y}') has no entry in zh-CN.json (namespace ${chunkN4SXZZPY_cjs.pluginI18nNamespace(s.id)}).`,{file:m.path,line:v+1})}})}}f(checkLocales,"checkLocales");function copyLength(e){return[...e.replace(/\{\{[^}]*\}\}/g,"X")].length}f(copyLength,"copyLength");var EXPLANATION_KEY=/(help|hint|description|desc|subtitle|intro)$/i,MODEL_TEXT_KEY=/prompt/i,EXPLANATION_LIMIT=24,ANY_STRING_LIMIT=40;async function checkTemplates(e,t,s){const n=s.agentTemplates??[];if(n.length===0)return;const l=(await runGit(t,["rev-parse","--show-prefix"]))?.trim()??"";for(const r of n){const a=r.entry.split("/").slice(0,-1).join("/"),o=`${a}/template.config.json`,p=await readJson(path.join(t,o));if(!p)continue;const i=p.content??{},h=i.usageGuide;typeof h?.summary=="string"&©Length(h.summary)>30&&e.add("TP-TPL-002",`${r.key}: usageGuide.summary is ${copyLength(h.summary)} characters; the landing page shows one line, keep it within 30.`,{file:o,evidence:"summary"});const d=i.quickPrompts;Array.isArray(d)&&(d.length<3||d.length>4)&&e.add("TP-TPL-002",`${r.key}: quickPrompts has ${d.length} prompt(s); offer 3\u20134 ready-to-click prompts.`,{file:o,evidence:"quickPrompts"});const u=(await runGit(t,["status","--porcelain","--untracked-files=all","--",a])??"").split(`
|
|
12
12
|
`).map(k=>k.slice(3).trim()).filter(k=>k&&!k.endsWith("/template.json"));if(u.length===0)continue;const g=await runGit(t,["show",`HEAD:${l}${o}`]);if(!g)continue;let w;try{w=JSON.parse(g).version}catch{continue}w!==void 0&&w===p.version&&e.add("TP-TPL-001",`${r.key} changed (${u.length} file(s)) but its version is still ${String(p.version)}; raise version in ${o} so existing employees see the update.`,{file:o,evidence:"version"})}}f(checkTemplates,"checkTemplates");async function checkMigrations(e,t,s){const n=s.backend;if(!n)return;const{errors:l}=await chunk45Y4CR4R_cjs.verifyMigrationNames(folderReader(t),n.migrationsDirPath);for(const o of l)e.add("TP-BE-003",o,{file:n.migrationsDirPath});const r=await runGit(t,["diff","--name-status","HEAD","--",n.migrationsDirPath]);if(!r)return;const a=(await runGit(t,["rev-parse","--show-prefix"]))?.trim()??"";for(const o of r.split(`
|
|
13
|
-
`)){const[p,i]=o.split(" ");if(!i||p!=="M"&&p!=="D")continue;const h=i.startsWith(a)?i.slice(a.length):i;e.add("TP-BE-004",`${h} is a committed migration and was ${p==="M"?"edited":"deleted"}; an install may have applied it already. Revert it and add a new migration instead.`,{file:h})}}f(checkMigrations,"checkMigrations");async function checkClaw(e,t,s){const n=s.claw;if(!n)return;const l=path.join(t,n.dir,"skills");let r=[];try{r=(await promises.readdir(l,{withFileTypes:!0})).filter(a=>a.isDirectory()).map(a=>a.name)}catch{return}for(const a of r){const o=await promises.readFile(path.join(l,a,"SKILL.md"),"utf8").catch(()=>null),p=`${n.dir}/skills/${a}/SKILL.md`,i=o?/^---[\s\S]*?^name:\s*["']?([^"'\n]+)["']?\s*$/m.exec(o)?.[1]?.trim():void 0;i!==a&&e.add("TP-CLAW-002",o?`${p} declares name "${i??"(none)"}"; it must equal the folder name "${a}".`:`${n.dir}/skills/${a} has no SKILL.md.`,{file:p})}}f(checkClaw,"checkClaw");function checkSources(e,t,s,n,l,r){const a=t.hostApiRange&&
|
|
14
|
-
`).forEach((c,u)=>{if(!d.get(u)?.has("TP-PKG-003"))for(const g of c.matchAll(/(?:from\s+|import\s*\(\s*|require\s*\(\s*)['"](\.\.?\/[^'"]*)['"]/g)){const w=path.posix.normalize(path.posix.join(path.posix.dirname(i.path),g[1]));w.startsWith("../")&&e.add("TP-PKG-003",`"${g[1]}" resolves outside the plugin folder (${w}); a packed plugin only has its own files and published packages.`,h(u+1,c.trim()))}});for(const c of VERSIONED_CAPABILITIES){if(!o(c.since))continue;const u=c.name.split(".").pop();new RegExp(`\\.${u}\\s*(?:\\)|\\?(?!\\.)|&&)`).test(i.code)||eachMatch(i,"TP-MAN-002",c.pattern,(g,w,k)=>e.add("TP-MAN-002",`${c.name} needs host API ${c.since}, but hostApiRange is "${t.hostApiRange}"; raise it to ^${c.since}.`,h(g,k)))}}i.part!=="other"&&eachMatch(i,"TP-BE-002",/\b(?:roles|roleNames)\s*\??\.\s*(?:includes|indexOf)\(\s*['"`](?:admin|owner|super_?admin|tenant_?admin|member|manager)['"`]/i,(d,c,u)=>e.add("TP-BE-002","Do not decide by host role names; declare permissions in the access catalog (hostRoleBindings) and ask the engine (authorize / useAccess).",h(d,u)))}const p=n.filter(i=>i.part==="frontend"&&!i.test);if(s.frontend&&p.length>0&&(p.some(i=>/getDerivedStateFromError|componentDidCatch|PluginErrorBoundary/.test(i.code))||e.add("TP-UI-004","No error boundary in the frontend: the shell's boundary cannot see into your React root, so a render error leaves a blank page. Wrap the root in PluginErrorBoundary (@tego/plugin-sdk/react) or your own.",{file:s.frontend.dir}),r?.includes("tegoPluginTailwind"))){const i=new RegExp(`(?:className|class)=\\{?\\s*(?:cn\\()?\\s*['"\`][^'"\`]*(?<![\\w-])${t.id.replace(/[.]/g,"\\.")}(?![\\w-])`);p.some(h=>i.test(h.code))||e.add("TP-UI-003",`tegoPluginTailwind scopes every utility under .${t.id}, but no element carries className="${t.id}": none of the plugin's own classes will apply. Put it on the root element (and on portalled content, or use tegoPluginScopeRoot).`,{file:s.frontend.dir})}}f(checkSources,"checkSources");function checkAccessAndTests(e,t,s,n,l=()=>!0){const r=n.filter(o=>o.test);for(const o of r)!l(o)||!/\.(?:test|spec)\.[cm]?[jt]sx?$/.test(o.path)||/Business Cases/.test(o.text)||e.add("TP-TEST-002",`${o.path} has no Business Cases list: start it with the cases it covers (one line each, an id per case) and begin each it() title with its id.`,{file:o.path,line:1,evidence:"no business cases"});if(s.backend){const o=n.filter(p=>p.part==="backend"&&!p.test);o.some(p=>/\bnew\s+Hono\b/.test(p.code))&&!o.some(p=>/\.onError\s*\(/.test(p.code))&&e.add("TP-BE-007","The backend app has no error handler (app.onError(pluginErrorHandler())): an uncaught error becomes Hono's plain-text 500 instead of JSON the frontend can show.",{file:s.backend.dir})}if(r.length===0&&e.add("TP-TEST-001","The plugin has no *.test.ts files; backend routes and pure frontend logic need tests (createTestPluginHost for SQL)."),!t.access||!s.backend)return;r.some(o=>o.text.includes("assertRouteCoverage"))||e.add("TP-ACC-001","The manifest declares an access catalog but no test calls assertRouteCoverage(app): a route without a policy would be open to everyone who can open the plugin.",{file:s.backend.dir}),n.filter(o=>(o.part==="backend"||o.part==="shared")&&!o.test).some(o=>/\breconcile\s*\(/.test(o.code))||e.add("TP-ACC-002","The manifest declares an access catalog but start() never calls reconcile(host): built-in roles and catalog changes never reach the database.",{file:s.backend.dir})}f(checkAccessAndTests,"checkAccessAndTests");async function runDoctor(e){const t=e.root;await promises.stat(path.join(t,"tego.plugin.json")).catch(()=>{throw new Error(`No tego.plugin.json in ${t}; run doctor in the plugin folder.`)});const s=new Findings;let n;try{n=JSON.parse(await promises.readFile(path.join(t,"tego.plugin.json"),"utf8"))}catch(c){return s.add("TP-MAN-001",`tego.plugin.json is not valid JSON: ${c instanceof Error?c.message:String(c)}`,{file:"tego.plugin.json"}),summarize(s,void 0)}const l=
|
|
13
|
+
`)){const[p,i]=o.split(" ");if(!i||p!=="M"&&p!=="D")continue;const h=i.startsWith(a)?i.slice(a.length):i;e.add("TP-BE-004",`${h} is a committed migration and was ${p==="M"?"edited":"deleted"}; an install may have applied it already. Revert it and add a new migration instead.`,{file:h})}}f(checkMigrations,"checkMigrations");async function checkClaw(e,t,s){const n=s.claw;if(!n)return;const l=path.join(t,n.dir,"skills");let r=[];try{r=(await promises.readdir(l,{withFileTypes:!0})).filter(a=>a.isDirectory()).map(a=>a.name)}catch{return}for(const a of r){const o=await promises.readFile(path.join(l,a,"SKILL.md"),"utf8").catch(()=>null),p=`${n.dir}/skills/${a}/SKILL.md`,i=o?/^---[\s\S]*?^name:\s*["']?([^"'\n]+)["']?\s*$/m.exec(o)?.[1]?.trim():void 0;i!==a&&e.add("TP-CLAW-002",o?`${p} declares name "${i??"(none)"}"; it must equal the folder name "${a}".`:`${n.dir}/skills/${a} has no SKILL.md.`,{file:p})}}f(checkClaw,"checkClaw");function checkSources(e,t,s,n,l,r){const a=t.hostApiRange&&chunkIOZFHJ4F_cjs.isValidHostApiRange(t.hostApiRange)?chunkIOZFHJ4F_cjs.hostApiRangeMinimum(t.hostApiRange):void 0,o=f(i=>{if(!a)return!1;const[h,d,c]=a.split(".").map(Number),[u,g,w]=i.split(".").map(Number);return h!==u?h<u:d!==g?d<g:c<w},"below");for(const i of n){if(!l(i)||i.test)continue;const h=f((d,c)=>({file:i.path,line:d,evidence:c}),"at");if(i.part==="frontend"){if(i.path.endsWith(".css")||i.path.endsWith(".tsx")||i.path.endsWith(".ts")){const d=ignoredLines(i.text);for(const c of findColorIssues(i.text,path.extname(i.path)))d.get(c.line-1)?.has("TP-UI-001")||e.add("TP-UI-001",c.kind==="dark"?`"${c.match}" is a hand-written dark override; tokens already carry both themes.`:`"${c.match}" is a literal color; use a token class (bg-card, text-muted-foreground, bg-primary\u2026).`,h(c.line,c.match))}eachMatch(i,"TP-DESK-001",/\beval\s*\(|\bnew\s+Function\s*\(/,(d,c,u)=>e.add("TP-DESK-001","eval / new Function: the desktop app's CSP (script-src 'self') refuses code compiled from strings.",h(d,u))),eachMatch(i,"TP-DESK-002",/\b(?:window\.|document\.)?location\.origin\b/,(d,c,u)=>e.add("TP-DESK-002","location.origin is tauri://localhost in the desktop app; build host addresses with ctx.hostUrl(path) and share links from ctx.publicPagesUrl.",h(d,u))),eachMatch(i,"TP-DESK-003",/\bwindow\.open\s*\(/,(d,c,u)=>e.add("TP-DESK-003","window.open does not reliably open links in the desktop app; use ctx.openExternal(url) (hostApiRange ^1.37.0), or ctx.download for a file.",h(d,u))),eachMatch(i,"TP-DESK-004",/\.download\s*=(?!=)|<a\b[^>]*\sdownload(?:[\s=>]|$)/,(d,c,u)=>e.add("TP-DESK-004","An <a download> save is not handled by the desktop app; use ctx.download({ path | blob, fileName }) (hostApiRange ^1.37.0).",h(d,u))),eachMatch(i,"TP-DESK-005",/\bnew\s+EventSource\s*\(|\.body\s*(?:\?\.|!\.|\.)\s*getReader\s*\(/,(d,c,u)=>e.add("TP-DESK-005","The desktop app buffers ctx.api responses whole (and caps them at 100 MB), so streams arrive at the end; subscribe to ctx.events for pushed updates.",h(d,u))),eachMatch(i,"TP-DESK-006",/\bnavigator\.clipboard\./,(d,c,u)=>e.add("TP-DESK-006","navigator.clipboard is not available in every shell; use ctx.copyText(text) (hostApiRange ^1.37.0).",h(d,u))),eachMatch(i,"TP-WEB-001",/\bfetch\s*\(\s*['"`]https?:\/\//,(d,c,u)=>e.add("TP-WEB-001","The web app's CSP allows connections to the platform only; call your backend through ctx.api and let it reach the service (declare it in parts.backend.egress).",h(d,u))),eachMatch(i,"TP-WEB-002",/\bimport\s*\(\s*['"`]https?:|createElement\(\s*['"]script['"]\s*\)|<script\b[^>]*\bsrc=/,(d,c,u)=>e.add("TP-WEB-002","Remote or injected scripts are refused by both shells' CSP; bundle the code.",h(d,u))),eachMatch(i,"TP-UI-002",/from\s+['"]@radix-ui\//,(d,c,u)=>e.add("TP-UI-002","Import the @tego/ui wrapper instead of @radix-ui/* (the shell shares @tego/ui; a second Radix copy breaks focus and portals).",h(d,u))),eachMatch(i,"TP-WEB-003",/\bfetch\s*\(\s*['"`](?!https?:|data:|blob:)/,(d,c,u)=>e.add("TP-WEB-003","Call your backend through ctx.api (createApiClient from @tego/plugin-sdk/react): a raw fetch misses the base path and the credentials in the desktop app, and loses the error body.",h(d,u))),eachMatch(i,"TP-WEB-004",/\bdocument\.cookie\b/,(d,c,u)=>e.add("TP-WEB-004","Cookies do not carry the session in the desktop app; ctx.api sends the credentials in both shells.",h(d,u)))}if(i.part==="backend"&&(eachMatch(i,"TP-BE-001",/\bheaders?\s*(?:\.get)?\(\s*['"]x-tego-plugin-(?:user-id|tenant-id|roles|avatar-id|delegation)['"]|\bheaders\s*\[\s*['"]x-tego-plugin-(?:user-id|tenant-id|roles|avatar-id|delegation)['"]/i,(d,c,u)=>e.add("TP-BE-001","Take the caller from the verified ctx (ctx.userId, ctx.tenantId, ctx.roles) that serveBackend hands you; the raw x-tego-plugin-* headers are unverified here.",h(d,u))),eachMatch(i,"TP-BE-005",/\bconsole\.(?:error|warn)\s*\(/,(d,c,u)=>e.add("TP-BE-005","Log through host.log so the message reaches the platform's logs; console output from a plugin container is not collected.",h(d,u))),eachMatch(i,"TP-BE-006",/\bprocess\.env\b(?!\.NODE_ENV\b|\[\s*['"]NODE_ENV['"]\s*\])/,(d,c,u)=>e.add("TP-BE-006","Read configuration from host.config.current() (declared in the manifest's config schema); the platform does not pass a plugin its own environment variables.",h(d,u))),eachMatch(i,"TP-WEB-004",/from\s+['"]hono\/cookie['"]/,(d,c,u)=>e.add("TP-WEB-004","The caller is the verified ctx that serveBackend hands you; cookies are not how plugins authenticate.",h(d,u)))),i.part!=="other"){eachMatch(i,"TP-PKG-003",/(?:from\s+|import\s*\(\s*|require\s*\(\s*)['"](?:@tego\/shared(?:\/|['"])|@tego\/chat(?!\/plugin-surface)(?:\/|['"]))/,(c,u,g)=>e.add("TP-PKG-003","Plugins only import published packages (@tego/plugin-sdk, @tego/ui, @tego/chat/plugin-surface, \u2026); @tego/shared and host app code do not exist outside the platform.",h(c,g)));const d=ignoredLines(i.text);i.code.split(`
|
|
14
|
+
`).forEach((c,u)=>{if(!d.get(u)?.has("TP-PKG-003"))for(const g of c.matchAll(/(?:from\s+|import\s*\(\s*|require\s*\(\s*)['"](\.\.?\/[^'"]*)['"]/g)){const w=path.posix.normalize(path.posix.join(path.posix.dirname(i.path),g[1]));w.startsWith("../")&&e.add("TP-PKG-003",`"${g[1]}" resolves outside the plugin folder (${w}); a packed plugin only has its own files and published packages.`,h(u+1,c.trim()))}});for(const c of VERSIONED_CAPABILITIES){if(!o(c.since))continue;const u=c.name.split(".").pop();new RegExp(`\\.${u}\\s*(?:\\)|\\?(?!\\.)|&&)`).test(i.code)||eachMatch(i,"TP-MAN-002",c.pattern,(g,w,k)=>e.add("TP-MAN-002",`${c.name} needs host API ${c.since}, but hostApiRange is "${t.hostApiRange}"; raise it to ^${c.since}.`,h(g,k)))}}i.part!=="other"&&eachMatch(i,"TP-BE-002",/\b(?:roles|roleNames)\s*\??\.\s*(?:includes|indexOf)\(\s*['"`](?:admin|owner|super_?admin|tenant_?admin|member|manager)['"`]/i,(d,c,u)=>e.add("TP-BE-002","Do not decide by host role names; declare permissions in the access catalog (hostRoleBindings) and ask the engine (authorize / useAccess).",h(d,u)))}const p=n.filter(i=>i.part==="frontend"&&!i.test);if(s.frontend&&p.length>0&&(p.some(i=>/getDerivedStateFromError|componentDidCatch|PluginErrorBoundary/.test(i.code))||e.add("TP-UI-004","No error boundary in the frontend: the shell's boundary cannot see into your React root, so a render error leaves a blank page. Wrap the root in PluginErrorBoundary (@tego/plugin-sdk/react) or your own.",{file:s.frontend.dir}),r?.includes("tegoPluginTailwind"))){const i=new RegExp(`(?:className|class)=\\{?\\s*(?:cn\\()?\\s*['"\`][^'"\`]*(?<![\\w-])${t.id.replace(/[.]/g,"\\.")}(?![\\w-])`);p.some(h=>i.test(h.code))||e.add("TP-UI-003",`tegoPluginTailwind scopes every utility under .${t.id}, but no element carries className="${t.id}": none of the plugin's own classes will apply. Put it on the root element (and on portalled content, or use tegoPluginScopeRoot).`,{file:s.frontend.dir})}}f(checkSources,"checkSources");function checkAccessAndTests(e,t,s,n,l=()=>!0){const r=n.filter(o=>o.test);for(const o of r)!l(o)||!/\.(?:test|spec)\.[cm]?[jt]sx?$/.test(o.path)||/Business Cases/.test(o.text)||e.add("TP-TEST-002",`${o.path} has no Business Cases list: start it with the cases it covers (one line each, an id per case) and begin each it() title with its id.`,{file:o.path,line:1,evidence:"no business cases"});if(s.backend){const o=n.filter(p=>p.part==="backend"&&!p.test);o.some(p=>/\bnew\s+Hono\b/.test(p.code))&&!o.some(p=>/\.onError\s*\(/.test(p.code))&&e.add("TP-BE-007","The backend app has no error handler (app.onError(pluginErrorHandler())): an uncaught error becomes Hono's plain-text 500 instead of JSON the frontend can show.",{file:s.backend.dir})}if(r.length===0&&e.add("TP-TEST-001","The plugin has no *.test.ts files; backend routes and pure frontend logic need tests (createTestPluginHost for SQL)."),!t.access||!s.backend)return;r.some(o=>o.text.includes("assertRouteCoverage"))||e.add("TP-ACC-001","The manifest declares an access catalog but no test calls assertRouteCoverage(app): a route without a policy would be open to everyone who can open the plugin.",{file:s.backend.dir}),n.filter(o=>(o.part==="backend"||o.part==="shared")&&!o.test).some(o=>/\breconcile\s*\(/.test(o.code))||e.add("TP-ACC-002","The manifest declares an access catalog but start() never calls reconcile(host): built-in roles and catalog changes never reach the database.",{file:s.backend.dir})}f(checkAccessAndTests,"checkAccessAndTests");async function runDoctor(e){const t=e.root;await promises.stat(path.join(t,"tego.plugin.json")).catch(()=>{throw new Error(`No tego.plugin.json in ${t}; run doctor in the plugin folder.`)});const s=new Findings;let n;try{n=JSON.parse(await promises.readFile(path.join(t,"tego.plugin.json"),"utf8"))}catch(c){return s.add("TP-MAN-001",`tego.plugin.json is not valid JSON: ${c instanceof Error?c.message:String(c)}`,{file:"tego.plugin.json"}),summarize(s,void 0)}const l=chunkH6GXDFLP_cjs.parseTegoPluginManifest(n);if(!l.ok||!l.manifest){for(const c of l.errors)s.add("TP-MAN-001",c,{file:"tego.plugin.json"});return summarize(s,n.id)}const r=l.manifest,a=chunk2P6UEAPV_cjs.resolvePluginParts(r),o=[];await walk(t,"",o);const p=await Promise.all(o.map(async c=>{const u=await promises.readFile(path.join(t,c),"utf8");return{path:c,part:partOf(c,a),text:u,code:stripComments(u),test:TEST_FILE.test(c)}})),i=e.files?.map(c=>toPosix(path.relative(t,path.join(t,c)))),h=f(c=>!i||i.includes(c.path),"scanned"),d=a.frontend?await promises.readFile(path.join(t,a.frontend.dir,"vite.config.ts"),"utf8").catch(()=>null):null;return checkPackageJson(s,await readJson(path.join(t,"package.json"))),await checkLocales(s,t,r,a,p.filter(h)),await checkMigrations(s,t,a),await checkClaw(s,t,a),checkSources(s,r,a,p,h,d),checkAccessAndTests(s,r,a,p,h),await checkTemplates(s,t,r),summarize(s,r.id)}f(runDoctor,"runDoctor");function summarize(e,t){const s={errors:e.list.filter(r=>r.severity==="error").length,warnings:e.list.filter(r=>r.severity==="warning").length},n=new Map(DOCTOR_RULES.map((r,a)=>[r.id,a])),l=[...e.list].sort((r,a)=>(r.severity===a.severity?0:r.severity==="error"?-1:1)||(n.get(r.rule)??0)-(n.get(a.rule)??0)||(r.file??"").localeCompare(a.file??"")||(r.line??0)-(a.line??0));return{...t?{pluginId:t}:{},findings:l,...s}}f(summarize,"summarize");var UPLOAD_BLOCKING_RULES=["TP-MAN-001","TP-I18N-001","TP-BE-003"];function formatDoctorReport(e){const t=e.findings.map(s=>{const n=s.file?`${s.file}${s.line?`:${s.line}`:""} `:"";return`${s.severity==="error"?"\u2716":"\u26A0"} ${s.rule} ${n}${s.message}`});return t.push(e.findings.length===0?"\u2705 doctor: no problems":`${e.errors} error(s), ${e.warnings} warning(s). Each rule is explained in node_modules/@tego/plugin-sdk/agent/RULES.md.`),t.join(`
|
|
15
15
|
`)}f(formatDoctorReport,"formatDoctorReport"),exports.DEFAULT_CHECK_STEPS=DEFAULT_CHECK_STEPS,exports.DOCTOR_RULES=DOCTOR_RULES,exports.GUIDE_SCOPES=GUIDE_SCOPES,exports.UPLOAD_BLOCKING_RULES=UPLOAD_BLOCKING_RULES,exports.detectPackageManager=detectPackageManager,exports.doctorRule=doctorRule,exports.findColorIssues=findColorIssues,exports.findingFingerprint=findingFingerprint,exports.formatCheckReport=formatCheckReport,exports.formatDoctorReport=formatDoctorReport,exports.parseEslintJson=parseEslintJson,exports.parseTestFailures=parseTestFailures,exports.parseTypeErrors=parseTypeErrors,exports.ruleCatalog=ruleCatalog,exports.runChecks=runChecks,exports.runDoctor=runDoctor;
|
package/dist/doctor.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
var pe=Object.defineProperty;var f=(e,t)=>pe(e,"name",{value:t,configurable:!0});import{parseTegoPluginManifest as ue}from"./chunk-
|
|
1
|
+
var pe=Object.defineProperty;var f=(e,t)=>pe(e,"name",{value:t,configurable:!0});import{parseTegoPluginManifest as ue}from"./chunk-2KXEYXM2.js";import{verifyFrontendLocales as he,flattenLocaleKeys as fe,verifyMigrationNames as me}from"./chunk-2NTAWYCG.js";import{resolvePluginParts as ge}from"./chunk-IVJECPGG.js";import{pluginI18nNamespace as be}from"./chunk-INCA3GFW.js";import{isValidHostApiRange as we,hostApiRangeMinimum as Te}from"./chunk-XOXUS4VI.js";import"./chunk-VMCGDDGP.js";import"./chunk-SNUCO2MK.js";import{spawn as ke,execFile as Pe}from"child_process";import{access as ye,readFile as S,stat as ve,readdir as B}from"fs/promises";import{resolve as L,join as P,dirname as Ee,relative as z,isAbsolute as xe,sep as G,extname as H,posix as O}from"path";import{createHash as $e}from"crypto";var Se="ui-colors-allow",Ae="slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose",Ne="bg|text|border(?:-[trblxyse])?|ring(?:-offset)?|from|via|to|fill|stroke|outline|divide|placeholder|caret|accent|decoration|shadow",Ce=[new RegExp(`(?<![\\w-])(?:${Ne})-(?:(?:${Ae})-(?:50|[1-9]00|950)|white|black)(?:\\/\\d{1,3})?(?![\\w-])`,"g"),/-\[(?:#[0-9a-fA-F]{3,8}|(?:rgba?|hsla?)\(\s*\d+)/g,/(?<![a-zA-Z0-9[-])(?:rgba?|hsla?)\(\s*\d+/g],Ie=/['"`]#[0-9a-fA-F]{3,8}['"`]/g,_e=/(?<![\w&-])#[0-9a-fA-F]{3,8}(?![\w-])/g,q=/(?<![\w-])dark:(?=\S)/g,De=/\.dark(?![\w-])/g;function J(e){return e.replace(/(^|[\s{(,;])\/\*[\s\S]*?\*\//g,(t,s)=>s+t.slice(s.length).replace(/[^\n]/g," ")).replace(/(^|\s)\/\/.*$/gm,(t,s)=>s)}f(J,"stripComments");function Re(e){const t=new Set;return e.forEach((s,n)=>{s.includes(Se)&&(t.add(n),/^\s*(?:\/\/|\/\*|\*|\{\/\*)/.test(s)&&t.add(n+1))}),t}f(Re,"allowedLines");function X(e,t){const s=t===".css",n=Re(e.split(`
|
|
2
2
|
`)),l=J(e).split(`
|
|
3
|
-
`),
|
|
4
|
-
${
|
|
5
|
-
`)){const l=n.trim();if(l.startsWith(">"))continue;const
|
|
6
|
-
`)){const
|
|
3
|
+
`),r=[...Ce,s?_e:Ie],a=s?[q,De]:[q],o=[];return l.forEach((p,i)=>{if(n.has(i))return;const h=[];for(const[d,c]of[["color",r],["dark",a]])for(const u of c)for(const b of p.matchAll(u))h.push({kind:d,match:b[0],at:b.index??0});h.sort((d,c)=>d.kind.localeCompare(c.kind)||d.at-c.at).forEach(({kind:d,match:c})=>o.push({line:i+1,kind:d,match:c}))}),o}f(X,"findColorIssues");function A(e,t,s){return $e("sha1").update(`${e}\0${t??""}\0${s.replace(/\s+/g," ").trim()}`).digest("hex").slice(0,16)}f(A,"findingFingerprint");var m=f((e,t,s,n)=>({id:e,severity:t,guide:s,title:n}),"rule"),I=[m("TP-MAN-001","error","manifest.md","tego.plugin.json is valid"),m("TP-MAN-002","error","host-api-versions.md","hostApiRange admits every host capability the code uses"),m("TP-PKG-001","error","manifest.md","SDK packages come from npm aliases or workspace links, never a plain @tego/* name"),m("TP-PKG-002","warning","testing.md","package.json has the lint, type-check, test and build gates"),m("TP-PKG-003","error","manifest.md","No imports of @tego/shared or host app code; only published packages"),m("TP-CLAW-002","warning","claw.md","Each claw skill's frontmatter name matches its folder"),m("TP-DESK-001","error","desktop-web.md","No eval or new Function (the desktop CSP refuses them)"),m("TP-DESK-002","error","desktop-web.md","No addresses built from location.origin (tauri://localhost on desktop)"),m("TP-DESK-003","error","desktop-web.md","External links open with ctx.openExternal, not window.open"),m("TP-DESK-004","error","desktop-web.md","Files are saved with ctx.download, not <a download> or a.download"),m("TP-DESK-005","warning","desktop-web.md","No streamed responses or EventSource from the frontend (the desktop proxy buffers; use ctx.events)"),m("TP-DESK-006","warning","desktop-web.md","Clipboard writes go through ctx.copyText"),m("TP-WEB-001","error","desktop-web.md","The frontend calls no third-party URL (the web CSP blocks it); call the backend, which declares egress"),m("TP-WEB-002","error","desktop-web.md","No remote or injected scripts; everything ships in the bundle"),m("TP-WEB-003","error","frontend.md","The frontend calls its backend through ctx.api (createApiClient), never a raw fetch of a path"),m("TP-WEB-004","error","desktop-web.md","No cookie-based auth (document.cookie, hono/cookie); ctx.api carries the credentials"),m("TP-UI-001","error","ui.md","Colors come from token classes: no hex, rgb(), palette classes or dark: overrides"),m("TP-UI-002","error","ui.md","No direct @radix-ui/* imports; use @tego/ui"),m("TP-UI-003","error","ui.md","The plugin's root element carries its scope class (.<pluginId>) when it compiles its own Tailwind"),m("TP-UI-004","error","frontend.md","The frontend has its own error boundary"),m("TP-UI-005","warning","ui.md","Interface copy stays within budget: explanations \u2264 24 characters, any string \u2264 40 (zh-CN)"),m("TP-I18N-001","error","i18n.md","All ten locales, with the same keys as zh-CN, and every nav label defined"),m("TP-I18N-002","error","i18n.md","Every locale value keeps the {{placeholders}} of zh-CN and is not empty"),m("TP-I18N-003","error","i18n.md","Every literal t('\u2026') key exists in zh-CN"),m("TP-I18N-004","warning","i18n.md","Current terminology: \u6570\u5B57\u5458\u5DE5 / Employee, never \u6570\u5B57\u5206\u8EAB / digital avatar"),m("TP-I18N-005","error","i18n.md","tegoAccess.* strings are merged by pack; the plugin locales do not copy them"),m("TP-BE-001","error","backend.md","Identity comes from the verified ctx, never from the raw x-tego-plugin-* headers"),m("TP-BE-002","error","access.md","No host role names in business code; ask the access engine"),m("TP-BE-003","error","migrations.md","Migrations are <ordinal>_<name>.sql with unique ordinals"),m("TP-BE-004","error","migrations.md","Committed migrations are never edited or deleted"),m("TP-BE-005","warning","backend.md","Backend errors are logged through host.log, not console"),m("TP-BE-006","error","backend.md","Configuration comes from host.config, never process.env"),m("TP-BE-007","warning","backend.md","The backend app turns every error into JSON with pluginErrorHandler"),m("TP-ACC-001","error","access.md","With an access catalog, a test asserts every route has a policy (assertRouteCoverage)"),m("TP-ACC-002","error","access.md","With an access catalog, start() reconciles it (reconcile(host))"),m("TP-TEST-001","warning","testing.md","The plugin has tests"),m("TP-TEST-002","warning","testing.md","Each test file starts with a Business Cases list"),m("TP-TPL-001","error","agent-templates.md","A template whose content changed raises its version (existing employees only see updates by version)"),m("TP-TPL-002","warning","agent-templates.md","usageGuide.summary is at most 30 characters and quickPrompts lists 3\u20134 prompts")],je=new Map(I.map(e=>[e.id,e]));function V(e){return je.get(e)}f(V,"doctorRule");var Y={"manifest.md":["tego.plugin.json","package.json"],"host-api-versions.md":["tego.plugin.json"],"desktop-web.md":["<frontend>/src/**"],"frontend.md":["<frontend>/src/**"],"ui.md":["<frontend>/src/**/*.tsx","<frontend>/src/**/*.css"],"i18n.md":["<frontend>/i18n/**"],"backend.md":["<backend>/src/**"],"storage.md":["<backend>/src/**/storage*","<backend>/src/**/upload*","<backend>/src/**/files*"],"migrations.md":["<backend>/migrations/**","<backend>/src/**/schema*.ts","<backend>/drizzle.config.ts"],"access.md":["shared/access.json","<backend>/src/access*.ts","<backend>/src/**/access/**"],"claw.md":["<claw>/**"],"testing.md":["**/*.test.ts"],"agent-templates.md":["templates/**"]};function Be(e={}){const t=f(n=>n.replace("<frontend>",e.frontend??"frontend").replace("<backend>",e.backend??"backend").replace("<claw>",e.claw??"claw"),"resolve2"),s=Y;return{catalogVersion:1,rules:I.map(n=>({...n,appliesTo:(s[n.guide]??[]).map(t),enforcedBy:"doctor"})),guides:Object.entries(s).map(([n,l])=>({file:n,appliesTo:l.map(t)}))}}f(Be,"ruleCatalog");var Z=["doctor","lint","type-check","test"],Le=[["pnpm-lock.yaml","pnpm"],["package-lock.json","npm"],["yarn.lock","yarn"],["bun.lockb","bun"],["bun.lock","bun"]],Q=4e3,K=50;async function ee(e){let t=L(e);for(;;){for(const[n,l]of Le)if(await ye(P(t,n)).then(()=>!0,()=>!1))return l;const s=Ee(t);if(s===t)return"npm";t=s}}f(ee,"detectPackageManager");function te(e,t,s,n){return new Promise(l=>{const r=ke(e,t,{cwd:s,signal:n,shell:process.platform==="win32",env:{...process.env,FORCE_COLOR:"0",NO_COLOR:"1",CI:"1"}});let a="";r.stdout.on("data",o=>a+=String(o)),r.stderr.on("data",o=>a+=String(o)),r.on("error",o=>l({code:null,output:`${a}
|
|
4
|
+
${o.message}`})),r.on("close",o=>l({code:o,output:a}))})}f(te,"run");function W(e){return e.replace(/\u001b\[[0-9;]*m/g,"")}f(W,"stripAnsi");function M(e,t){const s=xe(t)?t:L(e,t),n=z(e,s);return(n.startsWith("..")?t:n).split(G).join("/")}f(M,"toRelative");var Oe=/^(.+?)\((\d+),\d+\):\s+error\s+(TS\d+):\s+(.*)$/,Ke=/^(.+?):(\d+):\d+\s+-\s+error\s+(TS\d+):\s+(.*)$/;function se(e,t){const s=[];for(const n of W(e).split(`
|
|
5
|
+
`)){const l=n.trim();if(l.startsWith(">"))continue;const r=Oe.exec(l)??Ke.exec(l);if(!r)continue;const a=M(t,r[1]);if(s.push({source:"type-check",severity:"error",rule:r[3],file:a,line:Number(r[2]),message:r[4],fingerprint:A(r[3],a,r[4])}),s.length>=K)break}return s}f(se,"parseTypeErrors");function ne(e,t){const s=e.indexOf("[{"),n=e.lastIndexOf("}]");if(s<0||n<s)return e.trim().startsWith("[]")?[]:void 0;let l;try{l=JSON.parse(e.slice(s,n+2))}catch{return}const r=[];for(const a of l){const o=M(t,a.filePath);for(const p of a.messages){const i=p.ruleId??"eslint";if(r.push({source:"lint",severity:p.severity>=2?"error":"warning",rule:i,file:o,...p.line?{line:p.line}:{},message:p.message,fingerprint:A(i,o,p.message)}),r.length>=K)return r}}return r}f(ne,"parseEslintJson");function re(e,t){const s=[],n=new Set;for(const l of W(e).split(`
|
|
6
|
+
`)){const r=/^\s*(?:FAIL|×|✗)\s+(\S+\.(?:test|spec)\.[cm]?[jt]sx?)\s*(?:>\s*(.*))?$/.exec(l);if(!r)continue;const a=M(t,r[1]),o=r[2]?.trim().replace(/^$/,"a test failed")??"a test failed",p=`${a}\0${o}`;if(!n.has(p)&&(n.add(p),s.push({source:"test",severity:"error",rule:"test",file:a,message:o,fingerprint:A("test",a,o)}),s.length>=K))break}return s}f(re,"parseTestFailures");function _(e){const t=W(e).trim();return t.length>Q?`\u2026${t.slice(-Q)}`:t}f(_,"tail");async function We(e){const t=L(e.root),s=e.steps??Z,l=JSON.parse(await S(P(t,"package.json"),"utf8").catch(()=>"{}")).scripts??{},r=await ee(t),a=[],o=[];for(const p of s){const i=Date.now(),h=f((b,k)=>{a.push({step:p,status:b,durationMs:Date.now()-i,...k?{detail:k}:{}})},"done");if(e.signal?.aborted)break;if(p==="doctor"){const b=await ce(e.files?{root:t,files:e.files}:{root:t});for(const k of b.findings)o.push({source:"doctor",...k});h(b.errors>0?"failed":"passed");continue}const d=l[p];if(!d){h("skipped",`package.json has no "${p}" script`);continue}if(p==="lint"&&/^\s*eslint\b/.test(d)){const b=e.files?.length?e.files.filter(w=>/\.[cm]?[jt]sx?$/.test(w)):["."];if(b.length===0){h("skipped","no lintable files among the given ones");continue}const y=await te(r,[...r==="npm"?["exec","--","eslint"]:["exec","eslint"],"--format","json","--no-warn-ignored",...b],t,e.signal),g=ne(y.output,t);if(g){o.push(...g),h(g.some(w=>w.severity==="error")?"failed":"passed");continue}if(y.code===0){h("passed");continue}o.push({source:"lint",severity:"error",rule:"lint",message:_(y.output),fingerprint:A("lint",void 0,"lint failed")}),h("failed",_(y.output));continue}const c=await te(r,["run",p],t,e.signal);if(c.code===0){h("passed");continue}const u=p==="type-check"?se(c.output,t):p==="test"?re(c.output,t):[];o.push(...u.length>0?u:[{source:p,severity:"error",rule:p,message:_(c.output),fingerprint:A(p,void 0,`${p} failed`)}]),h("failed",_(c.output))}return{passed:!a.some(p=>p.status==="failed"),steps:a,findings:o}}f(We,"runChecks");function Me(e){const t=e.steps.map(s=>`${s.status==="passed"?"\u2713":s.status==="failed"?"\u2716":"\u2013"} ${s.step}${s.status==="skipped"&&s.detail?` (${s.detail})`:""}`);for(const s of e.findings){const n=s.file?`${s.file}${s.line?`:${s.line}`:""} `:"";t.push(` ${s.severity==="error"?"\u2716":"\u26A0"} [${s.source}] ${s.rule??""} ${n}${s.message.split(`
|
|
7
7
|
`)[0]}`)}return t.push(e.passed?"\u2705 check: passed":"\u2716 check: failed"),t.join(`
|
|
8
|
-
`)}f(Me,"formatCheckReport");var Ue=[{pattern:/\.openExternal\s*\(/,since:"1.37.0",name:"ctx.openExternal"},{pattern:/\.copyText\s*\(/,since:"1.37.0",name:"ctx.copyText"},{pattern:/\b(?:ctx|host|context)\.download\s*\(/,since:"1.37.0",name:"ctx.download"},{pattern:/\b(?:ctx|host|context)\.platform\b/,since:"1.37.0",name:"ctx.platform"},{pattern:/\b(?:ctx|context)\.events\b/,since:"1.3.0",name:"ctx.events"},{pattern:/\bhost\.directory\b/,since:"1.5.0",name:"host.directory"},{pattern:/\b(?:ctx|context)\.ai\b/,since:"1.9.0",name:"ctx.ai"},{pattern:/\.hostUrl\s*\(/,since:"1.12.0",name:"ctx.hostUrl"},{pattern:/\.uploadPath\s*\(/,since:"1.12.0",name:"storage.uploadPath"},{pattern:/\.collapseSidebar\s*\(/,since:"1.14.0",name:"ctx.layout.collapseSidebar"},{pattern:/\.managedPath\s*\(/,since:"1.15.0",name:"storage.managedPath"},{pattern:/\bstorage\.copy\s*\(/,since:"1.19.0",name:"storage.copy"},{pattern:/\b(?:ctx|context)\.office\b/,since:"1.21.0",name:"ctx.office"},{pattern:/\bhost\.tenantId\b/,since:"1.22.0",name:"host.tenantId"},{pattern:/\bproactive\.emit\s*\(/,since:"1.26.0",name:"proactive.emit"},{pattern:/\bmail\.send\s*\(/,since:"1.29.0",name:"mail.send"},{pattern:/\battention\.request\s*\(/,since:"1.34.0",name:"attention.request"}],Fe=new Set([".ts",".tsx",".mts",".cts",".js",".jsx",".mjs",".css"]),ze=new Set(["node_modules","dist","build",".tego-dev",".turbo",".drizzle","__fixtures__","coverage"]),Ge=/\.(?:test|spec)\.[cm]?[jt]sx?$|(?:^|[\\/])test[\\/]|(?:^|[\\/])test-[\w-]+\.[cm]?[jt]s$|-double\.[cm]?[jt]s$|\.d\.ts$/,He=/tego-doctor-ignore\s+([A-Z0-9-]+(?:\s*,\s*[A-Z0-9-]+)*)/,qe=/\{\{\s*([\w.-]+)\s*\}\}/g,Je=["lint","type-check","test","build"];function Xe(e){return e.split(G).join("/")}f(Xe,"toPosix");async function
|
|
9
|
-
`).forEach((s,n)=>{const l=He.exec(s);if(!l?.[1])return;const
|
|
10
|
-
`).forEach((
|
|
11
|
-
`).forEach((v,x)=>{if(!w.get(x)?.has("TP-I18N-003"))for(const E of v.matchAll(/\bt\(\s*['"]([A-Za-z0-9_.-]+)['"]/g)){const $=E[1],N=$.split(".")[0];!$.includes(".")||!k.has(N)||y($)||e.add("TP-I18N-003",`t('${$}') has no entry in zh-CN.json (namespace ${be(s.id)}).`,{file:g.path,line:x+1})}})}}f(Qe,"checkLocales");function U(e){return[...e.replace(/\{\{[^}]*\}\}/g,"X")].length}f(U,"copyLength");var et=/(help|hint|description|desc|subtitle|intro)$/i,tt=/prompt/i,st=24,nt=40;async function
|
|
12
|
-
`).map(y=>y.slice(3).trim()).filter(y=>y&&!y.endsWith("/template.json"));if(u.length===0)continue;const b=await C(t,["show",`HEAD:${l}${
|
|
13
|
-
`)){const[p,i]=
|
|
14
|
-
`).forEach((c,u)=>{if(!d.get(u)?.has("TP-PKG-003"))for(const b of c.matchAll(/(?:from\s+|import\s*\(\s*|require\s*\(\s*)['"](\.\.?\/[^'"]*)['"]/g)){const k=O.normalize(O.join(O.dirname(i.path),b[1]));k.startsWith("../")&&e.add("TP-PKG-003",`"${b[1]}" resolves outside the plugin folder (${k}); a packed plugin only has its own files and published packages.`,h(u+1,c.trim()))}});for(const c of Ue){if(!
|
|
15
|
-
`)}f(dt,"formatDoctorReport");export{Z as DEFAULT_CHECK_STEPS,I as DOCTOR_RULES,Y as GUIDE_SCOPES,lt as UPLOAD_BLOCKING_RULES,ee as detectPackageManager,V as doctorRule,X as findColorIssues,A as findingFingerprint,Me as formatCheckReport,dt as formatDoctorReport,ne as parseEslintJson,
|
|
8
|
+
`)}f(Me,"formatCheckReport");var Ue=[{pattern:/\.openExternal\s*\(/,since:"1.37.0",name:"ctx.openExternal"},{pattern:/\.copyText\s*\(/,since:"1.37.0",name:"ctx.copyText"},{pattern:/\b(?:ctx|host|context)\.download\s*\(/,since:"1.37.0",name:"ctx.download"},{pattern:/\b(?:ctx|host|context)\.platform\b/,since:"1.37.0",name:"ctx.platform"},{pattern:/\b(?:ctx|context)\.events\b/,since:"1.3.0",name:"ctx.events"},{pattern:/\bhost\.directory\b/,since:"1.5.0",name:"host.directory"},{pattern:/\b(?:ctx|context)\.ai\b/,since:"1.9.0",name:"ctx.ai"},{pattern:/\.hostUrl\s*\(/,since:"1.12.0",name:"ctx.hostUrl"},{pattern:/\.uploadPath\s*\(/,since:"1.12.0",name:"storage.uploadPath"},{pattern:/\.collapseSidebar\s*\(/,since:"1.14.0",name:"ctx.layout.collapseSidebar"},{pattern:/\.managedPath\s*\(/,since:"1.15.0",name:"storage.managedPath"},{pattern:/\bstorage\.copy\s*\(/,since:"1.19.0",name:"storage.copy"},{pattern:/\b(?:ctx|context)\.office\b/,since:"1.21.0",name:"ctx.office"},{pattern:/\bhost\.tenantId\b/,since:"1.22.0",name:"host.tenantId"},{pattern:/\bproactive\.emit\s*\(/,since:"1.26.0",name:"proactive.emit"},{pattern:/\bmail\.send\s*\(/,since:"1.29.0",name:"mail.send"},{pattern:/\battention\.request\s*\(/,since:"1.34.0",name:"attention.request"},{pattern:/\bDecisionCardSurface\b/,since:"1.38.0",name:"DecisionCardSurface"},{pattern:/\bsources\.latestUserMessage\s*\(/,since:"1.39.0",name:"sources.latestUserMessage"}],Fe=new Set([".ts",".tsx",".mts",".cts",".js",".jsx",".mjs",".css"]),ze=new Set(["node_modules","dist","build",".tego-dev",".turbo",".drizzle","__fixtures__","coverage"]),Ge=/\.(?:test|spec)\.[cm]?[jt]sx?$|(?:^|[\\/])test[\\/]|(?:^|[\\/])test-[\w-]+\.[cm]?[jt]s$|-double\.[cm]?[jt]s$|\.d\.ts$/,He=/tego-doctor-ignore\s+([A-Z0-9-]+(?:\s*,\s*[A-Z0-9-]+)*)/,qe=/\{\{\s*([\w.-]+)\s*\}\}/g,Je=["lint","type-check","test","build"];function Xe(e){return e.split(G).join("/")}f(Xe,"toPosix");async function oe(e,t,s){let n;try{n=await B(P(e,t),{withFileTypes:!0})}catch{return}for(const l of n){const r=t?`${t}/${l.name}`:l.name;l.isDirectory()?!ze.has(l.name)&&!l.name.startsWith(".")&&await oe(e,r,s):Fe.has(H(l.name))&&s.push(r)}}f(oe,"walk");function ae(e){return{async list(t){try{return(await B(P(e,t),{withFileTypes:!0})).filter(n=>n.isFile()).map(n=>n.name)}catch{return[]}},readText:f(t=>S(P(e,t),"utf8").catch(()=>null),"readText")}}f(ae,"folderReader");function C(e,t){return new Promise(s=>{Pe("git",["-C",e,...t],{maxBuffer:8*1024*1024},(n,l)=>s(n?null:l))})}f(C,"runGit");function D(e){const t=new Map;return e.split(`
|
|
9
|
+
`).forEach((s,n)=>{const l=He.exec(s);if(!l?.[1])return;const r=new Set(l[1].split(",").map(o=>o.trim())),a=f(o=>t.set(o,new Set([...t.get(o)??[],...r])),"add");a(n),/^\s*(?:\/\/|\/\*|\*|\{\/\*)/.test(s)&&a(n+1)}),t}f(D,"ignoredLines");var Ve=class{static{f(this,"Findings")}list=[];add(e,t,s={}){const{severity:n,guide:l}=V(e),{evidence:r,...a}=s;this.list.push({rule:e,severity:n,message:t,guide:l,...a,fingerprint:A(e,a.file,r??t)})}};function T(e,t,s,n){const l=D(e.text);e.code.split(`
|
|
10
|
+
`).forEach((r,a)=>{if(l.get(a)?.has(t))return;const o=s.exec(r);o&&n(a+1,o[0],r.trim())})}f(T,"eachMatch");function Ye(e,t){const s=f(n=>!!n&&(e===n||e.startsWith(`${n}/`)),"inside");return s(t.frontend?.dir)||s(t.publicFrontend?.dir)?"frontend":s(t.backend?.dir)?"backend":s(t.claw?.dir)?"claw":s("shared")?"shared":"other"}f(Ye,"partOf");async function ie(e){try{return JSON.parse(await S(e,"utf8"))}catch{return}}f(ie,"readJson");function Ze(e,t){if(!t)return;for(const l of["dependencies","devDependencies","peerDependencies"]){const r=t[l];if(!(!r||typeof r!="object"))for(const[a,o]of Object.entries(r))!a.startsWith("@tego/")||typeof o!="string"||/^(?:npm:@zhama\/tego-|workspace:|file:|link:|portal:)/.test(o)||e.add("TP-PKG-001",`${l}.${a} is "${o}": on npm @tego/* belongs to another project. Install it as "npm:@zhama/tego-${a.slice(6)}@^<sdk version>".`,{file:"package.json"})}const s=t.scripts??{},n=Je.filter(l=>typeof s[l]!="string");n.length>0&&e.add("TP-PKG-002",`package.json has no ${n.map(l=>`"${l}"`).join(", ")} script; the platform's gates are lint, type-check, test and build.`,{file:"package.json"})}f(Ze,"checkPackageJson");async function Qe(e,t,s,n,l){const r=n.frontend;if(!r)return;const{bundles:a,errors:o}=await he(ae(t),r);for(const g of o)e.add("TP-I18N-001",g,{file:r.i18nDirPath});const p=a.get("zh-CN");if(!p)return;const i=f((g,w="",v=new Map)=>{if(typeof g!="object"||g===null||Array.isArray(g))return v.set(w,g),v;for(const[x,E]of Object.entries(g))i(E,w?`${w}.${x}`:x,v);return v},"leaves"),h=f(g=>new Set(typeof g=="string"?[...g.matchAll(qe)].map(w=>w[1]):[]),"placeholders"),d=i(p),c=`${r.i18nDirPath}/zh-CN.json`;[...d.keys()].some(g=>g==="tegoAccess"||g.startsWith("tegoAccess."))&&e.add("TP-I18N-005","zh-CN.json contains tegoAccess.* strings; pack merges the access UI strings, remove the copy from every locale.",{file:c,evidence:"tegoAccess"});let u=0;for(const[g,w]of d){if(typeof w!="string"||u>=10)continue;const v=g.slice(g.lastIndexOf(".")+1);if(tt.test(v))continue;const x=et.test(v)?st:nt,E=U(w);E>x&&(u++,e.add("TP-UI-005",`"${g}" is ${E} characters (budget ${x}); shorten it, or move the explanation into an InfoTip.`,{file:c,evidence:g}))}for(const[g,w]of a){const v=`${r.i18nDirPath}/${g}.json`;let x=0;for(const[E,$]of i(w)){if(x>=10)break;if(typeof $=="string"&&$.trim()===""){e.add("TP-I18N-002",`"${E}" is empty in ${g}.json.`,{file:v}),x++;continue}if(g==="zh-CN"||!d.has(E))continue;const N=h(d.get(E)),R=h($),le=[...N].filter(j=>!R.has(j)),de=[...R].filter(j=>!N.has(j));(le.length||de.length)&&(e.add("TP-I18N-002",`"${E}" in ${g}.json has placeholders {${[...R].join(", ")}} but zh-CN has {${[...N].join(", ")}}.`,{file:v}),x++)}for(const[E,$]of i(w))if(typeof $=="string"&&/数字分身|digital avatar/i.test($)){e.add("TP-I18N-004",`"${E}" in ${g}.json says "${/数字分身/.test($)?"\u6570\u5B57\u5206\u8EAB":"digital avatar"}"; the platform calls them \u6570\u5B57\u5458\u5DE5 / Employee ({{avatarTerm}}).`,{file:v});break}}const b=fe(p),k=new Set(Object.keys(p)),y=f(g=>b.has(g)||["_one","_other","_zero","_few","_many","_two"].some(w=>b.has(g+w))||[...b].some(w=>w.startsWith(`${g}.`)),"exists");for(const g of l){if(g.part!=="frontend"||g.test)continue;const w=D(g.text);g.code.split(`
|
|
11
|
+
`).forEach((v,x)=>{if(!w.get(x)?.has("TP-I18N-003"))for(const E of v.matchAll(/\bt\(\s*['"]([A-Za-z0-9_.-]+)['"]/g)){const $=E[1],N=$.split(".")[0];!$.includes(".")||!k.has(N)||y($)||e.add("TP-I18N-003",`t('${$}') has no entry in zh-CN.json (namespace ${be(s.id)}).`,{file:g.path,line:x+1})}})}}f(Qe,"checkLocales");function U(e){return[...e.replace(/\{\{[^}]*\}\}/g,"X")].length}f(U,"copyLength");var et=/(help|hint|description|desc|subtitle|intro)$/i,tt=/prompt/i,st=24,nt=40;async function rt(e,t,s){const n=s.agentTemplates??[];if(n.length===0)return;const l=(await C(t,["rev-parse","--show-prefix"]))?.trim()??"";for(const r of n){const a=r.entry.split("/").slice(0,-1).join("/"),o=`${a}/template.config.json`,p=await ie(P(t,o));if(!p)continue;const i=p.content??{},h=i.usageGuide;typeof h?.summary=="string"&&U(h.summary)>30&&e.add("TP-TPL-002",`${r.key}: usageGuide.summary is ${U(h.summary)} characters; the landing page shows one line, keep it within 30.`,{file:o,evidence:"summary"});const d=i.quickPrompts;Array.isArray(d)&&(d.length<3||d.length>4)&&e.add("TP-TPL-002",`${r.key}: quickPrompts has ${d.length} prompt(s); offer 3\u20134 ready-to-click prompts.`,{file:o,evidence:"quickPrompts"});const u=(await C(t,["status","--porcelain","--untracked-files=all","--",a])??"").split(`
|
|
12
|
+
`).map(y=>y.slice(3).trim()).filter(y=>y&&!y.endsWith("/template.json"));if(u.length===0)continue;const b=await C(t,["show",`HEAD:${l}${o}`]);if(!b)continue;let k;try{k=JSON.parse(b).version}catch{continue}k!==void 0&&k===p.version&&e.add("TP-TPL-001",`${r.key} changed (${u.length} file(s)) but its version is still ${String(p.version)}; raise version in ${o} so existing employees see the update.`,{file:o,evidence:"version"})}}f(rt,"checkTemplates");async function ot(e,t,s){const n=s.backend;if(!n)return;const{errors:l}=await me(ae(t),n.migrationsDirPath);for(const o of l)e.add("TP-BE-003",o,{file:n.migrationsDirPath});const r=await C(t,["diff","--name-status","HEAD","--",n.migrationsDirPath]);if(!r)return;const a=(await C(t,["rev-parse","--show-prefix"]))?.trim()??"";for(const o of r.split(`
|
|
13
|
+
`)){const[p,i]=o.split(" ");if(!i||p!=="M"&&p!=="D")continue;const h=i.startsWith(a)?i.slice(a.length):i;e.add("TP-BE-004",`${h} is a committed migration and was ${p==="M"?"edited":"deleted"}; an install may have applied it already. Revert it and add a new migration instead.`,{file:h})}}f(ot,"checkMigrations");async function at(e,t,s){const n=s.claw;if(!n)return;const l=P(t,n.dir,"skills");let r=[];try{r=(await B(l,{withFileTypes:!0})).filter(a=>a.isDirectory()).map(a=>a.name)}catch{return}for(const a of r){const o=await S(P(l,a,"SKILL.md"),"utf8").catch(()=>null),p=`${n.dir}/skills/${a}/SKILL.md`,i=o?/^---[\s\S]*?^name:\s*["']?([^"'\n]+)["']?\s*$/m.exec(o)?.[1]?.trim():void 0;i!==a&&e.add("TP-CLAW-002",o?`${p} declares name "${i??"(none)"}"; it must equal the folder name "${a}".`:`${n.dir}/skills/${a} has no SKILL.md.`,{file:p})}}f(at,"checkClaw");function it(e,t,s,n,l,r){const a=t.hostApiRange&&we(t.hostApiRange)?Te(t.hostApiRange):void 0,o=f(i=>{if(!a)return!1;const[h,d,c]=a.split(".").map(Number),[u,b,k]=i.split(".").map(Number);return h!==u?h<u:d!==b?d<b:c<k},"below");for(const i of n){if(!l(i)||i.test)continue;const h=f((d,c)=>({file:i.path,line:d,evidence:c}),"at");if(i.part==="frontend"){if(i.path.endsWith(".css")||i.path.endsWith(".tsx")||i.path.endsWith(".ts")){const d=D(i.text);for(const c of X(i.text,H(i.path)))d.get(c.line-1)?.has("TP-UI-001")||e.add("TP-UI-001",c.kind==="dark"?`"${c.match}" is a hand-written dark override; tokens already carry both themes.`:`"${c.match}" is a literal color; use a token class (bg-card, text-muted-foreground, bg-primary\u2026).`,h(c.line,c.match))}T(i,"TP-DESK-001",/\beval\s*\(|\bnew\s+Function\s*\(/,(d,c,u)=>e.add("TP-DESK-001","eval / new Function: the desktop app's CSP (script-src 'self') refuses code compiled from strings.",h(d,u))),T(i,"TP-DESK-002",/\b(?:window\.|document\.)?location\.origin\b/,(d,c,u)=>e.add("TP-DESK-002","location.origin is tauri://localhost in the desktop app; build host addresses with ctx.hostUrl(path) and share links from ctx.publicPagesUrl.",h(d,u))),T(i,"TP-DESK-003",/\bwindow\.open\s*\(/,(d,c,u)=>e.add("TP-DESK-003","window.open does not reliably open links in the desktop app; use ctx.openExternal(url) (hostApiRange ^1.37.0), or ctx.download for a file.",h(d,u))),T(i,"TP-DESK-004",/\.download\s*=(?!=)|<a\b[^>]*\sdownload(?:[\s=>]|$)/,(d,c,u)=>e.add("TP-DESK-004","An <a download> save is not handled by the desktop app; use ctx.download({ path | blob, fileName }) (hostApiRange ^1.37.0).",h(d,u))),T(i,"TP-DESK-005",/\bnew\s+EventSource\s*\(|\.body\s*(?:\?\.|!\.|\.)\s*getReader\s*\(/,(d,c,u)=>e.add("TP-DESK-005","The desktop app buffers ctx.api responses whole (and caps them at 100 MB), so streams arrive at the end; subscribe to ctx.events for pushed updates.",h(d,u))),T(i,"TP-DESK-006",/\bnavigator\.clipboard\./,(d,c,u)=>e.add("TP-DESK-006","navigator.clipboard is not available in every shell; use ctx.copyText(text) (hostApiRange ^1.37.0).",h(d,u))),T(i,"TP-WEB-001",/\bfetch\s*\(\s*['"`]https?:\/\//,(d,c,u)=>e.add("TP-WEB-001","The web app's CSP allows connections to the platform only; call your backend through ctx.api and let it reach the service (declare it in parts.backend.egress).",h(d,u))),T(i,"TP-WEB-002",/\bimport\s*\(\s*['"`]https?:|createElement\(\s*['"]script['"]\s*\)|<script\b[^>]*\bsrc=/,(d,c,u)=>e.add("TP-WEB-002","Remote or injected scripts are refused by both shells' CSP; bundle the code.",h(d,u))),T(i,"TP-UI-002",/from\s+['"]@radix-ui\//,(d,c,u)=>e.add("TP-UI-002","Import the @tego/ui wrapper instead of @radix-ui/* (the shell shares @tego/ui; a second Radix copy breaks focus and portals).",h(d,u))),T(i,"TP-WEB-003",/\bfetch\s*\(\s*['"`](?!https?:|data:|blob:)/,(d,c,u)=>e.add("TP-WEB-003","Call your backend through ctx.api (createApiClient from @tego/plugin-sdk/react): a raw fetch misses the base path and the credentials in the desktop app, and loses the error body.",h(d,u))),T(i,"TP-WEB-004",/\bdocument\.cookie\b/,(d,c,u)=>e.add("TP-WEB-004","Cookies do not carry the session in the desktop app; ctx.api sends the credentials in both shells.",h(d,u)))}if(i.part==="backend"&&(T(i,"TP-BE-001",/\bheaders?\s*(?:\.get)?\(\s*['"]x-tego-plugin-(?:user-id|tenant-id|roles|avatar-id|delegation)['"]|\bheaders\s*\[\s*['"]x-tego-plugin-(?:user-id|tenant-id|roles|avatar-id|delegation)['"]/i,(d,c,u)=>e.add("TP-BE-001","Take the caller from the verified ctx (ctx.userId, ctx.tenantId, ctx.roles) that serveBackend hands you; the raw x-tego-plugin-* headers are unverified here.",h(d,u))),T(i,"TP-BE-005",/\bconsole\.(?:error|warn)\s*\(/,(d,c,u)=>e.add("TP-BE-005","Log through host.log so the message reaches the platform's logs; console output from a plugin container is not collected.",h(d,u))),T(i,"TP-BE-006",/\bprocess\.env\b(?!\.NODE_ENV\b|\[\s*['"]NODE_ENV['"]\s*\])/,(d,c,u)=>e.add("TP-BE-006","Read configuration from host.config.current() (declared in the manifest's config schema); the platform does not pass a plugin its own environment variables.",h(d,u))),T(i,"TP-WEB-004",/from\s+['"]hono\/cookie['"]/,(d,c,u)=>e.add("TP-WEB-004","The caller is the verified ctx that serveBackend hands you; cookies are not how plugins authenticate.",h(d,u)))),i.part!=="other"){T(i,"TP-PKG-003",/(?:from\s+|import\s*\(\s*|require\s*\(\s*)['"](?:@tego\/shared(?:\/|['"])|@tego\/chat(?!\/plugin-surface)(?:\/|['"]))/,(c,u,b)=>e.add("TP-PKG-003","Plugins only import published packages (@tego/plugin-sdk, @tego/ui, @tego/chat/plugin-surface, \u2026); @tego/shared and host app code do not exist outside the platform.",h(c,b)));const d=D(i.text);i.code.split(`
|
|
14
|
+
`).forEach((c,u)=>{if(!d.get(u)?.has("TP-PKG-003"))for(const b of c.matchAll(/(?:from\s+|import\s*\(\s*|require\s*\(\s*)['"](\.\.?\/[^'"]*)['"]/g)){const k=O.normalize(O.join(O.dirname(i.path),b[1]));k.startsWith("../")&&e.add("TP-PKG-003",`"${b[1]}" resolves outside the plugin folder (${k}); a packed plugin only has its own files and published packages.`,h(u+1,c.trim()))}});for(const c of Ue){if(!o(c.since))continue;const u=c.name.split(".").pop();new RegExp(`\\.${u}\\s*(?:\\)|\\?(?!\\.)|&&)`).test(i.code)||T(i,"TP-MAN-002",c.pattern,(b,k,y)=>e.add("TP-MAN-002",`${c.name} needs host API ${c.since}, but hostApiRange is "${t.hostApiRange}"; raise it to ^${c.since}.`,h(b,y)))}}i.part!=="other"&&T(i,"TP-BE-002",/\b(?:roles|roleNames)\s*\??\.\s*(?:includes|indexOf)\(\s*['"`](?:admin|owner|super_?admin|tenant_?admin|member|manager)['"`]/i,(d,c,u)=>e.add("TP-BE-002","Do not decide by host role names; declare permissions in the access catalog (hostRoleBindings) and ask the engine (authorize / useAccess).",h(d,u)))}const p=n.filter(i=>i.part==="frontend"&&!i.test);if(s.frontend&&p.length>0&&(p.some(i=>/getDerivedStateFromError|componentDidCatch|PluginErrorBoundary/.test(i.code))||e.add("TP-UI-004","No error boundary in the frontend: the shell's boundary cannot see into your React root, so a render error leaves a blank page. Wrap the root in PluginErrorBoundary (@tego/plugin-sdk/react) or your own.",{file:s.frontend.dir}),r?.includes("tegoPluginTailwind"))){const i=new RegExp(`(?:className|class)=\\{?\\s*(?:cn\\()?\\s*['"\`][^'"\`]*(?<![\\w-])${t.id.replace(/[.]/g,"\\.")}(?![\\w-])`);p.some(h=>i.test(h.code))||e.add("TP-UI-003",`tegoPluginTailwind scopes every utility under .${t.id}, but no element carries className="${t.id}": none of the plugin's own classes will apply. Put it on the root element (and on portalled content, or use tegoPluginScopeRoot).`,{file:s.frontend.dir})}}f(it,"checkSources");function ct(e,t,s,n,l=()=>!0){const r=n.filter(o=>o.test);for(const o of r)!l(o)||!/\.(?:test|spec)\.[cm]?[jt]sx?$/.test(o.path)||/Business Cases/.test(o.text)||e.add("TP-TEST-002",`${o.path} has no Business Cases list: start it with the cases it covers (one line each, an id per case) and begin each it() title with its id.`,{file:o.path,line:1,evidence:"no business cases"});if(s.backend){const o=n.filter(p=>p.part==="backend"&&!p.test);o.some(p=>/\bnew\s+Hono\b/.test(p.code))&&!o.some(p=>/\.onError\s*\(/.test(p.code))&&e.add("TP-BE-007","The backend app has no error handler (app.onError(pluginErrorHandler())): an uncaught error becomes Hono's plain-text 500 instead of JSON the frontend can show.",{file:s.backend.dir})}if(r.length===0&&e.add("TP-TEST-001","The plugin has no *.test.ts files; backend routes and pure frontend logic need tests (createTestPluginHost for SQL)."),!t.access||!s.backend)return;r.some(o=>o.text.includes("assertRouteCoverage"))||e.add("TP-ACC-001","The manifest declares an access catalog but no test calls assertRouteCoverage(app): a route without a policy would be open to everyone who can open the plugin.",{file:s.backend.dir}),n.filter(o=>(o.part==="backend"||o.part==="shared")&&!o.test).some(o=>/\breconcile\s*\(/.test(o.code))||e.add("TP-ACC-002","The manifest declares an access catalog but start() never calls reconcile(host): built-in roles and catalog changes never reach the database.",{file:s.backend.dir})}f(ct,"checkAccessAndTests");async function ce(e){const t=e.root;await ve(P(t,"tego.plugin.json")).catch(()=>{throw new Error(`No tego.plugin.json in ${t}; run doctor in the plugin folder.`)});const s=new Ve;let n;try{n=JSON.parse(await S(P(t,"tego.plugin.json"),"utf8"))}catch(c){return s.add("TP-MAN-001",`tego.plugin.json is not valid JSON: ${c instanceof Error?c.message:String(c)}`,{file:"tego.plugin.json"}),F(s,void 0)}const l=ue(n);if(!l.ok||!l.manifest){for(const c of l.errors)s.add("TP-MAN-001",c,{file:"tego.plugin.json"});return F(s,n.id)}const r=l.manifest,a=ge(r),o=[];await oe(t,"",o);const p=await Promise.all(o.map(async c=>{const u=await S(P(t,c),"utf8");return{path:c,part:Ye(c,a),text:u,code:J(u),test:Ge.test(c)}})),i=e.files?.map(c=>Xe(z(t,P(t,c)))),h=f(c=>!i||i.includes(c.path),"scanned"),d=a.frontend?await S(P(t,a.frontend.dir,"vite.config.ts"),"utf8").catch(()=>null):null;return Ze(s,await ie(P(t,"package.json"))),await Qe(s,t,r,a,p.filter(h)),await ot(s,t,a),await at(s,t,a),it(s,r,a,p,h,d),ct(s,r,a,p,h),await rt(s,t,r),F(s,r.id)}f(ce,"runDoctor");function F(e,t){const s={errors:e.list.filter(r=>r.severity==="error").length,warnings:e.list.filter(r=>r.severity==="warning").length},n=new Map(I.map((r,a)=>[r.id,a])),l=[...e.list].sort((r,a)=>(r.severity===a.severity?0:r.severity==="error"?-1:1)||(n.get(r.rule)??0)-(n.get(a.rule)??0)||(r.file??"").localeCompare(a.file??"")||(r.line??0)-(a.line??0));return{...t?{pluginId:t}:{},findings:l,...s}}f(F,"summarize");var lt=["TP-MAN-001","TP-I18N-001","TP-BE-003"];function dt(e){const t=e.findings.map(s=>{const n=s.file?`${s.file}${s.line?`:${s.line}`:""} `:"";return`${s.severity==="error"?"\u2716":"\u26A0"} ${s.rule} ${n}${s.message}`});return t.push(e.findings.length===0?"\u2705 doctor: no problems":`${e.errors} error(s), ${e.warnings} warning(s). Each rule is explained in node_modules/@tego/plugin-sdk/agent/RULES.md.`),t.join(`
|
|
15
|
+
`)}f(dt,"formatDoctorReport");export{Z as DEFAULT_CHECK_STEPS,I as DOCTOR_RULES,Y as GUIDE_SCOPES,lt as UPLOAD_BLOCKING_RULES,ee as detectPackageManager,V as doctorRule,X as findColorIssues,A as findingFingerprint,Me as formatCheckReport,dt as formatDoctorReport,ne as parseEslintJson,re as parseTestFailures,se as parseTypeErrors,Be as ruleCatalog,We as runChecks,ce as runDoctor};
|
package/dist/hono.d.cts
CHANGED
|
@@ -2,6 +2,7 @@ import { MiddlewareHandler, NotFoundHandler, Context, ErrorHandler } from 'hono'
|
|
|
2
2
|
import { PluginHostApi } from './server.cjs';
|
|
3
3
|
import 'zod';
|
|
4
4
|
import './frontend.cjs';
|
|
5
|
+
import './decision-card-IF2c4wvM.cjs';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Hono helpers for a plugin backend: refusals with a code the frontend can
|
package/dist/hono.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { MiddlewareHandler, NotFoundHandler, Context, ErrorHandler } from 'hono'
|
|
|
2
2
|
import { PluginHostApi } from './server.js';
|
|
3
3
|
import 'zod';
|
|
4
4
|
import './frontend.js';
|
|
5
|
+
import './decision-card-IF2c4wvM.js';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Hono helpers for a plugin backend: refusals with a code the frontend can
|
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var a=Object.defineProperty;var e=(t,n)=>a(t,"name",{value:n,configurable:!0});var chunkMWMGJJ74_cjs=require("./chunk-MWMGJJ74.cjs"),chunk2P6UEAPV_cjs=require("./chunk-2P6UEAPV.cjs"),chunkN4SXZZPY_cjs=require("./chunk-N4SXZZPY.cjs"),
|
|
1
|
+
"use strict";var a=Object.defineProperty;var e=(t,n)=>a(t,"name",{value:n,configurable:!0});var chunkMWMGJJ74_cjs=require("./chunk-MWMGJJ74.cjs"),chunk2P6UEAPV_cjs=require("./chunk-2P6UEAPV.cjs"),chunkN4SXZZPY_cjs=require("./chunk-N4SXZZPY.cjs"),chunkIOZFHJ4F_cjs=require("./chunk-IOZFHJ4F.cjs"),chunk66LDWN4T_cjs=require("./chunk-66LDWN4T.cjs");function readClawTools(t){if(!t)return[];const n=t.contracts;if(typeof n!="object"||n===null)return[];const u=n.tools;return Array.isArray(u)?u.filter(i=>typeof i=="string"):[]}e(readClawTools,"readClawTools");function auditDeclarationGaps(t,n){const u=Object.keys(t.audit?.tools??{}),i=new Set(n);return{undeclared:n.filter(o=>!t.audit?.tools?.[o]),unknown:u.filter(o=>!i.has(o))}}e(auditDeclarationGaps,"auditDeclarationGaps");function buildInstallDisclosure(t,n=null,u=null){const i=(t.parts.frontend?.nav??[]).map(r=>({key:r.key,labelKey:r.labelKey,permission:chunkN4SXZZPY_cjs.pluginMenuPermission(t.id,r.key),path:chunkN4SXZZPY_cjs.pluginNavPath(t.id,r.path)})),o=chunk2P6UEAPV_cjs.resolvePluginParts(t).backend,s=readClawTools(n);return{requiredPermissions:[...new Set(t.permissions?.required??[])].sort(),egress:o?.egress??[],publicRoutes:(o?.publicRoutes??[]).map(chunk2P6UEAPV_cjs.formatPublicRoute).sort(),...o?{backendRuntime:o.runtime}:{},scope:chunk2P6UEAPV_cjs.resolvePluginScope(t),parts:chunk2P6UEAPV_cjs.declaredPartNames(t),declares:chunk2P6UEAPV_cjs.resolvePluginDeclares(t),nav:i,clawTools:s,auditUndeclaredTools:auditDeclarationGaps(t,s).undeclared,config:{frontend:chunkMWMGJJ74_cjs.summarizeConfigDeclaration(t.parts.frontend?.config),backend:chunkMWMGJJ74_cjs.summarizeConfigDeclaration(t.parts.backend?.config),claw:chunkMWMGJJ74_cjs.summarizeClawConfig(n)},...u?{access:{permissions:Object.values(u.permissions).filter(r=>!r.reserved).length,roles:Object.keys(u.roles).length}}:{}}}e(buildInstallDisclosure,"buildInstallDisclosure");function requiresBackendConfig(t){return t.config.backend.required.length>0}e(requiresBackendConfig,"requiresBackendConfig");function diffInstallDisclosure(t,n){const u=[];for(const r of n.requiredPermissions??[])t?.requiredPermissions?.includes(r)||u.push({kind:"permission",value:r});for(const r of n.egress??[])!t?.egress?.includes(r)&&!t?.egress?.includes("*")&&u.push({kind:"egress",value:r});for(const r of n.publicRoutes??[])t?.publicRoutes?.includes(r)||u.push({kind:"publicRoute",value:r});const i=new Set(t?.parts??[]);for(const r of n.parts)i.has(r)||u.push({kind:"part",value:r});for(const r of["database","storage","events"])n.declares[r]&&(t?.declares[r]||u.push({kind:"declares",value:r}));const o=new Set((t?.nav??[]).map(r=>r.key));for(const r of n.nav)o.has(r.key)||u.push({kind:"nav",value:r.key});const s=new Set(t?.clawTools??[]);for(const r of n.clawTools)s.has(r)||u.push({kind:"clawTool",value:r});for(const r of["frontend","backend","claw"]){const l=new Set(t?.config[r]?.required??[]);for(const c of n.config[r].required)l.has(c)||u.push({kind:"configRequired",value:`${r}.${c}`})}return u}e(diffInstallDisclosure,"diffInstallDisclosure"),Object.defineProperty(exports,"EMPTY_CONFIG_SECTION_SUMMARY",{enumerable:!0,get:e(function(){return chunkMWMGJJ74_cjs.EMPTY_CONFIG_SECTION_SUMMARY},"get")}),Object.defineProperty(exports,"PLUGIN_CONFIG_SECRET_SENTINEL",{enumerable:!0,get:e(function(){return chunkMWMGJJ74_cjs.PLUGIN_CONFIG_SECRET_SENTINEL},"get")}),Object.defineProperty(exports,"PLUGIN_CONFIG_SECTIONS",{enumerable:!0,get:e(function(){return chunkMWMGJJ74_cjs.PLUGIN_CONFIG_SECTIONS},"get")}),Object.defineProperty(exports,"applyConfigDefaults",{enumerable:!0,get:e(function(){return chunkMWMGJJ74_cjs.applyConfigDefaults},"get")}),Object.defineProperty(exports,"configFieldLabel",{enumerable:!0,get:e(function(){return chunkMWMGJJ74_cjs.configFieldLabel},"get")}),Object.defineProperty(exports,"redactSecrets",{enumerable:!0,get:e(function(){return chunkMWMGJJ74_cjs.redactSecrets},"get")}),Object.defineProperty(exports,"restoreSecrets",{enumerable:!0,get:e(function(){return chunkMWMGJJ74_cjs.restoreSecrets},"get")}),Object.defineProperty(exports,"summarizeClawConfig",{enumerable:!0,get:e(function(){return chunkMWMGJJ74_cjs.summarizeClawConfig},"get")}),Object.defineProperty(exports,"summarizeConfigDeclaration",{enumerable:!0,get:e(function(){return chunkMWMGJJ74_cjs.summarizeConfigDeclaration},"get")}),Object.defineProperty(exports,"CLAW_MANIFEST_FILENAME",{enumerable:!0,get:e(function(){return chunk2P6UEAPV_cjs.CLAW_MANIFEST_FILENAME},"get")}),Object.defineProperty(exports,"PLUGIN_AVATAR_PUBLIC_API_PATH",{enumerable:!0,get:e(function(){return chunk2P6UEAPV_cjs.PLUGIN_AVATAR_PUBLIC_API_PATH},"get")}),Object.defineProperty(exports,"PLUGIN_AVATAR_PUBLIC_PAGES_PATH",{enumerable:!0,get:e(function(){return chunk2P6UEAPV_cjs.PLUGIN_AVATAR_PUBLIC_PAGES_PATH},"get")}),Object.defineProperty(exports,"PLUGIN_BACKEND_PLATFORMS",{enumerable:!0,get:e(function(){return chunk2P6UEAPV_cjs.PLUGIN_BACKEND_PLATFORMS},"get")}),Object.defineProperty(exports,"PLUGIN_BACKEND_RUNTIMES",{enumerable:!0,get:e(function(){return chunk2P6UEAPV_cjs.PLUGIN_BACKEND_RUNTIMES},"get")}),Object.defineProperty(exports,"PLUGIN_MANIFEST_FILENAME",{enumerable:!0,get:e(function(){return chunk2P6UEAPV_cjs.PLUGIN_MANIFEST_FILENAME},"get")}),Object.defineProperty(exports,"PLUGIN_NAV_ICONS",{enumerable:!0,get:e(function(){return chunk2P6UEAPV_cjs.PLUGIN_NAV_ICONS},"get")}),Object.defineProperty(exports,"PLUGIN_NAV_ICON_FALLBACK",{enumerable:!0,get:e(function(){return chunk2P6UEAPV_cjs.PLUGIN_NAV_ICON_FALLBACK},"get")}),Object.defineProperty(exports,"PLUGIN_PART_DEFAULTS",{enumerable:!0,get:e(function(){return chunk2P6UEAPV_cjs.PLUGIN_PART_DEFAULTS},"get")}),Object.defineProperty(exports,"PLUGIN_PART_NAMES",{enumerable:!0,get:e(function(){return chunk2P6UEAPV_cjs.PLUGIN_PART_NAMES},"get")}),Object.defineProperty(exports,"PLUGIN_PUBLIC_API_PATH",{enumerable:!0,get:e(function(){return chunk2P6UEAPV_cjs.PLUGIN_PUBLIC_API_PATH},"get")}),Object.defineProperty(exports,"PLUGIN_PUBLIC_PAGES_PATH",{enumerable:!0,get:e(function(){return chunk2P6UEAPV_cjs.PLUGIN_PUBLIC_PAGES_PATH},"get")}),Object.defineProperty(exports,"PLUGIN_PUBLIC_ROUTE_METHODS",{enumerable:!0,get:e(function(){return chunk2P6UEAPV_cjs.PLUGIN_PUBLIC_ROUTE_METHODS},"get")}),Object.defineProperty(exports,"declaredPartNames",{enumerable:!0,get:e(function(){return chunk2P6UEAPV_cjs.declaredPartNames},"get")}),Object.defineProperty(exports,"formatPublicRoute",{enumerable:!0,get:e(function(){return chunk2P6UEAPV_cjs.formatPublicRoute},"get")}),Object.defineProperty(exports,"matchPublicRoute",{enumerable:!0,get:e(function(){return chunk2P6UEAPV_cjs.matchPublicRoute},"get")}),Object.defineProperty(exports,"publicApiBaseFromPagePath",{enumerable:!0,get:e(function(){return chunk2P6UEAPV_cjs.publicApiBaseFromPagePath},"get")}),Object.defineProperty(exports,"resolvePluginDeclares",{enumerable:!0,get:e(function(){return chunk2P6UEAPV_cjs.resolvePluginDeclares},"get")}),Object.defineProperty(exports,"resolvePluginParts",{enumerable:!0,get:e(function(){return chunk2P6UEAPV_cjs.resolvePluginParts},"get")}),Object.defineProperty(exports,"resolvePluginScope",{enumerable:!0,get:e(function(){return chunk2P6UEAPV_cjs.resolvePluginScope},"get")}),Object.defineProperty(exports,"MAX_PLUGIN_MENU_PERMISSION_LENGTH",{enumerable:!0,get:e(function(){return chunkN4SXZZPY_cjs.MAX_PLUGIN_MENU_PERMISSION_LENGTH},"get")}),Object.defineProperty(exports,"PLUGIN_SUPPORTED_LOCALES",{enumerable:!0,get:e(function(){return chunkN4SXZZPY_cjs.PLUGIN_SUPPORTED_LOCALES},"get")}),Object.defineProperty(exports,"localizedTextToString",{enumerable:!0,get:e(function(){return chunkN4SXZZPY_cjs.localizedTextToString},"get")}),Object.defineProperty(exports,"pluginApiPath",{enumerable:!0,get:e(function(){return chunkN4SXZZPY_cjs.pluginApiPath},"get")}),Object.defineProperty(exports,"pluginBasePath",{enumerable:!0,get:e(function(){return chunkN4SXZZPY_cjs.pluginBasePath},"get")}),Object.defineProperty(exports,"pluginI18nKey",{enumerable:!0,get:e(function(){return chunkN4SXZZPY_cjs.pluginI18nKey},"get")}),Object.defineProperty(exports,"pluginI18nNamespace",{enumerable:!0,get:e(function(){return chunkN4SXZZPY_cjs.pluginI18nNamespace},"get")}),Object.defineProperty(exports,"pluginMenuKey",{enumerable:!0,get:e(function(){return chunkN4SXZZPY_cjs.pluginMenuKey},"get")}),Object.defineProperty(exports,"pluginMenuPermission",{enumerable:!0,get:e(function(){return chunkN4SXZZPY_cjs.pluginMenuPermission},"get")}),Object.defineProperty(exports,"pluginNavPath",{enumerable:!0,get:e(function(){return chunkN4SXZZPY_cjs.pluginNavPath},"get")}),Object.defineProperty(exports,"HOST_API_VERSION",{enumerable:!0,get:e(function(){return chunkIOZFHJ4F_cjs.HOST_API_VERSION},"get")}),Object.defineProperty(exports,"SHARED_MODULE_IDS",{enumerable:!0,get:e(function(){return chunkIOZFHJ4F_cjs.SHARED_MODULE_IDS},"get")}),Object.defineProperty(exports,"hostApiRangeMinimum",{enumerable:!0,get:e(function(){return chunkIOZFHJ4F_cjs.hostApiRangeMinimum},"get")}),Object.defineProperty(exports,"isValidHostApiRange",{enumerable:!0,get:e(function(){return chunkIOZFHJ4F_cjs.isValidHostApiRange},"get")}),Object.defineProperty(exports,"satisfiesHostApiRange",{enumerable:!0,get:e(function(){return chunkIOZFHJ4F_cjs.satisfiesHostApiRange},"get")}),Object.defineProperty(exports,"decisionCardPath",{enumerable:!0,get:e(function(){return chunk66LDWN4T_cjs.decisionCardPath},"get")}),exports.auditDeclarationGaps=auditDeclarationGaps,exports.buildInstallDisclosure=buildInstallDisclosure,exports.diffInstallDisclosure=diffInstallDisclosure,exports.requiresBackendConfig=requiresBackendConfig;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { a as PluginPublicRoute, b as PluginBackendRuntime, c as PluginScope, d as PluginPartName, e as PluginDeclares, f as PluginConfigSection, g as PluginConfigSectionSummary, T as TegoPluginManifest, L as LocalizedText, h as PluginNavIcon, P as PluginNavEntry, i as PluginAgentReadinessDeclaration } from './manifest-types-CUTNfJTa.cjs';
|
|
2
2
|
export { C as CLAW_MANIFEST_FILENAME, E as EMPTY_CONFIG_SECTION_SUMMARY, j as PLUGIN_BACKEND_PLATFORMS, k as PLUGIN_BACKEND_RUNTIMES, l as PLUGIN_CONFIG_SECRET_SENTINEL, m as PLUGIN_CONFIG_SECTIONS, n as PLUGIN_MANIFEST_FILENAME, o as PLUGIN_NAV_ICONS, p as PLUGIN_NAV_ICON_FALLBACK, q as PLUGIN_PART_DEFAULTS, r as PLUGIN_PART_NAMES, s as PluginAgentReadinessItem, t as PluginAgentReadinessResponse, u as PluginAuditDeclarations, v as PluginAuditToolDeclaration, w as PluginBackendLimits, x as PluginBackendPart, y as PluginBackendPlatform, z as PluginClawPart, A as PluginConfigDeclaration, B as PluginConfigFieldSchema, D as PluginConfigFieldType, F as PluginConfigSchema, G as PluginConfigUiHint, H as PluginFrontendPart, I as PluginParts, J as PluginPermissions, K as PluginPublicFrontendPart, M as PluginPublicRouteMethod, N as PluginSchemaRepair, O as PluginWorkDelegationDeclaration, R as ResolvedBackendPart, Q as ResolvedClawPart, S as ResolvedFrontendPart, U as ResolvedPluginParts, V as ResolvedPublicFrontendPart, W as applyConfigDefaults, X as configFieldLabel, Y as declaredPartNames, Z as redactSecrets, _ as resolvePluginDeclares, $ as resolvePluginParts, a0 as resolvePluginScope, a1 as restoreSecrets, a2 as summarizeClawConfig, a3 as summarizeConfigDeclaration } from './manifest-types-CUTNfJTa.cjs';
|
|
3
3
|
import { A as AccessCatalog } from './catalog-CSSbrfEf.cjs';
|
|
4
|
+
export { D as DecisionCard, a as DecisionCardBlock, b as DecisionCardButton, c as DecisionCardButtonId, d as DecisionCardChoice, e as DecisionCardCommit, f as DecisionCardField, g as DecisionCardGroup, h as DecisionCardItem, i as DecisionCardItemResult, j as DecisionCardSource, k as DecisionCardState, l as DecisionItemStatus, m as decisionCardPath } from './decision-card-IF2c4wvM.cjs';
|
|
4
5
|
import 'zod';
|
|
5
6
|
|
|
6
7
|
/**
|
|
@@ -277,12 +278,24 @@ declare function diffInstallDisclosure(accepted: PluginInstallDisclosure | null,
|
|
|
277
278
|
* `openExternal`, `download` and `copyText`. `window.open`, `<a download>`
|
|
278
279
|
* and `navigator.clipboard` are unreliable inside the desktop webview.
|
|
279
280
|
*
|
|
281
|
+
* 1.38.0 adds decision cards: a tool result carrying a `DecisionCardBlock` draws a
|
|
282
|
+
* card in the message, which the client reads from and commits to the plugin's
|
|
283
|
+
* `/decision-intents/:id` routes with the person's own token. `DecisionCardSurface`
|
|
284
|
+
* in `@tego/chat/plugin-surface` draws the same card on a plugin's page.
|
|
285
|
+
*
|
|
286
|
+
* 1.39.0 adds `sources.latestUserMessage`, with the `origin` the host now writes on
|
|
287
|
+
* every message a person sends, so a plugin can accept a plain answer as a decision
|
|
288
|
+
* without taking a model's word for who said it.
|
|
289
|
+
*
|
|
290
|
+
* 1.40.0 adds `recipientUserId` to an attention item's status, so a plugin can tell a
|
|
291
|
+
* delegate answering for someone away from a stranger answering.
|
|
292
|
+
*
|
|
280
293
|
* This numbers the *host API surface*, not the `@tego/plugin-sdk` npm package
|
|
281
294
|
* — the two move on their own clocks. A manifest declares compatibility
|
|
282
295
|
* against this constant through `hostApiRange`; the SDK's own package version
|
|
283
296
|
* is a build-time dependency and never reaches a manifest.
|
|
284
297
|
*/
|
|
285
|
-
declare const HOST_API_VERSION = "1.
|
|
298
|
+
declare const HOST_API_VERSION = "1.40.0";
|
|
286
299
|
/**
|
|
287
300
|
* Module specifiers the shell provides to plugin frontends.
|
|
288
301
|
*
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { a as PluginPublicRoute, b as PluginBackendRuntime, c as PluginScope, d as PluginPartName, e as PluginDeclares, f as PluginConfigSection, g as PluginConfigSectionSummary, T as TegoPluginManifest, L as LocalizedText, h as PluginNavIcon, P as PluginNavEntry, i as PluginAgentReadinessDeclaration } from './manifest-types-CUTNfJTa.js';
|
|
2
2
|
export { C as CLAW_MANIFEST_FILENAME, E as EMPTY_CONFIG_SECTION_SUMMARY, j as PLUGIN_BACKEND_PLATFORMS, k as PLUGIN_BACKEND_RUNTIMES, l as PLUGIN_CONFIG_SECRET_SENTINEL, m as PLUGIN_CONFIG_SECTIONS, n as PLUGIN_MANIFEST_FILENAME, o as PLUGIN_NAV_ICONS, p as PLUGIN_NAV_ICON_FALLBACK, q as PLUGIN_PART_DEFAULTS, r as PLUGIN_PART_NAMES, s as PluginAgentReadinessItem, t as PluginAgentReadinessResponse, u as PluginAuditDeclarations, v as PluginAuditToolDeclaration, w as PluginBackendLimits, x as PluginBackendPart, y as PluginBackendPlatform, z as PluginClawPart, A as PluginConfigDeclaration, B as PluginConfigFieldSchema, D as PluginConfigFieldType, F as PluginConfigSchema, G as PluginConfigUiHint, H as PluginFrontendPart, I as PluginParts, J as PluginPermissions, K as PluginPublicFrontendPart, M as PluginPublicRouteMethod, N as PluginSchemaRepair, O as PluginWorkDelegationDeclaration, R as ResolvedBackendPart, Q as ResolvedClawPart, S as ResolvedFrontendPart, U as ResolvedPluginParts, V as ResolvedPublicFrontendPart, W as applyConfigDefaults, X as configFieldLabel, Y as declaredPartNames, Z as redactSecrets, _ as resolvePluginDeclares, $ as resolvePluginParts, a0 as resolvePluginScope, a1 as restoreSecrets, a2 as summarizeClawConfig, a3 as summarizeConfigDeclaration } from './manifest-types-CUTNfJTa.js';
|
|
3
3
|
import { A as AccessCatalog } from './catalog-CSSbrfEf.js';
|
|
4
|
+
export { D as DecisionCard, a as DecisionCardBlock, b as DecisionCardButton, c as DecisionCardButtonId, d as DecisionCardChoice, e as DecisionCardCommit, f as DecisionCardField, g as DecisionCardGroup, h as DecisionCardItem, i as DecisionCardItemResult, j as DecisionCardSource, k as DecisionCardState, l as DecisionItemStatus, m as decisionCardPath } from './decision-card-IF2c4wvM.js';
|
|
4
5
|
import 'zod';
|
|
5
6
|
|
|
6
7
|
/**
|
|
@@ -277,12 +278,24 @@ declare function diffInstallDisclosure(accepted: PluginInstallDisclosure | null,
|
|
|
277
278
|
* `openExternal`, `download` and `copyText`. `window.open`, `<a download>`
|
|
278
279
|
* and `navigator.clipboard` are unreliable inside the desktop webview.
|
|
279
280
|
*
|
|
281
|
+
* 1.38.0 adds decision cards: a tool result carrying a `DecisionCardBlock` draws a
|
|
282
|
+
* card in the message, which the client reads from and commits to the plugin's
|
|
283
|
+
* `/decision-intents/:id` routes with the person's own token. `DecisionCardSurface`
|
|
284
|
+
* in `@tego/chat/plugin-surface` draws the same card on a plugin's page.
|
|
285
|
+
*
|
|
286
|
+
* 1.39.0 adds `sources.latestUserMessage`, with the `origin` the host now writes on
|
|
287
|
+
* every message a person sends, so a plugin can accept a plain answer as a decision
|
|
288
|
+
* without taking a model's word for who said it.
|
|
289
|
+
*
|
|
290
|
+
* 1.40.0 adds `recipientUserId` to an attention item's status, so a plugin can tell a
|
|
291
|
+
* delegate answering for someone away from a stranger answering.
|
|
292
|
+
*
|
|
280
293
|
* This numbers the *host API surface*, not the `@tego/plugin-sdk` npm package
|
|
281
294
|
* — the two move on their own clocks. A manifest declares compatibility
|
|
282
295
|
* against this constant through `hostApiRange`; the SDK's own package version
|
|
283
296
|
* is a build-time dependency and never reaches a manifest.
|
|
284
297
|
*/
|
|
285
|
-
declare const HOST_API_VERSION = "1.
|
|
298
|
+
declare const HOST_API_VERSION = "1.40.0";
|
|
286
299
|
/**
|
|
287
300
|
* Module specifiers the shell provides to plugin frontends.
|
|
288
301
|
*
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var P=Object.defineProperty;var l=(o,s)=>P(o,"name",{value:s,configurable:!0});import{summarizeClawConfig as f,summarizeConfigDeclaration as a}from"./chunk-DEGQDJF6.js";export{EMPTY_CONFIG_SECTION_SUMMARY,PLUGIN_CONFIG_SECRET_SENTINEL,PLUGIN_CONFIG_SECTIONS,applyConfigDefaults,configFieldLabel,redactSecrets,restoreSecrets,summarizeClawConfig,summarizeConfigDeclaration}from"./chunk-DEGQDJF6.js";import{resolvePluginParts as p,resolvePluginDeclares as _,declaredPartNames as I,resolvePluginScope as N,formatPublicRoute as A}from"./chunk-IVJECPGG.js";export{CLAW_MANIFEST_FILENAME,PLUGIN_AVATAR_PUBLIC_API_PATH,PLUGIN_AVATAR_PUBLIC_PAGES_PATH,PLUGIN_BACKEND_PLATFORMS,PLUGIN_BACKEND_RUNTIMES,PLUGIN_MANIFEST_FILENAME,PLUGIN_NAV_ICONS,PLUGIN_NAV_ICON_FALLBACK,PLUGIN_PART_DEFAULTS,PLUGIN_PART_NAMES,PLUGIN_PUBLIC_API_PATH,PLUGIN_PUBLIC_PAGES_PATH,PLUGIN_PUBLIC_ROUTE_METHODS,declaredPartNames,formatPublicRoute,matchPublicRoute,publicApiBaseFromPagePath,resolvePluginDeclares,resolvePluginParts,resolvePluginScope}from"./chunk-IVJECPGG.js";import{pluginNavPath as g,pluginMenuPermission as S}from"./chunk-INCA3GFW.js";export{MAX_PLUGIN_MENU_PERMISSION_LENGTH,PLUGIN_SUPPORTED_LOCALES,localizedTextToString,pluginApiPath,pluginBasePath,pluginI18nKey,pluginI18nNamespace,pluginMenuKey,pluginMenuPermission,pluginNavPath}from"./chunk-INCA3GFW.js";export{HOST_API_VERSION,SHARED_MODULE_IDS,hostApiRangeMinimum,isValidHostApiRange,satisfiesHostApiRange}from"./chunk-XOXUS4VI.js";export{decisionCardPath}from"./chunk-JV2G2Z33.js";function L(o){if(!o)return[];const s=o.contracts;if(typeof s!="object"||s===null)return[];const n=s.tools;return Array.isArray(n)?n.filter(i=>typeof i=="string"):[]}l(L,"readClawTools");function c(o,s){const n=Object.keys(o.audit?.tools??{}),i=new Set(s);return{undeclared:s.filter(r=>!o.audit?.tools?.[r]),unknown:n.filter(r=>!i.has(r))}}l(c,"auditDeclarationGaps");function m(o,s=null,n=null){const i=(o.parts.frontend?.nav??[]).map(e=>({key:e.key,labelKey:e.labelKey,permission:S(o.id,e.key),path:g(o.id,e.path)})),r=p(o).backend,t=L(s);return{requiredPermissions:[...new Set(o.permissions?.required??[])].sort(),egress:r?.egress??[],publicRoutes:(r?.publicRoutes??[]).map(A).sort(),...r?{backendRuntime:r.runtime}:{},scope:N(o),parts:I(o),declares:_(o),nav:i,clawTools:t,auditUndeclaredTools:c(o,t).undeclared,config:{frontend:a(o.parts.frontend?.config),backend:a(o.parts.backend?.config),claw:f(s)},...n?{access:{permissions:Object.values(n.permissions).filter(e=>!e.reserved).length,roles:Object.keys(n.roles).length}}:{}}}l(m,"buildInstallDisclosure");function T(o){return o.config.backend.required.length>0}l(T,"requiresBackendConfig");function E(o,s){const n=[];for(const e of s.requiredPermissions??[])o?.requiredPermissions?.includes(e)||n.push({kind:"permission",value:e});for(const e of s.egress??[])!o?.egress?.includes(e)&&!o?.egress?.includes("*")&&n.push({kind:"egress",value:e});for(const e of s.publicRoutes??[])o?.publicRoutes?.includes(e)||n.push({kind:"publicRoute",value:e});const i=new Set(o?.parts??[]);for(const e of s.parts)i.has(e)||n.push({kind:"part",value:e});for(const e of["database","storage","events"])s.declares[e]&&(o?.declares[e]||n.push({kind:"declares",value:e}));const r=new Set((o?.nav??[]).map(e=>e.key));for(const e of s.nav)r.has(e.key)||n.push({kind:"nav",value:e.key});const t=new Set(o?.clawTools??[]);for(const e of s.clawTools)t.has(e)||n.push({kind:"clawTool",value:e});for(const e of["frontend","backend","claw"]){const d=new Set(o?.config[e]?.required??[]);for(const u of s.config[e].required)d.has(u)||n.push({kind:"configRequired",value:`${e}.${u}`})}return n}l(E,"diffInstallDisclosure");export{c as auditDeclarationGaps,m as buildInstallDisclosure,E as diffInstallDisclosure,T as requiresBackendConfig};
|
package/dist/manifest.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var n=Object.defineProperty;var e=(r,t)=>n(r,"name",{value:t,configurable:!0});var
|
|
1
|
+
"use strict";var n=Object.defineProperty;var e=(r,t)=>n(r,"name",{value:t,configurable:!0});var chunkH6GXDFLP_cjs=require("./chunk-H6GXDFLP.cjs"),chunk45Y4CR4R_cjs=require("./chunk-45Y4CR4R.cjs");require("./chunk-2P6UEAPV.cjs"),require("./chunk-N4SXZZPY.cjs"),require("./chunk-IOZFHJ4F.cjs"),require("./chunk-AJY7ZHXK.cjs"),require("./chunk-OP64Y4IN.cjs"),Object.defineProperty(exports,"PYTHON_LOCK_FILENAME",{enumerable:!0,get:e(function(){return chunkH6GXDFLP_cjs.PYTHON_LOCK_FILENAME},"get")}),Object.defineProperty(exports,"PYTHON_RUNNER_VERSION",{enumerable:!0,get:e(function(){return chunkH6GXDFLP_cjs.PYTHON_RUNNER_VERSION},"get")}),Object.defineProperty(exports,"PYTHON_WHEELS_DIR",{enumerable:!0,get:e(function(){return chunkH6GXDFLP_cjs.PYTHON_WHEELS_DIR},"get")}),Object.defineProperty(exports,"checkPluginAccessCatalog",{enumerable:!0,get:e(function(){return chunkH6GXDFLP_cjs.checkPluginAccessCatalog},"get")}),Object.defineProperty(exports,"checkPythonVendoring",{enumerable:!0,get:e(function(){return chunkH6GXDFLP_cjs.checkPythonVendoring},"get")}),Object.defineProperty(exports,"normalizePackageName",{enumerable:!0,get:e(function(){return chunkH6GXDFLP_cjs.normalizePackageName},"get")}),Object.defineProperty(exports,"parseRequirementsLock",{enumerable:!0,get:e(function(){return chunkH6GXDFLP_cjs.parseRequirementsLock},"get")}),Object.defineProperty(exports,"parseTegoPluginManifest",{enumerable:!0,get:e(function(){return chunkH6GXDFLP_cjs.parseTegoPluginManifest},"get")}),Object.defineProperty(exports,"parseWheelFilename",{enumerable:!0,get:e(function(){return chunkH6GXDFLP_cjs.parseWheelFilename},"get")}),Object.defineProperty(exports,"tegoPluginManifestSchema",{enumerable:!0,get:e(function(){return chunkH6GXDFLP_cjs.tegoPluginManifestSchema},"get")}),Object.defineProperty(exports,"LOCALE_FILENAME_PATTERN",{enumerable:!0,get:e(function(){return chunk45Y4CR4R_cjs.LOCALE_FILENAME_PATTERN},"get")}),Object.defineProperty(exports,"MIGRATION_FILENAME_PATTERN",{enumerable:!0,get:e(function(){return chunk45Y4CR4R_cjs.MIGRATION_FILENAME_PATTERN},"get")}),Object.defineProperty(exports,"flattenLocaleKeys",{enumerable:!0,get:e(function(){return chunk45Y4CR4R_cjs.flattenLocaleKeys},"get")}),Object.defineProperty(exports,"verifyFrontendLocales",{enumerable:!0,get:e(function(){return chunk45Y4CR4R_cjs.verifyFrontendLocales},"get")}),Object.defineProperty(exports,"verifyMigrationNames",{enumerable:!0,get:e(function(){return chunk45Y4CR4R_cjs.verifyMigrationNames},"get")});
|
package/dist/manifest.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export{PYTHON_LOCK_FILENAME,PYTHON_RUNNER_VERSION,PYTHON_WHEELS_DIR,checkPluginAccessCatalog,checkPythonVendoring,normalizePackageName,parseRequirementsLock,parseTegoPluginManifest,parseWheelFilename,tegoPluginManifestSchema}from"./chunk-
|
|
1
|
+
export{PYTHON_LOCK_FILENAME,PYTHON_RUNNER_VERSION,PYTHON_WHEELS_DIR,checkPluginAccessCatalog,checkPythonVendoring,normalizePackageName,parseRequirementsLock,parseTegoPluginManifest,parseWheelFilename,tegoPluginManifestSchema}from"./chunk-2KXEYXM2.js";export{LOCALE_FILENAME_PATTERN,MIGRATION_FILENAME_PATTERN,flattenLocaleKeys,verifyFrontendLocales,verifyMigrationNames}from"./chunk-2NTAWYCG.js";import"./chunk-IVJECPGG.js";import"./chunk-INCA3GFW.js";import"./chunk-XOXUS4VI.js";import"./chunk-VMCGDDGP.js";import"./chunk-SNUCO2MK.js";
|