@hassangameryt/freeflow 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (312) hide show
  1. package/.agents/plugins/marketplace.json +1 -1
  2. package/.claude-plugin/marketplace.json +4 -4
  3. package/{plugins/freeflow/.claude-plugin → .claude-plugin}/plugin.json +5 -2
  4. package/{plugins/freeflow/.codex-plugin → .codex-plugin}/plugin.json +8 -9
  5. package/README.md +194 -127
  6. package/assets/feedback-loop.png +0 -0
  7. package/assets/freeflow-plugin-icon.png +0 -0
  8. package/assets/freeflow_workflow_map.png +0 -0
  9. package/assets/workflow.png +0 -0
  10. package/command-surface.json +129 -0
  11. package/hooks/freeflow-runtime-context.mjs +419 -0
  12. package/hooks/tests/check-runtime-context-hook.sh +223 -0
  13. package/package.json +41 -22
  14. package/pi-extension/dist/index.js +269 -0
  15. package/pi-extension/dist/integrations/mcp-config.js +29 -0
  16. package/pi-extension/dist/output-router/host-producer-identification.js +232 -0
  17. package/pi-extension/dist/output-router/native-safety-net.js +216 -0
  18. package/pi-extension/dist/output-router/observed-tool-routing.js +122 -0
  19. package/pi-extension/dist/output-router/renderers.js +1023 -0
  20. package/pi-extension/dist/output-router/router-tools.js +554 -0
  21. package/pi-extension/dist/output-router/schemas.js +383 -0
  22. package/pi-extension/dist/output-router/status.js +744 -0
  23. package/pi-extension/dist/output-router/utils.js +502 -0
  24. package/pi-extension/dist/runtime/runtime-context.js +798 -0
  25. package/pi-extension/dist/settings/settings-tui.js +314 -0
  26. package/pi-extension/dist/settings/settings-ui.js +1487 -0
  27. package/pi-extension/freeflow/index.js +1 -0
  28. package/pi-extension/src/index.ts +295 -0
  29. package/pi-extension/src/integrations/mcp-config.ts +32 -0
  30. package/pi-extension/src/output-router/host-producer-identification.ts +256 -0
  31. package/pi-extension/src/output-router/native-safety-net.ts +248 -0
  32. package/pi-extension/src/output-router/observed-tool-routing.ts +134 -0
  33. package/pi-extension/src/output-router/renderers.ts +1108 -0
  34. package/pi-extension/src/output-router/router-tools.ts +579 -0
  35. package/pi-extension/src/output-router/schemas.ts +387 -0
  36. package/pi-extension/src/output-router/status.ts +791 -0
  37. package/pi-extension/src/output-router/utils.ts +554 -0
  38. package/pi-extension/src/runtime/runtime-context.ts +895 -0
  39. package/pi-extension/src/settings/settings-tui.ts +408 -0
  40. package/pi-extension/src/settings/settings-ui.ts +1730 -0
  41. package/pi-extension/tests/integration/pi-extension.test.js +4018 -0
  42. package/pi-extension/tests/output-router/observed-producers.test.js +132 -0
  43. package/pi-extension/tests/output-router/search.test.js +722 -0
  44. package/pi-extension/tests/settings/settings-tui.test.js +243 -0
  45. package/pi-extension/tsconfig.json +16 -0
  46. package/{plugins/freeflow/router/dist → router/dist/benchmarks}/benchmark-harness.d.ts +16 -13
  47. package/router/dist/benchmarks/benchmark-harness.js +92 -0
  48. package/router/dist/benchmarks/benchmarks.d.ts +109 -0
  49. package/router/dist/benchmarks/benchmarks.js +979 -0
  50. package/router/dist/benchmarks/codex-qa-benchmarks.d.ts +95 -0
  51. package/router/dist/benchmarks/codex-qa-benchmarks.js +500 -0
  52. package/router/dist/benchmarks/command-benchmarks.d.ts +143 -0
  53. package/router/dist/benchmarks/command-benchmarks.js +698 -0
  54. package/router/dist/benchmarks/context-mode-normalized-benchmarks.d.ts +82 -0
  55. package/router/dist/benchmarks/context-mode-normalized-benchmarks.js +773 -0
  56. package/router/dist/benchmarks/context-mode-real-deep-benchmark.d.ts +132 -0
  57. package/router/dist/benchmarks/context-mode-real-deep-benchmark.js +1380 -0
  58. package/router/dist/benchmarks/context-saving-baseline.d.ts +60 -0
  59. package/router/dist/benchmarks/context-saving-baseline.js +470 -0
  60. package/router/dist/benchmarks/index-benchmarks.d.ts +103 -0
  61. package/router/dist/benchmarks/index-benchmarks.js +862 -0
  62. package/router/dist/benchmarks/storage-policy-benchmarks.d.ts +101 -0
  63. package/router/dist/benchmarks/storage-policy-benchmarks.js +618 -0
  64. package/router/dist/benchmarks/vault-index-benchmarks.d.ts +56 -0
  65. package/router/dist/benchmarks/vault-index-benchmarks.js +322 -0
  66. package/router/dist/config/config.d.ts +103 -0
  67. package/router/dist/config/config.js +632 -0
  68. package/{plugins/freeflow/router/dist → router/dist/config}/router-contract.d.ts +2 -2
  69. package/router/dist/config/router-contract.js +54 -0
  70. package/{plugins/freeflow/router/dist → router/dist/config}/schema.d.ts +19 -10
  71. package/router/dist/config/schema.js +648 -0
  72. package/router/dist/config/types.d.ts +573 -0
  73. package/router/dist/config/types.js +41 -0
  74. package/router/dist/evidence/bounded-evidence.d.ts +40 -0
  75. package/router/dist/evidence/bounded-evidence.js +323 -0
  76. package/router/dist/evidence/evidence-range-selector.d.ts +24 -0
  77. package/router/dist/evidence/evidence-range-selector.js +232 -0
  78. package/router/dist/evidence/evidence-search.d.ts +27 -0
  79. package/router/dist/evidence/evidence-search.js +490 -0
  80. package/{plugins/freeflow/router/dist → router/dist/evidence}/evidence.d.ts +18 -18
  81. package/router/dist/evidence/evidence.js +200 -0
  82. package/router/dist/evidence/failure-contracts.d.ts +37 -0
  83. package/router/dist/evidence/failure-contracts.js +166 -0
  84. package/router/dist/evidence/line-ranges.d.ts +24 -0
  85. package/router/dist/evidence/line-ranges.js +22 -0
  86. package/router/dist/experiments/local-index.d.ts +64 -0
  87. package/router/dist/experiments/local-index.js +581 -0
  88. package/router/dist/index.d.ts +30 -0
  89. package/router/dist/index.js +32 -0
  90. package/router/dist/local/local-traversal.d.ts +18 -0
  91. package/router/dist/local/local-traversal.js +334 -0
  92. package/router/dist/processing/engine.d.ts +132 -0
  93. package/router/dist/processing/engine.js +533 -0
  94. package/router/dist/processing/reducers.d.ts +280 -0
  95. package/router/dist/processing/reducers.js +1673 -0
  96. package/router/dist/processing/renderers.d.ts +31 -0
  97. package/router/dist/processing/renderers.js +195 -0
  98. package/router/dist/processing/scripts.d.ts +91 -0
  99. package/router/dist/processing/scripts.js +386 -0
  100. package/{plugins/freeflow/router/dist → router/dist/repo}/repo-traversal.d.ts +9 -9
  101. package/router/dist/repo/repo-traversal.js +245 -0
  102. package/router/dist/routing/observed-reducers.d.ts +41 -0
  103. package/router/dist/routing/observed-reducers.js +661 -0
  104. package/router/dist/routing/observed-routing.d.ts +26 -0
  105. package/router/dist/routing/observed-routing.js +378 -0
  106. package/router/dist/routing/parsers.d.ts +16 -0
  107. package/router/dist/routing/parsers.js +521 -0
  108. package/router/dist/routing/run-filters.d.ts +60 -0
  109. package/router/dist/routing/run-filters.js +238 -0
  110. package/router/dist/routing/run-reducers.d.ts +49 -0
  111. package/router/dist/routing/run-reducers.js +122 -0
  112. package/router/dist/sandbox/adapter-roots.d.ts +46 -0
  113. package/router/dist/sandbox/adapter-roots.js +66 -0
  114. package/router/dist/sandbox/eryx-python-adapter.d.ts +13 -0
  115. package/router/dist/sandbox/eryx-python-adapter.js +868 -0
  116. package/router/dist/sandbox/jq-wasm-adapter.d.ts +9 -0
  117. package/router/dist/sandbox/jq-wasm-adapter.js +429 -0
  118. package/router/dist/sandbox/quickjs-wasi-adapter.d.ts +13 -0
  119. package/router/dist/sandbox/quickjs-wasi-adapter.js +384 -0
  120. package/router/dist/sandbox/script-sandbox.d.ts +134 -0
  121. package/router/dist/sandbox/script-sandbox.js +337 -0
  122. package/router/dist/setup/script-transform-adapters.d.ts +32 -0
  123. package/router/dist/setup/script-transform-adapters.js +278 -0
  124. package/router/dist/tools/batch.d.ts +33 -0
  125. package/router/dist/tools/batch.js +586 -0
  126. package/router/dist/tools/run.d.ts +62 -0
  127. package/router/dist/tools/run.js +1304 -0
  128. package/router/dist/tools/search.d.ts +66 -0
  129. package/router/dist/tools/search.js +1364 -0
  130. package/router/dist/transform/engine.d.ts +168 -0
  131. package/router/dist/transform/engine.js +2072 -0
  132. package/router/dist/vault/vault-index.d.ts +125 -0
  133. package/router/dist/vault/vault-index.js +416 -0
  134. package/router/dist/vault/vault.d.ts +138 -0
  135. package/router/dist/vault/vault.js +630 -0
  136. package/runtime/interaction-contract.md +16 -0
  137. package/skills/bypass/SKILL.md +82 -0
  138. package/skills/commit-work/SKILL.md +106 -0
  139. package/skills/commit-work/references/staging-decisions.md +73 -0
  140. package/skills/decision-gate/SKILL.md +48 -0
  141. package/skills/design-for-depth/SKILL.md +106 -0
  142. package/skills/design-for-depth/references/design-pressure-signals.md +314 -0
  143. package/skills/design-for-depth/references/interface-design-loop.md +156 -0
  144. package/skills/design-for-depth/references/software-design-philosophy.md +248 -0
  145. package/skills/diagnose-failure/SKILL.md +105 -0
  146. package/skills/diagnose-failure/references/diagnostic-loop-catalog.md +36 -0
  147. package/skills/diagnose-failure/references/flaky-and-performance.md +43 -0
  148. package/skills/discuss/SKILL.md +121 -0
  149. package/skills/discuss/references/checkpoints.md +66 -0
  150. package/skills/evaluate-skill/SKILL.md +105 -0
  151. package/skills/evaluate-skill/references/definition-schema.md +345 -0
  152. package/skills/evaluate-skill/references/evaluation-design.md +86 -0
  153. package/skills/evaluate-skill/references/execution-and-evidence.md +85 -0
  154. package/skills/evaluate-skill/references/review-and-revision.md +51 -0
  155. package/skills/evaluate-skill/scripts/lib/definitions.mjs +296 -0
  156. package/skills/evaluate-skill/scripts/lib/evidence.mjs +46 -0
  157. package/skills/evaluate-skill/scripts/lib/grade-comparison.mjs +60 -0
  158. package/skills/evaluate-skill/scripts/lib/grade-interaction.mjs +148 -0
  159. package/skills/evaluate-skill/scripts/lib/grade-support.mjs +126 -0
  160. package/skills/evaluate-skill/scripts/lib/grade-workspace.mjs +192 -0
  161. package/skills/evaluate-skill/scripts/lib/grade.mjs +56 -0
  162. package/skills/evaluate-skill/scripts/lib/pi.mjs +489 -0
  163. package/skills/evaluate-skill/scripts/lib/process.mjs +79 -0
  164. package/skills/evaluate-skill/scripts/lib/rpc.mjs +444 -0
  165. package/skills/evaluate-skill/scripts/lib/runner.mjs +346 -0
  166. package/skills/evaluate-skill/scripts/lib/sandbox.mjs +409 -0
  167. package/skills/evaluate-skill/scripts/lib/view.mjs +337 -0
  168. package/skills/evaluate-skill/scripts/pi-guard.mjs +164 -0
  169. package/skills/evaluate-skill/scripts/skill-eval.mjs +101 -0
  170. package/skills/execute-work/SKILL.md +99 -0
  171. package/skills/execute-work/references/code-practices.md +78 -0
  172. package/skills/execute-work/references/execution-loop.md +77 -0
  173. package/skills/finish-branch/SKILL.md +117 -0
  174. package/skills/finish-branch/references/integration-options.md +80 -0
  175. package/skills/handoff/SKILL.md +92 -0
  176. package/skills/handoff/references/templates.md +141 -0
  177. package/skills/launch-work/SKILL.md +116 -0
  178. package/skills/launch-work/references/launch-readiness.md +85 -0
  179. package/skills/migration-work/SKILL.md +115 -0
  180. package/skills/migration-work/references/migration-lifecycle.md +82 -0
  181. package/skills/mode-contract/SKILL.md +82 -0
  182. package/skills/output-router/SKILL.md +206 -0
  183. package/skills/output-router/references/safety-policy.md +40 -0
  184. package/skills/release-work/SKILL.md +118 -0
  185. package/skills/release-work/references/release-evidence.md +78 -0
  186. package/skills/review-artifact/SKILL.md +126 -0
  187. package/skills/review-artifact/references/reviewer-prompt.md +148 -0
  188. package/skills/review-work/SKILL.md +126 -0
  189. package/skills/review-work/references/reviewer-prompt.md +134 -0
  190. package/skills/review-work/references/security-risk-lens.md +67 -0
  191. package/skills/setup-freeflow/SKILL.md +98 -0
  192. package/skills/setup-freeflow/references/activation-contract.md +120 -0
  193. package/skills/setup-freeflow/references/host-setup.md +72 -0
  194. package/skills/setup-freeflow/references/output-router-setup.md +110 -0
  195. package/skills/simplify-code/SKILL.md +80 -0
  196. package/skills/simplify-code/references/simplification-patterns.md +70 -0
  197. package/skills/tdd/SKILL.md +96 -0
  198. package/skills/tdd/references/test-design.md +96 -0
  199. package/skills/track-work/SKILL.md +84 -0
  200. package/skills/track-work/references/working-record-schema.md +378 -0
  201. package/skills/verify-work/SKILL.md +85 -0
  202. package/skills/verify-work/references/browser-runtime-evidence.md +55 -0
  203. package/skills/verify-work/references/integration-evidence.md +51 -0
  204. package/skills/verify-work/references/performance-evidence.md +57 -0
  205. package/skills/workflow/SKILL.md +91 -0
  206. package/skills/workflow/references/domain-skill-composition.md +57 -0
  207. package/skills/workflow/references/workflow-loop.md +92 -0
  208. package/skills/write-plan/SKILL.md +123 -0
  209. package/skills/write-plan/references/plan-shapes.md +156 -0
  210. package/skills/write-skill/SKILL.md +119 -0
  211. package/skills/write-skill/references/activation-boundaries.md +62 -0
  212. package/skills/write-skill/references/agent-first-instructions.md +87 -0
  213. package/skills/write-skill/references/progressive-disclosure.md +63 -0
  214. package/skills/write-skill/scripts/lib/skill-author.mjs +653 -0
  215. package/skills/write-skill/scripts/skill-author.mjs +104 -0
  216. package/skills/write-spec/SKILL.md +104 -0
  217. package/skills/write-spec/references/artifact-standards.md +86 -0
  218. package/skills/write-spec/references/decision-records.md +77 -0
  219. package/skills/write-spec/references/spec-shapes.md +128 -0
  220. package/plugins/freeflow/command-surface.json +0 -77
  221. package/plugins/freeflow/docs/README.md +0 -40
  222. package/plugins/freeflow/docs/adr/0001-three-modes.md +0 -27
  223. package/plugins/freeflow/docs/adr/0002-evals-before-hooks.md +0 -17
  224. package/plugins/freeflow/docs/adr/0003-release-boundary.md +0 -21
  225. package/plugins/freeflow/docs/adr/0004-research-replaces-shallow-discovery-skills.md +0 -26
  226. package/plugins/freeflow/docs/adr/README.md +0 -8
  227. package/plugins/freeflow/docs/architecture.md +0 -86
  228. package/plugins/freeflow/docs/output-router.md +0 -212
  229. package/plugins/freeflow/docs/release-evidence.md +0 -69
  230. package/plugins/freeflow/docs/skills.md +0 -43
  231. package/plugins/freeflow/docs/workflow.md +0 -57
  232. package/plugins/freeflow/hooks/freeflow-runtime-context.mjs +0 -257
  233. package/plugins/freeflow/pi-extension/index.js +0 -1176
  234. package/plugins/freeflow/router/dist/benchmark-harness.js +0 -92
  235. package/plugins/freeflow/router/dist/benchmarks.d.ts +0 -102
  236. package/plugins/freeflow/router/dist/benchmarks.js +0 -880
  237. package/plugins/freeflow/router/dist/bounded-evidence.d.ts +0 -40
  238. package/plugins/freeflow/router/dist/bounded-evidence.js +0 -310
  239. package/plugins/freeflow/router/dist/codex-qa-benchmarks.d.ts +0 -91
  240. package/plugins/freeflow/router/dist/codex-qa-benchmarks.js +0 -461
  241. package/plugins/freeflow/router/dist/command-benchmarks.d.ts +0 -128
  242. package/plugins/freeflow/router/dist/command-benchmarks.js +0 -652
  243. package/plugins/freeflow/router/dist/config.d.ts +0 -22
  244. package/plugins/freeflow/router/dist/config.js +0 -114
  245. package/plugins/freeflow/router/dist/evidence-range-selector.d.ts +0 -22
  246. package/plugins/freeflow/router/dist/evidence-range-selector.js +0 -180
  247. package/plugins/freeflow/router/dist/evidence-search.d.ts +0 -25
  248. package/plugins/freeflow/router/dist/evidence-search.js +0 -450
  249. package/plugins/freeflow/router/dist/evidence.js +0 -196
  250. package/plugins/freeflow/router/dist/experimental-local-index.d.ts +0 -1
  251. package/plugins/freeflow/router/dist/experimental-local-index.js +0 -1
  252. package/plugins/freeflow/router/dist/experiments/local-index.d.ts +0 -58
  253. package/plugins/freeflow/router/dist/experiments/local-index.js +0 -562
  254. package/plugins/freeflow/router/dist/index-benchmarks.d.ts +0 -84
  255. package/plugins/freeflow/router/dist/index-benchmarks.js +0 -458
  256. package/plugins/freeflow/router/dist/index.d.ts +0 -12
  257. package/plugins/freeflow/router/dist/index.js +0 -12
  258. package/plugins/freeflow/router/dist/line-ranges.d.ts +0 -22
  259. package/plugins/freeflow/router/dist/line-ranges.js +0 -22
  260. package/plugins/freeflow/router/dist/parsers.d.ts +0 -16
  261. package/plugins/freeflow/router/dist/parsers.js +0 -473
  262. package/plugins/freeflow/router/dist/repo-traversal.js +0 -242
  263. package/plugins/freeflow/router/dist/retrieve.d.ts +0 -33
  264. package/plugins/freeflow/router/dist/retrieve.js +0 -855
  265. package/plugins/freeflow/router/dist/router-contract.js +0 -54
  266. package/plugins/freeflow/router/dist/run.d.ts +0 -26
  267. package/plugins/freeflow/router/dist/run.js +0 -356
  268. package/plugins/freeflow/router/dist/schema.js +0 -354
  269. package/plugins/freeflow/router/dist/types.d.ts +0 -234
  270. package/plugins/freeflow/router/dist/types.js +0 -9
  271. package/plugins/freeflow/router/dist/vault.d.ts +0 -80
  272. package/plugins/freeflow/router/dist/vault.js +0 -484
  273. package/plugins/freeflow/skills/bypass/SKILL.md +0 -68
  274. package/plugins/freeflow/skills/commit-work/SKILL.md +0 -98
  275. package/plugins/freeflow/skills/commit-work/references/staging-decisions.md +0 -51
  276. package/plugins/freeflow/skills/diagnose-failure/SKILL.md +0 -95
  277. package/plugins/freeflow/skills/diagnose-failure/references/feedback-loop-catalog.md +0 -25
  278. package/plugins/freeflow/skills/diagnose-failure/references/flaky-and-performance.md +0 -31
  279. package/plugins/freeflow/skills/evaluate-skill/SKILL.md +0 -96
  280. package/plugins/freeflow/skills/evaluate-skill/references/eval-patterns.md +0 -41
  281. package/plugins/freeflow/skills/evaluate-skill/references/grading-priority.md +0 -33
  282. package/plugins/freeflow/skills/execute-plan/SKILL.md +0 -132
  283. package/plugins/freeflow/skills/execute-plan/references/execution-map.md +0 -122
  284. package/plugins/freeflow/skills/handoff/SKILL.md +0 -66
  285. package/plugins/freeflow/skills/handoff/references/templates.md +0 -64
  286. package/plugins/freeflow/skills/interview-gate/SKILL.md +0 -90
  287. package/plugins/freeflow/skills/mode-contract/SKILL.md +0 -79
  288. package/plugins/freeflow/skills/output-router/SKILL.md +0 -69
  289. package/plugins/freeflow/skills/output-router/references/safety-policy.md +0 -33
  290. package/plugins/freeflow/skills/research/ARTIFACT-DESTINATIONS.md +0 -58
  291. package/plugins/freeflow/skills/research/CHECKPOINTS.md +0 -74
  292. package/plugins/freeflow/skills/research/LANGUAGE.md +0 -61
  293. package/plugins/freeflow/skills/research/SKILL.md +0 -128
  294. package/plugins/freeflow/skills/review-artifact/SKILL.md +0 -115
  295. package/plugins/freeflow/skills/review-artifact/references/reviewer-prompt.md +0 -56
  296. package/plugins/freeflow/skills/review-work/SKILL.md +0 -96
  297. package/plugins/freeflow/skills/review-work/references/reviewer-prompt.md +0 -97
  298. package/plugins/freeflow/skills/setup-freeflow/SKILL.md +0 -94
  299. package/plugins/freeflow/skills/setup-freeflow/references/activation-contract.md +0 -99
  300. package/plugins/freeflow/skills/setup-freeflow/references/host-setup.md +0 -35
  301. package/plugins/freeflow/skills/setup-freeflow/references/output-router-setup.md +0 -52
  302. package/plugins/freeflow/skills/verify-work/SKILL.md +0 -62
  303. package/plugins/freeflow/skills/workflow/SKILL.md +0 -95
  304. package/plugins/freeflow/skills/workflow/references/workflow-map.md +0 -102
  305. package/plugins/freeflow/skills/write-plan/SKILL.md +0 -88
  306. package/plugins/freeflow/skills/write-plan/references/plan-shapes.md +0 -70
  307. package/plugins/freeflow/skills/write-skill/SKILL.md +0 -88
  308. package/plugins/freeflow/skills/write-spec/SKILL.md +0 -90
  309. package/plugins/freeflow/skills/write-spec/references/artifact-standards.md +0 -67
  310. package/plugins/freeflow/skills/write-spec/references/spec-shapes.md +0 -69
  311. /package/{plugins/freeflow/assets → assets}/freeflow-plugin-icon-transparent.png +0 -0
  312. /package/{plugins/freeflow/hooks → hooks}/hooks.json +0 -0
@@ -0,0 +1,653 @@
1
+ import { constants as fsConstants } from "node:fs";
2
+ import { access, lstat, mkdir, readFile, readdir, realpath, writeFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+
5
+ const SKILL_NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
6
+ const EXTERNAL_LINK = /^[a-z][a-z0-9+.-]*:/i;
7
+ const YAML_NON_STRING =
8
+ /^(?:~|null|true|false|[-+]?(?:\d[\d_]*(?:\.\d[\d_]*)?(?:e[-+]?\d+)?|\.inf|\.nan)|\d{4}-\d{2}-\d{2}(?:[Tt ].*)?)$/i;
9
+
10
+ export class SkillAuthorError extends Error {
11
+ constructor(code, message, exitCode = 1) {
12
+ super(message);
13
+ this.name = "SkillAuthorError";
14
+ this.code = code;
15
+ this.exitCode = exitCode;
16
+ }
17
+ }
18
+
19
+ export function validateSkillName(name) {
20
+ if (typeof name !== "string" || name.length < 1 || name.length > 64 || !SKILL_NAME.test(name)) {
21
+ throw new SkillAuthorError(
22
+ "invalid-name",
23
+ "Skill name must be 1-64 lowercase letters, numbers, or single hyphen-separated segments",
24
+ 2,
25
+ );
26
+ }
27
+ }
28
+
29
+ export function validateDescription(description) {
30
+ if (typeof description !== "string" || description.length < 1 || description.length > 1024) {
31
+ throw new SkillAuthorError("invalid-description", "Description must be 1-1024 characters", 2);
32
+ }
33
+ }
34
+
35
+ export async function initSkill({ directory, name, description }) {
36
+ validateSkillName(name);
37
+ validateDescription(description);
38
+
39
+ const skillDirectory = path.resolve(directory);
40
+ const skillFile = path.join(skillDirectory, "SKILL.md");
41
+ const title = name
42
+ .split("-")
43
+ .map((word) => word[0].toUpperCase() + word.slice(1))
44
+ .join(" ");
45
+ const contents = `---\nname: ${JSON.stringify(name)}\ndescription: ${JSON.stringify(description)}\n---\n\n# ${title}\n`;
46
+
47
+ await mkdir(skillDirectory, { recursive: true });
48
+ try {
49
+ await writeFile(skillFile, contents, { encoding: "utf8", flag: "wx" });
50
+ } catch (error) {
51
+ if (error?.code === "EEXIST") {
52
+ throw new SkillAuthorError("skill-exists", `Refusing to overwrite existing skill file: ${skillFile}`);
53
+ }
54
+ throw error;
55
+ }
56
+
57
+ return {
58
+ command: "init",
59
+ status: "created",
60
+ skillDirectory,
61
+ skillFile,
62
+ name,
63
+ };
64
+ }
65
+
66
+ function toPortablePath(value) {
67
+ return value.split(path.sep).join("/");
68
+ }
69
+
70
+ function relativePath(root, target) {
71
+ return toPortablePath(path.relative(root, target));
72
+ }
73
+
74
+ function isContained(root, target) {
75
+ const relative = path.relative(root, target);
76
+ return relative === "" || (!relative.startsWith(`..${path.sep}`) && relative !== ".." && !path.isAbsolute(relative));
77
+ }
78
+
79
+ function errorFinding(code, source, message) {
80
+ return { severity: "error", code, path: source, message };
81
+ }
82
+
83
+ function parseScalar(value) {
84
+ if (value.startsWith('"')) {
85
+ try {
86
+ const parsed = JSON.parse(value);
87
+ return typeof parsed === "string" ? { value: parsed } : { error: "must be a string scalar" };
88
+ } catch {
89
+ return { error: "contains an invalid double-quoted scalar" };
90
+ }
91
+ }
92
+
93
+ if (
94
+ value.length === 0 ||
95
+ YAML_NON_STRING.test(value) ||
96
+ /^[-'?:,[\]{}#&*!|>%@`]/.test(value) ||
97
+ value.includes(": ") ||
98
+ value.includes(" #")
99
+ ) {
100
+ return { error: "uses unsupported compact scalar syntax; use a JSON-compatible double-quoted string" };
101
+ }
102
+ return { value };
103
+ }
104
+
105
+ function parseSkillDocument(contents) {
106
+ const findings = [];
107
+ const match = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(contents);
108
+ if (!match) {
109
+ return {
110
+ frontmatter: {},
111
+ body: contents,
112
+ findings: [errorFinding("invalid-frontmatter", "SKILL.md", "SKILL.md must start with delimited frontmatter")],
113
+ };
114
+ }
115
+
116
+ const frontmatter = {};
117
+ for (const [index, line] of match[1].split(/\r?\n/).entries()) {
118
+ if (line.trim() === "") continue;
119
+ const field = /^([A-Za-z][A-Za-z0-9-]*):\s*(.*)$/.exec(line);
120
+ if (!field) {
121
+ findings.push(
122
+ errorFinding("unsupported-frontmatter", "SKILL.md", `Unsupported frontmatter syntax on line ${index + 2}`),
123
+ );
124
+ continue;
125
+ }
126
+ if (Object.hasOwn(frontmatter, field[1])) {
127
+ findings.push(errorFinding("duplicate-frontmatter", "SKILL.md", `Duplicate frontmatter field: ${field[1]}`));
128
+ continue;
129
+ }
130
+ const scalar = parseScalar(field[2]);
131
+ if (scalar.error) {
132
+ findings.push(errorFinding("unsupported-frontmatter", "SKILL.md", `${field[1]} ${scalar.error}`));
133
+ continue;
134
+ }
135
+ frontmatter[field[1]] = scalar.value;
136
+ }
137
+
138
+ return {
139
+ frontmatter,
140
+ body: contents.slice(match[0].length),
141
+ findings,
142
+ };
143
+ }
144
+
145
+ function isEscaped(text, index) {
146
+ let backslashes = 0;
147
+ for (let cursor = index - 1; cursor >= 0 && text[cursor] === "\\"; cursor -= 1) backslashes += 1;
148
+ return backslashes % 2 === 1;
149
+ }
150
+
151
+ function findUnescapedMarker(text, marker, start) {
152
+ let index = text.indexOf(marker, start);
153
+ while (index !== -1 && isEscaped(text, index)) index = text.indexOf(marker, index + marker.length);
154
+ return index;
155
+ }
156
+
157
+ function maskInlineCode(line) {
158
+ let masked = "";
159
+ for (let index = 0; index < line.length;) {
160
+ if (line[index] !== "`" || isEscaped(line, index)) {
161
+ masked += line[index];
162
+ index += 1;
163
+ continue;
164
+ }
165
+
166
+ let markerLength = 1;
167
+ while (line[index + markerLength] === "`") markerLength += 1;
168
+ const marker = "`".repeat(markerLength);
169
+ const closing = findUnescapedMarker(line, marker, index + markerLength);
170
+ if (closing === -1) {
171
+ masked += marker;
172
+ index += markerLength;
173
+ continue;
174
+ }
175
+
176
+ masked += " ".repeat(closing + markerLength - index);
177
+ index = closing + markerLength;
178
+ }
179
+ return masked;
180
+ }
181
+
182
+ function openingFence(line) {
183
+ const match = /^ {0,3}(`{3,}|~{3,})(.*)$/.exec(line);
184
+ if (!match || (match[1][0] === "`" && match[2].includes("`"))) return null;
185
+ return { marker: match[1][0], length: match[1].length };
186
+ }
187
+
188
+ function closesFence(line, fence) {
189
+ const match = /^ {0,3}([`~]+)[ \t]*$/.exec(line);
190
+ return Boolean(match && match[1][0] === fence.marker && match[1].length >= fence.length);
191
+ }
192
+
193
+ function normalizeReferenceLabel(label) {
194
+ return label.replace(/\s+/g, " ").trim().toLowerCase();
195
+ }
196
+
197
+ function findClosingBracket(text, openingIndex) {
198
+ let depth = 0;
199
+ for (let index = openingIndex; index < text.length; index += 1) {
200
+ if (text[index] === "\\") {
201
+ index += 1;
202
+ continue;
203
+ }
204
+ if (text[index] === "[") depth += 1;
205
+ if (text[index] === "]") {
206
+ depth -= 1;
207
+ if (depth === 0) return index;
208
+ }
209
+ }
210
+ return -1;
211
+ }
212
+
213
+ function skipHorizontalSpace(text, start) {
214
+ let index = start;
215
+ while (text[index] === " " || text[index] === "\t") index += 1;
216
+ return index;
217
+ }
218
+
219
+ function readQuotedTitle(text, start) {
220
+ const opener = text[start];
221
+ if (opener !== '"' && opener !== "'" && opener !== "(") return null;
222
+ const closer = opener === "(" ? ")" : opener;
223
+ for (let index = start + 1; index < text.length; index += 1) {
224
+ if (text[index] === "\\") {
225
+ index += 1;
226
+ continue;
227
+ }
228
+ if (text[index] === closer) return index + 1;
229
+ }
230
+ return null;
231
+ }
232
+
233
+ function readLinkDestination(text, start, inline) {
234
+ let index = skipHorizontalSpace(text, start);
235
+ let destination = "";
236
+
237
+ if (text[index] === "<") {
238
+ const closing = text.indexOf(">", index + 1);
239
+ if (closing === -1) return null;
240
+ destination = text.slice(index + 1, closing);
241
+ index = closing + 1;
242
+ } else {
243
+ const destinationStart = index;
244
+ let depth = 0;
245
+ for (; index < text.length; index += 1) {
246
+ const character = text[index];
247
+ if (character === "\\") {
248
+ index += 1;
249
+ continue;
250
+ }
251
+ if (character === "(") {
252
+ depth += 1;
253
+ continue;
254
+ }
255
+ if (character === ")") {
256
+ if (depth === 0 && inline) break;
257
+ if (depth === 0) return null;
258
+ depth -= 1;
259
+ continue;
260
+ }
261
+ if ((character === " " || character === "\t") && depth === 0) break;
262
+ }
263
+ if (depth !== 0) return null;
264
+ destination = text.slice(destinationStart, index);
265
+ }
266
+
267
+ const beforeSpace = index;
268
+ index = skipHorizontalSpace(text, index);
269
+ if (index > beforeSpace) {
270
+ const afterTitle = readQuotedTitle(text, index);
271
+ if (afterTitle !== null) index = skipHorizontalSpace(text, afterTitle);
272
+ }
273
+
274
+ if (inline) {
275
+ if (text[index] !== ")") return null;
276
+ return { destination, end: index + 1 };
277
+ }
278
+ if (index !== text.length || destination === "") return null;
279
+ return { destination, end: index };
280
+ }
281
+
282
+ function readReferenceDefinition(line) {
283
+ const match = /^ {0,3}\[([^\]]+)\]:[ \t]*(.*)$/.exec(line);
284
+ if (!match) return null;
285
+ const target = readLinkDestination(match[2], 0, false);
286
+ if (!target) return null;
287
+ return { label: normalizeReferenceLabel(match[1]), destination: target.destination };
288
+ }
289
+
290
+ function scanLineLinks(line, targets) {
291
+ for (let index = 0; index < line.length; index += 1) {
292
+ if (line[index] !== "[" || (index > 0 && line[index - 1] === "\\")) continue;
293
+ const closing = findClosingBracket(line, index);
294
+ if (closing === -1) continue;
295
+
296
+ const label = line.slice(index + 1, closing);
297
+ const next = line[closing + 1];
298
+ if (next === "(") {
299
+ const inline = readLinkDestination(line, closing + 2, true);
300
+ if (inline) {
301
+ targets.push({ destination: inline.destination });
302
+ index = inline.end - 1;
303
+ } else {
304
+ index = closing;
305
+ }
306
+ continue;
307
+ }
308
+
309
+ if (next === "[") {
310
+ const referenceClosing = findClosingBracket(line, closing + 1);
311
+ if (referenceClosing !== -1) {
312
+ const explicit = line.slice(closing + 2, referenceClosing);
313
+ targets.push({ reference: normalizeReferenceLabel(explicit || label) });
314
+ index = referenceClosing;
315
+ continue;
316
+ }
317
+ }
318
+
319
+ targets.push({ reference: normalizeReferenceLabel(label) });
320
+ index = closing;
321
+ }
322
+ }
323
+
324
+ function parseMarkdown(contents) {
325
+ const definitions = new Map();
326
+ const targets = [];
327
+ let fence = null;
328
+ let heading = null;
329
+ let previousText = null;
330
+
331
+ for (const line of contents.split(/\r?\n/)) {
332
+ if (fence) {
333
+ if (closesFence(line, fence)) fence = null;
334
+ continue;
335
+ }
336
+
337
+ const opened = openingFence(line);
338
+ if (opened) {
339
+ fence = opened;
340
+ previousText = null;
341
+ continue;
342
+ }
343
+
344
+ const prose = maskInlineCode(line);
345
+ if (!heading) {
346
+ const atx = /^ {0,3}#[ \t]+(.+)$/.exec(prose);
347
+ if (atx) {
348
+ const text = atx[1].replace(/[ \t]+#+[ \t]*$/, "").trim();
349
+ if (text) heading = `# ${text}`;
350
+ } else if (/^ {0,3}=+[ \t]*$/.test(prose) && previousText) {
351
+ heading = `# ${previousText}`;
352
+ }
353
+ }
354
+
355
+ const definition = readReferenceDefinition(prose);
356
+ if (definition) {
357
+ if (!definitions.has(definition.label)) definitions.set(definition.label, definition.destination);
358
+ previousText = null;
359
+ continue;
360
+ }
361
+
362
+ scanLineLinks(prose, targets);
363
+ previousText = prose.trim() || null;
364
+ }
365
+
366
+ const links = [];
367
+ for (const target of targets) {
368
+ if (Object.hasOwn(target, "destination")) {
369
+ links.push(target.destination);
370
+ } else if (definitions.has(target.reference)) {
371
+ links.push(definitions.get(target.reference));
372
+ }
373
+ }
374
+ return { heading, links };
375
+ }
376
+
377
+ function validateDocument(document) {
378
+ const findings = [...document.findings];
379
+ try {
380
+ validateSkillName(document.frontmatter.name);
381
+ } catch (error) {
382
+ findings.push(errorFinding("invalid-name", "SKILL.md", error.message));
383
+ }
384
+ try {
385
+ validateDescription(document.frontmatter.description);
386
+ } catch (error) {
387
+ findings.push(errorFinding("invalid-description", "SKILL.md", error.message));
388
+ }
389
+
390
+ const heading = parseMarkdown(document.body).heading;
391
+ if (!heading) {
392
+ findings.push(errorFinding("missing-heading", "SKILL.md", "Skill body must contain a top-level heading"));
393
+ }
394
+ return { findings, heading };
395
+ }
396
+
397
+ function markdownLinks(contents) {
398
+ const links = [];
399
+ for (let target of parseMarkdown(contents).links) {
400
+ target = target.trim();
401
+ if (!target || target.startsWith("#") || EXTERNAL_LINK.test(target)) continue;
402
+ target = target.split("#", 1)[0];
403
+ try {
404
+ links.push(decodeURIComponent(target));
405
+ } catch {
406
+ links.push(target);
407
+ }
408
+ }
409
+ return links;
410
+ }
411
+
412
+ async function fileResource(file, displayPath) {
413
+ await access(file, fsConstants.R_OK);
414
+ const stats = await lstat(file);
415
+ if (!stats.isFile() && !stats.isSymbolicLink()) {
416
+ throw new Error("resource is not a file");
417
+ }
418
+ const contents = await readFile(file);
419
+ return { path: displayPath, bytes: contents.byteLength, contents: contents.toString("utf8") };
420
+ }
421
+
422
+ async function walkFiles(root, directory = root) {
423
+ const files = [];
424
+ for (const entry of await readdir(directory, { withFileTypes: true })) {
425
+ const entryPath = path.join(directory, entry.name);
426
+ if (entry.isDirectory()) {
427
+ files.push(...(await walkFiles(root, entryPath)));
428
+ } else if (entry.isFile() || entry.isSymbolicLink()) {
429
+ const stats = await lstat(entryPath);
430
+ files.push({ path: relativePath(root, entryPath), bytes: stats.size });
431
+ }
432
+ }
433
+ return files;
434
+ }
435
+
436
+ async function inspectResources({ skillDirectory, packageRoot, canonicalSkill, canonicalPackage, body, findings }) {
437
+ const localReferences = new Map();
438
+ const packageDependencies = new Map();
439
+ const visitedMarkdown = new Set();
440
+
441
+ async function followMarkdown(sourceFile, sourceContents) {
442
+ const canonicalSource = await realpath(sourceFile);
443
+ if (visitedMarkdown.has(canonicalSource)) return;
444
+ visitedMarkdown.add(canonicalSource);
445
+
446
+ for (const linkedTarget of markdownLinks(sourceContents)) {
447
+ const lexicalTarget = path.resolve(path.dirname(sourceFile), linkedTarget);
448
+ const source = relativePath(skillDirectory, sourceFile);
449
+ if (!isContained(packageRoot, lexicalTarget)) {
450
+ findings.push(errorFinding("package-escape", source, `Linked resource escapes package: ${linkedTarget}`));
451
+ continue;
452
+ }
453
+
454
+ const local = isContained(skillDirectory, lexicalTarget);
455
+ let canonicalTarget;
456
+ try {
457
+ canonicalTarget = await realpath(lexicalTarget);
458
+ } catch {
459
+ findings.push(errorFinding("missing-resource", source, `Linked resource is missing: ${linkedTarget}`));
460
+ continue;
461
+ }
462
+
463
+ const canonicalBoundary = local ? canonicalSkill : canonicalPackage;
464
+ if (!isContained(canonicalBoundary, canonicalTarget)) {
465
+ findings.push(
466
+ errorFinding(
467
+ local ? "local-symlink-escape" : "package-symlink-escape",
468
+ source,
469
+ `Linked resource escapes its canonical boundary: ${linkedTarget}`,
470
+ ),
471
+ );
472
+ continue;
473
+ }
474
+
475
+ const displayPath = relativePath(skillDirectory, lexicalTarget);
476
+ let resource;
477
+ try {
478
+ resource = await fileResource(lexicalTarget, displayPath);
479
+ } catch {
480
+ findings.push(
481
+ errorFinding("unreadable-resource", source, `Linked resource is not a readable file: ${linkedTarget}`),
482
+ );
483
+ continue;
484
+ }
485
+
486
+ if (local) {
487
+ localReferences.set(displayPath, { path: resource.path, bytes: resource.bytes });
488
+ if (path.extname(lexicalTarget).toLowerCase() === ".md") {
489
+ await followMarkdown(lexicalTarget, resource.contents);
490
+ }
491
+ } else {
492
+ packageDependencies.set(displayPath, { path: resource.path, bytes: resource.bytes });
493
+ }
494
+ }
495
+ }
496
+
497
+ await followMarkdown(path.join(skillDirectory, "SKILL.md"), body);
498
+ const allFiles = await walkFiles(skillDirectory);
499
+ const linkedFiles = new Set(localReferences.keys());
500
+ const scripts = allFiles.filter((file) => file.path.startsWith("scripts/"));
501
+ const unlinkedFiles = allFiles.filter((file) => file.path !== "SKILL.md" && !linkedFiles.has(file.path));
502
+
503
+ return {
504
+ localReferences: [...localReferences.values()].sort((left, right) => left.path.localeCompare(right.path)),
505
+ packageDependencies: [...packageDependencies.values()].sort((left, right) => left.path.localeCompare(right.path)),
506
+ scripts: scripts.sort((left, right) => left.path.localeCompare(right.path)),
507
+ unlinkedFiles: unlinkedFiles.sort((left, right) => left.path.localeCompare(right.path)),
508
+ };
509
+ }
510
+
511
+ async function findPackageRoot(skillDirectory) {
512
+ const fallback = path.dirname(skillDirectory);
513
+ let candidate = fallback;
514
+ while (true) {
515
+ try {
516
+ await access(path.join(candidate, "package.json"), fsConstants.R_OK);
517
+ return candidate;
518
+ } catch {
519
+ const parent = path.dirname(candidate);
520
+ if (parent === candidate) return fallback;
521
+ candidate = parent;
522
+ }
523
+ }
524
+ }
525
+
526
+ async function analyzeSkill(directory, packageDirectory) {
527
+ const skillDirectory = path.resolve(directory);
528
+ const packageRoot = packageDirectory ? path.resolve(packageDirectory) : await findPackageRoot(skillDirectory);
529
+ const findings = [];
530
+
531
+ if (!isContained(packageRoot, skillDirectory)) {
532
+ findings.push(errorFinding("skill-package-escape", ".", "Skill directory must be contained by the package root"));
533
+ return {
534
+ skillDirectory,
535
+ packageRoot,
536
+ frontmatter: {},
537
+ heading: null,
538
+ body: "",
539
+ resources: emptyResources(),
540
+ findings,
541
+ };
542
+ }
543
+
544
+ let canonicalPackage;
545
+ let canonicalSkill;
546
+ let canonicalSkillFile;
547
+ let contents;
548
+ try {
549
+ [canonicalPackage, canonicalSkill, canonicalSkillFile, contents] = await Promise.all([
550
+ realpath(packageRoot),
551
+ realpath(skillDirectory),
552
+ realpath(path.join(skillDirectory, "SKILL.md")),
553
+ readFile(path.join(skillDirectory, "SKILL.md"), "utf8"),
554
+ ]);
555
+ } catch {
556
+ findings.push(errorFinding("unreadable-skill", "SKILL.md", "Skill directory and SKILL.md must be readable"));
557
+ return {
558
+ skillDirectory,
559
+ packageRoot,
560
+ frontmatter: {},
561
+ heading: null,
562
+ body: "",
563
+ resources: emptyResources(),
564
+ findings,
565
+ };
566
+ }
567
+
568
+ if (!isContained(canonicalPackage, canonicalSkill)) {
569
+ findings.push(errorFinding("skill-symlink-escape", ".", "Skill directory escapes the canonical package root"));
570
+ return {
571
+ skillDirectory,
572
+ packageRoot,
573
+ frontmatter: {},
574
+ heading: null,
575
+ body: "",
576
+ resources: emptyResources(),
577
+ findings,
578
+ };
579
+ }
580
+ if (!isContained(canonicalSkill, canonicalSkillFile)) {
581
+ findings.push(
582
+ errorFinding("skill-file-symlink-escape", "SKILL.md", "SKILL.md escapes the canonical skill directory"),
583
+ );
584
+ return {
585
+ skillDirectory,
586
+ packageRoot,
587
+ frontmatter: {},
588
+ heading: null,
589
+ body: "",
590
+ resources: emptyResources(),
591
+ findings,
592
+ };
593
+ }
594
+
595
+ const document = parseSkillDocument(contents);
596
+ const validation = validateDocument(document);
597
+ findings.push(...validation.findings);
598
+ const resources = await inspectResources({
599
+ skillDirectory,
600
+ packageRoot,
601
+ canonicalSkill,
602
+ canonicalPackage,
603
+ body: document.body,
604
+ findings,
605
+ });
606
+
607
+ return {
608
+ skillDirectory,
609
+ packageRoot,
610
+ frontmatter: document.frontmatter,
611
+ heading: validation.heading,
612
+ body: document.body,
613
+ resources,
614
+ findings,
615
+ };
616
+ }
617
+
618
+ function emptyResources() {
619
+ return { localReferences: [], packageDependencies: [], scripts: [], unlinkedFiles: [] };
620
+ }
621
+
622
+ export async function validateSkill({ directory, packageRoot }) {
623
+ const analysis = await analyzeSkill(directory, packageRoot);
624
+ return {
625
+ command: "validate",
626
+ status: analysis.findings.some((finding) => finding.severity === "error") ? "invalid" : "valid",
627
+ skillDirectory: analysis.skillDirectory,
628
+ packageRoot: analysis.packageRoot,
629
+ frontmatter: analysis.frontmatter,
630
+ heading: analysis.heading,
631
+ resources: analysis.resources,
632
+ findings: analysis.findings,
633
+ };
634
+ }
635
+
636
+ export async function inspectSkill({ directory, packageRoot }) {
637
+ const analysis = await analyzeSkill(directory, packageRoot);
638
+ const invalid = analysis.findings.some((finding) => finding.severity === "error");
639
+ return {
640
+ command: "inspect",
641
+ status: invalid ? "invalid" : "ok",
642
+ skillDirectory: analysis.skillDirectory,
643
+ packageRoot: analysis.packageRoot,
644
+ skill: {
645
+ name: analysis.frontmatter.name ?? null,
646
+ description: analysis.frontmatter.description ?? null,
647
+ heading: analysis.heading,
648
+ bodyBytes: Buffer.byteLength(analysis.body, "utf8"),
649
+ },
650
+ resources: analysis.resources,
651
+ findings: analysis.findings,
652
+ };
653
+ }