xcoder-dna 0.0.0-stage → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (390) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +87 -2
  3. package/dist/agents/ai-chat-debate.d.ts +36 -0
  4. package/dist/agents/ai-chat-debate.js +300 -0
  5. package/dist/agents/ai-chat-report-writer.d.ts +7 -0
  6. package/dist/agents/ai-chat-report-writer.js +100 -0
  7. package/dist/agents/duplicate-detector.d.ts +41 -0
  8. package/dist/agents/duplicate-detector.js +65 -0
  9. package/dist/agents/genesis-agent.d.ts +61 -0
  10. package/dist/agents/genesis-agent.js +340 -0
  11. package/dist/agents/samurai-agent.d.ts +53 -0
  12. package/dist/agents/samurai-agent.js +279 -0
  13. package/dist/agents/sub-agent.d.ts +56 -0
  14. package/dist/agents/sub-agent.js +353 -0
  15. package/dist/backlog/backlog-requirement-validator.d.ts +10 -0
  16. package/dist/backlog/backlog-requirement-validator.js +60 -0
  17. package/dist/backlog/backlog-store.d.ts +29 -0
  18. package/dist/backlog/backlog-store.js +148 -0
  19. package/dist/backlog/po-agent.d.ts +16 -0
  20. package/dist/backlog/po-agent.js +121 -0
  21. package/dist/backlog/types.d.ts +25 -0
  22. package/dist/backlog/types.js +4 -0
  23. package/dist/bench/run.d.ts +11 -0
  24. package/dist/bench/run.js +185 -0
  25. package/dist/bench/types.d.ts +50 -0
  26. package/dist/bench/types.js +1 -0
  27. package/dist/cli/swarm.d.ts +10 -0
  28. package/dist/cli/swarm.js +100 -0
  29. package/dist/cli/task.d.ts +14 -0
  30. package/dist/cli/task.js +121 -0
  31. package/dist/cli/wbc-arg.d.ts +6 -0
  32. package/dist/cli/wbc-arg.js +40 -0
  33. package/dist/cli/wbc.d.ts +45 -0
  34. package/dist/cli/wbc.js +98 -0
  35. package/dist/commands/license.d.ts +9 -0
  36. package/dist/commands/license.js +140 -0
  37. package/dist/commands/release.d.ts +44 -0
  38. package/dist/commands/release.js +180 -0
  39. package/dist/commands/test.d.ts +50 -0
  40. package/dist/commands/test.js +385 -0
  41. package/dist/config/loadConfig.d.ts +82 -0
  42. package/dist/config/loadConfig.js +240 -0
  43. package/dist/config/secure-mode.d.ts +29 -0
  44. package/dist/config/secure-mode.js +63 -0
  45. package/dist/config/user-config.d.ts +39 -0
  46. package/dist/config/user-config.js +202 -0
  47. package/dist/engine/reasoning-engine.d.ts +21 -0
  48. package/dist/engine/reasoning-engine.js +59 -0
  49. package/dist/events/bus.d.ts +22 -0
  50. package/dist/events/bus.js +18 -0
  51. package/dist/index.d.ts +79 -0
  52. package/dist/index.js +77 -0
  53. package/dist/indexing/ignoreRules.d.ts +1 -0
  54. package/dist/indexing/ignoreRules.js +53 -0
  55. package/dist/instance-swarm/instance-swarm-orchestrator.d.ts +57 -0
  56. package/dist/instance-swarm/instance-swarm-orchestrator.js +204 -0
  57. package/dist/instance-swarm/protocol-name-resolver.d.ts +23 -0
  58. package/dist/instance-swarm/protocol-name-resolver.js +64 -0
  59. package/dist/license/gate.d.ts +31 -0
  60. package/dist/license/gate.js +188 -0
  61. package/dist/license/http-client.d.ts +43 -0
  62. package/dist/license/http-client.js +195 -0
  63. package/dist/license/manager.d.ts +39 -0
  64. package/dist/license/manager.js +175 -0
  65. package/dist/license/receipt.d.ts +27 -0
  66. package/dist/license/receipt.js +108 -0
  67. package/dist/license/state-codec.d.ts +72 -0
  68. package/dist/license/state-codec.js +218 -0
  69. package/dist/license/state-paths.d.ts +17 -0
  70. package/dist/license/state-paths.js +64 -0
  71. package/dist/license/store.d.ts +44 -0
  72. package/dist/license/store.js +223 -0
  73. package/dist/license/trust-anchor.d.ts +24 -0
  74. package/dist/license/trust-anchor.js +74 -0
  75. package/dist/license/types.d.ts +34 -0
  76. package/dist/license/types.js +19 -0
  77. package/dist/llm/budget.d.ts +23 -0
  78. package/dist/llm/budget.js +58 -0
  79. package/dist/llm/client.d.ts +53 -0
  80. package/dist/llm/client.js +122 -0
  81. package/dist/llm/factory.d.ts +14 -0
  82. package/dist/llm/factory.js +32 -0
  83. package/dist/orchestrator/defect-logger.d.ts +31 -0
  84. package/dist/orchestrator/defect-logger.js +101 -0
  85. package/dist/orchestrator/defect-manager.d.ts +118 -0
  86. package/dist/orchestrator/defect-manager.js +338 -0
  87. package/dist/orchestrator/defect-migration.d.ts +14 -0
  88. package/dist/orchestrator/defect-migration.js +174 -0
  89. package/dist/orchestrator/defect-store.d.ts +48 -0
  90. package/dist/orchestrator/defect-store.js +76 -0
  91. package/dist/orchestrator/healing.d.ts +14 -0
  92. package/dist/orchestrator/healing.js +11 -0
  93. package/dist/orchestrator/interactive-steering.d.ts +64 -0
  94. package/dist/orchestrator/interactive-steering.js +107 -0
  95. package/dist/orchestrator/interrupt-handler.d.ts +30 -0
  96. package/dist/orchestrator/interrupt-handler.js +70 -0
  97. package/dist/orchestrator/lesson-manager.d.ts +25 -0
  98. package/dist/orchestrator/lesson-manager.js +122 -0
  99. package/dist/orchestrator/progress-summary.d.ts +15 -0
  100. package/dist/orchestrator/progress-summary.js +106 -0
  101. package/dist/orchestrator/reasoning-score.d.ts +49 -0
  102. package/dist/orchestrator/reasoning-score.js +117 -0
  103. package/dist/orchestrator/swarm.d.ts +36 -0
  104. package/dist/orchestrator/swarm.js +77 -0
  105. package/dist/orchestrator/task-factory.d.ts +15 -0
  106. package/dist/orchestrator/task-factory.js +176 -0
  107. package/dist/orchestrator/validation-gate.d.ts +23 -0
  108. package/dist/orchestrator/validation-gate.js +155 -0
  109. package/dist/protocol/react-contract.d.ts +136 -0
  110. package/dist/protocol/react-contract.js +698 -0
  111. package/dist/protocol/wks-protocol-manager.d.ts +22 -0
  112. package/dist/protocol/wks-protocol-manager.js +72 -0
  113. package/dist/sdlc-runtime/conversation-runner.d.ts +8 -0
  114. package/dist/sdlc-runtime/conversation-runner.js +64 -0
  115. package/dist/sdlc-runtime/dag-utils.d.ts +15 -0
  116. package/dist/sdlc-runtime/dag-utils.js +67 -0
  117. package/dist/sdlc-runtime/defect-integration.d.ts +7 -0
  118. package/dist/sdlc-runtime/defect-integration.js +99 -0
  119. package/dist/sdlc-runtime/execution-loop.d.ts +38 -0
  120. package/dist/sdlc-runtime/execution-loop.js +274 -0
  121. package/dist/sdlc-runtime/lesson-recorder.d.ts +9 -0
  122. package/dist/sdlc-runtime/lesson-recorder.js +27 -0
  123. package/dist/sdlc-runtime/plan-initializer.d.ts +47 -0
  124. package/dist/sdlc-runtime/plan-initializer.js +135 -0
  125. package/dist/sdlc-runtime/progress-persister.d.ts +10 -0
  126. package/dist/sdlc-runtime/progress-persister.js +34 -0
  127. package/dist/sdlc-runtime/protocol-loader.d.ts +10 -0
  128. package/dist/sdlc-runtime/protocol-loader.js +21 -0
  129. package/dist/sdlc-runtime/reporting.d.ts +24 -0
  130. package/dist/sdlc-runtime/reporting.js +68 -0
  131. package/dist/sdlc-runtime/sdlc-orchestrator.d.ts +59 -0
  132. package/dist/sdlc-runtime/sdlc-orchestrator.js +170 -0
  133. package/dist/sdlc-runtime/sdlc-skill.d.ts +44 -0
  134. package/dist/sdlc-runtime/sdlc-skill.js +148 -0
  135. package/dist/sdlc-runtime/steering-handler.d.ts +14 -0
  136. package/dist/sdlc-runtime/steering-handler.js +51 -0
  137. package/dist/sdlc-runtime/trace-recorder.d.ts +10 -0
  138. package/dist/sdlc-runtime/trace-recorder.js +7 -0
  139. package/dist/security/index.d.ts +16 -0
  140. package/dist/security/index.js +12 -0
  141. package/dist/security/input-screen.d.ts +30 -0
  142. package/dist/security/input-screen.js +75 -0
  143. package/dist/security/tool-call-guard.d.ts +16 -0
  144. package/dist/security/tool-call-guard.js +163 -0
  145. package/dist/security/untrusted-content.d.ts +34 -0
  146. package/dist/security/untrusted-content.js +63 -0
  147. package/dist/skills/advanced-dfir/SKILL.md +85 -0
  148. package/dist/skills/advanced-pentest-lab/SKILL.md +88 -0
  149. package/dist/skills/analyst/SKILL.md +55 -0
  150. package/dist/skills/architect/SKILL.md +54 -0
  151. package/dist/skills/aws/SKILL.md +92 -0
  152. package/dist/skills/azure/SKILL.md +90 -0
  153. package/dist/skills/bylaw-context.d.ts +45 -0
  154. package/dist/skills/bylaw-context.js +61 -0
  155. package/dist/skills/bylaw-review.d.ts +20 -0
  156. package/dist/skills/bylaw-review.js +101 -0
  157. package/dist/skills/conversation/SKILL.md +29 -0
  158. package/dist/skills/design-patterns/SKILL.md +190 -0
  159. package/dist/skills/devops/SKILL.md +55 -0
  160. package/dist/skills/directives.d.ts +20 -0
  161. package/dist/skills/directives.js +313 -0
  162. package/dist/skills/docker/SKILL.md +76 -0
  163. package/dist/skills/docker-expert/SKILL.md +56 -0
  164. package/dist/skills/enterprise-systems-analyst/SKILL.md +83 -0
  165. package/dist/skills/enterprise-systems-analyst/references/crm.md +41 -0
  166. package/dist/skills/enterprise-systems-analyst/references/erp.md +40 -0
  167. package/dist/skills/enterprise-systems-analyst/references/financial-systems.md +35 -0
  168. package/dist/skills/enterprise-systems-analyst/references/hcm.md +38 -0
  169. package/dist/skills/enterprise-systems-analyst/references/mrp.md +37 -0
  170. package/dist/skills/enterprise-systems-analyst/references/scm.md +32 -0
  171. package/dist/skills/ethical-hacking-methodology/SKILL.md +119 -0
  172. package/dist/skills/externalSkillLoader.d.ts +25 -0
  173. package/dist/skills/externalSkillLoader.js +65 -0
  174. package/dist/skills/externalSkillRegistry.d.ts +33 -0
  175. package/dist/skills/externalSkillRegistry.js +94 -0
  176. package/dist/skills/filesystem-management/SKILL.md +61 -0
  177. package/dist/skills/first-run-seed.d.ts +26 -0
  178. package/dist/skills/first-run-seed.js +75 -0
  179. package/dist/skills/git-vcs/SKILL.md +84 -0
  180. package/dist/skills/kafka/SKILL.md +89 -0
  181. package/dist/skills/kubernetes/SKILL.md +93 -0
  182. package/dist/skills/kubernetes-expert/SKILL.md +54 -0
  183. package/dist/skills/openshift/SKILL.md +93 -0
  184. package/dist/skills/pentester/SKILL.md +59 -0
  185. package/dist/skills/performance-tester/SKILL.md +57 -0
  186. package/dist/skills/playwright-ui-tester/SKILL.md +67 -0
  187. package/dist/skills/programmer/SKILL.md +58 -0
  188. package/dist/skills/project-scaffolding/SKILL.md +133 -0
  189. package/dist/skills/qa-engineer/SKILL.md +91 -0
  190. package/dist/skills/rca/SKILL.md +58 -0
  191. package/dist/skills/redhat/SKILL.md +92 -0
  192. package/dist/skills/rosa/SKILL.md +80 -0
  193. package/dist/skills/scrum-framework/SKILL.md +74 -0
  194. package/dist/skills/scrum-framework/references/artifacts_and_quality.md +79 -0
  195. package/dist/skills/scrum-framework/references/roles_and_events.md +105 -0
  196. package/dist/skills/scrum-master-agent/SKILL.md +74 -0
  197. package/dist/skills/scrum-master-agent/references/behaviors.md +56 -0
  198. package/dist/skills/sdlc-skill.d.ts +1 -0
  199. package/dist/skills/sdlc-skill.js +4 -0
  200. package/dist/skills/secops/SKILL.md +52 -0
  201. package/dist/skills/secure-architect.bylaws.d.ts +68 -0
  202. package/dist/skills/secure-architect.bylaws.js +525 -0
  203. package/dist/skills/seed-skills.d.ts +48 -0
  204. package/dist/skills/seed-skills.js +213 -0
  205. package/dist/skills/skill-authoring/SKILL.md +123 -0
  206. package/dist/skills/skill-header.d.ts +51 -0
  207. package/dist/skills/skill-header.js +163 -0
  208. package/dist/skills/skill-path-guard.d.ts +41 -0
  209. package/dist/skills/skill-path-guard.js +95 -0
  210. package/dist/skills/skillRouter.d.ts +51 -0
  211. package/dist/skills/skillRouter.js +90 -0
  212. package/dist/skills/skills-root.d.ts +64 -0
  213. package/dist/skills/skills-root.js +168 -0
  214. package/dist/skills/software-architect/SKILL.md +77 -0
  215. package/dist/skills/software-engineer/SKILL.md +74 -0
  216. package/dist/skills/solid-principles/SKILL.md +94 -0
  217. package/dist/skills/task-planning/SKILL.md +115 -0
  218. package/dist/skills/tester/SKILL.md +54 -0
  219. package/dist/skills/ubuntu/SKILL.md +87 -0
  220. package/dist/skills/ui-ux-design/SKILL.md +114 -0
  221. package/dist/skills/workspace-context/SKILL.md +103 -0
  222. package/dist/sprint/controller.d.ts +37 -0
  223. package/dist/sprint/controller.js +220 -0
  224. package/dist/sprint/dag-builder.d.ts +14 -0
  225. package/dist/sprint/dag-builder.js +105 -0
  226. package/dist/sprint/reporter.d.ts +11 -0
  227. package/dist/sprint/reporter.js +97 -0
  228. package/dist/sprint/state-store.d.ts +10 -0
  229. package/dist/sprint/state-store.js +71 -0
  230. package/dist/sprint/types.d.ts +24 -0
  231. package/dist/sprint/types.js +4 -0
  232. package/dist/store/file-version-store.d.ts +24 -0
  233. package/dist/store/file-version-store.js +73 -0
  234. package/dist/store/task-store.d.ts +90 -0
  235. package/dist/store/task-store.js +166 -0
  236. package/dist/task-ingest/architect-detailer.d.ts +43 -0
  237. package/dist/task-ingest/architect-detailer.js +141 -0
  238. package/dist/task-ingest/detail-schema.d.ts +9 -0
  239. package/dist/task-ingest/detail-schema.js +195 -0
  240. package/dist/task-ingest/detailed-run.d.ts +36 -0
  241. package/dist/task-ingest/detailed-run.js +108 -0
  242. package/dist/task-ingest/extract-id.d.ts +9 -0
  243. package/dist/task-ingest/extract-id.js +24 -0
  244. package/dist/task-ingest/index.d.ts +23 -0
  245. package/dist/task-ingest/index.js +14 -0
  246. package/dist/task-ingest/ingest.d.ts +45 -0
  247. package/dist/task-ingest/ingest.js +163 -0
  248. package/dist/task-ingest/list-ingested.d.ts +13 -0
  249. package/dist/task-ingest/list-ingested.js +30 -0
  250. package/dist/task-ingest/pending-filter.d.ts +30 -0
  251. package/dist/task-ingest/pending-filter.js +59 -0
  252. package/dist/task-ingest/scanner.d.ts +56 -0
  253. package/dist/task-ingest/scanner.js +216 -0
  254. package/dist/task-ingest/source-reader.d.ts +25 -0
  255. package/dist/task-ingest/source-reader.js +93 -0
  256. package/dist/task-ingest/spec-builder.d.ts +34 -0
  257. package/dist/task-ingest/spec-builder.js +89 -0
  258. package/dist/task-ingest/wbs-extract.d.ts +50 -0
  259. package/dist/task-ingest/wbs-extract.js +226 -0
  260. package/dist/task-ingest/wbs-parser.d.ts +36 -0
  261. package/dist/task-ingest/wbs-parser.js +170 -0
  262. package/dist/tools/apiTestTool.d.ts +37 -0
  263. package/dist/tools/apiTestTool.js +128 -0
  264. package/dist/tools/ast-tools.d.ts +183 -0
  265. package/dist/tools/ast-tools.js +853 -0
  266. package/dist/tools/browse-tool.d.ts +105 -0
  267. package/dist/tools/browse-tool.js +394 -0
  268. package/dist/tools/credential.d.ts +17 -0
  269. package/dist/tools/credential.js +32 -0
  270. package/dist/tools/dependency-graph.d.ts +23 -0
  271. package/dist/tools/dependency-graph.js +164 -0
  272. package/dist/tools/edit-tool.d.ts +34 -0
  273. package/dist/tools/edit-tool.js +95 -0
  274. package/dist/tools/file-header-manager.d.ts +27 -0
  275. package/dist/tools/file-header-manager.js +153 -0
  276. package/dist/tools/fs-tools.d.ts +154 -0
  277. package/dist/tools/fs-tools.js +337 -0
  278. package/dist/tools/git-tools.d.ts +79 -0
  279. package/dist/tools/git-tools.js +348 -0
  280. package/dist/tools/github-tool.d.ts +48 -0
  281. package/dist/tools/github-tool.js +147 -0
  282. package/dist/tools/globTool.d.ts +20 -0
  283. package/dist/tools/globTool.js +34 -0
  284. package/dist/tools/grepTool.d.ts +19 -0
  285. package/dist/tools/grepTool.js +44 -0
  286. package/dist/tools/index-freshness.d.ts +58 -0
  287. package/dist/tools/index-freshness.js +190 -0
  288. package/dist/tools/index-glob.d.ts +48 -0
  289. package/dist/tools/index-glob.js +175 -0
  290. package/dist/tools/load-skill-tool.d.ts +15 -0
  291. package/dist/tools/load-skill-tool.js +114 -0
  292. package/dist/tools/patch-tools.d.ts +58 -0
  293. package/dist/tools/patch-tools.js +153 -0
  294. package/dist/tools/playwright-tool.d.ts +66 -0
  295. package/dist/tools/playwright-tool.js +121 -0
  296. package/dist/tools/project-detect.d.ts +20 -0
  297. package/dist/tools/project-detect.js +87 -0
  298. package/dist/tools/prompt-guidance.d.ts +22 -0
  299. package/dist/tools/prompt-guidance.js +23 -0
  300. package/dist/tools/scheduleTool.d.ts +19 -0
  301. package/dist/tools/scheduleTool.js +181 -0
  302. package/dist/tools/search-tools.d.ts +27 -0
  303. package/dist/tools/search-tools.js +78 -0
  304. package/dist/tools/sed-tool.d.ts +66 -0
  305. package/dist/tools/sed-tool.js +402 -0
  306. package/dist/tools/shell-result.d.ts +11 -0
  307. package/dist/tools/shell-result.js +1 -0
  308. package/dist/tools/shell-tool.d.ts +45 -0
  309. package/dist/tools/shell-tool.js +141 -0
  310. package/dist/tools/siteCrawlerTool.d.ts +32 -0
  311. package/dist/tools/siteCrawlerTool.js +205 -0
  312. package/dist/tools/tool-registry.d.ts +67 -0
  313. package/dist/tools/tool-registry.js +596 -0
  314. package/dist/tools/validate-tool.d.ts +23 -0
  315. package/dist/tools/validate-tool.js +201 -0
  316. package/dist/tools/workspace-index.d.ts +46 -0
  317. package/dist/tools/workspace-index.js +173 -0
  318. package/dist/types.d.ts +222 -0
  319. package/dist/types.js +7 -0
  320. package/dist/updater/index.d.ts +14 -0
  321. package/dist/updater/index.js +120 -0
  322. package/dist/util/banner.d.ts +25 -0
  323. package/dist/util/banner.js +140 -0
  324. package/dist/util/command-suggester.d.ts +6 -0
  325. package/dist/util/command-suggester.js +25 -0
  326. package/dist/util/confirm-interrupt.d.ts +10 -0
  327. package/dist/util/confirm-interrupt.js +31 -0
  328. package/dist/util/continue-trigger.d.ts +11 -0
  329. package/dist/util/continue-trigger.js +20 -0
  330. package/dist/util/env-info.d.ts +18 -0
  331. package/dist/util/env-info.js +37 -0
  332. package/dist/util/file-access-log.d.ts +35 -0
  333. package/dist/util/file-access-log.js +140 -0
  334. package/dist/util/footer-manager.d.ts +124 -0
  335. package/dist/util/footer-manager.js +195 -0
  336. package/dist/util/footer-renderer.d.ts +67 -0
  337. package/dist/util/footer-renderer.js +319 -0
  338. package/dist/util/footer-state.d.ts +34 -0
  339. package/dist/util/footer-state.js +18 -0
  340. package/dist/util/footer-text.d.ts +51 -0
  341. package/dist/util/footer-text.js +74 -0
  342. package/dist/util/ids.d.ts +1 -0
  343. package/dist/util/ids.js +4 -0
  344. package/dist/util/levenshtein.d.ts +6 -0
  345. package/dist/util/levenshtein.js +34 -0
  346. package/dist/util/logger.d.ts +10 -0
  347. package/dist/util/logger.js +41 -0
  348. package/dist/util/logs.d.ts +26 -0
  349. package/dist/util/logs.js +88 -0
  350. package/dist/util/output-handler.d.ts +89 -0
  351. package/dist/util/output-handler.js +193 -0
  352. package/dist/util/output-interceptor.d.ts +18 -0
  353. package/dist/util/output-interceptor.js +49 -0
  354. package/dist/util/output-paths.d.ts +43 -0
  355. package/dist/util/output-paths.js +121 -0
  356. package/dist/util/package-json.d.ts +6 -0
  357. package/dist/util/package-json.js +36 -0
  358. package/dist/util/process-kill.d.ts +27 -0
  359. package/dist/util/process-kill.js +148 -0
  360. package/dist/util/protocol-error-logger.d.ts +33 -0
  361. package/dist/util/protocol-error-logger.js +73 -0
  362. package/dist/util/redact.d.ts +25 -0
  363. package/dist/util/redact.js +103 -0
  364. package/dist/util/scroll-region-guard.d.ts +33 -0
  365. package/dist/util/scroll-region-guard.js +63 -0
  366. package/dist/util/spinner.d.ts +15 -0
  367. package/dist/util/spinner.js +49 -0
  368. package/dist/util/terminal-output.d.ts +34 -0
  369. package/dist/util/terminal-output.js +1 -0
  370. package/dist/util/terminal-silent.d.ts +6 -0
  371. package/dist/util/terminal-silent.js +20 -0
  372. package/dist/util/terminal.d.ts +56 -0
  373. package/dist/util/terminal.js +124 -0
  374. package/dist/util/text-sanitize.d.ts +8 -0
  375. package/dist/util/text-sanitize.js +45 -0
  376. package/dist/util/thinking-spinner.d.ts +27 -0
  377. package/dist/util/thinking-spinner.js +189 -0
  378. package/dist/util/tool-error-logger.d.ts +33 -0
  379. package/dist/util/tool-error-logger.js +77 -0
  380. package/dist/util/tty.d.ts +13 -0
  381. package/dist/util/tty.js +16 -0
  382. package/dist/util/xcoder-paths.d.ts +56 -0
  383. package/dist/util/xcoder-paths.js +63 -0
  384. package/dist/version.d.ts +2 -0
  385. package/dist/version.js +4 -0
  386. package/dist/worker-agent/to-subtask-node.d.ts +35 -0
  387. package/dist/worker-agent/to-subtask-node.js +41 -0
  388. package/dist/worker-agent/worker-orchestrator.d.ts +104 -0
  389. package/dist/worker-agent/worker-orchestrator.js +201 -0
  390. package/package.json +38 -3
@@ -0,0 +1,213 @@
1
+ // xcoder:version 5 | xcoder:task task-f9cf18 | xcoder:updated 2026-11-02T16:15:28.268Z | xcoder:subtask code-st-077ea2
2
+ /**
3
+ * Idempotent, non-destructive seeding of the home skills directory.
4
+ *
5
+ * On first run the shipped packs are copied to ~/.xcoder/skills so the user
6
+ * owns an editable tree that survives uninstall. Precision matters here, so
7
+ * ownership is tracked with a per-file SHA-256 manifest:
8
+ *
9
+ * absent -> write (created)
10
+ * present, hashes match -> leave alone (unchanged)
11
+ * present, differs, IN manifest -> overwrite (repaired)
12
+ * present, differs, NOT in manifest -> never touch (preserved)
13
+ * extra pack not in source -> never touch (preserved)
14
+ *
15
+ * There are NO deletes anywhere in this module — the one failure mode that
16
+ * cannot be undone is deleting a file the user wrote.
17
+ */
18
+ import { createHash } from "node:crypto";
19
+ import fs from "node:fs";
20
+ import fsp from "node:fs/promises";
21
+ import path from "node:path";
22
+ import { getUserHomeSkillsDir, probeSkillsDir } from "./skills-root.js";
23
+ import { MAX_PACK_DIRS, validateSkillName } from "./skill-path-guard.js";
24
+ /** Ceiling on files considered inside one pack (defensive, not a real limit). */
25
+ const MAX_PACK_FILES = 2048;
26
+ /** Ceiling on directory nesting inside one pack. */
27
+ const MAX_DEPTH = 8;
28
+ function sha256(buf) {
29
+ return createHash("sha256").update(buf).digest("hex");
30
+ }
31
+ /** Walk `baseDir` and return posix-relative file paths -> sha256, all bounded. */
32
+ async function hashTree(baseDir) {
33
+ const out = new Map();
34
+ async function walk(dir, prefix, depth) {
35
+ if (depth > MAX_DEPTH)
36
+ return;
37
+ let entries;
38
+ try {
39
+ entries = await fsp.readdir(dir, { withFileTypes: true });
40
+ }
41
+ catch {
42
+ return;
43
+ }
44
+ for (const entry of entries) {
45
+ if (out.size >= MAX_PACK_FILES)
46
+ return;
47
+ if (entry.isSymbolicLink())
48
+ continue;
49
+ if (!validateSkillName(entry.name).ok)
50
+ continue;
51
+ const abs = path.join(dir, entry.name);
52
+ const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
53
+ if (entry.isDirectory()) {
54
+ await walk(abs, rel, depth + 1);
55
+ }
56
+ else if (entry.isFile()) {
57
+ try {
58
+ out.set(rel, sha256(await fsp.readFile(abs)));
59
+ }
60
+ catch {
61
+ // Unreadable source file: skip rather than abort the whole seed.
62
+ }
63
+ }
64
+ }
65
+ }
66
+ await walk(baseDir, "", 0);
67
+ return out;
68
+ }
69
+ /** Read and schema-check an existing manifest; a bad manifest means "no manifest". */
70
+ function readManifest(manifestPath) {
71
+ let raw;
72
+ try {
73
+ raw = fs.readFileSync(manifestPath, "utf-8");
74
+ }
75
+ catch {
76
+ return undefined;
77
+ }
78
+ try {
79
+ const parsed = JSON.parse(raw);
80
+ if (typeof parsed !== "object" || parsed === null)
81
+ return undefined;
82
+ const m = parsed;
83
+ if (m["schemaVersion"] !== 1)
84
+ return undefined;
85
+ if (typeof m["packs"] !== "object" || m["packs"] === null)
86
+ return undefined;
87
+ return parsed;
88
+ }
89
+ catch {
90
+ return undefined;
91
+ }
92
+ }
93
+ /**
94
+ * Seed the home skills directory from the shipped packs.
95
+ *
96
+ * Never throws: every failure is recorded in `errors` and the run continues.
97
+ */
98
+ export async function seedSkillsHome(opts) {
99
+ const homeDir = opts.homeDir;
100
+ const targetSkillsDir = getUserHomeSkillsDir(homeDir);
101
+ const manifestPath = opts.manifestPath ?? path.join(path.dirname(targetSkillsDir), "skills-manifest.json");
102
+ const result = {
103
+ action: "skipped",
104
+ written: [],
105
+ updated: [],
106
+ preserved: [],
107
+ errors: [],
108
+ manifestPath,
109
+ };
110
+ let sourcePacks;
111
+ try {
112
+ sourcePacks = fs.readdirSync(opts.sourceDir, { withFileTypes: true });
113
+ }
114
+ catch {
115
+ result.errors.push({ path: opts.sourceDir, reason: "source directory unreadable" });
116
+ return result;
117
+ }
118
+ const targetExisted = probeSkillsDir(targetSkillsDir).count > 0 || fs.existsSync(targetSkillsDir);
119
+ const manifest = readManifest(manifestPath);
120
+ const nextManifest = {
121
+ schemaVersion: 1,
122
+ generatedAt: new Date().toISOString(),
123
+ generator: "first-run",
124
+ packs: { ...(manifest?.packs ?? {}) },
125
+ };
126
+ let visited = 0;
127
+ for (const entry of sourcePacks) {
128
+ if (visited >= MAX_PACK_DIRS)
129
+ break;
130
+ if (!entry.isDirectory())
131
+ continue;
132
+ visited++;
133
+ if (!validateSkillName(entry.name).ok)
134
+ continue;
135
+ const sourcePackDir = path.join(opts.sourceDir, entry.name);
136
+ const targetPackDir = path.join(targetSkillsDir, entry.name);
137
+ const files = await hashTree(sourcePackDir);
138
+ if (files.size === 0) {
139
+ result.preserved.push(entry.name);
140
+ continue;
141
+ }
142
+ const ownedByUs = manifest?.packs?.[entry.name] !== undefined;
143
+ const targetPackExists = fs.existsSync(targetPackDir);
144
+ let touchedThisPack = false;
145
+ for (const [rel, sourceHash] of files) {
146
+ const targetFile = path.join(targetPackDir, ...rel.split("/"));
147
+ const exists = fs.existsSync(targetFile);
148
+ if (exists) {
149
+ const targetHash = sha256(await fsp.readFile(targetFile));
150
+ if (targetHash === sourceHash)
151
+ continue;
152
+ if (!ownedByUs) {
153
+ result.preserved.push(`${entry.name}/${rel}`);
154
+ continue;
155
+ }
156
+ if (!opts.dryRun) {
157
+ try {
158
+ await fsp.mkdir(path.dirname(targetFile), { recursive: true });
159
+ await fsp.writeFile(targetFile, await fsp.readFile(path.join(sourcePackDir, ...rel.split("/"))));
160
+ }
161
+ catch (e) {
162
+ result.errors.push({ path: targetFile, reason: e.message });
163
+ continue;
164
+ }
165
+ }
166
+ touchedThisPack = true;
167
+ result.updated.push(`${entry.name}/${rel}`);
168
+ nextManifest.packs[entry.name] = { ...(nextManifest.packs[entry.name] ?? {}), [rel]: sourceHash };
169
+ continue;
170
+ }
171
+ if (!opts.dryRun) {
172
+ try {
173
+ await fsp.mkdir(path.dirname(targetFile), { recursive: true });
174
+ await fsp.writeFile(targetFile, await fsp.readFile(path.join(sourcePackDir, ...rel.split("/"))));
175
+ }
176
+ catch (e) {
177
+ result.errors.push({ path: targetFile, reason: e.message });
178
+ continue;
179
+ }
180
+ }
181
+ touchedThisPack = true;
182
+ result.written.push(`${entry.name}/${rel}`);
183
+ nextManifest.packs[entry.name] = { ...(nextManifest.packs[entry.name] ?? {}), [rel]: sourceHash };
184
+ }
185
+ // A pack that already exists on disk whose files we neither wrote nor
186
+ // repaired is the user's, not ours — record it and drop any manifest claim.
187
+ if (targetPackExists && !touchedThisPack) {
188
+ result.preserved.push(entry.name);
189
+ delete nextManifest.packs[entry.name];
190
+ }
191
+ }
192
+ if (opts.dryRun) {
193
+ result.action = "skipped";
194
+ return result;
195
+ }
196
+ try {
197
+ await fsp.mkdir(path.dirname(manifestPath), { recursive: true });
198
+ await fsp.writeFile(manifestPath, `${JSON.stringify(nextManifest, null, 2)}\n`, "utf-8");
199
+ }
200
+ catch (e) {
201
+ result.errors.push({ path: manifestPath, reason: e.message });
202
+ }
203
+ if (result.updated.length > 0) {
204
+ result.action = "repaired";
205
+ }
206
+ else if (result.written.length > 0) {
207
+ result.action = targetExisted ? "repaired" : "created";
208
+ }
209
+ else {
210
+ result.action = "unchanged";
211
+ }
212
+ return result;
213
+ }
@@ -0,0 +1,123 @@
1
+ ---
2
+ name: skill-authoring
3
+ role: meta
4
+ description: Author new role skills for xcoder's own hot-pluggable skill system (agent/skills/<name>/SKILL.md) — correct frontmatter, well-scoped triggers, and a body that actually changes agent behavior instead of restating generic advice.
5
+ triggers:
6
+ - "create a skill"
7
+ - "new skill"
8
+ - "add a skill"
9
+ - "author a skill"
10
+ - "build a skill"
11
+ - "skill for"
12
+ - "write a skill"
13
+ - "skill.md"
14
+ - "skill registry"
15
+ version: "1.0.0"
16
+ requires_tools:
17
+ - read_tool
18
+ - write_edit_tool
19
+ - glob_tool
20
+ - grep_tool
21
+ composes_with:
22
+ - ui-ux-design
23
+ - task-planning
24
+ ---
25
+
26
+ ## Purpose
27
+
28
+ This skill is for when the task is to extend xcoder itself — adding a new role skill so future
29
+ tasks route to specialized instructions instead of the generic base ReAct loop. It is a meta-skill:
30
+ it does not solve the user's underlying problem, it produces a well-formed `SKILL.md` that will.
31
+
32
+ ## How xcoder actually loads skills (read this before writing anything)
33
+
34
+ `src/core/skillRegistry.ts` is the source of truth — re-read it if this ever drifts:
35
+
36
+ - Skills live at `agent/skills/<name>/SKILL.md` (one directory per skill; the directory name
37
+ doesn't have to match `name` in the frontmatter, but keep them identical to avoid confusion).
38
+ - The file MUST match `^---\n(yaml)\n---\n(body)$` — a YAML frontmatter block bounded by `---`
39
+ lines, then a markdown body. `loadHeaders()` parses ONLY the frontmatter (cheap, called often);
40
+ `loadSkill()` parses frontmatter + body (only when a skill is actually selected).
41
+ - `route(taskDescription)` lowercases the task text and, for every skill, counts how many of its
42
+ `triggers` are substrings of it. Skills with score > 0 are returned sorted by score descending.
43
+ Multiple skills can and do fire for the same task — that's intentional (`composes_with` documents
44
+ which skills are expected to be selected together).
45
+ - There is no dedup, no stemming, no fuzzy match. `"deploy"` will not match `"deployment"` unless
46
+ you also list `"deployment"`. Write triggers as the literal substrings a real task description
47
+ would contain, including short synonyms and common phrasings — but not words so short/common
48
+ they'd false-positive on unrelated tasks (a bad trigger like `"add"` or `"fix"` will make a skill
49
+ fire on nearly everything).
50
+
51
+ ## The frontmatter schema (exact — matches `SkillHeader` in `src/core/types.ts`)
52
+
53
+ ```yaml
54
+ name: kebab-case-unique-id # must be unique across agent/skills/*
55
+ role: short-noun-phrase # e.g. "design", "planning", "meta", "devops" — used for display
56
+ description: one sentence, third person, states what the skill is for and when it applies
57
+ triggers:
58
+ - "phrase one"
59
+ - "phrase two" # lowercase; substring match against the lowercased task text
60
+ version: "1.0.0" # semver as a string
61
+ requires_tools: # tool names this skill's instructions assume are available
62
+ - some_tool
63
+ composes_with: # names of OTHER skills this one is commonly selected alongside
64
+ - other-skill-name
65
+ ```
66
+
67
+ All seven keys are required by the `SkillHeader` interface — an omitted key will still parse as
68
+ YAML but will be `undefined` at runtime and can break anything that reads it (e.g. `role` showing
69
+ as `undefined` in `xcoder --skills`). Fill in every field even if a list is empty (`[]`).
70
+
71
+ ## Writing a body that changes behavior (the part that's easy to get wrong)
72
+
73
+ A skill body that just restates domain knowledge the model already has adds tokens without adding
74
+ value. A good skill body encodes the things that are true of *this specific codebase or workflow*
75
+ that the model can't otherwise infer:
76
+
77
+ - **Concrete conventions**: exact file paths, exact tool names, exact naming patterns this project
78
+ uses (e.g. "phase reports go to `tasks/<slug>-phase-<N>.md`", not "save your progress somewhere").
79
+ - **Decision rules tied to how the agent is actually scored or evaluated** — if there's a scoring
80
+ or validation mechanism (see the `task-planning` skill for this project's health-score system),
81
+ reference it by name and describe the specific behaviors it rewards/penalizes, not generic
82
+ "write good code" advice.
83
+ - **Failure patterns worth naming** — a skill earns its keep by preventing a mistake the model
84
+ would otherwise make on this exact class of task.
85
+
86
+ Recommended section structure (matches the `LoadedSkill.body` doc comment — "Process / Strategies
87
+ / Instructions / Planning / Experience" — use the subset that's actually useful, don't pad):
88
+
89
+ ```markdown
90
+ ## Process
91
+ The concrete step order for this kind of task.
92
+
93
+ ## Instructions
94
+ Non-negotiable rules — the things that are wrong if skipped.
95
+
96
+ ## Strategies
97
+ Judgment calls / tradeoffs / "prefer X over Y when...".
98
+
99
+ ## Experience
100
+ Known failure modes and how to recognize/avoid them.
101
+ ```
102
+
103
+ ## Checklist before considering a new skill done
104
+
105
+ 1. Frontmatter has all 7 keys, `name` is unique, `triggers` are realistic lowercase substrings.
106
+ 2. `requires_tools` lists only tools that actually exist (check `src/tools/toolSchemas.ts` — grep
107
+ for `"_tool"` to get the current list rather than assuming).
108
+ 3. `composes_with` references skills that actually exist under `agent/skills/`.
109
+ 4. The body contains at least one thing a generalist model would NOT already know or would get
110
+ wrong without it. If you can't identify that thing, the task probably doesn't need a new skill.
111
+ 5. Sanity-check by running `xcoder --skills` (uses `loadHeaders()`) to confirm it parses and shows
112
+ up with the right role/triggers, and test that the intended task phrasing actually routes to it
113
+ via `SkillRegistry.route(...)` logic (substring match, case-insensitive).
114
+
115
+ ## Common mistakes
116
+
117
+ - Triggers that are single common words (`"design"`, `"plan"`) with no accompanying context words —
118
+ causes the skill to fire on unrelated tasks and dilutes routing quality for everything else.
119
+ - Forgetting `version`/`requires_tools`/`composes_with` because they "don't seem important" — they
120
+ parse fine as `undefined` and the failure only shows up later, somewhere else.
121
+ - A body that's a generic essay instead of project-specific, actionable instructions.
122
+ - Copy-pasting another skill's triggers "just in case" — this makes two skills fire together on
123
+ tasks that only need one, bloating the context sent to the LLM for no benefit.
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Bounded, schema-validated SKILL.md frontmatter parsing.
3
+ *
4
+ * The previous implementation did an unbounded `readFileSync` and then
5
+ * dereferenced `header.name` straight off whatever `yaml.load` returned. A
6
+ * SKILL.md whose frontmatter was a YAML scalar (or an array) therefore threw
7
+ * a TypeError that took the whole scan down. This module closes that by:
8
+ *
9
+ * 1. reading at most `MAX_FRONTMATTER_BYTES` before parsing anything,
10
+ * 2. parsing with `JSON_SCHEMA` so YAML cannot construct arbitrary values,
11
+ * 3. refusing prototype-poisoning keys, and
12
+ * 4. normalising the result into a `SkillHeader` that downstream code can
13
+ * dereference without a guard.
14
+ *
15
+ * Every failure returns `{ ok: false, reason }` instead of throwing.
16
+ */
17
+ import type { SkillHeader } from "../types.js";
18
+ /** Why a SKILL.md header could not be turned into a `SkillHeader`. */
19
+ export type HeaderParseFailure = "unreadable" | "too-large" | "no-frontmatter" | "yaml-error" | "not-object" | "schema-invalid" | "unsafe-key";
20
+ export interface HeaderParseResult {
21
+ ok: boolean;
22
+ header?: SkillHeader;
23
+ body?: string;
24
+ reason?: HeaderParseFailure;
25
+ /** How many bytes of the file were actually read for the header. */
26
+ bytesRead: number;
27
+ }
28
+ /**
29
+ * Collapse a header value onto one line and cap its length, so a description
30
+ * containing newlines or control characters cannot restructure the prompt
31
+ * block the skill list is injected into (LLM01).
32
+ */
33
+ export declare function sanitizeHeaderText(value: string, maxLen: number): string;
34
+ /** True when `value` can be safely dereferenced as a `SkillHeader`. */
35
+ export declare function isSkillHeader(value: unknown): value is SkillHeader;
36
+ /**
37
+ * Parse a SKILL.md header from an in-memory string.
38
+ * Pure — no filesystem access, so it is cheap to unit test.
39
+ */
40
+ export declare function parseSkillHeaderBytes(raw: string): HeaderParseResult;
41
+ /**
42
+ * Parse a SKILL.md from disk.
43
+ *
44
+ * The header is always parsed from a bounded prefix (8 KiB by default); the
45
+ * body is only re-read in full when `withBody` is set, so the header path can
46
+ * never be made to allocate an arbitrary amount of memory.
47
+ */
48
+ export declare function parseSkillHeaderFile(skillPath: string, opts?: {
49
+ withBody?: boolean;
50
+ maxBytes?: number;
51
+ }): HeaderParseResult;
@@ -0,0 +1,163 @@
1
+ // xcoder:version 1 | xcoder:task task-f9cf18 | xcoder:updated 2026-11-02T16:14:11.010Z | xcoder:subtask code-st-077ea2
2
+ /**
3
+ * Bounded, schema-validated SKILL.md frontmatter parsing.
4
+ *
5
+ * The previous implementation did an unbounded `readFileSync` and then
6
+ * dereferenced `header.name` straight off whatever `yaml.load` returned. A
7
+ * SKILL.md whose frontmatter was a YAML scalar (or an array) therefore threw
8
+ * a TypeError that took the whole scan down. This module closes that by:
9
+ *
10
+ * 1. reading at most `MAX_FRONTMATTER_BYTES` before parsing anything,
11
+ * 2. parsing with `JSON_SCHEMA` so YAML cannot construct arbitrary values,
12
+ * 3. refusing prototype-poisoning keys, and
13
+ * 4. normalising the result into a `SkillHeader` that downstream code can
14
+ * dereference without a guard.
15
+ *
16
+ * Every failure returns `{ ok: false, reason }` instead of throwing.
17
+ */
18
+ import fs from "node:fs";
19
+ import * as yaml from "js-yaml";
20
+ import { MAX_FRONTMATTER_BYTES } from "./skill-path-guard.js";
21
+ /** Keys that would let a crafted header reach `Object.prototype`. */
22
+ const UNSAFE_KEYS = new Set(["__proto__", "constructor", "prototype"]);
23
+ /** Matches `---` ... `---` followed by the body. Applied to a bounded prefix. */
24
+ const FRONTMATTER_RE = /^(?:\uFEFF)?---\r?\n([\s\S]*?)\r?\n---[ \t]*(?:\r?\n|$)/;
25
+ /** Longest single-line value kept in one header field (defensive cap). */
26
+ const MAX_FIELD_LEN = 4096;
27
+ /**
28
+ * Collapse a header value onto one line and cap its length, so a description
29
+ * containing newlines or control characters cannot restructure the prompt
30
+ * block the skill list is injected into (LLM01).
31
+ */
32
+ export function sanitizeHeaderText(value, maxLen) {
33
+ return value
34
+ .replace(/[\u0000-\u001f\u007f]/g, " ")
35
+ .replace(/\s+/g, " ")
36
+ .trim()
37
+ .slice(0, Math.max(0, maxLen));
38
+ }
39
+ /** True when `value` can be safely dereferenced as a `SkillHeader`. */
40
+ export function isSkillHeader(value) {
41
+ if (typeof value !== "object" || value === null || Array.isArray(value))
42
+ return false;
43
+ const v = value;
44
+ return (typeof v["name"] === "string" &&
45
+ v["name"].length > 0 &&
46
+ typeof v["description"] === "string");
47
+ }
48
+ function toStringArray(value) {
49
+ if (!Array.isArray(value))
50
+ return [];
51
+ return value
52
+ .filter((item) => typeof item === "string")
53
+ .map((item) => sanitizeHeaderText(item, 256))
54
+ .filter((item) => item.length > 0);
55
+ }
56
+ function asString(value) {
57
+ return typeof value === "string" ? sanitizeHeaderText(value, MAX_FIELD_LEN) : "";
58
+ }
59
+ /** Read at most `maxBytes` from the head of a file. */
60
+ function readPrefix(filePath, maxBytes) {
61
+ const fd = fs.openSync(filePath, "r");
62
+ try {
63
+ const buf = Buffer.allocUnsafe(maxBytes);
64
+ const bytesRead = fs.readSync(fd, buf, 0, maxBytes, 0);
65
+ return { text: buf.subarray(0, bytesRead).toString("utf-8"), bytesRead };
66
+ }
67
+ finally {
68
+ fs.closeSync(fd);
69
+ }
70
+ }
71
+ /**
72
+ * Parse a SKILL.md header from an in-memory string.
73
+ * Pure — no filesystem access, so it is cheap to unit test.
74
+ */
75
+ export function parseSkillHeaderBytes(raw) {
76
+ const bytesRead = Buffer.byteLength(raw, "utf-8");
77
+ const match = FRONTMATTER_RE.exec(raw);
78
+ if (!match) {
79
+ return { ok: false, reason: "no-frontmatter", bytesRead };
80
+ }
81
+ const yamlBlock = match[1] ?? "";
82
+ if (Buffer.byteLength(yamlBlock, "utf-8") > MAX_FRONTMATTER_BYTES) {
83
+ return { ok: false, reason: "too-large", bytesRead };
84
+ }
85
+ let loaded;
86
+ try {
87
+ loaded = yaml.load(yamlBlock, { schema: yaml.JSON_SCHEMA });
88
+ }
89
+ catch {
90
+ return { ok: false, reason: "yaml-error", bytesRead };
91
+ }
92
+ if (typeof loaded !== "object" || loaded === null || Array.isArray(loaded)) {
93
+ return { ok: false, reason: "not-object", bytesRead };
94
+ }
95
+ const record = loaded;
96
+ for (const key of Object.keys(record)) {
97
+ if (UNSAFE_KEYS.has(key)) {
98
+ return { ok: false, reason: "unsafe-key", bytesRead };
99
+ }
100
+ }
101
+ if (!isSkillHeader(record)) {
102
+ return { ok: false, reason: "schema-invalid", bytesRead };
103
+ }
104
+ const header = {
105
+ name: asString(record["name"]),
106
+ role: asString(record["role"]),
107
+ description: asString(record["description"]),
108
+ triggers: toStringArray(record["triggers"]),
109
+ version: asString(record["version"]),
110
+ requires_tools: toStringArray(record["requires_tools"]),
111
+ composes_with: toStringArray(record["composes_with"]),
112
+ };
113
+ if (header.name.length === 0) {
114
+ return { ok: false, reason: "schema-invalid", bytesRead };
115
+ }
116
+ return {
117
+ ok: true,
118
+ header,
119
+ body: raw.slice(match[0].length).trim(),
120
+ bytesRead,
121
+ };
122
+ }
123
+ /**
124
+ * Parse a SKILL.md from disk.
125
+ *
126
+ * The header is always parsed from a bounded prefix (8 KiB by default); the
127
+ * body is only re-read in full when `withBody` is set, so the header path can
128
+ * never be made to allocate an arbitrary amount of memory.
129
+ */
130
+ export function parseSkillHeaderFile(skillPath, opts) {
131
+ const maxBytes = opts?.maxBytes ?? MAX_FRONTMATTER_BYTES;
132
+ let prefix;
133
+ try {
134
+ prefix = readPrefix(skillPath, maxBytes);
135
+ }
136
+ catch {
137
+ return { ok: false, reason: "unreadable", bytesRead: 0 };
138
+ }
139
+ const parsed = parseSkillHeaderBytes(prefix.text);
140
+ if (!parsed.ok) {
141
+ // A file that starts with `---` but whose header delimiter never arrives
142
+ // inside the window is a too-large header, not a missing one.
143
+ const truncated = prefix.bytesRead >= maxBytes;
144
+ const looksLikeFrontmatter = /^(?:\uFEFF)?---\r?\n/.test(prefix.text);
145
+ if (truncated && looksLikeFrontmatter) {
146
+ return { ok: false, reason: "too-large", bytesRead: prefix.bytesRead };
147
+ }
148
+ return { ...parsed, bytesRead: prefix.bytesRead };
149
+ }
150
+ if (!opts?.withBody) {
151
+ return { ok: true, header: parsed.header, bytesRead: prefix.bytesRead };
152
+ }
153
+ try {
154
+ const fullParsed = parseSkillHeaderBytes(fs.readFileSync(skillPath, "utf-8"));
155
+ if (!fullParsed.ok) {
156
+ return { ...fullParsed, bytesRead: prefix.bytesRead };
157
+ }
158
+ return { ...fullParsed, bytesRead: prefix.bytesRead };
159
+ }
160
+ catch {
161
+ return { ok: false, reason: "unreadable", bytesRead: prefix.bytesRead };
162
+ }
163
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Skill path guard — validates a skill directory NAME in code, before any
3
+ * path join happens, and then proves containment with realpath.
4
+ *
5
+ * A skills directory is a trust boundary: whatever sits inside it is fed to
6
+ * the model. A directory called `../../.ssh` or `..\..\Windows` must never be
7
+ * joined onto the skills root, so policy lives here (PRM-09: enforce in code,
8
+ * never in a prompt) and failures are closed, never guessed.
9
+ */
10
+ /** Longest accepted skill directory name. Shipped packs are <= 24 chars. */
11
+ export declare const MAX_SKILL_NAME_LENGTH = 64;
12
+ /** Hard ceiling on how many pack directories a single probe will visit. */
13
+ export declare const MAX_PACK_DIRS = 512;
14
+ /**
15
+ * Hard ceiling on YAML frontmatter size. A skill whose header exceeds this is
16
+ * rejected outright — a soft cap would re-introduce the unbounded read this
17
+ * limit exists to prevent.
18
+ */
19
+ export declare const MAX_FRONTMATTER_BYTES = 8192;
20
+ /** Why a candidate skill directory name was refused. */
21
+ export type SkillNameRejectReason = "empty" | "too-long" | "separator" | "traversal" | "absolute" | "dot-prefixed" | "trailing-dot-or-space" | "reserved-device" | "illegal-char";
22
+ export interface SkillNameCheck {
23
+ ok: boolean;
24
+ reason?: SkillNameRejectReason;
25
+ }
26
+ /**
27
+ * Decide whether `name` is safe to use as a single path segment.
28
+ *
29
+ * This is pure string inspection — it never touches the filesystem, so it is
30
+ * cheap enough to call for every pack on every scan.
31
+ */
32
+ export declare function validateSkillName(name: string): SkillNameCheck;
33
+ /**
34
+ * Resolve `parentDir/dirName` and prove the result is a *direct child* of
35
+ * `parentDir` both lexically and after symlink resolution.
36
+ *
37
+ * Returns `undefined` when the name is unsafe, when the entry does not exist,
38
+ * or when the real path escapes the parent (a symlinked pack). Callers skip
39
+ * anything that comes back `undefined` — fail closed.
40
+ */
41
+ export declare function resolveSkillDir(parentDir: string, dirName: string): string | undefined;
@@ -0,0 +1,95 @@
1
+ // xcoder:version 1 | xcoder:task task-f9cf18 | xcoder:updated 2026-11-02T16:13:48.255Z | xcoder:subtask code-st-077ea2
2
+ /**
3
+ * Skill path guard — validates a skill directory NAME in code, before any
4
+ * path join happens, and then proves containment with realpath.
5
+ *
6
+ * A skills directory is a trust boundary: whatever sits inside it is fed to
7
+ * the model. A directory called `../../.ssh` or `..\..\Windows` must never be
8
+ * joined onto the skills root, so policy lives here (PRM-09: enforce in code,
9
+ * never in a prompt) and failures are closed, never guessed.
10
+ */
11
+ import fs from "node:fs";
12
+ import path from "node:path";
13
+ /** Longest accepted skill directory name. Shipped packs are <= 24 chars. */
14
+ export const MAX_SKILL_NAME_LENGTH = 64;
15
+ /** Hard ceiling on how many pack directories a single probe will visit. */
16
+ export const MAX_PACK_DIRS = 512;
17
+ /**
18
+ * Hard ceiling on YAML frontmatter size. A skill whose header exceeds this is
19
+ * rejected outright — a soft cap would re-introduce the unbounded read this
20
+ * limit exists to prevent.
21
+ */
22
+ export const MAX_FRONTMATTER_BYTES = 8192;
23
+ /** Windows device names are resolved by the OS even with an extension. */
24
+ const RESERVED_DEVICE_NAMES = new Set([
25
+ "con",
26
+ "prn",
27
+ "aux",
28
+ "nul",
29
+ ...Array.from({ length: 9 }, (_, i) => `com${i + 1}`),
30
+ ...Array.from({ length: 9 }, (_, i) => `lpt${i + 1}`),
31
+ ]);
32
+ /** Only these characters may appear in a skill directory name. */
33
+ const SAFE_NAME_RE = /^[A-Za-z0-9._-]+$/;
34
+ /**
35
+ * Decide whether `name` is safe to use as a single path segment.
36
+ *
37
+ * This is pure string inspection — it never touches the filesystem, so it is
38
+ * cheap enough to call for every pack on every scan.
39
+ */
40
+ export function validateSkillName(name) {
41
+ if (typeof name !== "string" || name.length === 0) {
42
+ return { ok: false, reason: "empty" };
43
+ }
44
+ if (name.length > MAX_SKILL_NAME_LENGTH) {
45
+ return { ok: false, reason: "too-long" };
46
+ }
47
+ if (path.isAbsolute(name) || /^[A-Za-z]:/.test(name)) {
48
+ return { ok: false, reason: "absolute" };
49
+ }
50
+ if (name.includes("/") || name.includes("\\")) {
51
+ return { ok: false, reason: "separator" };
52
+ }
53
+ if (name === "." || name === ".." || name.includes("..")) {
54
+ return { ok: false, reason: "traversal" };
55
+ }
56
+ if (name.startsWith(".")) {
57
+ return { ok: false, reason: "dot-prefixed" };
58
+ }
59
+ if (name.endsWith(".") || name.endsWith(" ")) {
60
+ return { ok: false, reason: "trailing-dot-or-space" };
61
+ }
62
+ if (RESERVED_DEVICE_NAMES.has(name.split(".")[0].toLowerCase())) {
63
+ return { ok: false, reason: "reserved-device" };
64
+ }
65
+ if (!SAFE_NAME_RE.test(name)) {
66
+ return { ok: false, reason: "illegal-char" };
67
+ }
68
+ return { ok: true };
69
+ }
70
+ /**
71
+ * Resolve `parentDir/dirName` and prove the result is a *direct child* of
72
+ * `parentDir` both lexically and after symlink resolution.
73
+ *
74
+ * Returns `undefined` when the name is unsafe, when the entry does not exist,
75
+ * or when the real path escapes the parent (a symlinked pack). Callers skip
76
+ * anything that comes back `undefined` — fail closed.
77
+ */
78
+ export function resolveSkillDir(parentDir, dirName) {
79
+ if (!validateSkillName(dirName).ok)
80
+ return undefined;
81
+ const parent = path.resolve(parentDir);
82
+ const candidate = path.resolve(parent, dirName);
83
+ if (candidate === parent || path.dirname(candidate) !== parent)
84
+ return undefined;
85
+ try {
86
+ const realParent = fs.realpathSync(parent);
87
+ const realCandidate = fs.realpathSync(candidate);
88
+ if (path.dirname(realCandidate) !== realParent)
89
+ return undefined;
90
+ return realCandidate;
91
+ }
92
+ catch {
93
+ return undefined;
94
+ }
95
+ }