contextos-agents 1.6.1 → 2.0.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (323) hide show
  1. package/.agents/AGENTS.md +6 -1
  2. package/.agents/adapters/aider/export.js +117 -97
  3. package/.agents/adapters/claude/export.js +68 -26
  4. package/.agents/adapters/copilot/export.js +90 -51
  5. package/.agents/adapters/cursor/export.js +83 -68
  6. package/.agents/adapters/drift-detector.js +196 -0
  7. package/.agents/adapters/gemini/export.js +76 -45
  8. package/.agents/adapters/pure-compiler.js +443 -0
  9. package/.agents/adapters/zed/export.js +109 -62
  10. package/.agents/compiled/registry.v2.json +504 -0
  11. package/.agents/compiled/registry.v2.sha256 +1 -0
  12. package/.agents/compiler/manifest-compiler.js +963 -0
  13. package/.agents/compiler/vendor/yaml.LICENSE.txt +13 -0
  14. package/.agents/compiler/vendor/yaml.SBOM.json +6 -0
  15. package/.agents/compiler/vendor/yaml.js +139 -0
  16. package/.agents/core/profiles/init.yaml +25 -0
  17. package/.agents/core/skills/context-manager/references/context-rules.md +59 -0
  18. package/.agents/core/skills/context-manager/skill.yaml +10 -5
  19. package/.agents/core/skills/context-os/SKILL.md +3 -6
  20. package/.agents/core/skills/context-os/skill.yaml +14 -8
  21. package/.agents/core/skills/engineering-workflow/SKILL.md +1 -1
  22. package/.agents/core/skills/engineering-workflow/skill.yaml +7 -7
  23. package/.agents/core/skills/gemini-precision/SKILL.md +4 -0
  24. package/.agents/core/skills/gemini-precision/skill.yaml +5 -6
  25. package/.agents/core/skills/gstack-roles/SKILL.md +3 -1
  26. package/.agents/core/skills/gstack-roles/skill.yaml +9 -6
  27. package/.agents/core/skills/ponytail-mindset/skill.yaml +7 -7
  28. package/.agents/core/skills/security/skill.yaml +21 -2
  29. package/.agents/ctx.js +587 -111
  30. package/.agents/customization-dx.js +282 -0
  31. package/.agents/doctor.js +877 -33
  32. package/.agents/filesystem/index.js +71 -0
  33. package/.agents/filesystem/journaled-transaction.js +451 -0
  34. package/.agents/filesystem/lockfile-v2.js +275 -0
  35. package/.agents/filesystem/platform-hardening.js +222 -0
  36. package/.agents/filesystem/project-lock.js +218 -0
  37. package/.agents/filesystem/safe-path.js +256 -0
  38. package/.agents/generated/claude/skills/context-os/SKILL.md +1 -1
  39. package/.agents/generated/claude/skills/engineering-workflow/SKILL.md +1 -1
  40. package/.agents/generated/claude/skills/gemini-precision/SKILL.md +4 -0
  41. package/.agents/generated/claude/skills/gstack-roles/SKILL.md +3 -1
  42. package/.agents/generated/gemini/skills/context-os/SKILL.md +2 -2
  43. package/.agents/generated/gemini/skills/engineering-workflow/SKILL.md +1 -1
  44. package/.agents/generated/gemini/skills/gemini-precision/SKILL.md +4 -0
  45. package/.agents/generated/gemini/skills/gstack-roles/SKILL.md +3 -1
  46. package/.agents/plugins/contextos/hooks.json +25 -0
  47. package/.agents/plugins/contextos/plugin.json +19 -0
  48. package/.agents/plugins.js +432 -73
  49. package/.agents/profiles.js +507 -46
  50. package/.agents/resolver.js +50 -414
  51. package/.agents/schemas/attestation.review.v1.json +111 -0
  52. package/.agents/schemas/attestation.verification.v1.json +85 -0
  53. package/.agents/schemas/lockfile.v2.schema.json +134 -0
  54. package/.agents/schemas/profile.v2.schema.json +114 -0
  55. package/.agents/schemas/runtime.thread.v1.json +192 -0
  56. package/.agents/schemas/skill.manifest.v2.json +177 -0
  57. package/.agents/schemas/verification.spec.v1.json +39 -0
  58. package/.agents/schemas/workspace.graph.schema.json +106 -0
  59. package/.agents/stats.js +22 -9
  60. package/.agents/transaction-core/event-store.js +288 -0
  61. package/.agents/transaction-core/idempotency.js +129 -0
  62. package/.agents/transaction-core/ipc-lock.js +311 -0
  63. package/.agents/transaction-core/plugin-supply-chain-bundle.js +436 -0
  64. package/.agents/validate.js +143 -14
  65. package/.agents/watch.js +354 -102
  66. package/.agents/workspace/workspace-graph.js +778 -0
  67. package/README.md +59 -387
  68. package/benchmarks/v2/analysis/statistics.js +140 -0
  69. package/benchmarks/v2/analysis/stats.js +69 -0
  70. package/benchmarks/v2/arms/arm-definitions.js +79 -0
  71. package/benchmarks/v2/dataset.schema.json +34 -0
  72. package/benchmarks/v2/evaluators/index.js +25 -0
  73. package/benchmarks/v2/evaluators/verified-success.js +116 -0
  74. package/benchmarks/v2/harness/runner.js +88 -0
  75. package/benchmarks/v2/pilot-tasks.json +392 -0
  76. package/bin/commands/recover.js +88 -0
  77. package/bin/commands/uninstall.js +207 -0
  78. package/bin/commands/update.js +325 -0
  79. package/bin/commands.js +342 -0
  80. package/bin/index.js +326 -149
  81. package/bin/lib/detector.js +106 -0
  82. package/bin/lib/lockfile.js +253 -0
  83. package/bin/lib/safe-writer.js +290 -0
  84. package/package.json +85 -73
  85. package/registry.json +15 -7
  86. package/registry.schema.json +3 -1
  87. package/registry.v2.schema.json +86 -0
  88. package/.agents/core/profiles/backend.yaml +0 -47
  89. package/.agents/core/profiles/enterprise.yaml +0 -46
  90. package/.agents/core/profiles/frontend.yaml +0 -46
  91. package/.agents/core/profiles/hackathon.yaml +0 -45
  92. package/.agents/core/profiles/mvp.yaml +0 -44
  93. package/.agents/core/profiles/startup.yaml +0 -48
  94. package/.agents/core/skills/adapters/EXAMPLES.md +0 -19
  95. package/.agents/core/skills/adapters/SKILL.md +0 -105
  96. package/.agents/core/skills/adapters/TROUBLESHOOTING.md +0 -7
  97. package/.agents/core/skills/adapters/VALIDATION.json +0 -12
  98. package/.agents/core/skills/adapters/skill.yaml +0 -10
  99. package/.agents/core/skills/architecture-diagrams/SKILL.md +0 -108
  100. package/.agents/core/skills/architecture-diagrams/VALIDATION.json +0 -12
  101. package/.agents/core/skills/architecture-diagrams/skill.yaml +0 -8
  102. package/.agents/core/skills/brutalist-design/SKILL.md +0 -150
  103. package/.agents/core/skills/brutalist-design/VALIDATION.json +0 -12
  104. package/.agents/core/skills/brutalist-design/skill.yaml +0 -8
  105. package/.agents/core/skills/database/EXAMPLES.md +0 -74
  106. package/.agents/core/skills/database/SKILL.md +0 -101
  107. package/.agents/core/skills/database/TROUBLESHOOTING.md +0 -18
  108. package/.agents/core/skills/database/VALIDATION.json +0 -11
  109. package/.agents/core/skills/database/skill.yaml +0 -25
  110. package/.agents/core/skills/ddd/EXAMPLES.md +0 -42
  111. package/.agents/core/skills/ddd/SKILL.md +0 -247
  112. package/.agents/core/skills/ddd/TROUBLESHOOTING.md +0 -19
  113. package/.agents/core/skills/ddd/VALIDATION.json +0 -12
  114. package/.agents/core/skills/ddd/ddd.md +0 -178
  115. package/.agents/core/skills/ddd/skill.yaml +0 -10
  116. package/.agents/core/skills/decisions/EXAMPLES.md +0 -35
  117. package/.agents/core/skills/decisions/SKILL.md +0 -90
  118. package/.agents/core/skills/decisions/TROUBLESHOOTING.md +0 -13
  119. package/.agents/core/skills/decisions/VALIDATION.json +0 -12
  120. package/.agents/core/skills/decisions/skill.yaml +0 -10
  121. package/.agents/core/skills/docker/EXAMPLES.md +0 -56
  122. package/.agents/core/skills/docker/SKILL.md +0 -63
  123. package/.agents/core/skills/docker/TROUBLESHOOTING.md +0 -18
  124. package/.agents/core/skills/docker/VALIDATION.json +0 -11
  125. package/.agents/core/skills/docker/skill.yaml +0 -23
  126. package/.agents/core/skills/fastapi/EXAMPLES.md +0 -36
  127. package/.agents/core/skills/fastapi/SKILL.md +0 -148
  128. package/.agents/core/skills/fastapi/TROUBLESHOOTING.md +0 -19
  129. package/.agents/core/skills/fastapi/VALIDATION.json +0 -12
  130. package/.agents/core/skills/fastapi/fastapi.md +0 -112
  131. package/.agents/core/skills/fastapi/skill.yaml +0 -10
  132. package/.agents/core/skills/generators/EXAMPLES.md +0 -19
  133. package/.agents/core/skills/generators/SKILL.md +0 -112
  134. package/.agents/core/skills/generators/TROUBLESHOOTING.md +0 -7
  135. package/.agents/core/skills/generators/VALIDATION.json +0 -12
  136. package/.agents/core/skills/generators/skill.yaml +0 -10
  137. package/.agents/core/skills/generators/templates/API.md +0 -77
  138. package/.agents/core/skills/generators/templates/ARCHITECTURE.md +0 -70
  139. package/.agents/core/skills/generators/templates/DATABASE.md +0 -42
  140. package/.agents/core/skills/generators/templates/DECISION.md +0 -46
  141. package/.agents/core/skills/generators/templates/PRD.md +0 -67
  142. package/.agents/core/skills/generators/templates/PROJECT_GRAPH.md +0 -56
  143. package/.agents/core/skills/generators/templates/ROADMAP.md +0 -51
  144. package/.agents/core/skills/generators/templates/TASKS.md +0 -43
  145. package/.agents/core/skills/generators/templates/UI.md +0 -73
  146. package/.agents/core/skills/graphify/EXAMPLES.md +0 -73
  147. package/.agents/core/skills/graphify/SKILL.md +0 -130
  148. package/.agents/core/skills/graphify/VALIDATION.json +0 -12
  149. package/.agents/core/skills/graphify/skill.yaml +0 -13
  150. package/.agents/core/skills/impeccable-design/EXAMPLES.md +0 -26
  151. package/.agents/core/skills/impeccable-design/SKILL.md +0 -201
  152. package/.agents/core/skills/impeccable-design/TROUBLESHOOTING.md +0 -19
  153. package/.agents/core/skills/impeccable-design/VALIDATION.json +0 -12
  154. package/.agents/core/skills/impeccable-design/skill.yaml +0 -14
  155. package/.agents/core/skills/interview-me/SKILL.md +0 -97
  156. package/.agents/core/skills/interview-me/VALIDATION.json +0 -12
  157. package/.agents/core/skills/interview-me/skill.yaml +0 -8
  158. package/.agents/core/skills/microservices/EXAMPLES.md +0 -38
  159. package/.agents/core/skills/microservices/SKILL.md +0 -164
  160. package/.agents/core/skills/microservices/TROUBLESHOOTING.md +0 -19
  161. package/.agents/core/skills/microservices/VALIDATION.json +0 -12
  162. package/.agents/core/skills/microservices/microservices.md +0 -119
  163. package/.agents/core/skills/microservices/skill.yaml +0 -10
  164. package/.agents/core/skills/minimalist-design/SKILL.md +0 -113
  165. package/.agents/core/skills/minimalist-design/VALIDATION.json +0 -12
  166. package/.agents/core/skills/minimalist-design/skill.yaml +0 -8
  167. package/.agents/core/skills/nestjs/EXAMPLES.md +0 -40
  168. package/.agents/core/skills/nestjs/SKILL.md +0 -139
  169. package/.agents/core/skills/nestjs/TROUBLESHOOTING.md +0 -19
  170. package/.agents/core/skills/nestjs/VALIDATION.json +0 -12
  171. package/.agents/core/skills/nestjs/nestjs.md +0 -103
  172. package/.agents/core/skills/nestjs/skill.yaml +0 -10
  173. package/.agents/core/skills/nextjs/EXAMPLES.md +0 -40
  174. package/.agents/core/skills/nextjs/SKILL.md +0 -163
  175. package/.agents/core/skills/nextjs/TROUBLESHOOTING.md +0 -19
  176. package/.agents/core/skills/nextjs/VALIDATION.json +0 -12
  177. package/.agents/core/skills/nextjs/nextjs.md +0 -67
  178. package/.agents/core/skills/nextjs/skill.yaml +0 -10
  179. package/.agents/core/skills/node/EXAMPLES.md +0 -80
  180. package/.agents/core/skills/node/SKILL.md +0 -128
  181. package/.agents/core/skills/node/TROUBLESHOOTING.md +0 -19
  182. package/.agents/core/skills/node/VALIDATION.json +0 -12
  183. package/.agents/core/skills/node/node.md +0 -87
  184. package/.agents/core/skills/node/skill.yaml +0 -10
  185. package/.agents/core/skills/performance/EXAMPLES.md +0 -30
  186. package/.agents/core/skills/performance/SKILL.md +0 -75
  187. package/.agents/core/skills/performance/TROUBLESHOOTING.md +0 -19
  188. package/.agents/core/skills/performance/VALIDATION.json +0 -12
  189. package/.agents/core/skills/performance/performance.md +0 -52
  190. package/.agents/core/skills/performance/skill.yaml +0 -10
  191. package/.agents/core/skills/react/EXAMPLES.md +0 -79
  192. package/.agents/core/skills/react/SKILL.md +0 -132
  193. package/.agents/core/skills/react/TROUBLESHOOTING.md +0 -19
  194. package/.agents/core/skills/react/VALIDATION.json +0 -12
  195. package/.agents/core/skills/react/react.md +0 -93
  196. package/.agents/core/skills/react/skill.yaml +0 -10
  197. package/.agents/core/skills/react-best-practices/SKILL.md +0 -155
  198. package/.agents/core/skills/react-best-practices/VALIDATION.json +0 -12
  199. package/.agents/core/skills/react-best-practices/skill.yaml +0 -10
  200. package/.agents/core/skills/redesign-audit/SKILL.md +0 -117
  201. package/.agents/core/skills/redesign-audit/VALIDATION.json +0 -12
  202. package/.agents/core/skills/redesign-audit/skill.yaml +0 -8
  203. package/.agents/core/skills/soft-design/SKILL.md +0 -108
  204. package/.agents/core/skills/soft-design/VALIDATION.json +0 -12
  205. package/.agents/core/skills/soft-design/skill.yaml +0 -8
  206. package/.agents/core/skills/state-management/EXAMPLES.md +0 -56
  207. package/.agents/core/skills/state-management/SKILL.md +0 -48
  208. package/.agents/core/skills/state-management/TROUBLESHOOTING.md +0 -18
  209. package/.agents/core/skills/state-management/VALIDATION.json +0 -11
  210. package/.agents/core/skills/state-management/skill.yaml +0 -22
  211. package/.agents/core/skills/subagent-orchestrator/SKILL.md +0 -100
  212. package/.agents/core/skills/subagent-orchestrator/VALIDATION.json +0 -12
  213. package/.agents/core/skills/subagent-orchestrator/skill.yaml +0 -8
  214. package/.agents/core/skills/system-design/EXAMPLES.md +0 -75
  215. package/.agents/core/skills/system-design/SKILL.md +0 -419
  216. package/.agents/core/skills/system-design/TROUBLESHOOTING.md +0 -19
  217. package/.agents/core/skills/system-design/VALIDATION.json +0 -12
  218. package/.agents/core/skills/system-design/skill.yaml +0 -13
  219. package/.agents/core/skills/system-design/system-design.md +0 -112
  220. package/.agents/core/skills/testing/EXAMPLES.md +0 -71
  221. package/.agents/core/skills/testing/SKILL.md +0 -70
  222. package/.agents/core/skills/testing/TROUBLESHOOTING.md +0 -18
  223. package/.agents/core/skills/testing/VALIDATION.json +0 -11
  224. package/.agents/core/skills/testing/skill.yaml +0 -26
  225. package/.agents/core/skills/typescript/EXAMPLES.md +0 -64
  226. package/.agents/core/skills/typescript/SKILL.md +0 -112
  227. package/.agents/core/skills/typescript/TROUBLESHOOTING.md +0 -19
  228. package/.agents/core/skills/typescript/VALIDATION.json +0 -12
  229. package/.agents/core/skills/typescript/skill.yaml +0 -10
  230. package/.agents/core/skills/typescript/typescript.md +0 -71
  231. package/.agents/core/skills/ui-design/EXAMPLES.md +0 -21
  232. package/.agents/core/skills/ui-design/SKILL.md +0 -124
  233. package/.agents/core/skills/ui-design/TROUBLESHOOTING.md +0 -19
  234. package/.agents/core/skills/ui-design/VALIDATION.json +0 -12
  235. package/.agents/core/skills/ui-design/skill.yaml +0 -10
  236. package/.agents/core/skills/ui-design/ui.md +0 -88
  237. package/.agents/core/skills/ui-ux-pro/EXAMPLES.md +0 -62
  238. package/.agents/core/skills/ui-ux-pro/SKILL.md +0 -375
  239. package/.agents/core/skills/ui-ux-pro/TROUBLESHOOTING.md +0 -19
  240. package/.agents/core/skills/ui-ux-pro/VALIDATION.json +0 -12
  241. package/.agents/core/skills/ui-ux-pro/skill.yaml +0 -13
  242. package/.agents/core/skills/ux-design/EXAMPLES.md +0 -36
  243. package/.agents/core/skills/ux-design/SKILL.md +0 -116
  244. package/.agents/core/skills/ux-design/TROUBLESHOOTING.md +0 -19
  245. package/.agents/core/skills/ux-design/VALIDATION.json +0 -12
  246. package/.agents/core/skills/ux-design/skill.yaml +0 -10
  247. package/.agents/core/skills/ux-design/ux.md +0 -80
  248. package/.agents/core/skills/vercel-optimize/SKILL.md +0 -83
  249. package/.agents/core/skills/vercel-optimize/VALIDATION.json +0 -12
  250. package/.agents/core/skills/vercel-optimize/skill.yaml +0 -10
  251. package/.agents/core/skills/web-accessibility/EXAMPLES.md +0 -39
  252. package/.agents/core/skills/web-accessibility/SKILL.md +0 -170
  253. package/.agents/core/skills/web-accessibility/TROUBLESHOOTING.md +0 -19
  254. package/.agents/core/skills/web-accessibility/VALIDATION.json +0 -12
  255. package/.agents/core/skills/web-accessibility/accessibility.md +0 -63
  256. package/.agents/core/skills/web-accessibility/skill.yaml +0 -10
  257. package/.agents/generated/claude/skills/adapters/SKILL.md +0 -126
  258. package/.agents/generated/claude/skills/architecture-diagrams/SKILL.md +0 -101
  259. package/.agents/generated/claude/skills/brutalist-design/SKILL.md +0 -145
  260. package/.agents/generated/claude/skills/database/SKILL.md +0 -191
  261. package/.agents/generated/claude/skills/ddd/SKILL.md +0 -305
  262. package/.agents/generated/claude/skills/decisions/SKILL.md +0 -134
  263. package/.agents/generated/claude/skills/docker/SKILL.md +0 -135
  264. package/.agents/generated/claude/skills/fastapi/SKILL.md +0 -200
  265. package/.agents/generated/claude/skills/generators/SKILL.md +0 -133
  266. package/.agents/generated/claude/skills/graphify/SKILL.md +0 -198
  267. package/.agents/generated/claude/skills/impeccable-design/SKILL.md +0 -241
  268. package/.agents/generated/claude/skills/interview-me/SKILL.md +0 -90
  269. package/.agents/generated/claude/skills/microservices/SKILL.md +0 -218
  270. package/.agents/generated/claude/skills/minimalist-design/SKILL.md +0 -108
  271. package/.agents/generated/claude/skills/nestjs/SKILL.md +0 -195
  272. package/.agents/generated/claude/skills/nextjs/SKILL.md +0 -219
  273. package/.agents/generated/claude/skills/node/SKILL.md +0 -224
  274. package/.agents/generated/claude/skills/performance/SKILL.md +0 -121
  275. package/.agents/generated/claude/skills/react/SKILL.md +0 -227
  276. package/.agents/generated/claude/skills/react-best-practices/SKILL.md +0 -146
  277. package/.agents/generated/claude/skills/redesign-audit/SKILL.md +0 -112
  278. package/.agents/generated/claude/skills/soft-design/SKILL.md +0 -103
  279. package/.agents/generated/claude/skills/state-management/SKILL.md +0 -120
  280. package/.agents/generated/claude/skills/subagent-orchestrator/SKILL.md +0 -93
  281. package/.agents/generated/claude/skills/system-design/SKILL.md +0 -507
  282. package/.agents/generated/claude/skills/testing/SKILL.md +0 -157
  283. package/.agents/generated/claude/skills/typescript/SKILL.md +0 -192
  284. package/.agents/generated/claude/skills/ui-design/SKILL.md +0 -161
  285. package/.agents/generated/claude/skills/ui-ux-pro/SKILL.md +0 -451
  286. package/.agents/generated/claude/skills/ux-design/SKILL.md +0 -168
  287. package/.agents/generated/claude/skills/vercel-optimize/SKILL.md +0 -76
  288. package/.agents/generated/claude/skills/web-accessibility/SKILL.md +0 -225
  289. package/.agents/generated/gemini/skills/adapters/SKILL.md +0 -135
  290. package/.agents/generated/gemini/skills/architecture-diagrams/SKILL.md +0 -107
  291. package/.agents/generated/gemini/skills/brutalist-design/SKILL.md +0 -151
  292. package/.agents/generated/gemini/skills/database/SKILL.md +0 -200
  293. package/.agents/generated/gemini/skills/ddd/SKILL.md +0 -314
  294. package/.agents/generated/gemini/skills/decisions/SKILL.md +0 -143
  295. package/.agents/generated/gemini/skills/docker/SKILL.md +0 -144
  296. package/.agents/generated/gemini/skills/fastapi/SKILL.md +0 -209
  297. package/.agents/generated/gemini/skills/generators/SKILL.md +0 -142
  298. package/.agents/generated/gemini/skills/graphify/SKILL.md +0 -205
  299. package/.agents/generated/gemini/skills/impeccable-design/SKILL.md +0 -250
  300. package/.agents/generated/gemini/skills/interview-me/SKILL.md +0 -96
  301. package/.agents/generated/gemini/skills/microservices/SKILL.md +0 -227
  302. package/.agents/generated/gemini/skills/minimalist-design/SKILL.md +0 -114
  303. package/.agents/generated/gemini/skills/nestjs/SKILL.md +0 -204
  304. package/.agents/generated/gemini/skills/nextjs/SKILL.md +0 -298
  305. package/.agents/generated/gemini/skills/node/SKILL.md +0 -323
  306. package/.agents/generated/gemini/skills/performance/SKILL.md +0 -185
  307. package/.agents/generated/gemini/skills/react/SKILL.md +0 -332
  308. package/.agents/generated/gemini/skills/react-best-practices/SKILL.md +0 -152
  309. package/.agents/generated/gemini/skills/redesign-audit/SKILL.md +0 -118
  310. package/.agents/generated/gemini/skills/soft-design/SKILL.md +0 -109
  311. package/.agents/generated/gemini/skills/state-management/SKILL.md +0 -129
  312. package/.agents/generated/gemini/skills/subagent-orchestrator/SKILL.md +0 -99
  313. package/.agents/generated/gemini/skills/system-design/SKILL.md +0 -631
  314. package/.agents/generated/gemini/skills/testing/SKILL.md +0 -166
  315. package/.agents/generated/gemini/skills/typescript/SKILL.md +0 -275
  316. package/.agents/generated/gemini/skills/ui-design/SKILL.md +0 -170
  317. package/.agents/generated/gemini/skills/ui-ux-pro/SKILL.md +0 -460
  318. package/.agents/generated/gemini/skills/ux-design/SKILL.md +0 -177
  319. package/.agents/generated/gemini/skills/vercel-optimize/SKILL.md +0 -82
  320. package/.agents/generated/gemini/skills/web-accessibility/SKILL.md +0 -300
  321. package/.agents/mcp/runtime.py +0 -470
  322. package/.agents/mcp/server.mjs +0 -189271
  323. package/benchmarks/gemini-issues.js +0 -533
@@ -25,6 +25,17 @@ const path = require('path');
25
25
  const https = require('https');
26
26
  const crypto = require('crypto');
27
27
  const { execFileSync } = require('child_process');
28
+ const { ProjectMutationLock } = require('./filesystem/project-lock.js');
29
+ const JournaledTransaction = require('./filesystem/journaled-transaction.js');
30
+ const {
31
+ calculateTreeDigest,
32
+ validateArchiveEntry,
33
+ validatePluginPinning,
34
+ ScriptGrantManager,
35
+ AtomicPluginUpdater,
36
+ THREAT_CODES,
37
+ verifyNpmTarballIntegrity,
38
+ } = require('./transaction-core/plugin-supply-chain-bundle.js');
28
39
 
29
40
  // ── Paths ─────────────────────────────────────────────────────────────────────
30
41
  const ROOT = process.cwd();
@@ -36,6 +47,17 @@ const REGISTRY_URL = 'https://raw.githubusercontent.com/kok-o/contextos-agents
36
47
  const MAX_DOWNLOAD_BYTES = 5 * 1024 * 1024;
37
48
  const SAFE_SKILL_NAME = /^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/;
38
49
 
50
+ // ── ANSI helpers ──────────────────────────────────────────────────────────────
51
+ const NO_COLOR = process.env.NO_COLOR || !process.stdout.isTTY;
52
+ const c = {
53
+ green: (s) => NO_COLOR ? s : `\x1b[32m${s}\x1b[0m`,
54
+ red: (s) => NO_COLOR ? s : `\x1b[31m${s}\x1b[0m`,
55
+ yellow: (s) => NO_COLOR ? s : `\x1b[33m${s}\x1b[0m`,
56
+ cyan: (s) => NO_COLOR ? s : `\x1b[36m${s}\x1b[0m`,
57
+ bold: (s) => NO_COLOR ? s : `\x1b[1m${s}\x1b[0m`,
58
+ dim: (s) => NO_COLOR ? s : `\x1b[2m${s}\x1b[0m`,
59
+ };
60
+
39
61
  // ── Prompt Injection Scanner ──────────────────────────────────────────────────
40
62
  const PROMPT_INJECTION_PATTERNS = [
41
63
  /ignore\s+(all\s+)?previous\s+instructions/i,
@@ -48,6 +70,18 @@ const PROMPT_INJECTION_PATTERNS = [
48
70
  /\b(?:eval|exec|Function)\s*\(/i,
49
71
  ];
50
72
 
73
+ // ── Shell Escapes & Script Injection Scanner ──────────────────────────────────
74
+ const SHELL_ESCAPE_PATTERNS = [
75
+ /(?:curl|wget)\s+[^|\n\r]+?\|\s*(?:bash|sh|zsh|powershell|pwsh|cmd)/i,
76
+ /\bpowershell(?:\.exe)?\s+.*-(?:enc|encodedcommand|ep\s+bypass)\b/i,
77
+ /\b(?:rm\s+-rf\s+[\/~]|del\s+\/[sfq]\s+[c-z]:[\\\/])/i,
78
+ /\b(?:mkfifo|mknod)\s+.*?;/i,
79
+ /\b(?:nc|netcat|ncat)\s+-[elp]+/i,
80
+ /<script\b[^>]*>[\s\S]*?<\/script>/i,
81
+ /javascript\s*:\s*[^\s'"]+/i,
82
+ /data:text\/html/i,
83
+ ];
84
+
51
85
  function scanForPromptInjection(content, options = {}) {
52
86
  if (typeof content !== 'string') return false;
53
87
  const found = PROMPT_INJECTION_PATTERNS.filter(p => p.test(content));
@@ -62,16 +96,124 @@ function scanForPromptInjection(content, options = {}) {
62
96
  return false;
63
97
  }
64
98
 
65
- // ── ANSI helpers ──────────────────────────────────────────────────────────────
66
- const NO_COLOR = process.env.NO_COLOR || !process.stdout.isTTY;
67
- const c = {
68
- green: (s) => NO_COLOR ? s : `\x1b[32m${s}\x1b[0m`,
69
- red: (s) => NO_COLOR ? s : `\x1b[31m${s}\x1b[0m`,
70
- yellow: (s) => NO_COLOR ? s : `\x1b[33m${s}\x1b[0m`,
71
- cyan: (s) => NO_COLOR ? s : `\x1b[36m${s}\x1b[0m`,
72
- bold: (s) => NO_COLOR ? s : `\x1b[1m${s}\x1b[0m`,
73
- dim: (s) => NO_COLOR ? s : `\x1b[2m${s}\x1b[0m`,
74
- };
99
+ function scanContentSecurity(content, filePath, options = {}) {
100
+ if (typeof content !== 'string') return false;
101
+
102
+ const injectionMatches = PROMPT_INJECTION_PATTERNS.filter(p => p.test(content));
103
+ const shellMatches = SHELL_ESCAPE_PATTERNS.filter(p => p.test(content));
104
+
105
+ const allMatches = [
106
+ ...injectionMatches.map(p => `prompt_injection: ${p.source}`),
107
+ ...shellMatches.map(p => `shell_or_script: ${p.source}`),
108
+ ];
109
+
110
+ if (allMatches.length > 0) {
111
+ const rel = filePath ? path.basename(filePath) : 'content';
112
+ console.warn(c.red(` 🚨 Unsafe pattern or injection detected in plugin file '${rel}':`));
113
+ console.warn(c.yellow(` Matches: ${allMatches.join('; ')}`));
114
+ if (options.throwOnMatch) {
115
+ throw new Error(`Security validation failed: unsafe content or script injection detected in '${rel}' (${allMatches.length} pattern matches). Installation aborted. Pass --force-unsafe-prompts to override.`);
116
+ }
117
+ return true;
118
+ }
119
+ return false;
120
+ }
121
+
122
+ function isScannableFile(fileName) {
123
+ const ext = path.extname(fileName).toLowerCase();
124
+ const scannableExtensions = new Set([
125
+ '.md', '.mdc', '.markdown', '.txt',
126
+ '.yaml', '.yml', '.json',
127
+ '.sh', '.bash', '.zsh', '.ps1', '.bat', '.cmd',
128
+ '.js', '.ts', '.mjs', '.cjs', '.py',
129
+ ]);
130
+ return scannableExtensions.has(ext) || !ext;
131
+ }
132
+
133
+ /**
134
+ * Recursively scan an entire plugin directory tree:
135
+ * - Rejects any symlinks that traverse outside the plugin directory
136
+ * - Scans all documentation and code files (EXAMPLES.md, TROUBLESHOOTING.md, references/, etc.)
137
+ * for prompt injection, script injection, and dangerous shell escape patterns.
138
+ */
139
+ function scanPluginDirectory(pluginDir, options = {}) {
140
+ const throwOnMatch = options.throwOnMatch !== undefined ? options.throwOnMatch : !options.forceUnsafe;
141
+ if (!fs.existsSync(pluginDir)) return true;
142
+
143
+ const realPluginDir = fs.realpathSync(pluginDir);
144
+
145
+ function walk(currentDir) {
146
+ let entries;
147
+ try {
148
+ entries = fs.readdirSync(currentDir);
149
+ } catch {
150
+ return;
151
+ }
152
+
153
+ for (const name of entries) {
154
+ const fullPath = path.join(currentDir, name);
155
+ let lstat;
156
+ try {
157
+ lstat = fs.lstatSync(fullPath);
158
+ } catch {
159
+ continue;
160
+ }
161
+
162
+ // 1. Check symlink traversal
163
+ if (lstat.isSymbolicLink()) {
164
+ let linkTarget;
165
+ try {
166
+ linkTarget = fs.readlinkSync(fullPath);
167
+ } catch (e) {
168
+ throw new Error(`Security validation failed: Unreadable symlink '${name}'. Installation aborted.`);
169
+ }
170
+
171
+ const resolvedTarget = path.resolve(currentDir, linkTarget);
172
+ let realTarget;
173
+ try {
174
+ realTarget = fs.realpathSync(resolvedTarget);
175
+ } catch {
176
+ realTarget = resolvedTarget;
177
+ }
178
+
179
+ const normalizedRealPlugin = realPluginDir.replace(/\\/g, '/').toLowerCase();
180
+ const normalizedRealTarget = realTarget.replace(/\\/g, '/').toLowerCase();
181
+
182
+ if (
183
+ !normalizedRealTarget.startsWith(normalizedRealPlugin + '/') &&
184
+ normalizedRealTarget !== normalizedRealPlugin
185
+ ) {
186
+ throw new Error(
187
+ `Security validation failed: Symlink traversal detected in '${name}' pointing outside plugin directory to '${linkTarget}'. Installation aborted.`
188
+ );
189
+ }
190
+ }
191
+
192
+ // 2. Walk subdirectories
193
+ if (lstat.isDirectory()) {
194
+ if (name === '.git' || name === 'node_modules') continue;
195
+ walk(fullPath);
196
+ } else if (lstat.isFile()) {
197
+ if (name === '.source') continue;
198
+ if (lstat.size > 2 * 1024 * 1024) continue;
199
+
200
+ if (isScannableFile(name)) {
201
+ try {
202
+ const content = fs.readFileSync(fullPath, 'utf8');
203
+ scanContentSecurity(content, fullPath, { throwOnMatch });
204
+ } catch (err) {
205
+ if (err.message && err.message.startsWith('Security validation failed')) {
206
+ throw err;
207
+ }
208
+ }
209
+ }
210
+ }
211
+ }
212
+ }
213
+
214
+ walk(pluginDir);
215
+ return true;
216
+ }
75
217
 
76
218
  // ── plugins.json lock file ─────────────────────────────────────────────────────
77
219
  function readLock() {
@@ -83,9 +225,19 @@ function readLock() {
83
225
  }
84
226
  }
85
227
 
86
- function writeLock(lock) {
87
- fs.mkdirSync(AGENTS_DIR, { recursive: true });
88
- fs.writeFileSync(PLUGINS_JSON, JSON.stringify(lock, null, 2) + '\n');
228
+ function writeLock(lock, tx = null) {
229
+ if (tx) {
230
+ const relLockPath = toPosix(path.relative(ROOT, PLUGINS_JSON));
231
+ tx.stageWrite(relLockPath, JSON.stringify(lock, null, 2) + '\n');
232
+ } else {
233
+ fs.mkdirSync(AGENTS_DIR, { recursive: true });
234
+ fs.writeFileSync(PLUGINS_JSON, JSON.stringify(lock, null, 2) + '\n');
235
+ }
236
+ }
237
+
238
+ // POSIX path helper
239
+ function toPosix(p) {
240
+ return p.replace(/\\/g, '/');
89
241
  }
90
242
 
91
243
  // ── HTTP fetch helper (no deps) ───────────────────────────────────────────────
@@ -162,7 +314,17 @@ function parseRef(ref) {
162
314
  throw new Error('Invalid GitHub plugin reference');
163
315
  }
164
316
 
165
- return { type: 'github', owner, repo, gitRef, isPinned: repoRef.includes('@'), subPath, raw: ref };
317
+ const isPinned = repoRef.includes('@');
318
+ return {
319
+ type: 'github',
320
+ owner,
321
+ repo,
322
+ gitRef,
323
+ commit: isPinned ? gitRef : undefined,
324
+ isPinned,
325
+ subPath,
326
+ raw: ref,
327
+ };
166
328
  }
167
329
  // npm package
168
330
  if (!isValidNpmPackage(ref)) {
@@ -172,7 +334,7 @@ function parseRef(ref) {
172
334
  }
173
335
 
174
336
  // ── GitHub installer ──────────────────────────────────────────────────────────
175
- async function installFromGitHub(descriptor, skillName, dryRun, checksum, forceUnsafe = false) {
337
+ async function installFromGitHub(descriptor, skillName, dryRun, checksum, forceUnsafe = false, tx = null) {
176
338
  const { owner, repo, gitRef, isPinned, subPath } = descriptor;
177
339
  const skillPath = subPath || '';
178
340
  const base = `https://raw.githubusercontent.com/${owner}/${repo}/${gitRef}`;
@@ -219,6 +381,7 @@ async function installFromGitHub(descriptor, skillName, dryRun, checksum, forceU
219
381
  }
220
382
 
221
383
  const targetDir = path.join(PLUGINS_DIR, skillName);
384
+ const relTargetDir = path.relative(ROOT, targetDir).replace(/\\/g, '/');
222
385
 
223
386
  if (dryRun) {
224
387
  console.log(c.yellow(` [DRY-RUN] Would install to: ${targetDir}`));
@@ -226,14 +389,30 @@ async function installFromGitHub(descriptor, skillName, dryRun, checksum, forceU
226
389
  return sha256;
227
390
  }
228
391
 
229
- fs.mkdirSync(targetDir, { recursive: true });
230
- fs.writeFileSync(path.join(targetDir, 'SKILL.md'), skillMdContent);
392
+ // Pre-scan using temp dir to keep `scanPluginDirectory` happy, as it reads from disk
393
+ const temporaryPrefix = fs.mkdtempSync(path.join(os.tmpdir(), 'contextos-github-'));
394
+ try {
395
+ fs.writeFileSync(path.join(temporaryPrefix, 'SKILL.md'), skillMdContent);
396
+ if (yamlContent) fs.writeFileSync(path.join(temporaryPrefix, 'skill.yaml'), yamlContent);
397
+ scanPluginDirectory(temporaryPrefix, { throwOnMatch: !forceUnsafe, forceUnsafe });
398
+ } finally {
399
+ fs.rmSync(temporaryPrefix, { recursive: true, force: true });
400
+ }
401
+
402
+ const manifest = {};
403
+
404
+ const skillMdHash = crypto.createHash('sha256').update(skillMdContent).digest('hex');
405
+ tx.stageWrite(`${relTargetDir}/SKILL.md`, skillMdContent);
406
+ manifest['SKILL.md'] = skillMdHash;
407
+
231
408
  if (yamlContent) {
232
- fs.writeFileSync(path.join(targetDir, 'skill.yaml'), yamlContent);
409
+ const yamlHash = crypto.createHash('sha256').update(yamlContent).digest('hex');
410
+ tx.stageWrite(`${relTargetDir}/skill.yaml`, yamlContent);
411
+ manifest['skill.yaml'] = yamlHash;
233
412
  }
234
413
 
235
- // Write a source marker so we know where to update from
236
- fs.writeFileSync(path.join(targetDir, '.source'), JSON.stringify({
414
+ // Write a source marker with manifest
415
+ const sourceContent = JSON.stringify({
237
416
  type: 'github',
238
417
  owner,
239
418
  repo,
@@ -242,7 +421,9 @@ async function installFromGitHub(descriptor, skillName, dryRun, checksum, forceU
242
421
  ref: descriptor.raw,
243
422
  sha256,
244
423
  installedAt: new Date().toISOString(),
245
- }, null, 2));
424
+ manifest,
425
+ }, null, 2);
426
+ tx.stageWrite(`${relTargetDir}/.source`, sourceContent);
246
427
 
247
428
  console.log(c.green(` ✓ Installed SKILL.md (${skillMdContent.length} bytes, sha256: ${sha256.slice(0, 12)}...)`));
248
429
  if (yamlContent) console.log(c.green(` ✓ Installed skill.yaml`));
@@ -250,12 +431,15 @@ async function installFromGitHub(descriptor, skillName, dryRun, checksum, forceU
250
431
  }
251
432
 
252
433
  // ── npm installer ─────────────────────────────────────────────────────────────
253
- function installFromNpm(descriptor, skillName, dryRun, checksum, forceUnsafe = false) {
434
+ function installFromNpm(descriptor, skillName, dryRun, checksum, forceUnsafe = false, tx = null) {
254
435
  const pkgName = descriptor.package;
255
436
 
437
+ const targetDir = path.join(PLUGINS_DIR, skillName);
438
+ const relTargetDir = path.relative(ROOT, targetDir).replace(/\\/g, '/');
439
+
256
440
  if (dryRun) {
257
441
  console.log(c.yellow(` [DRY-RUN] Would run: npm install ${pkgName}`));
258
- console.log(c.yellow(` [DRY-RUN] Would copy skill to: ${path.join(PLUGINS_DIR, skillName)}`));
442
+ console.log(c.yellow(` [DRY-RUN] Would copy skill to: ${targetDir}`));
259
443
  return null;
260
444
  }
261
445
 
@@ -286,21 +470,42 @@ function installFromNpm(descriptor, skillName, dryRun, checksum, forceUnsafe = f
286
470
  if (checksum && sha256.toLowerCase() !== checksum.toLowerCase()) {
287
471
  throw new Error(`Checksum mismatch for ${skillName}: expected ${checksum}, got ${sha256}`);
288
472
  }
289
- scanForPromptInjection(skillMdContent, { throwOnMatch: !forceUnsafe });
473
+ // Deep security scan across all plugin files (EXAMPLES.md, TROUBLESHOOTING.md, references/, symlinks)
474
+ scanPluginDirectory(skillSrc, { throwOnMatch: !forceUnsafe, forceUnsafe });
475
+
476
+ const manifest = {};
477
+
478
+ // Helper to walk and stage all files
479
+ const stageRecursive = (dir, relBase) => {
480
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
481
+ if (entry.name === '.git' || entry.name === 'node_modules') continue;
482
+ const fullPath = path.join(dir, entry.name);
483
+ const entryRel = relBase ? `${relBase}/${entry.name}` : entry.name;
484
+
485
+ if (entry.isDirectory()) {
486
+ stageRecursive(fullPath, entryRel);
487
+ } else if (entry.isFile() || entry.isSymbolicLink()) {
488
+ // Read content to stage it
489
+ const content = fs.readFileSync(fullPath);
490
+ const hash = crypto.createHash('sha256').update(content).digest('hex');
491
+ tx.stageWrite(`${relTargetDir}/${entryRel}`, content);
492
+ manifest[entryRel] = hash;
493
+ }
494
+ }
495
+ };
290
496
 
291
- const targetDir = path.join(PLUGINS_DIR, skillName);
292
- fs.rmSync(targetDir, { recursive: true, force: true });
293
- fs.mkdirSync(targetDir, { recursive: true });
294
- fs.cpSync(skillSrc, targetDir, { recursive: true, force: true });
497
+ stageRecursive(skillSrc, '');
295
498
 
296
499
  // Write source marker
297
- fs.writeFileSync(path.join(targetDir, '.source'), JSON.stringify({
500
+ const sourceContent = JSON.stringify({
298
501
  type: 'npm',
299
502
  package: pkgName,
300
503
  ref: descriptor.raw,
301
504
  sha256,
302
505
  installedAt: new Date().toISOString(),
303
- }, null, 2));
506
+ manifest,
507
+ }, null, 2);
508
+ tx.stageWrite(`${relTargetDir}/.source`, sourceContent);
304
509
  } finally {
305
510
  fs.rmSync(temporaryPrefix, { recursive: true, force: true });
306
511
  }
@@ -333,6 +538,8 @@ async function add(ref, options = {}) {
333
538
  const dryRun = Boolean(options.dryRun || options['dry-run']);
334
539
  const checksum = options.checksum || null;
335
540
  const forceUnsafe = Boolean(options.forceUnsafe || options['force-unsafe-prompts']);
541
+ const allowFloating = Boolean(options.allowFloating || options['allow-floating']);
542
+ const force = Boolean(options.force);
336
543
 
337
544
  if (!ref) {
338
545
  console.error(c.red('Usage: ctx.js skill add <ref> [--checksum <sha256>]'));
@@ -347,6 +554,27 @@ async function add(ref, options = {}) {
347
554
  throw new Error(`Invalid plugin name derived from reference: '${skillName}'`);
348
555
  }
349
556
 
557
+ // Supply-chain source pinning check (Wave 6)
558
+ if (!dryRun) {
559
+ const pinning = validatePluginPinning(
560
+ {
561
+ type: descriptor.type,
562
+ commit: descriptor.commit,
563
+ version: descriptor.version,
564
+ integrity: descriptor.integrity,
565
+ },
566
+ { allowFloating }
567
+ );
568
+
569
+ if (!pinning.valid) {
570
+ console.error(c.red(`\n[ERROR] Supply-chain pinning check failed: ${pinning.error}`));
571
+ if (pinning.code === THREAT_CODES.FLOATING_SOURCE_BLOCKED) {
572
+ console.error(c.yellow(' Pass --allow-floating to override this check with explicit user consent.'));
573
+ }
574
+ process.exit(1);
575
+ }
576
+ }
577
+
350
578
  console.log('');
351
579
  console.log(c.bold(`Installing skill: ${c.cyan(skillName)}`));
352
580
  console.log(c.dim(` Source: ${ref} (${descriptor.type})`));
@@ -363,45 +591,86 @@ async function add(ref, options = {}) {
363
591
  // Check if already installed
364
592
  const lock = readLock();
365
593
  const existing = lock.plugins.find(p => p.name === skillName);
594
+ const targetDir = path.join(PLUGINS_DIR, skillName);
595
+
366
596
  if (existing && !dryRun) {
597
+ // Check for local modifications (Wave 6 / Section 20.7)
598
+ if (fs.existsSync(targetDir) && existing.treeDigest && !force) {
599
+ if (AtomicPluginUpdater.isModifiedLocally(targetDir, existing.treeDigest)) {
600
+ const conflictFile = path.join(targetDir, 'CONFLICT_LOCAL_MODIFICATIONS.md');
601
+ fs.writeFileSync(
602
+ conflictFile,
603
+ `# Conflict: Local Modifications Detected\n\nPlugin "${skillName}" contains local uncommitted modifications.\nAutomatic update was blocked to preserve your changes.\n\nTo overwrite local modifications, re-run with --force.\n`,
604
+ 'utf8'
605
+ );
606
+ console.error(c.red(`\n[ERROR] Plugin in "${targetDir}" has local modifications.`));
607
+ console.error(c.red(` Update blocked to prevent overwriting user changes.`));
608
+ console.error(c.yellow(` Conflict artifact written to: ${conflictFile}`));
609
+ console.error(c.yellow(` Pass --force to overwrite.`));
610
+ process.exit(1);
611
+ }
612
+ }
367
613
  console.log(c.yellow(`\n[WARN] '${skillName}' is already installed (from: ${existing.ref})`));
368
614
  console.log(c.yellow(' Re-installing will overwrite it. Continuing...'));
369
615
  }
370
616
 
371
- let installedSha256 = null;
617
+ const projectLock = new ProjectMutationLock(ROOT);
618
+ const tx = new JournaledTransaction(ROOT);
619
+ let lockToken = null;
620
+
372
621
  try {
622
+ lockToken = projectLock.acquire({ command: 'skill:add' });
623
+
624
+ let installedSha256 = null;
373
625
  if (descriptor.type === 'github') {
374
- installedSha256 = await installFromGitHub(descriptor, skillName, dryRun, checksum, forceUnsafe);
626
+ installedSha256 = await installFromGitHub(descriptor, skillName, dryRun, checksum, forceUnsafe, tx);
375
627
  } else {
376
- installedSha256 = installFromNpm(descriptor, skillName, dryRun, checksum, forceUnsafe);
628
+ installedSha256 = installFromNpm(descriptor, skillName, dryRun, checksum, forceUnsafe, tx);
629
+ }
630
+
631
+ if (!dryRun) {
632
+ // Update lock file
633
+ const idx = lock.plugins.findIndex(p => p.name === skillName);
634
+ const entry = {
635
+ name: skillName,
636
+ ref,
637
+ type: descriptor.type,
638
+ commit: descriptor.commit,
639
+ version: descriptor.version,
640
+ integrity: descriptor.integrity,
641
+ sha256: installedSha256,
642
+ installedAt: new Date().toISOString(),
643
+ };
644
+ if (idx >= 0) lock.plugins[idx] = entry;
645
+ else lock.plugins.push(entry);
646
+
647
+ writeLock(lock, tx);
648
+ tx.commit();
649
+
650
+ // Compute and record full-tree digest into lock
651
+ if (fs.existsSync(targetDir)) {
652
+ const digestResult = calculateTreeDigest(targetDir);
653
+ entry.treeDigest = digestResult.treeDigest;
654
+ writeLock(lock);
655
+ }
656
+
657
+ console.log('');
658
+ console.log(c.green(c.bold('✓ Skill installed successfully!')));
659
+ console.log(c.dim(` Location: .agents/plugins/${skillName}/`));
660
+ console.log('');
661
+ console.log(' Next steps:');
662
+ console.log(` node .agents/ctx.js export all # recompile with new skill`);
663
+ console.log(` node .agents/ctx.js validate # verify everything is consistent`);
664
+ console.log('');
377
665
  }
378
666
  } catch (err) {
667
+ if (tx && tx.state === 'PLANNED') {
668
+ // We don't need to do anything since it wasn't committed
669
+ }
379
670
  console.error(c.red(`\n[ERROR] Failed to install '${skillName}': ${err.message}`));
380
671
  process.exit(1);
381
- }
382
-
383
- if (!dryRun) {
384
- // Update lock file
385
- const idx = lock.plugins.findIndex(p => p.name === skillName);
386
- const entry = {
387
- name: skillName,
388
- ref,
389
- type: descriptor.type,
390
- sha256: installedSha256,
391
- installedAt: new Date().toISOString(),
392
- };
393
- if (idx >= 0) lock.plugins[idx] = entry;
394
- else lock.plugins.push(entry);
395
- writeLock(lock);
396
-
397
- console.log('');
398
- console.log(c.green(c.bold('✓ Skill installed successfully!')));
399
- console.log(c.dim(` Location: .agents/plugins/${skillName}/`));
400
- console.log('');
401
- console.log(' Next steps:');
402
- console.log(` node .agents/ctx.js export all # recompile with new skill`);
403
- console.log(` node .agents/ctx.js validate # verify everything is consistent`);
404
- console.log('');
672
+ } finally {
673
+ if (lockToken) projectLock.release(lockToken);
405
674
  }
406
675
  }
407
676
 
@@ -436,15 +705,85 @@ function remove(skillName) {
436
705
  process.exit(1);
437
706
  }
438
707
 
439
- if (fs.existsSync(skillDir)) {
440
- fs.rmSync(skillDir, { recursive: true, force: true });
441
- }
708
+ const projectLock = new ProjectMutationLock(ROOT);
709
+ const tx = new JournaledTransaction(ROOT);
710
+ let lockToken = null;
442
711
 
443
- lock.plugins = lock.plugins.filter(p => p.name !== skillName);
444
- writeLock(lock);
712
+ try {
713
+ lockToken = projectLock.acquire({ command: 'skill:remove' });
714
+
715
+ if (fs.existsSync(skillDir)) {
716
+ // Find the manifest if it exists
717
+ const sourceFile = path.join(skillDir, '.source');
718
+ let manifest = null;
719
+ if (fs.existsSync(sourceFile)) {
720
+ try {
721
+ const sourceData = JSON.parse(fs.readFileSync(sourceFile, 'utf8'));
722
+ manifest = sourceData.manifest || null;
723
+ } catch {}
724
+ }
445
725
 
446
- console.log(c.green(`✓ Removed plugin: ${skillName}`));
447
- console.log(c.dim(' Run: node .agents/ctx.js export all (to recompile)'));
726
+ const stageRecursiveDeletes = (dir, relBase) => {
727
+ let hasPreservedFiles = false;
728
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
729
+ const fullPath = path.join(dir, entry.name);
730
+ const entryRel = relBase ? `${relBase}/${entry.name}` : entry.name;
731
+
732
+ if (entry.isDirectory()) {
733
+ const preserved = stageRecursiveDeletes(fullPath, entryRel);
734
+ if (preserved) hasPreservedFiles = true;
735
+ else tx.stageDeleteDir(toPosix(path.relative(ROOT, fullPath)));
736
+ } else {
737
+ // Check against manifest
738
+ let shouldDelete = false;
739
+ if (manifest && manifest[entryRel]) {
740
+ const currentHash = crypto.createHash('sha256').update(fs.readFileSync(fullPath)).digest('hex');
741
+ if (currentHash === manifest[entryRel]) {
742
+ shouldDelete = true;
743
+ }
744
+ } else if (!manifest) {
745
+ // If we have NO manifest, we fallback to W3.4 "Remove не удаляет modified/unmanaged files"
746
+ // But without a manifest, we don't know what was installed vs modified.
747
+ // We'll safely skip all files unless we can confidently delete them, OR we can just assume
748
+ // if it's the exact SHA256 of SKILL.md, delete it. But it's safer to only delete known things.
749
+ // We'll just delete SKILL.md and skill.yaml if they match the lockfile hash
750
+ if (entry.name === 'SKILL.md' || entry.name === 'skill.yaml' || entry.name === '.source') {
751
+ shouldDelete = true;
752
+ }
753
+ }
754
+
755
+ if (entry.name === '.source') shouldDelete = true;
756
+
757
+ if (shouldDelete) {
758
+ tx.stageDelete(toPosix(path.relative(ROOT, fullPath)));
759
+ } else {
760
+ hasPreservedFiles = true;
761
+ console.log(c.yellow(` [WARN] Preserving modified/unmanaged file: ${entryRel}`));
762
+ }
763
+ }
764
+ }
765
+ return hasPreservedFiles;
766
+ };
767
+
768
+ const preserved = stageRecursiveDeletes(skillDir, '');
769
+ if (!preserved) {
770
+ tx.stageDeleteDir(toPosix(path.relative(ROOT, skillDir)));
771
+ }
772
+ }
773
+
774
+ lock.plugins = lock.plugins.filter(p => p.name !== skillName);
775
+ writeLock(lock, tx);
776
+
777
+ tx.commit();
778
+
779
+ console.log(c.green(`✓ Removed plugin: ${skillName}`));
780
+ console.log(c.dim(' Run: node .agents/ctx.js export all (to recompile)'));
781
+ } catch (err) {
782
+ console.error(c.red(`\n[ERROR] Failed to remove '${skillName}': ${err.message}`));
783
+ process.exit(1);
784
+ } finally {
785
+ if (lockToken) projectLock.release(lockToken);
786
+ }
448
787
  }
449
788
 
450
789
  /**
@@ -546,22 +885,39 @@ async function search(query) {
546
885
  * both core (builtin) and plugins.
547
886
  * Adapters call this instead of reading CORE_SKILLS directly.
548
887
  */
549
- function collectAllSkillDirs() {
888
+ function collectAllSkillDirs(targetRoot) {
889
+ const root = targetRoot || process.cwd();
890
+ const agentsDir = path.join(root, '.agents');
891
+ const localCore = path.join(agentsDir, 'core', 'skills');
892
+ const localPlugins = path.join(agentsDir, 'plugins');
893
+
894
+ const coreDir = fs.existsSync(localCore) ? localCore : CORE_SKILLS;
895
+ const pluginsDir = fs.existsSync(localPlugins) ? localPlugins : PLUGINS_DIR;
550
896
  const dirs = [];
551
897
 
552
898
  // Core skills
553
- if (fs.existsSync(CORE_SKILLS)) {
554
- for (const name of fs.readdirSync(CORE_SKILLS)) {
555
- const d = path.join(CORE_SKILLS, name);
899
+ if (fs.existsSync(coreDir)) {
900
+ for (const name of fs.readdirSync(coreDir)) {
901
+ const d = path.join(coreDir, name);
556
902
  if (fs.statSync(d).isDirectory()) dirs.push(d);
557
903
  }
558
904
  }
559
905
 
560
- // Plugin skills
561
- if (fs.existsSync(PLUGINS_DIR)) {
562
- for (const name of fs.readdirSync(PLUGINS_DIR)) {
563
- const d = path.join(PLUGINS_DIR, name);
564
- if (fs.statSync(d).isDirectory()) dirs.push(d);
906
+ // Plugin skills (supports both standalone skill dirs and plugin bundles with skills/)
907
+ if (fs.existsSync(pluginsDir)) {
908
+ for (const name of fs.readdirSync(pluginsDir)) {
909
+ const d = path.join(pluginsDir, name);
910
+ if (!fs.statSync(d).isDirectory()) continue;
911
+
912
+ const nestedSkills = path.join(d, 'skills');
913
+ if (fs.existsSync(nestedSkills) && fs.statSync(nestedSkills).isDirectory()) {
914
+ for (const sub of fs.readdirSync(nestedSkills)) {
915
+ const subDir = path.join(nestedSkills, sub);
916
+ if (fs.statSync(subDir).isDirectory()) dirs.push(subDir);
917
+ }
918
+ } else if (fs.existsSync(path.join(d, 'SKILL.md')) || fs.existsSync(path.join(d, 'skill.yaml'))) {
919
+ dirs.push(d);
920
+ }
565
921
  }
566
922
  }
567
923
 
@@ -579,6 +935,9 @@ module.exports = {
579
935
  deriveSkillName,
580
936
  isSafeSkillName,
581
937
  scanForPromptInjection,
938
+ scanContentSecurity,
939
+ scanPluginDirectory,
582
940
  PROMPT_INJECTION_PATTERNS,
941
+ SHELL_ESCAPE_PATTERNS,
583
942
  PLUGINS_DIR,
584
943
  };