psyclaw 0.24.0 → 0.25.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (277) hide show
  1. package/README.md +46 -53
  2. package/{dist/apps → apps}/panel/index.html +28 -50
  3. package/package.json +10 -20
  4. package/scripts/__pycache__/build_dist.cpython-314.pyc +0 -0
  5. package/scripts/__pycache__/build_whitepaper_docx.cpython-314.pyc +0 -0
  6. package/scripts/build_preprint_docx.py +729 -0
  7. package/scripts/build_preprint_figures.py +238 -0
  8. package/scripts/build_three_line_table_fixture.py +56 -0
  9. package/scripts/build_whitepaper_docx.py +768 -0
  10. package/scripts/check-branding.mjs +159 -0
  11. package/scripts/copy-panel.mjs +11 -0
  12. package/scripts/rebrand-pi.mjs +292 -0
  13. package/scripts/verify_docx.py +41 -0
  14. package/skills/core/citation-audit/SKILL.md +9 -9
  15. package/skills/core/evidence-capture/SKILL.md +9 -9
  16. package/skills/core/manifest.json +15 -15
  17. package/skills/core/research-brief/SKILL.md +8 -8
  18. package/skills/core/research-intake/SKILL.md +9 -9
  19. package/dist/src/adapters/pi/extension.d.ts +0 -19
  20. package/dist/src/adapters/pi/extension.js +0 -735
  21. package/dist/src/adapters/pi/extension.js.map +0 -1
  22. package/dist/src/adapters/pi/model.d.ts +0 -72
  23. package/dist/src/adapters/pi/model.js +0 -152
  24. package/dist/src/adapters/pi/model.js.map +0 -1
  25. package/dist/src/adapters/pi/resources.d.ts +0 -23
  26. package/dist/src/adapters/pi/resources.js +0 -54
  27. package/dist/src/adapters/pi/resources.js.map +0 -1
  28. package/dist/src/adapters/pi/rpc.d.ts +0 -56
  29. package/dist/src/adapters/pi/rpc.js +0 -248
  30. package/dist/src/adapters/pi/rpc.js.map +0 -1
  31. package/dist/src/adapters/pi/session.d.ts +0 -30
  32. package/dist/src/adapters/pi/session.js +0 -64
  33. package/dist/src/adapters/pi/session.js.map +0 -1
  34. package/dist/src/agents/catalog.d.ts +0 -30
  35. package/dist/src/agents/catalog.js +0 -118
  36. package/dist/src/agents/catalog.js.map +0 -1
  37. package/dist/src/agents/discover.d.ts +0 -28
  38. package/dist/src/agents/discover.js +0 -127
  39. package/dist/src/agents/discover.js.map +0 -1
  40. package/dist/src/agents/import.d.ts +0 -67
  41. package/dist/src/agents/import.js +0 -801
  42. package/dist/src/agents/import.js.map +0 -1
  43. package/dist/src/analysis/hooks.d.ts +0 -81
  44. package/dist/src/analysis/hooks.js +0 -139
  45. package/dist/src/analysis/hooks.js.map +0 -1
  46. package/dist/src/analysis/pre-registration.d.ts +0 -18
  47. package/dist/src/analysis/pre-registration.js +0 -86
  48. package/dist/src/analysis/pre-registration.js.map +0 -1
  49. package/dist/src/branding.d.ts +0 -43
  50. package/dist/src/branding.js +0 -127
  51. package/dist/src/branding.js.map +0 -1
  52. package/dist/src/chat.d.ts +0 -16
  53. package/dist/src/chat.js +0 -67
  54. package/dist/src/chat.js.map +0 -1
  55. package/dist/src/cli.d.ts +0 -2
  56. package/dist/src/cli.js +0 -316
  57. package/dist/src/cli.js.map +0 -1
  58. package/dist/src/core/citations.d.ts +0 -44
  59. package/dist/src/core/citations.js +0 -48
  60. package/dist/src/core/citations.js.map +0 -1
  61. package/dist/src/core/contracts.d.ts +0 -119
  62. package/dist/src/core/contracts.js +0 -2
  63. package/dist/src/core/contracts.js.map +0 -1
  64. package/dist/src/core/docx.d.ts +0 -11
  65. package/dist/src/core/docx.js +0 -46
  66. package/dist/src/core/docx.js.map +0 -1
  67. package/dist/src/core/doi.d.ts +0 -41
  68. package/dist/src/core/doi.js +0 -82
  69. package/dist/src/core/doi.js.map +0 -1
  70. package/dist/src/core/evidence-policy.d.ts +0 -24
  71. package/dist/src/core/evidence-policy.js +0 -308
  72. package/dist/src/core/evidence-policy.js.map +0 -1
  73. package/dist/src/core/filekind.d.ts +0 -15
  74. package/dist/src/core/filekind.js +0 -82
  75. package/dist/src/core/filekind.js.map +0 -1
  76. package/dist/src/core/hash.d.ts +0 -2
  77. package/dist/src/core/hash.js +0 -17
  78. package/dist/src/core/hash.js.map +0 -1
  79. package/dist/src/core/pricing.d.ts +0 -38
  80. package/dist/src/core/pricing.js +0 -110
  81. package/dist/src/core/pricing.js.map +0 -1
  82. package/dist/src/core/redact.d.ts +0 -12
  83. package/dist/src/core/redact.js +0 -48
  84. package/dist/src/core/redact.js.map +0 -1
  85. package/dist/src/core/references.d.ts +0 -55
  86. package/dist/src/core/references.js +0 -102
  87. package/dist/src/core/references.js.map +0 -1
  88. package/dist/src/core/schemas.d.ts +0 -162
  89. package/dist/src/core/schemas.js +0 -160
  90. package/dist/src/core/schemas.js.map +0 -1
  91. package/dist/src/extension.d.ts +0 -1
  92. package/dist/src/extension.js +0 -2
  93. package/dist/src/extension.js.map +0 -1
  94. package/dist/src/index.d.ts +0 -72
  95. package/dist/src/index.js +0 -73
  96. package/dist/src/index.js.map +0 -1
  97. package/dist/src/install/installer.d.ts +0 -147
  98. package/dist/src/install/installer.js +0 -743
  99. package/dist/src/install/installer.js.map +0 -1
  100. package/dist/src/integrations/contracts.d.ts +0 -35
  101. package/dist/src/integrations/contracts.js +0 -27
  102. package/dist/src/integrations/contracts.js.map +0 -1
  103. package/dist/src/integrations/mcp.d.ts +0 -155
  104. package/dist/src/integrations/mcp.js +0 -826
  105. package/dist/src/integrations/mcp.js.map +0 -1
  106. package/dist/src/knowledge-map/contracts.d.ts +0 -20
  107. package/dist/src/knowledge-map/contracts.js +0 -2
  108. package/dist/src/knowledge-map/contracts.js.map +0 -1
  109. package/dist/src/knowledge-map/index.d.ts +0 -4
  110. package/dist/src/knowledge-map/index.js +0 -39
  111. package/dist/src/knowledge-map/index.js.map +0 -1
  112. package/dist/src/literature/contracts.d.ts +0 -106
  113. package/dist/src/literature/contracts.js +0 -2
  114. package/dist/src/literature/contracts.js.map +0 -1
  115. package/dist/src/literature/fulltext.d.ts +0 -7
  116. package/dist/src/literature/fulltext.js +0 -24
  117. package/dist/src/literature/fulltext.js.map +0 -1
  118. package/dist/src/literature/knowledge-map.d.ts +0 -2
  119. package/dist/src/literature/knowledge-map.js +0 -20
  120. package/dist/src/literature/knowledge-map.js.map +0 -1
  121. package/dist/src/literature/ledger.d.ts +0 -3
  122. package/dist/src/literature/ledger.js +0 -17
  123. package/dist/src/literature/ledger.js.map +0 -1
  124. package/dist/src/literature/metadata.d.ts +0 -6
  125. package/dist/src/literature/metadata.js +0 -32
  126. package/dist/src/literature/metadata.js.map +0 -1
  127. package/dist/src/memory/store.d.ts +0 -22
  128. package/dist/src/memory/store.js +0 -73
  129. package/dist/src/memory/store.js.map +0 -1
  130. package/dist/src/orchestration/contracts.d.ts +0 -61
  131. package/dist/src/orchestration/contracts.js +0 -2
  132. package/dist/src/orchestration/contracts.js.map +0 -1
  133. package/dist/src/orchestration/pi-executor.d.ts +0 -28
  134. package/dist/src/orchestration/pi-executor.js +0 -169
  135. package/dist/src/orchestration/pi-executor.js.map +0 -1
  136. package/dist/src/orchestration/runner.d.ts +0 -196
  137. package/dist/src/orchestration/runner.js +0 -991
  138. package/dist/src/orchestration/runner.js.map +0 -1
  139. package/dist/src/orchestration/scheduler.d.ts +0 -8
  140. package/dist/src/orchestration/scheduler.js +0 -335
  141. package/dist/src/orchestration/scheduler.js.map +0 -1
  142. package/dist/src/panel/events.d.ts +0 -20
  143. package/dist/src/panel/events.js +0 -93
  144. package/dist/src/panel/events.js.map +0 -1
  145. package/dist/src/panel/extension.d.ts +0 -8
  146. package/dist/src/panel/extension.js +0 -114
  147. package/dist/src/panel/extension.js.map +0 -1
  148. package/dist/src/panel/literature-map.d.ts +0 -26
  149. package/dist/src/panel/literature-map.js +0 -34
  150. package/dist/src/panel/literature-map.js.map +0 -1
  151. package/dist/src/panel/projection.d.ts +0 -51
  152. package/dist/src/panel/projection.js +0 -288
  153. package/dist/src/panel/projection.js.map +0 -1
  154. package/dist/src/panel/server.d.ts +0 -46
  155. package/dist/src/panel/server.js +0 -1610
  156. package/dist/src/panel/server.js.map +0 -1
  157. package/dist/src/panel/usage.d.ts +0 -53
  158. package/dist/src/panel/usage.js +0 -150
  159. package/dist/src/panel/usage.js.map +0 -1
  160. package/dist/src/project/bootstrap.d.ts +0 -10
  161. package/dist/src/project/bootstrap.js +0 -83
  162. package/dist/src/project/bootstrap.js.map +0 -1
  163. package/dist/src/project/hitl.d.ts +0 -12
  164. package/dist/src/project/hitl.js +0 -43
  165. package/dist/src/project/hitl.js.map +0 -1
  166. package/dist/src/project/jsonl.d.ts +0 -16
  167. package/dist/src/project/jsonl.js +0 -93
  168. package/dist/src/project/jsonl.js.map +0 -1
  169. package/dist/src/project/manuscript.d.ts +0 -21
  170. package/dist/src/project/manuscript.js +0 -74
  171. package/dist/src/project/manuscript.js.map +0 -1
  172. package/dist/src/project/paths.d.ts +0 -38
  173. package/dist/src/project/paths.js +0 -166
  174. package/dist/src/project/paths.js.map +0 -1
  175. package/dist/src/project/versions.d.ts +0 -10
  176. package/dist/src/project/versions.js +0 -14
  177. package/dist/src/project/versions.js.map +0 -1
  178. package/dist/src/psyclaw-theme.d.ts +0 -88
  179. package/dist/src/psyclaw-theme.js +0 -89
  180. package/dist/src/psyclaw-theme.js.map +0 -1
  181. package/dist/src/research/brief.d.ts +0 -11
  182. package/dist/src/research/brief.js +0 -81
  183. package/dist/src/research/brief.js.map +0 -1
  184. package/dist/src/research/ledger.d.ts +0 -12
  185. package/dist/src/research/ledger.js +0 -43
  186. package/dist/src/research/ledger.js.map +0 -1
  187. package/dist/src/run.d.ts +0 -20
  188. package/dist/src/run.js +0 -34
  189. package/dist/src/run.js.map +0 -1
  190. package/dist/src/setup.d.ts +0 -54
  191. package/dist/src/setup.js +0 -221
  192. package/dist/src/setup.js.map +0 -1
  193. package/dist/src/skills/contracts.d.ts +0 -105
  194. package/dist/src/skills/contracts.js +0 -3
  195. package/dist/src/skills/contracts.js.map +0 -1
  196. package/dist/src/skills/preflight.d.ts +0 -10
  197. package/dist/src/skills/preflight.js +0 -39
  198. package/dist/src/skills/preflight.js.map +0 -1
  199. package/dist/src/skills/registry.d.ts +0 -28
  200. package/dist/src/skills/registry.js +0 -556
  201. package/dist/src/skills/registry.js.map +0 -1
  202. package/dist/src/style/artifact.d.ts +0 -6
  203. package/dist/src/style/artifact.js +0 -61
  204. package/dist/src/style/artifact.js.map +0 -1
  205. package/dist/src/style/cli-ui.d.ts +0 -41
  206. package/dist/src/style/cli-ui.js +0 -119
  207. package/dist/src/style/cli-ui.js.map +0 -1
  208. package/dist/src/style/contracts.d.ts +0 -34
  209. package/dist/src/style/contracts.js +0 -2
  210. package/dist/src/style/contracts.js.map +0 -1
  211. package/dist/src/style/registry.d.ts +0 -16
  212. package/dist/src/style/registry.js +0 -50
  213. package/dist/src/style/registry.js.map +0 -1
  214. package/dist/src/trust/manifest.d.ts +0 -42
  215. package/dist/src/trust/manifest.js +0 -53
  216. package/dist/src/trust/manifest.js.map +0 -1
  217. package/dist/src/trust/sbom.d.ts +0 -54
  218. package/dist/src/trust/sbom.js +0 -77
  219. package/dist/src/trust/sbom.js.map +0 -1
  220. package/dist/src/tui/app.d.ts +0 -3
  221. package/dist/src/tui/app.js +0 -103
  222. package/dist/src/tui/app.js.map +0 -1
  223. package/dist/src/tui/model.d.ts +0 -13
  224. package/dist/src/tui/model.js +0 -25
  225. package/dist/src/tui/model.js.map +0 -1
  226. package/dist/src/updates/check.d.ts +0 -43
  227. package/dist/src/updates/check.js +0 -68
  228. package/dist/src/updates/check.js.map +0 -1
  229. package/dist/src/updates/manifest.d.ts +0 -19
  230. package/dist/src/updates/manifest.js +0 -58
  231. package/dist/src/updates/manifest.js.map +0 -1
  232. package/dist/src/updates/registry.d.ts +0 -21
  233. package/dist/src/updates/registry.js +0 -82
  234. package/dist/src/updates/registry.js.map +0 -1
  235. package/dist/src/updates/skills.d.ts +0 -15
  236. package/dist/src/updates/skills.js +0 -39
  237. package/dist/src/updates/skills.js.map +0 -1
  238. package/dist/src/updates/status.d.ts +0 -7
  239. package/dist/src/updates/status.js +0 -37
  240. package/dist/src/updates/status.js.map +0 -1
  241. package/dist/src/updates/update.d.ts +0 -43
  242. package/dist/src/updates/update.js +0 -126
  243. package/dist/src/updates/update.js.map +0 -1
  244. package/dist/src/wizard.d.ts +0 -7
  245. package/dist/src/wizard.js +0 -90
  246. package/dist/src/wizard.js.map +0 -1
  247. package/dist/src/workflows/analysis-delegation.d.ts +0 -3
  248. package/dist/src/workflows/analysis-delegation.js +0 -96
  249. package/dist/src/workflows/analysis-delegation.js.map +0 -1
  250. package/dist/src/workflows/expert-review-runner.d.ts +0 -29
  251. package/dist/src/workflows/expert-review-runner.js +0 -35
  252. package/dist/src/workflows/expert-review-runner.js.map +0 -1
  253. package/dist/src/workflows/expert-review.d.ts +0 -140
  254. package/dist/src/workflows/expert-review.js +0 -111
  255. package/dist/src/workflows/expert-review.js.map +0 -1
  256. package/dist/src/workflows/institutional-fulltext.d.ts +0 -16
  257. package/dist/src/workflows/institutional-fulltext.js +0 -110
  258. package/dist/src/workflows/institutional-fulltext.js.map +0 -1
  259. package/dist/src/workflows/literature-review.d.ts +0 -3
  260. package/dist/src/workflows/literature-review.js +0 -76
  261. package/dist/src/workflows/literature-review.js.map +0 -1
  262. package/dist/src/workflows/meta-analysis.d.ts +0 -38
  263. package/dist/src/workflows/meta-analysis.js +0 -185
  264. package/dist/src/workflows/meta-analysis.js.map +0 -1
  265. package/dist/src/workflows/publish.d.ts +0 -51
  266. package/dist/src/workflows/publish.js +0 -153
  267. package/dist/src/workflows/publish.js.map +0 -1
  268. package/dist/src/workflows/spec.d.ts +0 -51
  269. package/dist/src/workflows/spec.js +0 -157
  270. package/dist/src/workflows/spec.js.map +0 -1
  271. package/dist/src/workflows/style-contract.d.ts +0 -24
  272. package/dist/src/workflows/style-contract.js +0 -23
  273. package/dist/src/workflows/style-contract.js.map +0 -1
  274. package/dist/src/workflows/writing-review.d.ts +0 -15
  275. package/dist/src/workflows/writing-review.js +0 -82
  276. package/dist/src/workflows/writing-review.js.map +0 -1
  277. package/package.json.psyclaw +0 -21
@@ -1,39 +0,0 @@
1
- /**
2
- * Content preflight for skill bodies. This is a heuristic tripwire, not an OS
3
- * boundary: a body that instructs the agent to bypass gates or move data out
4
- * of the project is marked suspicious and the skill is never executable.
5
- */
6
- const SUSPICIOUS_PATTERNS = [
7
- {
8
- name: "bypass-gate",
9
- source: "\\b(?:bypass|circumvent|disable|skip|override|ignore|turn\\s+off)\\s+" +
10
- "(?:the\\s+)?(?:gate|integrity|verification|check|audit|policy|approval|allowlist)s?\\b",
11
- flags: "i",
12
- },
13
- {
14
- name: "exfiltrate-data",
15
- source: "\\b(?:upload|exfiltrate|send|transmit|forward|export)\\s+" +
16
- "(?:all|every|the|our)\\s+(?:files?|data|documents?|credentials|secrets|tokens)\\b",
17
- flags: "i",
18
- },
19
- {
20
- name: "collect-secrets",
21
- source: "\\b(?:read|collect|steal|harvest)\\s+" +
22
- "(?:credentials|secrets|tokens|api[ -]?keys|passwords|env(?:ironment)?)\\b",
23
- flags: "i",
24
- },
25
- {
26
- name: "disable-audit",
27
- source: "\\b(?:disable|suppress|turn\\s+off)\\s+(?:logging|audit(?:ing)?|tracing)\\b",
28
- flags: "i",
29
- },
30
- ];
31
- export function preflightSkillBody(body) {
32
- const findings = [];
33
- for (const { name, source, flags } of SUSPICIOUS_PATTERNS) {
34
- if (new RegExp(source, flags).test(body))
35
- findings.push(name);
36
- }
37
- return { suspicious: findings.length > 0, findings };
38
- }
39
- //# sourceMappingURL=preflight.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"preflight.js","sourceRoot":"","sources":["../../../src/skills/preflight.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAaH,MAAM,mBAAmB,GAAiC;IACxD;QACE,IAAI,EAAE,aAAa;QACnB,MAAM,EAAE,uEAAuE;YAC7E,wFAAwF;QAC1F,KAAK,EAAE,GAAG;KACX;IACD;QACE,IAAI,EAAE,iBAAiB;QACvB,MAAM,EAAE,2DAA2D;YACjE,mFAAmF;QACrF,KAAK,EAAE,GAAG;KACX;IACD;QACE,IAAI,EAAE,iBAAiB;QACvB,MAAM,EAAE,uCAAuC;YAC7C,2EAA2E;QAC7E,KAAK,EAAE,GAAG;KACX;IACD;QACE,IAAI,EAAE,eAAe;QACrB,MAAM,EAAE,6EAA6E;QACrF,KAAK,EAAE,GAAG;KACX;CACF,CAAC;AAEF,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,mBAAmB,EAAE,CAAC;QAC1D,IAAI,IAAI,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAChE,CAAC;IACD,OAAO,EAAE,UAAU,EAAE,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,QAAQ,EAAE,CAAC;AACvD,CAAC"}
@@ -1,28 +0,0 @@
1
- import { type LoadedSkill, type SkillDescriptor, type SkillDiagnostic, type SkillDiscoveryReport, type SkillRegistryOptions, type SkillSearchOptions } from "./contracts.js";
2
- type Metadata = Record<string, unknown>;
3
- interface ParsedSkill {
4
- metadata: Metadata;
5
- body: string;
6
- }
7
- /** Parse only the leading YAML document and return the Markdown body separately. */
8
- declare function parseSkillDocument(text: string, path: string): ParsedSkill;
9
- export declare class SkillRegistry {
10
- private readonly roots;
11
- private readonly approvals;
12
- private readonly entries;
13
- private readonly diagnosticLog;
14
- constructor(roots?: string[]);
15
- constructor(options?: SkillRegistryOptions);
16
- discover(roots?: string[]): Promise<SkillDiscoveryReport>;
17
- list(options?: {
18
- enabledOnly?: boolean;
19
- }): SkillDescriptor[];
20
- diagnostics(): SkillDiagnostic[];
21
- search(query: string, options?: SkillSearchOptions): SkillDescriptor[];
22
- enable(id: string): SkillDescriptor;
23
- disable(id: string): SkillDescriptor;
24
- load(id: string): Promise<LoadedSkill>;
25
- private unique;
26
- private setEnabled;
27
- }
28
- export { parseSkillDocument };
@@ -1,556 +0,0 @@
1
- import { createHash } from "node:crypto";
2
- import { lstat, readdir, readFile, realpath } from "node:fs/promises";
3
- import { isAbsolute, join, relative, resolve, sep } from "node:path";
4
- import { parse as parseYaml } from "yaml";
5
- import { SKILL_DESCRIPTOR_VERSION, } from "./contracts.js";
6
- import { preflightSkillBody } from "./preflight.js";
7
- const LICENSE_STATUSES = new Set([
8
- "verified",
9
- "declared",
10
- "missing",
11
- "invalid",
12
- "unknown",
13
- ]);
14
- const DEPENDENCY_STATUSES = new Set([
15
- "ready",
16
- "declared",
17
- "missing",
18
- "blocked",
19
- "unknown",
20
- ]);
21
- const TRUST_STATUSES = new Set(["trusted", "untrusted", "blocked", "unknown"]);
22
- const RISK_LEVELS = new Set(["low", "medium", "high", "critical", "unknown"]);
23
- function diagnostic(code, message, path, extra = {}) {
24
- return {
25
- code,
26
- message,
27
- ...(path === undefined ? {} : { path }),
28
- severity: extra.severity ?? (code === "duplicate-id" || code === "path-traversal" || code === "symlink-path" ? "error" : "warning"),
29
- ...(extra.skillId === undefined ? {} : { skillId: extra.skillId }),
30
- ...(extra.relatedPaths === undefined ? {} : { relatedPaths: extra.relatedPaths }),
31
- };
32
- }
33
- function nonEmptyString(value) {
34
- return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
35
- }
36
- function metadataString(metadata, ...keys) {
37
- for (const key of keys) {
38
- const value = nonEmptyString(metadata[key]);
39
- if (value !== undefined)
40
- return value;
41
- }
42
- return undefined;
43
- }
44
- function statusFromMetadata(metadata, keys, allowed, fallback) {
45
- const value = metadataString(metadata, ...keys);
46
- return value !== undefined && allowed.has(value) ? value : fallback;
47
- }
48
- function deriveLicenseStatus(metadata) {
49
- // A skill cannot self-certify "verified": the frontmatter alone can only
50
- // declare a license or report it missing. Verification comes from a host
51
- // manifest/preflight, never from the skill's own claim.
52
- const license = nonEmptyString(metadata.license);
53
- return license === undefined ? "missing" : "declared";
54
- }
55
- function hasDependencies(metadata) {
56
- const dependencies = metadata.dependencies;
57
- if (Array.isArray(dependencies))
58
- return dependencies.length > 0;
59
- if (dependencies !== null && typeof dependencies === "object")
60
- return Object.keys(dependencies).length > 0;
61
- return nonEmptyString(dependencies) !== undefined;
62
- }
63
- function deriveDependencyStatus(metadata) {
64
- // "ready" is a host determination that declared dependencies are
65
- // satisfiable; the frontmatter can only declare or omit dependencies.
66
- return hasDependencies(metadata) ? "declared" : "ready";
67
- }
68
- function deriveTrust(metadata) {
69
- // Trust is host-decided. A self-declared "blocked" is honored as explicit
70
- // self-exclusion; self-declared "trusted"/"untrusted" are not taken at face
71
- // value and remain "unknown" until the host approves.
72
- const value = metadataString(metadata, "trust", "trustStatus", "trust_status");
73
- return value === "blocked" ? "blocked" : "unknown";
74
- }
75
- function deriveRisk(metadata) {
76
- return statusFromMetadata(metadata, ["risk", "riskLevel", "risk_level"], RISK_LEVELS, "unknown");
77
- }
78
- function isPlainRecord(value) {
79
- return value !== null && typeof value === "object" && !Array.isArray(value);
80
- }
81
- function rejectTraversal(input) {
82
- const normalized = input.replaceAll("\\", "/");
83
- const withoutDrive = normalized.replace(/^[A-Za-z]:\/?/, "");
84
- if (withoutDrive.split("/").some((segment) => segment === "..")) {
85
- throw new Error(`Path traversal is not allowed: ${input}`);
86
- }
87
- }
88
- function assertContained(root, target) {
89
- const rel = relative(root, target).replaceAll("\\", "/");
90
- if (rel === ".." || rel.startsWith("../") || isAbsolute(rel) || rel.startsWith("/")) {
91
- throw new Error(`Path escapes skill root: ${target}`);
92
- }
93
- }
94
- /** Parse only the leading YAML document and return the Markdown body separately. */
95
- function parseSkillDocument(text, path) {
96
- const normalized = text.replace(/^\uFEFF/, "");
97
- const opening = normalized.match(/^---[ \t]*\r?\n/);
98
- if (opening === null)
99
- throw new Error(`SKILL.md must start with YAML frontmatter: ${path}`);
100
- const start = opening[0].length;
101
- const closing = /^(---|\.\.\.)[ \t]*(?:\r?\n|$)/m.exec(normalized.slice(start));
102
- if (closing === null)
103
- throw new Error(`YAML frontmatter is not terminated: ${path}`);
104
- const yamlText = normalized.slice(start, start + closing.index);
105
- let parsed;
106
- try {
107
- parsed = parseYaml(yamlText);
108
- }
109
- catch (error) {
110
- const message = error instanceof Error ? error.message : String(error);
111
- throw new Error(`Invalid YAML frontmatter: ${message}`);
112
- }
113
- if (parsed === null || parsed === undefined)
114
- parsed = {};
115
- if (!isPlainRecord(parsed))
116
- throw new Error(`YAML frontmatter must be a mapping: ${path}`);
117
- const body = normalized.slice(start + closing.index + closing[0].length);
118
- return { metadata: parsed, body };
119
- }
120
- function skillIdentity(metadata, filePath) {
121
- const name = nonEmptyString(metadata.name);
122
- const description = nonEmptyString(metadata.description);
123
- if (name === undefined || description === undefined) {
124
- throw new Error("Frontmatter requires non-empty name and description");
125
- }
126
- const id = nonEmptyString(metadata.id) ?? name;
127
- // IDs are lookup keys, never paths. Refuse separators and traversal markers.
128
- if (id.includes("/") || id.includes("\\") || id === "." || id === ".." || id.trim() !== id) {
129
- throw new Error(`Invalid skill id: ${id}`);
130
- }
131
- if (id.length > 128)
132
- throw new Error(`Skill id is too long: ${id}`);
133
- void filePath;
134
- return { id, name, description };
135
- }
136
- async function assertRoot(rootInput) {
137
- rejectTraversal(rootInput);
138
- const source = resolve(rootInput);
139
- const stat = await lstat(source);
140
- if (stat.isSymbolicLink())
141
- throw new Error(`Skill root is a symlink: ${source}`);
142
- if (!stat.isDirectory())
143
- throw new Error(`Skill root is not a directory: ${source}`);
144
- return { source, resolved: await realpath(source) };
145
- }
146
- async function walkSkillFiles(root, diagnostics) {
147
- const files = [];
148
- const visit = async (directory) => {
149
- let entries;
150
- try {
151
- entries = await readdir(directory, { withFileTypes: true });
152
- }
153
- catch (error) {
154
- diagnostics.push(diagnostic("read-error", `Unable to read skill directory: ${String(error)}`, directory));
155
- return;
156
- }
157
- for (const entry of entries) {
158
- const candidate = resolve(directory, entry.name);
159
- try {
160
- assertContained(root.resolved, await realpath(directory));
161
- }
162
- catch (error) {
163
- diagnostics.push(diagnostic("path-traversal", String(error), candidate));
164
- continue;
165
- }
166
- if (entry.isSymbolicLink()) {
167
- diagnostics.push(diagnostic("symlink-path", "Symlinked skill paths are not allowed", candidate));
168
- continue;
169
- }
170
- if (entry.isDirectory()) {
171
- await visit(candidate);
172
- }
173
- else if (entry.isFile() && entry.name === "SKILL.md") {
174
- files.push(candidate);
175
- }
176
- }
177
- };
178
- await visit(root.source);
179
- return files;
180
- }
181
- async function verifyFilePath(sourcePath, rootPath) {
182
- const sourceRoot = resolve(rootPath);
183
- const source = resolve(sourcePath);
184
- assertContained(sourceRoot, source);
185
- const rootStat = await lstat(sourceRoot);
186
- if (rootStat.isSymbolicLink())
187
- throw new Error(`Skill root is a symlink: ${sourceRoot}`);
188
- if (!rootStat.isDirectory())
189
- throw new Error(`Skill root is not a directory: ${sourceRoot}`);
190
- const resolvedRoot = await realpath(sourceRoot);
191
- let cursor = sourceRoot;
192
- for (const part of relative(sourceRoot, source).split(sep).filter(Boolean)) {
193
- cursor = join(cursor, part);
194
- const stat = await lstat(cursor);
195
- if (stat.isSymbolicLink())
196
- throw new Error(`Skill path contains a symlink: ${sourcePath}`);
197
- }
198
- const sourceStat = await lstat(source);
199
- if (sourceStat.isSymbolicLink())
200
- throw new Error(`Skill file is a symlink: ${sourcePath}`);
201
- if (!sourceStat.isFile())
202
- throw new Error(`Skill path is not a file: ${sourcePath}`);
203
- const resolved = await realpath(source);
204
- assertContained(resolvedRoot, resolved);
205
- const expectedResolved = resolve(resolvedRoot, relative(sourceRoot, source));
206
- // Compare paths relative to the controlled root. Ancestors above that root
207
- // may legitimately be OS-managed aliases such as macOS /var -> /private/var.
208
- if (!samePath(expectedResolved, resolved)) {
209
- throw new Error(`Skill path contains a symlink or changed while reading: ${sourcePath}`);
210
- }
211
- return resolved;
212
- }
213
- function samePath(left, right) {
214
- const normalize = process.platform === "win32"
215
- ? (value) => value.replace(/[\\/]+/g, "\\").replace(/\\$/, "").toLocaleLowerCase()
216
- : (value) => value;
217
- const normalizedLeft = normalize(left);
218
- const normalizedRight = normalize(right);
219
- return normalizedLeft === normalizedRight;
220
- }
221
- function pathKey(value) {
222
- return process.platform === "win32" ? value.toLocaleLowerCase() : value;
223
- }
224
- /**
225
- * Read a skill twice with canonical-path checks around the reads. This is a
226
- * small, fail-closed TOCTOU guard: if a path is swapped, symlinked, or
227
- * modified while it is being inspected, discovery/load refuses the file.
228
- */
229
- async function readSkillFileStable(sourcePath, rootPath) {
230
- const before = await verifyFilePath(sourcePath, rootPath);
231
- const firstBytes = await readFile(before);
232
- const firstText = firstBytes.toString("utf8");
233
- const firstHash = createHash("sha256").update(firstBytes).digest("hex");
234
- const after = await verifyFilePath(sourcePath, rootPath);
235
- if (!samePath(before, after)) {
236
- throw new Error(`Skill path changed while reading: ${sourcePath}`);
237
- }
238
- const secondBytes = await readFile(after);
239
- const secondText = secondBytes.toString("utf8");
240
- const secondHash = createHash("sha256").update(secondBytes).digest("hex");
241
- if (firstHash !== secondHash || !firstBytes.equals(secondBytes)) {
242
- throw new Error(`Skill changed while reading: ${sourcePath}`);
243
- }
244
- const finalPath = await verifyFilePath(sourcePath, rootPath);
245
- if (!samePath(after, finalPath)) {
246
- throw new Error(`Skill path changed after reading: ${sourcePath}`);
247
- }
248
- return { text: firstText, sha256: firstHash, resolvedPath: finalPath };
249
- }
250
- function approvalEntry(approvals, id) {
251
- if (approvals instanceof Map)
252
- return approvals.get(id);
253
- const record = approvals;
254
- return Object.prototype.hasOwnProperty.call(record, id) ? record[id] : undefined;
255
- }
256
- function isValidApproval(value) {
257
- if (value === null || typeof value !== "object" || Array.isArray(value))
258
- return false;
259
- const candidate = value;
260
- if (typeof candidate.approved !== "boolean")
261
- return false;
262
- if (candidate.sha256 !== undefined &&
263
- (typeof candidate.sha256 !== "string" || !/^[a-f0-9]{64}$/i.test(candidate.sha256)))
264
- return false;
265
- if (candidate.sourcePath !== undefined && typeof candidate.sourcePath !== "string")
266
- return false;
267
- if (candidate.resolvedPath !== undefined && typeof candidate.resolvedPath !== "string")
268
- return false;
269
- if (candidate.admission !== undefined && !isValidAdmissionEvidence(candidate.admission))
270
- return false;
271
- return true;
272
- }
273
- function isValidAdmissionEvidence(value) {
274
- if (value === null || typeof value !== "object" || Array.isArray(value))
275
- return false;
276
- const candidate = value;
277
- return candidate.schemaVersion === "psyclaw/skill-admission/v1" &&
278
- typeof candidate.contentSha256 === "string" && /^[a-f0-9]{64}$/i.test(candidate.contentSha256) &&
279
- typeof candidate.licenseSpdx === "string" && candidate.licenseSpdx.trim() !== "" &&
280
- candidate.licenseSpdx.toLocaleLowerCase() !== "unknown" &&
281
- typeof candidate.licenseEvidenceRef === "string" && candidate.licenseEvidenceRef.trim() !== "" &&
282
- typeof candidate.dependencyEvidenceRef === "string" && candidate.dependencyEvidenceRef.trim() !== "" &&
283
- typeof candidate.sbomSha256 === "string" && /^[a-f0-9]{64}$/i.test(candidate.sbomSha256);
284
- }
285
- function copyApproval(entry) {
286
- if (typeof entry === "boolean")
287
- return entry;
288
- if (!isValidApproval(entry))
289
- return false;
290
- return Object.freeze({
291
- approved: entry.approved,
292
- ...(entry.sha256 === undefined ? {} : { sha256: entry.sha256.toLocaleLowerCase() }),
293
- ...(entry.sourcePath === undefined ? {} : { sourcePath: entry.sourcePath }),
294
- ...(entry.resolvedPath === undefined ? {} : { resolvedPath: entry.resolvedPath }),
295
- ...(entry.admission === undefined ? {} : {
296
- admission: Object.freeze({
297
- schemaVersion: entry.admission.schemaVersion,
298
- contentSha256: entry.admission.contentSha256.toLocaleLowerCase(),
299
- licenseSpdx: entry.admission.licenseSpdx,
300
- licenseEvidenceRef: entry.admission.licenseEvidenceRef,
301
- dependencyEvidenceRef: entry.admission.dependencyEvidenceRef,
302
- sbomSha256: entry.admission.sbomSha256.toLocaleLowerCase(),
303
- }),
304
- }),
305
- });
306
- }
307
- function pathMatchesPin(pin, descriptor) {
308
- if (pin === undefined)
309
- return true;
310
- const normalizedPin = pin.replaceAll("\\", "/");
311
- if (normalizedPin.split("/").some((segment) => segment === ".."))
312
- return false;
313
- const resolvedPin = resolve(pin);
314
- const rootRelativePin = resolve(descriptor.rootPath, pin);
315
- return samePath(resolvedPin, descriptor.sourcePath) ||
316
- samePath(resolvedPin, descriptor.resolvedPath) ||
317
- samePath(rootRelativePin, descriptor.sourcePath) ||
318
- samePath(rootRelativePin, descriptor.resolvedPath);
319
- }
320
- function approvalFor(descriptor, approvals) {
321
- if (descriptor.trust === "blocked")
322
- return "blocked";
323
- const entry = approvalEntry(approvals, descriptor.id);
324
- if (entry === undefined)
325
- return "discover-only";
326
- const approval = typeof entry === "boolean"
327
- ? { approved: entry }
328
- : isValidApproval(entry)
329
- ? entry
330
- : { approved: false };
331
- if (!approval.approved)
332
- return "blocked";
333
- // A path/content pin is not a license or dependency audit. Require a
334
- // host-generated admission record before allowing executable content.
335
- // Frontmatter-derived license/dependency values never upgrade this record;
336
- // a verified external manifest may cover a skill whose frontmatter is terse.
337
- if (!approval.admission || !isValidAdmissionEvidence(approval.admission))
338
- return "discover-only";
339
- if (approval.admission.contentSha256.toLocaleLowerCase() !== descriptor.sha256)
340
- return "stale";
341
- // A no-pin approval (e.g. the `approvedIds` id shorthand) cannot grant
342
- // execution; a content SHA or path pin must bind the approval to the
343
- // discovered skill.
344
- const hasPin = approval.sha256 !== undefined ||
345
- approval.sourcePath !== undefined ||
346
- approval.resolvedPath !== undefined;
347
- if (!hasPin)
348
- return "discover-only";
349
- if (approval.sha256 !== undefined && approval.sha256.toLocaleLowerCase() !== descriptor.sha256)
350
- return "stale";
351
- if (!pathMatchesPin(approval.sourcePath, descriptor) || !pathMatchesPin(approval.resolvedPath, descriptor))
352
- return "stale";
353
- return "approved";
354
- }
355
- export class SkillRegistry {
356
- roots;
357
- approvals;
358
- entries = new Map();
359
- diagnosticLog = [];
360
- constructor(rootsOrOptions = []) {
361
- if (Array.isArray(rootsOrOptions)) {
362
- this.roots = [...rootsOrOptions];
363
- this.approvals = new Map();
364
- }
365
- else {
366
- this.roots = [...(rootsOrOptions.roots ?? [])];
367
- // `enableByDefault` is retained in the public options only so existing
368
- // callers fail safely after upgrade; frontmatter can never approve a
369
- // skill. Explicit approvals always come from the host options.
370
- const merged = new Map();
371
- const configured = rootsOrOptions.approvals ?? rootsOrOptions.approvalMap ?? rootsOrOptions.approval;
372
- if (configured !== undefined) {
373
- if (configured instanceof Map) {
374
- for (const [id, entry] of configured.entries())
375
- merged.set(id, copyApproval(entry));
376
- }
377
- else {
378
- for (const [id, entry] of Object.entries(configured))
379
- merged.set(id, copyApproval(entry));
380
- }
381
- }
382
- for (const id of rootsOrOptions.approvedIds ?? []) {
383
- // An explicit deny in the richer map wins over the shorthand list.
384
- if (!merged.has(id))
385
- merged.set(id, true);
386
- }
387
- this.approvals = merged;
388
- void rootsOrOptions.enableByDefault;
389
- }
390
- }
391
- async discover(roots = this.roots) {
392
- this.entries.clear();
393
- this.diagnosticLog.length = 0;
394
- const seenPaths = new Set();
395
- for (const rootInput of roots) {
396
- let root;
397
- try {
398
- root = await assertRoot(rootInput);
399
- }
400
- catch (error) {
401
- const code = /symlink/i.test(String(error))
402
- ? "root-symlink"
403
- : /not a directory/i.test(String(error))
404
- ? "root-not-directory"
405
- : /ENOENT|not found/i.test(String(error))
406
- ? "root-not-found"
407
- : /traversal/i.test(String(error))
408
- ? "path-traversal"
409
- : "read-error";
410
- this.diagnosticLog.push(diagnostic(code, String(error), resolve(rootInput)));
411
- continue;
412
- }
413
- const files = await walkSkillFiles(root, this.diagnosticLog);
414
- for (const sourcePath of files) {
415
- let descriptor;
416
- try {
417
- const stable = await readSkillFileStable(sourcePath, root.source);
418
- const resolvedPath = stable.resolvedPath;
419
- const text = stable.text;
420
- const parsed = parseSkillDocument(text, sourcePath);
421
- const identity = skillIdentity(parsed.metadata, sourcePath);
422
- const preflight = preflightSkillBody(parsed.body);
423
- const resolvedKey = pathKey(resolvedPath);
424
- if (seenPaths.has(resolvedKey))
425
- continue;
426
- seenPaths.add(resolvedKey);
427
- if (preflight.suspicious) {
428
- this.diagnosticLog.push(diagnostic("suspicious-body", `Skill body contains suspicious instructions: ${preflight.findings.join(", ")}`, sourcePath, { severity: "error", skillId: identity.id }));
429
- }
430
- const baseDescriptor = {
431
- schemaVersion: SKILL_DESCRIPTOR_VERSION,
432
- id: identity.id,
433
- name: identity.name,
434
- description: identity.description,
435
- sourcePath,
436
- resolvedPath,
437
- rootPath: root.source,
438
- sha256: stable.sha256,
439
- licenseStatus: deriveLicenseStatus(parsed.metadata),
440
- dependencyStatus: deriveDependencyStatus(parsed.metadata),
441
- trust: preflight.suspicious ? "blocked" : deriveTrust(parsed.metadata),
442
- risk: preflight.suspicious ? "critical" : deriveRisk(parsed.metadata),
443
- enabled: false,
444
- conflicted: false,
445
- metadata: Object.freeze({ ...parsed.metadata }),
446
- };
447
- descriptor = Object.freeze({
448
- ...baseDescriptor,
449
- approvalStatus: approvalFor(baseDescriptor, this.approvals),
450
- });
451
- }
452
- catch (error) {
453
- const message = String(error);
454
- const code = /frontmatter|YAML/i.test(message)
455
- ? "invalid-frontmatter"
456
- : /id|name and description/i.test(message)
457
- ? "invalid-metadata"
458
- : /hash/i.test(message)
459
- ? "hash-error"
460
- : /symlink/i.test(message)
461
- ? "symlink-path"
462
- : "read-error";
463
- this.diagnosticLog.push(diagnostic(code, message, sourcePath));
464
- continue;
465
- }
466
- const candidates = this.entries.get(descriptor.id) ?? [];
467
- if (candidates.length > 0 && candidates.every((item) => item.resolvedPath !== descriptor.resolvedPath)) {
468
- descriptor = { ...descriptor, conflicted: true };
469
- const marked = candidates.map((item) => ({ ...item, conflicted: true }));
470
- this.entries.set(descriptor.id, [...marked, descriptor]);
471
- this.diagnosticLog.push(diagnostic("duplicate-id", `Skill id '${descriptor.id}' is provided by multiple paths`, descriptor.sourcePath, { severity: "error", skillId: descriptor.id, relatedPaths: [...marked.map((item) => item.sourcePath), descriptor.sourcePath] }));
472
- }
473
- else if (!candidates.some((item) => item.resolvedPath === descriptor.resolvedPath)) {
474
- this.entries.set(descriptor.id, [...candidates, descriptor]);
475
- }
476
- }
477
- }
478
- return { skills: this.list(), diagnostics: this.diagnostics() };
479
- }
480
- list(options = {}) {
481
- const skills = [...this.entries.values()].flat();
482
- return (options.enabledOnly ? skills.filter((skill) => skill.enabled) : skills)
483
- .sort((left, right) => left.id.localeCompare(right.id) || left.sourcePath.localeCompare(right.sourcePath));
484
- }
485
- diagnostics() {
486
- return this.diagnosticLog.map((item) => ({
487
- ...item,
488
- ...(item.relatedPaths === undefined ? {} : { relatedPaths: [...item.relatedPaths] }),
489
- }));
490
- }
491
- search(query, options = {}) {
492
- const needle = query.trim().toLocaleLowerCase();
493
- const candidates = options.enabledOnly === undefined
494
- ? this.list()
495
- : this.list({ enabledOnly: options.enabledOnly });
496
- if (needle.length === 0)
497
- return options.limit === undefined ? candidates : candidates.slice(0, options.limit);
498
- const ranked = candidates
499
- .map((skill) => {
500
- const id = skill.id.toLocaleLowerCase();
501
- const name = skill.name.toLocaleLowerCase();
502
- const description = skill.description.toLocaleLowerCase();
503
- const keywords = JSON.stringify(skill.metadata.keywords ?? skill.metadata.tags ?? "").toLocaleLowerCase();
504
- const score = id === needle ? 0 : id.includes(needle) ? 1 : name.includes(needle) ? 2 : description.includes(needle) ? 3 : keywords.includes(needle) ? 4 : 5;
505
- return { skill, score };
506
- })
507
- .filter((item) => item.score < 5)
508
- .sort((left, right) => left.score - right.score || left.skill.id.localeCompare(right.skill.id) || left.skill.sourcePath.localeCompare(right.skill.sourcePath))
509
- .map((item) => item.skill);
510
- return options.limit === undefined ? ranked : ranked.slice(0, Math.max(0, options.limit));
511
- }
512
- enable(id) {
513
- return this.setEnabled(id, true);
514
- }
515
- disable(id) {
516
- return this.setEnabled(id, false);
517
- }
518
- async load(id) {
519
- const descriptor = this.unique(id);
520
- if (!descriptor.enabled)
521
- throw new Error(`Skill is not enabled: ${id}`);
522
- if (descriptor.approvalStatus !== "approved") {
523
- throw new Error(`Skill requires explicit approval: ${id}`);
524
- }
525
- const stable = await readSkillFileStable(descriptor.sourcePath, descriptor.rootPath);
526
- if (!samePath(stable.resolvedPath, descriptor.resolvedPath) || stable.sha256 !== descriptor.sha256) {
527
- throw new Error(`Skill changed since discovery: ${id}`);
528
- }
529
- const text = stable.text;
530
- const parsed = parseSkillDocument(text, descriptor.sourcePath);
531
- return { ...descriptor, body: parsed.body };
532
- }
533
- unique(id) {
534
- const candidates = this.entries.get(id) ?? [];
535
- if (candidates.length === 0) {
536
- throw new Error(`Skill not found: ${id}`);
537
- }
538
- if (candidates.length > 1) {
539
- throw new Error(`Skill id is ambiguous: ${id}`);
540
- }
541
- return candidates[0];
542
- }
543
- setEnabled(id, enabled) {
544
- const current = this.unique(id);
545
- if (enabled && current.trust === "blocked")
546
- throw new Error(`Skill is blocked by trust policy: ${id}`);
547
- if (enabled && current.approvalStatus !== "approved") {
548
- throw new Error(`Skill requires explicit approval: ${id}`);
549
- }
550
- const updated = Object.freeze({ ...current, enabled });
551
- this.entries.set(id, [updated]);
552
- return updated;
553
- }
554
- }
555
- export { parseSkillDocument };
556
- //# sourceMappingURL=registry.js.map