@goondocks/myco 0.15.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. package/README.md +6 -4
  2. package/dist/{agent-run-DUOJ3KDI.js → agent-run-S4MFWUSV.js} +8 -8
  3. package/dist/{agent-tasks-LUWBY5JD.js → agent-tasks-JZ77AZSK.js} +8 -8
  4. package/dist/{chunk-TRA3R4EC.js → chunk-34NHDRWI.js} +1 -3
  5. package/dist/chunk-34NHDRWI.js.map +1 -0
  6. package/dist/{chunk-LF5Z62X6.js → chunk-4JVHWBZF.js} +2 -2
  7. package/dist/{chunk-GDCSPMH4.js → chunk-6JZEAOLG.js} +3 -3
  8. package/dist/{chunk-DK5VEBB5.js → chunk-C3GNF7RJ.js} +21 -17
  9. package/dist/chunk-C3GNF7RJ.js.map +1 -0
  10. package/dist/{chunk-DJQOYEK3.js → chunk-CJ2KTRWI.js} +52 -4
  11. package/dist/chunk-CJ2KTRWI.js.map +1 -0
  12. package/dist/{chunk-3MEOYXOW.js → chunk-D2NTFSVO.js} +3 -3
  13. package/dist/{chunk-5YQ6VOFZ.js → chunk-DZWSHCAC.js} +2 -2
  14. package/dist/{chunk-CUADDHHU.js → chunk-E7NUADTQ.js} +9 -1
  15. package/dist/chunk-E7NUADTQ.js.map +1 -0
  16. package/dist/{chunk-R3YW7XVF.js → chunk-G6LM45FD.js} +3 -3
  17. package/dist/{chunk-SV6UCB2Z.js → chunk-GSKXOCFG.js} +3 -1
  18. package/dist/chunk-GSKXOCFG.js.map +1 -0
  19. package/dist/{chunk-RBFECYNA.js → chunk-I3S6L7QC.js} +2 -2
  20. package/dist/{chunk-SPJGJEFV.js → chunk-IRSNOBGD.js} +2 -2
  21. package/dist/{chunk-75J2BR4P.js → chunk-KYSLNB3C.js} +580 -415
  22. package/dist/chunk-KYSLNB3C.js.map +1 -0
  23. package/dist/{chunk-GYIA6XLB.js → chunk-MVBCON4D.js} +2 -2
  24. package/dist/chunk-MVBCON4D.js.map +1 -0
  25. package/dist/{chunk-GZ7MXWYX.js → chunk-OH334Y3J.js} +4 -4
  26. package/dist/{chunk-Z7TZJ2SP.js → chunk-P6C6ADBU.js} +2 -2
  27. package/dist/{chunk-OKCSSDFC.js → chunk-RPILIIYT.js} +2 -2
  28. package/dist/{chunk-X5IXK5KO.js → chunk-TIAYBVSI.js} +153 -18
  29. package/dist/chunk-TIAYBVSI.js.map +1 -0
  30. package/dist/{chunk-OMZCVRX6.js → chunk-U255A3RE.js} +2 -2
  31. package/dist/chunk-U5EW2VIQ.js +86 -0
  32. package/dist/chunk-U5EW2VIQ.js.map +1 -0
  33. package/dist/{chunk-DTE3SHYK.js → chunk-UILSK6DK.js} +2 -2
  34. package/dist/{chunk-EYMKBNRP.js → chunk-V2ZBYKDU.js} +3 -3
  35. package/dist/{chunk-DKGUCEWU.js → chunk-VEQLNB7E.js} +3 -3
  36. package/dist/{chunk-B3SF2CCW.js → chunk-VQZPWCBH.js} +4 -4
  37. package/dist/{chunk-HHZ3RTEI.js → chunk-VWXDSDJU.js} +2 -2
  38. package/dist/{chunk-X4XFJG6I.js → chunk-W7ZOOZMK.js} +3 -3
  39. package/dist/{chunk-23FJUKCN.js → chunk-WKAYMCPR.js} +2 -2
  40. package/dist/{chunk-4BQ5QE76.js → chunk-XAXQ72L3.js} +9 -2
  41. package/dist/chunk-XAXQ72L3.js.map +1 -0
  42. package/dist/{cli-YBD2GPK4.js → cli-U2FZT4GP.js} +42 -42
  43. package/dist/{client-CJ3X252K.js → client-MZL5SFQI.js} +5 -5
  44. package/dist/{config-MOWCOJJ4.js → config-VHHCGE4F.js} +4 -4
  45. package/dist/{detect-GFYKKHLJ.js → detect-6FNYONJF.js} +2 -2
  46. package/dist/{detect-providers-EU35RUL3.js → detect-providers-R7QOB3H6.js} +5 -5
  47. package/dist/{doctor-JR7NEL7K.js → doctor-OJW7SCDQ.js} +13 -13
  48. package/dist/{executor-7XOKS6HS.js → executor-HP3Y64PD.js} +718 -93
  49. package/dist/executor-HP3Y64PD.js.map +1 -0
  50. package/dist/{init-PDLKYWQ4.js → init-XVAONLZ7.js} +18 -18
  51. package/dist/{init-wizard-WH3SXNMB.js → init-wizard-3OPLXLNA.js} +8 -8
  52. package/dist/{installer-BTUNKWOU.js → installer-AARSFXI6.js} +2 -2
  53. package/dist/llm-LS7U7BHC.js +17 -0
  54. package/dist/{loader-WGDVRGLM.js → loader-QDWQTBX4.js} +4 -4
  55. package/dist/{loader-WC4U5NM5.js → loader-YQDG5GI5.js} +4 -4
  56. package/dist/{logs-WFBX2I7C.js → logs-TMKNLSJY.js} +3 -3
  57. package/dist/{main-JB3R3DQE.js → main-VAU5UPY7.js} +611 -214
  58. package/dist/main-VAU5UPY7.js.map +1 -0
  59. package/dist/{open-AADZPSLW.js → open-ES2AOXL5.js} +8 -8
  60. package/dist/{openai-embeddings-SEIV7AM3.js → openai-embeddings-FUW6CSN2.js} +5 -5
  61. package/dist/{openrouter-ELODIZRP.js → openrouter-YSIUSUQL.js} +5 -5
  62. package/dist/{post-compact-KNQ4DYLM.js → post-compact-CCSP4ZRC.js} +8 -8
  63. package/dist/{post-tool-use-OMWHFQLM.js → post-tool-use-5UIVOE7I.js} +7 -7
  64. package/dist/{post-tool-use-failure-KFP6MB7Z.js → post-tool-use-failure-SR2523FX.js} +8 -8
  65. package/dist/{pre-compact-2ZYE2HRB.js → pre-compact-2G2UWGDZ.js} +8 -8
  66. package/dist/{provider-check-B66E5PWS.js → provider-check-VEYONGNU.js} +5 -5
  67. package/dist/{registry-DHWVHXWY.js → registry-5R3DLJQH.js} +5 -5
  68. package/dist/{remove-QT7634L5.js → remove-L4HNBCSZ.js} +10 -10
  69. package/dist/{resolution-events-DBCRVZGU.js → resolution-events-CHOKR35X.js} +5 -5
  70. package/dist/{restart-YQNQEHOU.js → restart-ZQ3QNRF4.js} +9 -9
  71. package/dist/{search-C6JTQDWY.js → search-ECJ76TU3.js} +9 -9
  72. package/dist/{server-QJ3RWZZZ.js → server-4MUFDPDP.js} +5 -5
  73. package/dist/{session-JLVL5TYX.js → session-ZHYO3BBY.js} +10 -10
  74. package/dist/{session-end-XFZRRP5H.js → session-end-UJM3UODF.js} +7 -7
  75. package/dist/{session-start-XGINISXO.js → session-start-UXLG36AZ.js} +12 -12
  76. package/dist/{setup-llm-X2OCM6R7.js → setup-llm-NEN5XPNY.js} +9 -9
  77. package/dist/skill-staging-SWM7UC5D.js +25 -0
  78. package/dist/src/agent/definitions/tasks/full-intelligence.yaml +1 -1
  79. package/dist/src/agent/definitions/tasks/skill-generate.yaml +55 -21
  80. package/dist/src/cli.js +1 -1
  81. package/dist/src/daemon/main.js +1 -1
  82. package/dist/src/hooks/post-tool-use.js +1 -1
  83. package/dist/src/hooks/session-end.js +1 -1
  84. package/dist/src/hooks/session-start.js +1 -1
  85. package/dist/src/hooks/stop.js +1 -1
  86. package/dist/src/hooks/user-prompt-submit.js +1 -1
  87. package/dist/src/mcp/server.js +1 -1
  88. package/dist/src/symbionts/manifests/codex.yaml +1 -0
  89. package/dist/src/symbionts/templates/codex/settings.json +5 -0
  90. package/dist/src/worker/src/schema.ts +16 -0
  91. package/dist/{stats-2EAETG2T.js → stats-AYRSUFHR.js} +10 -10
  92. package/dist/{stop-WOBDYTSA.js → stop-5WD22XAH.js} +7 -7
  93. package/dist/{stop-failure-QEC7ZGBQ.js → stop-failure-FLFIOPJY.js} +8 -8
  94. package/dist/{subagent-start-H6DVRVOE.js → subagent-start-AMCPECUD.js} +8 -8
  95. package/dist/{subagent-stop-LKENKJ65.js → subagent-stop-4M4BUENR.js} +8 -8
  96. package/dist/{task-completed-ZZ47PRPD.js → task-completed-QJOEVDXZ.js} +8 -8
  97. package/dist/{team-J62N7VMG.js → team-FWEVWYIY.js} +6 -6
  98. package/dist/ui/assets/index-Bjv_ck3c.css +1 -0
  99. package/dist/ui/assets/index-RYHXSJv1.js +842 -0
  100. package/dist/ui/index.html +2 -2
  101. package/dist/{update-LX3CJ4TJ.js → update-F2LPJMUE.js} +10 -10
  102. package/dist/{user-prompt-submit-NNMLY3EW.js → user-prompt-submit-UOAIU3JW.js} +7 -7
  103. package/dist/{verify-AMRQXQ3K.js → verify-ITBMLK67.js} +9 -9
  104. package/dist/{version-6OJH5HLZ.js → version-2NJN3WW6.js} +2 -2
  105. package/dist/version-2NJN3WW6.js.map +1 -0
  106. package/package.json +2 -2
  107. package/dist/chunk-4BQ5QE76.js.map +0 -1
  108. package/dist/chunk-75J2BR4P.js.map +0 -1
  109. package/dist/chunk-CUADDHHU.js.map +0 -1
  110. package/dist/chunk-DJQOYEK3.js.map +0 -1
  111. package/dist/chunk-DK5VEBB5.js.map +0 -1
  112. package/dist/chunk-GYIA6XLB.js.map +0 -1
  113. package/dist/chunk-SV6UCB2Z.js.map +0 -1
  114. package/dist/chunk-TRA3R4EC.js.map +0 -1
  115. package/dist/chunk-X5IXK5KO.js.map +0 -1
  116. package/dist/executor-7XOKS6HS.js.map +0 -1
  117. package/dist/llm-DK44LYO6.js +0 -17
  118. package/dist/main-JB3R3DQE.js.map +0 -1
  119. package/dist/ui/assets/index-Bx9l8uxa.js +0 -837
  120. package/dist/ui/assets/index-DlEQ8A8Y.css +0 -1
  121. /package/dist/{agent-run-DUOJ3KDI.js.map → agent-run-S4MFWUSV.js.map} +0 -0
  122. /package/dist/{agent-tasks-LUWBY5JD.js.map → agent-tasks-JZ77AZSK.js.map} +0 -0
  123. /package/dist/{chunk-LF5Z62X6.js.map → chunk-4JVHWBZF.js.map} +0 -0
  124. /package/dist/{chunk-GDCSPMH4.js.map → chunk-6JZEAOLG.js.map} +0 -0
  125. /package/dist/{chunk-3MEOYXOW.js.map → chunk-D2NTFSVO.js.map} +0 -0
  126. /package/dist/{chunk-5YQ6VOFZ.js.map → chunk-DZWSHCAC.js.map} +0 -0
  127. /package/dist/{chunk-R3YW7XVF.js.map → chunk-G6LM45FD.js.map} +0 -0
  128. /package/dist/{chunk-RBFECYNA.js.map → chunk-I3S6L7QC.js.map} +0 -0
  129. /package/dist/{chunk-SPJGJEFV.js.map → chunk-IRSNOBGD.js.map} +0 -0
  130. /package/dist/{chunk-GZ7MXWYX.js.map → chunk-OH334Y3J.js.map} +0 -0
  131. /package/dist/{chunk-Z7TZJ2SP.js.map → chunk-P6C6ADBU.js.map} +0 -0
  132. /package/dist/{chunk-OKCSSDFC.js.map → chunk-RPILIIYT.js.map} +0 -0
  133. /package/dist/{chunk-OMZCVRX6.js.map → chunk-U255A3RE.js.map} +0 -0
  134. /package/dist/{chunk-DTE3SHYK.js.map → chunk-UILSK6DK.js.map} +0 -0
  135. /package/dist/{chunk-EYMKBNRP.js.map → chunk-V2ZBYKDU.js.map} +0 -0
  136. /package/dist/{chunk-DKGUCEWU.js.map → chunk-VEQLNB7E.js.map} +0 -0
  137. /package/dist/{chunk-B3SF2CCW.js.map → chunk-VQZPWCBH.js.map} +0 -0
  138. /package/dist/{chunk-HHZ3RTEI.js.map → chunk-VWXDSDJU.js.map} +0 -0
  139. /package/dist/{chunk-X4XFJG6I.js.map → chunk-W7ZOOZMK.js.map} +0 -0
  140. /package/dist/{chunk-23FJUKCN.js.map → chunk-WKAYMCPR.js.map} +0 -0
  141. /package/dist/{cli-YBD2GPK4.js.map → cli-U2FZT4GP.js.map} +0 -0
  142. /package/dist/{client-CJ3X252K.js.map → client-MZL5SFQI.js.map} +0 -0
  143. /package/dist/{config-MOWCOJJ4.js.map → config-VHHCGE4F.js.map} +0 -0
  144. /package/dist/{detect-GFYKKHLJ.js.map → detect-6FNYONJF.js.map} +0 -0
  145. /package/dist/{detect-providers-EU35RUL3.js.map → detect-providers-R7QOB3H6.js.map} +0 -0
  146. /package/dist/{doctor-JR7NEL7K.js.map → doctor-OJW7SCDQ.js.map} +0 -0
  147. /package/dist/{init-PDLKYWQ4.js.map → init-XVAONLZ7.js.map} +0 -0
  148. /package/dist/{init-wizard-WH3SXNMB.js.map → init-wizard-3OPLXLNA.js.map} +0 -0
  149. /package/dist/{installer-BTUNKWOU.js.map → installer-AARSFXI6.js.map} +0 -0
  150. /package/dist/{llm-DK44LYO6.js.map → llm-LS7U7BHC.js.map} +0 -0
  151. /package/dist/{loader-WC4U5NM5.js.map → loader-QDWQTBX4.js.map} +0 -0
  152. /package/dist/{loader-WGDVRGLM.js.map → loader-YQDG5GI5.js.map} +0 -0
  153. /package/dist/{logs-WFBX2I7C.js.map → logs-TMKNLSJY.js.map} +0 -0
  154. /package/dist/{open-AADZPSLW.js.map → open-ES2AOXL5.js.map} +0 -0
  155. /package/dist/{openai-embeddings-SEIV7AM3.js.map → openai-embeddings-FUW6CSN2.js.map} +0 -0
  156. /package/dist/{openrouter-ELODIZRP.js.map → openrouter-YSIUSUQL.js.map} +0 -0
  157. /package/dist/{post-compact-KNQ4DYLM.js.map → post-compact-CCSP4ZRC.js.map} +0 -0
  158. /package/dist/{post-tool-use-OMWHFQLM.js.map → post-tool-use-5UIVOE7I.js.map} +0 -0
  159. /package/dist/{post-tool-use-failure-KFP6MB7Z.js.map → post-tool-use-failure-SR2523FX.js.map} +0 -0
  160. /package/dist/{pre-compact-2ZYE2HRB.js.map → pre-compact-2G2UWGDZ.js.map} +0 -0
  161. /package/dist/{provider-check-B66E5PWS.js.map → provider-check-VEYONGNU.js.map} +0 -0
  162. /package/dist/{registry-DHWVHXWY.js.map → registry-5R3DLJQH.js.map} +0 -0
  163. /package/dist/{remove-QT7634L5.js.map → remove-L4HNBCSZ.js.map} +0 -0
  164. /package/dist/{resolution-events-DBCRVZGU.js.map → resolution-events-CHOKR35X.js.map} +0 -0
  165. /package/dist/{restart-YQNQEHOU.js.map → restart-ZQ3QNRF4.js.map} +0 -0
  166. /package/dist/{search-C6JTQDWY.js.map → search-ECJ76TU3.js.map} +0 -0
  167. /package/dist/{server-QJ3RWZZZ.js.map → server-4MUFDPDP.js.map} +0 -0
  168. /package/dist/{session-JLVL5TYX.js.map → session-ZHYO3BBY.js.map} +0 -0
  169. /package/dist/{session-end-XFZRRP5H.js.map → session-end-UJM3UODF.js.map} +0 -0
  170. /package/dist/{session-start-XGINISXO.js.map → session-start-UXLG36AZ.js.map} +0 -0
  171. /package/dist/{setup-llm-X2OCM6R7.js.map → setup-llm-NEN5XPNY.js.map} +0 -0
  172. /package/dist/{version-6OJH5HLZ.js.map → skill-staging-SWM7UC5D.js.map} +0 -0
  173. /package/dist/{stats-2EAETG2T.js.map → stats-AYRSUFHR.js.map} +0 -0
  174. /package/dist/{stop-WOBDYTSA.js.map → stop-5WD22XAH.js.map} +0 -0
  175. /package/dist/{stop-failure-QEC7ZGBQ.js.map → stop-failure-FLFIOPJY.js.map} +0 -0
  176. /package/dist/{subagent-start-H6DVRVOE.js.map → subagent-start-AMCPECUD.js.map} +0 -0
  177. /package/dist/{subagent-stop-LKENKJ65.js.map → subagent-stop-4M4BUENR.js.map} +0 -0
  178. /package/dist/{task-completed-ZZ47PRPD.js.map → task-completed-QJOEVDXZ.js.map} +0 -0
  179. /package/dist/{team-J62N7VMG.js.map → team-FWEVWYIY.js.map} +0 -0
  180. /package/dist/{update-LX3CJ4TJ.js.map → update-F2LPJMUE.js.map} +0 -0
  181. /package/dist/{user-prompt-submit-NNMLY3EW.js.map → user-prompt-submit-UOAIU3JW.js.map} +0 -0
  182. /package/dist/{verify-AMRQXQ3K.js.map → verify-ITBMLK67.js.map} +0 -0
@@ -1,5 +1,6 @@
1
1
  import { createRequire as __cr } from 'node:module'; const require = __cr(import.meta.url);
2
2
  import {
3
+ SKILL_GENERATE_TASK,
3
4
  STATUS_COMPLETED,
4
5
  STATUS_FAILED,
5
6
  STATUS_RUNNING,
@@ -30,13 +31,20 @@ import {
30
31
  updateRunStatus,
31
32
  updateSkillRecord,
32
33
  upsertDigestExtract
33
- } from "./chunk-75J2BR4P.js";
34
+ } from "./chunk-KYSLNB3C.js";
34
35
  import {
35
36
  fullTextSearch
36
- } from "./chunk-DTE3SHYK.js";
37
+ } from "./chunk-UILSK6DK.js";
38
+ import {
39
+ cleanupStagedSkill,
40
+ readStagedManifest,
41
+ readStagedSkill,
42
+ writeStagedManifest,
43
+ writeStagedSkill
44
+ } from "./chunk-U5EW2VIQ.js";
37
45
  import {
38
46
  loadAllTasks
39
- } from "./chunk-3MEOYXOW.js";
47
+ } from "./chunk-D2NTFSVO.js";
40
48
  import {
41
49
  getAgent,
42
50
  getDefaultTask,
@@ -45,31 +53,33 @@ import {
45
53
  loadSystemPrompt,
46
54
  resolveDefinitionsDir,
47
55
  resolveEffectiveConfig
48
- } from "./chunk-X4XFJG6I.js";
56
+ } from "./chunk-W7ZOOZMK.js";
49
57
  import {
50
58
  insertTurn
51
59
  } from "./chunk-QLCD77AN.js";
52
60
  import {
53
61
  insertResolutionEvent
54
- } from "./chunk-Z7TZJ2SP.js";
62
+ } from "./chunk-P6C6ADBU.js";
55
63
  import "./chunk-IB76KGBY.js";
56
64
  import {
57
65
  DEFAULT_IMPORTANCE,
58
66
  insertSpore,
59
67
  listSpores,
60
68
  updateSporeStatus
61
- } from "./chunk-SPJGJEFV.js";
69
+ } from "./chunk-IRSNOBGD.js";
62
70
  import {
63
71
  listSessions,
64
72
  updateSession
65
- } from "./chunk-23FJUKCN.js";
66
- import "./chunk-LF5Z62X6.js";
73
+ } from "./chunk-WKAYMCPR.js";
74
+ import "./chunk-4JVHWBZF.js";
67
75
  import {
76
+ AGENT_SETTABLE_STATUSES,
77
+ CANDIDATE_STATUS,
68
78
  createSchema
69
- } from "./chunk-DJQOYEK3.js";
79
+ } from "./chunk-CJ2KTRWI.js";
70
80
  import {
71
81
  loadConfig
72
- } from "./chunk-4BQ5QE76.js";
82
+ } from "./chunk-XAXQ72L3.js";
73
83
  import {
74
84
  getDatabase,
75
85
  initDatabase,
@@ -77,7 +87,7 @@ import {
77
87
  } from "./chunk-MYX5NCRH.js";
78
88
  import {
79
89
  getPluginVersion
80
- } from "./chunk-OMZCVRX6.js";
90
+ } from "./chunk-U255A3RE.js";
81
91
  import {
82
92
  findPackageRoot
83
93
  } from "./chunk-LPUQPDC2.js";
@@ -89,8 +99,8 @@ import {
89
99
  SEARCH_SIMILARITY_THRESHOLD,
90
100
  TEAM_SOURCE_PREFIX,
91
101
  epochSeconds
92
- } from "./chunk-TRA3R4EC.js";
93
- import "./chunk-CUADDHHU.js";
102
+ } from "./chunk-34NHDRWI.js";
103
+ import "./chunk-E7NUADTQ.js";
94
104
  import "./chunk-D7TYRPRM.js";
95
105
  import "./chunk-E4VLWIJC.js";
96
106
  import {
@@ -649,12 +659,115 @@ import { tool as tool4 } from "@anthropic-ai/claude-agent-sdk";
649
659
  var MAX_SKILL_LINES = 500;
650
660
  var REQUIRED_FRONTMATTER_FIELDS = ["name", "description", "managed_by", "user-invocable", "allowed-tools"];
651
661
  var PROTECTED_FRONTMATTER_FIELDS = ["user-invocable", "allowed-tools"];
662
+ var ALLOWED_CLAUDE_CODE_TOOLS = /* @__PURE__ */ new Set([
663
+ "Read",
664
+ "Edit",
665
+ "Write",
666
+ "MultiEdit",
667
+ "Bash",
668
+ "Grep",
669
+ "Glob",
670
+ "NotebookRead",
671
+ "NotebookEdit",
672
+ "WebFetch",
673
+ "WebSearch",
674
+ "Task",
675
+ "TodoWrite"
676
+ ]);
652
677
  function extractFrontmatterField(content, field) {
653
678
  const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
654
679
  if (!fmMatch) return void 0;
655
680
  const match = fmMatch[1].match(new RegExp(`^${field}:\\s*(.+)$`, "m"));
656
681
  return match?.[1].trim();
657
682
  }
683
+ function parseAllowedTools(rawValue) {
684
+ if (!rawValue) return null;
685
+ let stripped = rawValue.trim();
686
+ if (stripped.length === 0) return null;
687
+ if (stripped.startsWith("[") && stripped.endsWith("]")) {
688
+ stripped = stripped.slice(1, -1).trim();
689
+ }
690
+ if (stripped.length === 0) return null;
691
+ const parts = stripped.split(",").map((s) => s.trim().replace(/^['"]|['"]$/g, "")).filter((s) => s.length > 0);
692
+ if (parts.length === 0) return null;
693
+ const sentinels = /* @__PURE__ */ new Set(["None", "none", "null", "Null", "~"]);
694
+ if (parts.some((p) => sentinels.has(p))) return null;
695
+ return parts;
696
+ }
697
+ function tokenSet(text) {
698
+ const stopwords = /* @__PURE__ */ new Set([
699
+ "the",
700
+ "a",
701
+ "an",
702
+ "and",
703
+ "or",
704
+ "but",
705
+ "is",
706
+ "are",
707
+ "was",
708
+ "were",
709
+ "be",
710
+ "been",
711
+ "being",
712
+ "have",
713
+ "has",
714
+ "had",
715
+ "do",
716
+ "does",
717
+ "did",
718
+ "will",
719
+ "would",
720
+ "should",
721
+ "could",
722
+ "may",
723
+ "might",
724
+ "must",
725
+ "can",
726
+ "this",
727
+ "that",
728
+ "these",
729
+ "those",
730
+ "with",
731
+ "from",
732
+ "into",
733
+ "onto",
734
+ "for",
735
+ "when",
736
+ "where",
737
+ "which",
738
+ "what",
739
+ "who",
740
+ "how",
741
+ "why",
742
+ "use",
743
+ "uses",
744
+ "used",
745
+ "using",
746
+ "not",
747
+ "also",
748
+ "than",
749
+ "then",
750
+ "ensure",
751
+ "ensures",
752
+ "make",
753
+ "makes"
754
+ ]);
755
+ return new Set(
756
+ text.toLowerCase().replace(/[^a-z0-9_\s]/g, " ").split(/\s+/).filter((w) => w.length >= 4 && !stopwords.has(w))
757
+ );
758
+ }
759
+ function descriptionSimilarity(a, b) {
760
+ const aTokens = tokenSet(a);
761
+ const bTokens = tokenSet(b);
762
+ if (aTokens.size === 0 || bTokens.size === 0) return 0;
763
+ let intersection = 0;
764
+ for (const token of aTokens) {
765
+ if (bTokens.has(token)) intersection++;
766
+ }
767
+ const union = aTokens.size + bTokens.size - intersection;
768
+ return union === 0 ? 0 : intersection / union;
769
+ }
770
+ var DESCRIPTION_DUPLICATE_THRESHOLD = 0.4;
658
771
  function checkFrontmatterPreservation(existing, incoming) {
659
772
  const violations = [];
660
773
  for (const field of PROTECTED_FRONTMATTER_FIELDS) {
@@ -696,11 +809,25 @@ function validateSkillContent(content, dirName) {
696
809
  }
697
810
  const allowedToolsMatch = frontmatter.match(/^allowed-tools:\s*(.+)$/m);
698
811
  if (allowedToolsMatch) {
699
- const toolsValue = allowedToolsMatch[1].trim();
700
- if (toolsValue.includes("vault_")) {
812
+ const rawValue = allowedToolsMatch[1].trim();
813
+ if (rawValue.includes("vault_")) {
701
814
  issues.push(
702
815
  "allowed-tools contains vault agent tool names (vault_*). Skills run in Claude Code sessions -- use Claude Code tool names instead: Read, Edit, Write, Bash, Grep, Glob"
703
816
  );
817
+ } else {
818
+ const parsed = parseAllowedTools(rawValue);
819
+ if (parsed === null) {
820
+ issues.push(
821
+ `allowed-tools value is malformed or empty: "${rawValue}". Provide a comma-separated list of Claude Code tools, e.g. "Read, Edit, Write, Bash, Grep, Glob". Use the narrowest set the skill actually needs.`
822
+ );
823
+ } else {
824
+ const unknown = parsed.filter((t) => !ALLOWED_CLAUDE_CODE_TOOLS.has(t));
825
+ if (unknown.length > 0) {
826
+ issues.push(
827
+ `allowed-tools contains unknown tool name(s): ${unknown.join(", ")}. Valid Claude Code tools: ${[...ALLOWED_CLAUDE_CODE_TOOLS].join(", ")}.`
828
+ );
829
+ }
830
+ }
704
831
  }
705
832
  }
706
833
  const listToolLines = frontmatter.match(/^\s+-\s+vault_\w+/gm);
@@ -719,6 +846,200 @@ function validateSkillContent(content, dirName) {
719
846
  // src/agent/tools/skill-tools.ts
720
847
  function createSkillTools(deps) {
721
848
  const { agentId, machineId, projectRoot, vaultDir, recordTurn } = deps;
849
+ function findOverlappingCandidate(newTopic, existing) {
850
+ let best = null;
851
+ for (const candidate of existing) {
852
+ const score = descriptionSimilarity(newTopic, candidate.topic);
853
+ if (score >= DESCRIPTION_DUPLICATE_THRESHOLD && (!best || score > best.score)) {
854
+ best = { candidate, score };
855
+ }
856
+ }
857
+ return best;
858
+ }
859
+ function candidateOverlapError(match) {
860
+ const common = `already has an existing candidate with a similar topic: "${match.topic}"`;
861
+ switch (match.status) {
862
+ case CANDIDATE_STATUS.DISMISSED:
863
+ return `Candidate rejected: the vault ${common} that was previously dismissed. Do not re-identify dismissed topics.`;
864
+ case CANDIDATE_STATUS.GENERATED:
865
+ return `Candidate rejected: the vault ${common} that was already fulfilled by a generated skill. Do not re-identify.`;
866
+ case CANDIDATE_STATUS.APPROVED:
867
+ return `Candidate rejected: the vault ${common} that is already queued in approved state. Wait for the generate task to process it.`;
868
+ case CANDIDATE_STATUS.IDENTIFIED:
869
+ return `Candidate rejected: the vault ${common} already in the review queue. Update the existing candidate with new evidence (action: update) instead of creating a duplicate.`;
870
+ default:
871
+ return `Candidate rejected: the vault ${common} in status '${match.status}'.`;
872
+ }
873
+ }
874
+ function requireApprovedCandidate(candidateId) {
875
+ const candidate = getCandidate(candidateId);
876
+ if (!candidate) {
877
+ return {
878
+ error: `Candidate ${candidateId} not found. Skill writes require a candidate in the approved state.`
879
+ };
880
+ }
881
+ if (candidate.status !== CANDIDATE_STATUS.APPROVED) {
882
+ return {
883
+ error: `Candidate ${candidateId} is in '${candidate.status}' state. Skills can only be generated from candidates in 'approved' state \u2014 the human review step. If a candidate in an earlier state needs to become a skill, route it through the normal approval flow first.`,
884
+ candidate_status: candidate.status
885
+ };
886
+ }
887
+ return null;
888
+ }
889
+ function checkDedupGates(args) {
890
+ const existingSameName = getSkillRecordByName(args.name);
891
+ if (existingSameName) {
892
+ if (args.rejectSameName) {
893
+ return {
894
+ error: `Skill "${args.name}" already exists. This path is create-only. Use vault_write_skill to evolve the existing skill (it bumps the generation), or mark the current record stale via vault_skill_records first.`,
895
+ existing_skill: {
896
+ id: existingSameName.id,
897
+ name: existingSameName.name,
898
+ path: existingSameName.path
899
+ }
900
+ };
901
+ }
902
+ return null;
903
+ }
904
+ if (args.candidate_id) {
905
+ const candidate = getCandidate(args.candidate_id);
906
+ if (candidate?.skill_id) {
907
+ const linkedSkill = getSkillRecord(candidate.skill_id);
908
+ if (linkedSkill && linkedSkill.name !== args.name) {
909
+ return {
910
+ error: `Candidate ${args.candidate_id} is already fulfilled by skill "${linkedSkill.name}". Do not create a sibling skill. If the existing skill needs changes, write to the same name to evolve it (this bumps its generation), or mark it stale via vault_skill_records before replacing.`,
911
+ existing_skill: {
912
+ id: linkedSkill.id,
913
+ name: linkedSkill.name,
914
+ description: linkedSkill.description,
915
+ path: linkedSkill.path
916
+ }
917
+ };
918
+ }
919
+ }
920
+ }
921
+ const activeSkills = listSkillRecords({ agent_id: agentId, status: "active", limit: 200 });
922
+ let bestMatch = null;
923
+ for (const skill of activeSkills) {
924
+ const score = descriptionSimilarity(args.description, skill.description);
925
+ if (score >= DESCRIPTION_DUPLICATE_THRESHOLD && (!bestMatch || score > bestMatch.score)) {
926
+ bestMatch = { skill, score };
927
+ }
928
+ }
929
+ if (bestMatch) {
930
+ return {
931
+ error: `Description overlaps with existing active skill "${bestMatch.skill.name}" (Jaccard ${bestMatch.score.toFixed(2)}, threshold ${DESCRIPTION_DUPLICATE_THRESHOLD}). Do not create a duplicate. Either evolve the existing skill by writing to its name ("${bestMatch.skill.name}"), or reframe this skill so its description describes a distinct procedure.`,
932
+ overlapping_skill: {
933
+ id: bestMatch.skill.id,
934
+ name: bestMatch.skill.name,
935
+ description: bestMatch.skill.description,
936
+ path: bestMatch.skill.path
937
+ },
938
+ similarity: bestMatch.score
939
+ };
940
+ }
941
+ return null;
942
+ }
943
+ async function promoteNewSkill(params) {
944
+ const root = projectRoot ?? process.cwd();
945
+ const skillDir = resolve(root, ".agents", "skills", params.name);
946
+ const skillPath = resolve(skillDir, "SKILL.md");
947
+ const skillDirPreexisted = existsSync(skillDir);
948
+ async function cleanupCreatedSkillArtifactsOnRollback() {
949
+ try {
950
+ if (!skillDirPreexisted) {
951
+ rmSync(skillDir, { recursive: true, force: true });
952
+ } else {
953
+ rmSync(skillPath, { force: true });
954
+ }
955
+ } catch (rollbackErr) {
956
+ console.warn(
957
+ `[${params.label}] file rollback after DB failure also failed:`,
958
+ rollbackErr instanceof Error ? rollbackErr.message : rollbackErr
959
+ );
960
+ }
961
+ try {
962
+ const { syncSkillSymlinks } = await import("./installer-AARSFXI6.js");
963
+ syncSkillSymlinks(root, params.name, { remove: true });
964
+ } catch (rollbackErr) {
965
+ console.warn(
966
+ `[${params.label}] symlink rollback after DB failure also failed:`,
967
+ rollbackErr instanceof Error ? rollbackErr.message : rollbackErr
968
+ );
969
+ }
970
+ }
971
+ try {
972
+ mkdirSync(skillDir, { recursive: true });
973
+ writeFileSync(skillPath, params.content, "utf-8");
974
+ } catch (err) {
975
+ return {
976
+ error: `Failed to write skill file: ${err instanceof Error ? err.message : String(err)}`
977
+ };
978
+ }
979
+ try {
980
+ const { syncSkillSymlinks } = await import("./installer-AARSFXI6.js");
981
+ syncSkillSymlinks(root, params.name);
982
+ } catch (err) {
983
+ console.warn(
984
+ `[${params.label}] syncSkillSymlinks failed:`,
985
+ err instanceof Error ? err.message : err
986
+ );
987
+ }
988
+ const now = epochSeconds();
989
+ const relativePath = `.agents/skills/${params.name}/SKILL.md`;
990
+ const recordId = crypto2.randomUUID();
991
+ const generation = 1;
992
+ const txDb = getDatabase();
993
+ try {
994
+ txDb.transaction(() => {
995
+ insertSkillRecord({
996
+ id: recordId,
997
+ agent_id: agentId,
998
+ machine_id: machineId,
999
+ name: params.name,
1000
+ display_name: params.display_name,
1001
+ description: params.description,
1002
+ candidate_id: params.candidate_id ?? null,
1003
+ source_ids: params.source_ids,
1004
+ path: relativePath,
1005
+ created_at: now,
1006
+ updated_at: now
1007
+ });
1008
+ insertLineage({
1009
+ id: crypto2.randomUUID(),
1010
+ skill_id: recordId,
1011
+ generation,
1012
+ action: "created",
1013
+ rationale: params.rationale ?? "Initial skill creation",
1014
+ source_ids_added: params.source_ids,
1015
+ content_snapshot: params.content,
1016
+ created_at: now
1017
+ });
1018
+ params.linkCandidate?.(recordId, now);
1019
+ })();
1020
+ } catch (err) {
1021
+ await cleanupCreatedSkillArtifactsOnRollback();
1022
+ return {
1023
+ error: `Skill write aborted: database transaction failed and on-disk state was rolled back. ${err instanceof Error ? err.message : String(err)}`
1024
+ };
1025
+ }
1026
+ return {
1027
+ id: recordId,
1028
+ name: params.name,
1029
+ path: relativePath,
1030
+ generation
1031
+ };
1032
+ }
1033
+ function emitSkillNotification(kind, opts) {
1034
+ notify(vaultDir, {
1035
+ domain: "skills",
1036
+ type: kind === "created" ? "skill.created" : "skill.evolved",
1037
+ title: `Skill ${kind}: ${opts.display_name}`,
1038
+ message: opts.description.slice(0, 120),
1039
+ link: `/skills?skill=${encodeURIComponent(opts.name)}`,
1040
+ metadata: { skillId: opts.recordId, name: opts.name, generation: opts.generation }
1041
+ });
1042
+ }
722
1043
  const vaultSkillCandidates = tool4(
723
1044
  "vault_skill_candidates",
724
1045
  "Manage skill candidates (identified topics that may become skills). Supports list, get, create, and update actions.",
@@ -728,7 +1049,9 @@ function createSkillTools(deps) {
728
1049
  topic: external_exports.string().optional().describe("Skill topic (required for create)"),
729
1050
  rationale: external_exports.string().optional().describe("Why this should be a skill (required for create)"),
730
1051
  confidence: external_exports.number().optional().describe("Confidence score 0-1"),
731
- status: external_exports.enum(["identified", "approved", "generated", "dismissed"]).optional().describe("Candidate status. Only these values are valid."),
1052
+ status: external_exports.enum(AGENT_SETTABLE_STATUSES).optional().describe(
1053
+ "Candidate status \u2014 agent-settable values only. 'identified' is the initial state; 'dismissed' retires a candidate. 'approved' and 'generated' are lifecycle transitions owned by the human UI and vault_finalize_skill respectively."
1054
+ ),
732
1055
  source_ids: external_exports.string().optional().describe("JSON array of source spore/entity IDs"),
733
1056
  skill_id: external_exports.string().optional().describe("Associated skill record ID (after materialization)"),
734
1057
  limit: external_exports.number().optional().describe("Maximum candidates to return (for list)")
@@ -767,6 +1090,19 @@ function createSkillTools(deps) {
767
1090
  overlapping_skills: overlapping.map((s) => ({ name: s.name, display_name: s.display_name, description: s.description }))
768
1091
  });
769
1092
  }
1093
+ const allExisting = listCandidates({ agent_id: agentId, limit: 500 });
1094
+ const match = findOverlappingCandidate(args.topic, allExisting);
1095
+ if (match) {
1096
+ return textResult({
1097
+ error: candidateOverlapError(match.candidate),
1098
+ existing_candidate: {
1099
+ id: match.candidate.id,
1100
+ status: match.candidate.status,
1101
+ topic: match.candidate.topic
1102
+ },
1103
+ similarity: match.score
1104
+ });
1105
+ }
770
1106
  const now = epochSeconds();
771
1107
  const candidate = insertCandidate({
772
1108
  id: crypto2.randomUUID(),
@@ -874,7 +1210,7 @@ function createSkillTools(deps) {
874
1210
  console.warn("[vault_skill_records] Failed to remove skill directory:", err instanceof Error ? err.message : err);
875
1211
  }
876
1212
  try {
877
- const { syncSkillSymlinks } = await import("./installer-BTUNKWOU.js");
1213
+ const { syncSkillSymlinks } = await import("./installer-AARSFXI6.js");
878
1214
  syncSkillSymlinks(root, result.name, { remove: true });
879
1215
  } catch (err) {
880
1216
  console.warn("[vault_skill_records] Failed to remove symlinks:", err instanceof Error ? err.message : err);
@@ -915,9 +1251,18 @@ function createSkillTools(deps) {
915
1251
  error: 'Invalid skill name: must be a simple directory name without path separators or ".."'
916
1252
  });
917
1253
  }
1254
+ const dedupError = checkDedupGates({
1255
+ candidate_id: args.candidate_id,
1256
+ name: args.name,
1257
+ description: args.description
1258
+ });
1259
+ if (dedupError) {
1260
+ recordTurn("vault_write_skill", args);
1261
+ return textResult(dedupError);
1262
+ }
1263
+ const existing = getSkillRecordByName(args.name);
918
1264
  const root = projectRoot ?? process.cwd();
919
- const skillDir = resolve(root, ".agents", "skills", args.name);
920
- const skillPath = resolve(skillDir, "SKILL.md");
1265
+ const skillPath = resolve(root, ".agents", "skills", args.name, "SKILL.md");
921
1266
  if (existsSync(skillPath)) {
922
1267
  const existingContent = readFileSync(skillPath, "utf-8");
923
1268
  const violations = checkFrontmatterPreservation(existingContent, args.content);
@@ -929,28 +1274,73 @@ function createSkillTools(deps) {
929
1274
  });
930
1275
  }
931
1276
  }
1277
+ if (!existing) {
1278
+ if (args.candidate_id) {
1279
+ const candidateError = requireApprovedCandidate(args.candidate_id);
1280
+ if (candidateError) {
1281
+ recordTurn("vault_write_skill", args);
1282
+ return textResult(candidateError);
1283
+ }
1284
+ }
1285
+ const linkCandidate = (recordId2, now2) => {
1286
+ if (!args.candidate_id) return;
1287
+ const exact = updateCandidate(args.candidate_id, {
1288
+ status: CANDIDATE_STATUS.GENERATED,
1289
+ skill_id: recordId2,
1290
+ updated_at: now2
1291
+ });
1292
+ if (exact) return;
1293
+ const approvedCandidates = listCandidates({ status: CANDIDATE_STATUS.APPROVED, limit: 10 });
1294
+ const prefixMatch = approvedCandidates.find((c) => c.id.startsWith(args.candidate_id));
1295
+ if (prefixMatch) {
1296
+ updateCandidate(prefixMatch.id, {
1297
+ status: CANDIDATE_STATUS.GENERATED,
1298
+ skill_id: recordId2,
1299
+ updated_at: now2
1300
+ });
1301
+ }
1302
+ };
1303
+ const result = await promoteNewSkill({
1304
+ name: args.name,
1305
+ display_name: args.display_name,
1306
+ description: args.description,
1307
+ content: args.content,
1308
+ source_ids: args.source_ids,
1309
+ candidate_id: args.candidate_id,
1310
+ rationale: args.rationale,
1311
+ linkCandidate,
1312
+ label: "vault_write_skill"
1313
+ });
1314
+ recordTurn("vault_write_skill", args);
1315
+ if ("error" in result) return textResult(result);
1316
+ emitSkillNotification("created", {
1317
+ name: result.name,
1318
+ display_name: args.display_name,
1319
+ description: args.description,
1320
+ recordId: result.id,
1321
+ generation: result.generation
1322
+ });
1323
+ return textResult(result);
1324
+ }
1325
+ const priorSkillContent = readFileSync(skillPath, "utf-8");
932
1326
  try {
933
- mkdirSync(skillDir, { recursive: true });
934
1327
  writeFileSync(skillPath, args.content, "utf-8");
935
1328
  } catch (err) {
936
1329
  return textResult({ error: `Failed to write skill file: ${err instanceof Error ? err.message : String(err)}` });
937
1330
  }
938
1331
  try {
939
- const { syncSkillSymlinks } = await import("./installer-BTUNKWOU.js");
1332
+ const { syncSkillSymlinks } = await import("./installer-AARSFXI6.js");
940
1333
  syncSkillSymlinks(root, args.name);
941
1334
  } catch (err) {
942
1335
  console.warn("[vault_write_skill] syncSkillSymlinks failed:", err instanceof Error ? err.message : err);
943
1336
  }
944
1337
  const now = epochSeconds();
945
1338
  const relativePath = `.agents/skills/${args.name}/SKILL.md`;
946
- const existing = getSkillRecordByName(args.name);
947
- let recordId = "";
948
- let generation = 0;
1339
+ const generation = existing.generation + 1;
1340
+ const recordId = existing.id;
949
1341
  const txDb = getDatabase();
950
- txDb.transaction(() => {
951
- if (existing) {
952
- generation = existing.generation + 1;
953
- recordId = existing.id;
1342
+ try {
1343
+ txDb.transaction(() => {
954
1344
  updateSkillRecord(existing.id, {
955
1345
  display_name: args.display_name,
956
1346
  description: args.description,
@@ -969,63 +1359,27 @@ function createSkillTools(deps) {
969
1359
  content_snapshot: args.content,
970
1360
  created_at: now
971
1361
  });
972
- } else {
973
- recordId = crypto2.randomUUID();
974
- generation = 1;
975
- insertSkillRecord({
976
- id: recordId,
977
- agent_id: agentId,
978
- machine_id: machineId,
979
- name: args.name,
980
- display_name: args.display_name,
981
- description: args.description,
982
- candidate_id: args.candidate_id ?? null,
983
- source_ids: args.source_ids,
984
- path: relativePath,
985
- created_at: now,
986
- updated_at: now
987
- });
988
- insertLineage({
989
- id: crypto2.randomUUID(),
990
- skill_id: recordId,
991
- generation,
992
- action: "created",
993
- rationale: args.rationale ?? "Initial skill creation",
994
- source_ids_added: args.source_ids,
995
- content_snapshot: args.content,
996
- created_at: now
997
- });
998
- const approvedCandidates = listCandidates({ status: "approved", limit: 10 });
999
- let linkedCandidate = false;
1000
- if (args.candidate_id && !linkedCandidate) {
1001
- const exact = updateCandidate(args.candidate_id, {
1002
- status: "generated",
1003
- skill_id: recordId,
1004
- updated_at: now
1005
- });
1006
- if (exact) linkedCandidate = true;
1007
- }
1008
- if (args.candidate_id && !linkedCandidate) {
1009
- const prefixMatch = approvedCandidates.find((c) => c.id.startsWith(args.candidate_id));
1010
- if (prefixMatch) {
1011
- updateCandidate(prefixMatch.id, {
1012
- status: "generated",
1013
- skill_id: recordId,
1014
- updated_at: now
1015
- });
1016
- linkedCandidate = true;
1017
- }
1018
- }
1362
+ })();
1363
+ } catch (err) {
1364
+ try {
1365
+ writeFileSync(skillPath, priorSkillContent, "utf-8");
1366
+ } catch (rollbackErr) {
1367
+ console.warn(
1368
+ "[vault_write_skill] file rollback after DB failure also failed:",
1369
+ rollbackErr instanceof Error ? rollbackErr.message : rollbackErr
1370
+ );
1019
1371
  }
1020
- })();
1021
- const isNew = generation === 1;
1022
- notify(vaultDir, {
1023
- domain: "skills",
1024
- type: isNew ? "skill.created" : "skill.evolved",
1025
- title: isNew ? `Skill created: ${args.display_name}` : `Skill evolved: ${args.display_name}`,
1026
- message: args.description.slice(0, 120),
1027
- link: `/skills?skill=${encodeURIComponent(args.name)}`,
1028
- metadata: { skillId: recordId, name: args.name, generation }
1372
+ recordTurn("vault_write_skill", args);
1373
+ return textResult({
1374
+ error: `Skill write aborted: database transaction failed and on-disk state was rolled back. ${err instanceof Error ? err.message : String(err)}`
1375
+ });
1376
+ }
1377
+ emitSkillNotification("evolved", {
1378
+ name: args.name,
1379
+ display_name: args.display_name,
1380
+ description: args.description,
1381
+ recordId,
1382
+ generation
1029
1383
  });
1030
1384
  recordTurn("vault_write_skill", args);
1031
1385
  return textResult({
@@ -1037,10 +1391,145 @@ function createSkillTools(deps) {
1037
1391
  },
1038
1392
  { annotations: { openWorldHint: true } }
1039
1393
  );
1394
+ const vaultStageSkill = tool4(
1395
+ "vault_stage_skill",
1396
+ "Stage a provisional SKILL.md under .myco/staging/skills/<candidate_id>/ for later promotion by vault_finalize_skill. Use this from the skill-generate draft phase. The write is NOT live \u2014 the skill does not appear under .agents/skills/ and no DB rows are created until vault_finalize_skill is called with the same candidate_id.",
1397
+ {
1398
+ candidate_id: external_exports.string().describe(
1399
+ "Candidate ID from the instruction. Required \u2014 staging is keyed by candidate so the validate phase (and on-failure cleanup) can find the staged content."
1400
+ ),
1401
+ name: external_exports.string().describe("Final skill directory name (kebab-case, no colon). Stored in the manifest for finalize."),
1402
+ display_name: external_exports.string().describe("Human-readable display name"),
1403
+ description: external_exports.string().describe("Short description \u2014 used for the dedup gate and the final skill record"),
1404
+ content: external_exports.string().describe("Full SKILL.md content in markdown including frontmatter"),
1405
+ source_ids: external_exports.string().optional().describe("JSON array of source spore/entity IDs"),
1406
+ rationale: external_exports.string().optional().describe("Why this skill is being created \u2014 stored in lineage after finalize")
1407
+ },
1408
+ async (args) => {
1409
+ recordTurn("vault_stage_skill", args);
1410
+ if (!vaultDir) {
1411
+ return textResult({
1412
+ error: "vault_stage_skill requires vaultDir on the tool deps \u2014 staging has no location otherwise"
1413
+ });
1414
+ }
1415
+ const validationErrors = validateSkillContent(args.content, args.name);
1416
+ if (validationErrors.length > 0) {
1417
+ return textResult({
1418
+ error: "Skill validation failed. Fix these issues and re-stage.",
1419
+ issues: validationErrors
1420
+ });
1421
+ }
1422
+ if (!args.name || /[/\\]|\.\./.test(args.name)) {
1423
+ return textResult({
1424
+ error: 'Invalid skill name: must be a simple directory name without path separators or ".."'
1425
+ });
1426
+ }
1427
+ const candidateError = requireApprovedCandidate(args.candidate_id);
1428
+ if (candidateError) return textResult(candidateError);
1429
+ const dedupError = checkDedupGates({
1430
+ candidate_id: args.candidate_id,
1431
+ name: args.name,
1432
+ description: args.description,
1433
+ rejectSameName: true
1434
+ });
1435
+ if (dedupError) return textResult(dedupError);
1436
+ let stagingFilePath;
1437
+ try {
1438
+ stagingFilePath = writeStagedSkill(vaultDir, args.candidate_id, args.content);
1439
+ const manifest = {
1440
+ candidate_id: args.candidate_id,
1441
+ name: args.name,
1442
+ display_name: args.display_name,
1443
+ description: args.description,
1444
+ source_ids: args.source_ids ?? "[]",
1445
+ rationale: args.rationale ?? "Initial draft"
1446
+ };
1447
+ writeStagedManifest(vaultDir, args.candidate_id, manifest);
1448
+ } catch (err) {
1449
+ return textResult({
1450
+ error: `Failed to write staged skill: ${err instanceof Error ? err.message : String(err)}`
1451
+ });
1452
+ }
1453
+ return textResult({
1454
+ candidate_id: args.candidate_id,
1455
+ staging_path: stagingFilePath,
1456
+ status: "staged"
1457
+ });
1458
+ },
1459
+ { annotations: { openWorldHint: true } }
1460
+ );
1461
+ const vaultFinalizeSkill = tool4(
1462
+ "vault_finalize_skill",
1463
+ "Promote a staged skill to live at .agents/skills/<name>/ and insert the skill_records / lineage rows. Call this from skill-generate validate phase after your quality checks pass. Requires vault_stage_skill to have been called earlier with the same candidate_id; reads the staged SKILL.md + manifest rather than taking duplicate metadata.",
1464
+ {
1465
+ candidate_id: external_exports.string().describe("Candidate ID whose staged skill should be promoted. Must match a previous vault_stage_skill call.")
1466
+ },
1467
+ async (args) => {
1468
+ recordTurn("vault_finalize_skill", args);
1469
+ if (!vaultDir) {
1470
+ return textResult({
1471
+ error: "vault_finalize_skill requires vaultDir on the tool deps"
1472
+ });
1473
+ }
1474
+ const stagedContent = readStagedSkill(vaultDir, args.candidate_id);
1475
+ const manifest = readStagedManifest(vaultDir, args.candidate_id);
1476
+ if (!stagedContent || !manifest) {
1477
+ return textResult({
1478
+ error: `No staged skill found for candidate ${args.candidate_id}. Call vault_stage_skill first.`
1479
+ });
1480
+ }
1481
+ const candidateError = requireApprovedCandidate(args.candidate_id);
1482
+ if (candidateError) return textResult(candidateError);
1483
+ const validationErrors = validateSkillContent(stagedContent, manifest.name);
1484
+ if (validationErrors.length > 0) {
1485
+ return textResult({
1486
+ error: "Staged skill failed validation on finalize. Re-stage with valid content.",
1487
+ issues: validationErrors
1488
+ });
1489
+ }
1490
+ const dedupError = checkDedupGates({
1491
+ candidate_id: args.candidate_id,
1492
+ name: manifest.name,
1493
+ description: manifest.description,
1494
+ rejectSameName: true
1495
+ });
1496
+ if (dedupError) return textResult(dedupError);
1497
+ const result = await promoteNewSkill({
1498
+ name: manifest.name,
1499
+ display_name: manifest.display_name,
1500
+ description: manifest.description,
1501
+ content: stagedContent,
1502
+ source_ids: manifest.source_ids,
1503
+ candidate_id: manifest.candidate_id,
1504
+ rationale: manifest.rationale,
1505
+ linkCandidate: (recordId, now) => {
1506
+ updateCandidate(manifest.candidate_id, {
1507
+ status: CANDIDATE_STATUS.GENERATED,
1508
+ skill_id: recordId,
1509
+ updated_at: now
1510
+ });
1511
+ },
1512
+ label: "vault_finalize_skill"
1513
+ });
1514
+ if ("error" in result) return textResult(result);
1515
+ cleanupStagedSkill(vaultDir, args.candidate_id);
1516
+ emitSkillNotification("created", {
1517
+ name: manifest.name,
1518
+ display_name: manifest.display_name,
1519
+ description: manifest.description,
1520
+ recordId: result.id,
1521
+ generation: result.generation
1522
+ });
1523
+ return textResult(result);
1524
+ },
1525
+ { annotations: { openWorldHint: true } }
1526
+ );
1040
1527
  return [
1041
1528
  vaultSkillCandidates,
1042
1529
  vaultSkillRecords,
1043
- vaultWriteSkill
1530
+ vaultWriteSkill,
1531
+ vaultStageSkill,
1532
+ vaultFinalizeSkill
1044
1533
  ];
1045
1534
  }
1046
1535
 
@@ -1070,8 +1559,11 @@ var OBSERVABILITY_TOOL_NAMES = /* @__PURE__ */ new Set(["vault_report"]);
1070
1559
  var SKILL_TOOL_NAMES = /* @__PURE__ */ new Set([
1071
1560
  "vault_skill_candidates",
1072
1561
  "vault_skill_records",
1073
- "vault_write_skill"
1562
+ "vault_write_skill",
1563
+ "vault_stage_skill",
1564
+ "vault_finalize_skill"
1074
1565
  ]);
1566
+ var VAULT_TOOL_COUNT = READ_TOOL_NAMES.size + WRITE_TOOL_NAMES.size + OBSERVABILITY_TOOL_NAMES.size + SKILL_TOOL_NAMES.size;
1075
1567
  function setsOverlap(a, b) {
1076
1568
  for (const item of a) {
1077
1569
  if (b.has(item)) return true;
@@ -1534,6 +2026,7 @@ import { writeFileSync as writeFileSync2, unlinkSync } from "fs";
1534
2026
  import { tmpdir } from "os";
1535
2027
  import { join } from "path";
1536
2028
  var OLLAMA_PRELOAD_TIMEOUT_MS = 3e4;
2029
+ var DEFAULT_OLLAMA_CONTEXT_LENGTH = 32768;
1537
2030
  async function ensureOllamaContextVariant(model, contextLength) {
1538
2031
  const baseName = model.replace(/:latest$/, "");
1539
2032
  const variantName = `${baseName}-ctx${contextLength}`;
@@ -1560,6 +2053,60 @@ PARAMETER num_ctx ${contextLength}
1560
2053
  return model;
1561
2054
  }
1562
2055
  }
2056
+ async function resolveOllamaContextVariants(taskProvider, phaseOverrides, createVariant = ensureOllamaContextVariant) {
2057
+ const seen = /* @__PURE__ */ new Map();
2058
+ const recordOllama = (p) => {
2059
+ if (p?.type !== "ollama" || !p.model) return;
2060
+ const ctx = p.contextLength ?? DEFAULT_OLLAMA_CONTEXT_LENGTH;
2061
+ const set = seen.get(p.model) ?? /* @__PURE__ */ new Set();
2062
+ set.add(ctx);
2063
+ seen.set(p.model, set);
2064
+ };
2065
+ recordOllama(taskProvider);
2066
+ for (const override of Object.values(phaseOverrides)) {
2067
+ recordOllama(override.provider);
2068
+ }
2069
+ if (seen.size === 0) {
2070
+ return { taskProvider, phaseOverrides, conflicts: [] };
2071
+ }
2072
+ const resolvedContext = /* @__PURE__ */ new Map();
2073
+ const conflicts = [];
2074
+ for (const [model, values] of seen) {
2075
+ const sorted = [...values].sort((a, b) => a - b);
2076
+ const max = sorted[sorted.length - 1];
2077
+ resolvedContext.set(model, max);
2078
+ if (sorted.length > 1) {
2079
+ conflicts.push({ model, values: sorted, resolved: max });
2080
+ }
2081
+ }
2082
+ const variantEntries = await Promise.all(
2083
+ [...resolvedContext.entries()].map(async ([model, ctx]) => {
2084
+ const variant = await createVariant(model, ctx);
2085
+ return [model, variant];
2086
+ })
2087
+ );
2088
+ const variantByModel = new Map(variantEntries);
2089
+ const rewriteProvider = (p) => {
2090
+ if (!p) return p;
2091
+ if (p.type !== "ollama" || !p.model) return p;
2092
+ const variant = variantByModel.get(p.model);
2093
+ const resolvedCtx = resolvedContext.get(p.model);
2094
+ if (!variant) return p;
2095
+ return { ...p, model: variant, contextLength: resolvedCtx };
2096
+ };
2097
+ const rewrittenPhaseOverrides = {};
2098
+ for (const [name, override] of Object.entries(phaseOverrides)) {
2099
+ rewrittenPhaseOverrides[name] = {
2100
+ ...override,
2101
+ ...override.provider ? { provider: rewriteProvider(override.provider) } : {}
2102
+ };
2103
+ }
2104
+ return {
2105
+ taskProvider: rewriteProvider(taskProvider),
2106
+ phaseOverrides: rewrittenPhaseOverrides,
2107
+ conflicts
2108
+ };
2109
+ }
1563
2110
 
1564
2111
  // src/agent/wave-computation.ts
1565
2112
  import crypto3 from "crypto";
@@ -1624,6 +2171,38 @@ var MCP_SERVER_NAME = "myco-vault";
1624
2171
  var PERSIST_SESSION = true;
1625
2172
  var PROMPT_SECTION_PRIOR_PHASES = "## Prior Phase Results";
1626
2173
  var PROMPT_SECTION_CURRENT_PHASE = "## Current Phase: ";
2174
+ var DEBUG_TOOL_CALLS = process.env.MYCO_AGENT_DEBUG === "1";
2175
+ var TOOL_DEBUG_PREVIEW_CHARS = 240;
2176
+ function previewPayload(value) {
2177
+ const str = typeof value === "string" ? value : JSON.stringify(value);
2178
+ if (str === void 0) return "";
2179
+ return str.length > TOOL_DEBUG_PREVIEW_CHARS ? `${str.slice(0, TOOL_DEBUG_PREVIEW_CHARS)}\u2026(${str.length - TOOL_DEBUG_PREVIEW_CHARS} more chars)` : str;
2180
+ }
2181
+ function logToolUseBlocks(phaseName, message) {
2182
+ if (!DEBUG_TOOL_CALLS) return;
2183
+ const blocks = message.message?.content;
2184
+ if (!Array.isArray(blocks)) return;
2185
+ for (const block of blocks) {
2186
+ if (block.type === "tool_use") {
2187
+ console.log(
2188
+ `[agent:debug] ${phaseName} tool_use: ${block.name ?? "unknown"} input=${previewPayload(block.input)}`
2189
+ );
2190
+ }
2191
+ }
2192
+ }
2193
+ function logToolResultBlocks(phaseName, message) {
2194
+ if (!DEBUG_TOOL_CALLS) return;
2195
+ const blocks = message.message?.content;
2196
+ if (!Array.isArray(blocks)) return;
2197
+ for (const block of blocks) {
2198
+ if (block.type === "tool_result") {
2199
+ const flag = block.is_error ? " [ERROR]" : "";
2200
+ console.log(
2201
+ `[agent:debug] ${phaseName} tool_result${flag}: ${previewPayload(block.content)}`
2202
+ );
2203
+ }
2204
+ }
2205
+ }
1627
2206
  function composeTaskPrompt(vaultContext, taskDisplayName, taskPrompt, instruction) {
1628
2207
  const sessionIdMatch = instruction?.match(/\b([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\b/i);
1629
2208
  const sessionId = sessionIdMatch?.[1] ?? "";
@@ -1690,6 +2269,10 @@ async function executePhase(query, phasePrompt, phaseModel, systemPrompt, toolSe
1690
2269
  })) {
1691
2270
  if (message.type === "assistant") {
1692
2271
  agenticTurns++;
2272
+ logToolUseBlocks(phase.name, message);
2273
+ }
2274
+ if (message.type === "user") {
2275
+ logToolResultBlocks(phase.name, message);
1693
2276
  }
1694
2277
  if (message.type === "result") {
1695
2278
  phaseCost = message.total_cost_usd ?? 0;
@@ -1885,9 +2468,10 @@ async function runAgent(vaultDir, options) {
1885
2468
  config,
1886
2469
  definitionsDir,
1887
2470
  taskProviderOverride: resolvedTaskProvider,
1888
- phaseProviderOverrides
2471
+ phaseProviderOverrides: resolvedPhaseOverrides
1889
2472
  } = resolveRunConfig(agentId, requestedTask, vaultDir);
1890
2473
  let taskProviderOverride = resolvedTaskProvider;
2474
+ let phaseProviderOverrides = resolvedPhaseOverrides;
1891
2475
  const runId = options?.resumeRunId ?? crypto4.randomUUID();
1892
2476
  const now = epochSeconds();
1893
2477
  if (!options?.resumeRunId) {
@@ -1909,12 +2493,18 @@ async function runAgent(vaultDir, options) {
1909
2493
  provider: effectiveProvider?.type ?? "cloud",
1910
2494
  ...effectiveProvider?.baseUrl ? { baseUrl: effectiveProvider.baseUrl } : {}
1911
2495
  };
1912
- if (effectiveProvider?.type === "ollama" && effectiveProvider.contextLength && effectiveProvider.model) {
1913
- const variantModel = await ensureOllamaContextVariant(
1914
- effectiveProvider.model,
1915
- effectiveProvider.contextLength
2496
+ {
2497
+ const resolved = await resolveOllamaContextVariants(
2498
+ taskProviderOverride,
2499
+ phaseProviderOverrides
1916
2500
  );
1917
- taskProviderOverride = { ...taskProviderOverride, model: variantModel };
2501
+ taskProviderOverride = resolved.taskProvider;
2502
+ phaseProviderOverrides = resolved.phaseOverrides;
2503
+ for (const conflict of resolved.conflicts) {
2504
+ console.warn(
2505
+ `[agent] Ollama model "${conflict.model}" referenced with conflicting context_length values [${conflict.values.join(", ")}] \u2014 reconciled to ${conflict.resolved} to avoid loading multiple variants. Configure one value per model to silence this warning.`
2506
+ );
2507
+ }
1918
2508
  }
1919
2509
  const taskAbortController = new AbortController();
1920
2510
  const timeoutMs = config.timeoutSeconds * MS_PER_SECOND;
@@ -1946,6 +2536,17 @@ async function runAgent(vaultDir, options) {
1946
2536
  tokensUsed = result.tokensUsed;
1947
2537
  costUsd = result.costUsd;
1948
2538
  phaseResults = result.phases;
2539
+ const requiredPhaseNames = new Set(
2540
+ config.phases.filter((p) => p.required).map((p) => p.name)
2541
+ );
2542
+ const failedRequired = phaseResults.find(
2543
+ (p) => p.status === "failed" && requiredPhaseNames.has(p.name)
2544
+ );
2545
+ if (failedRequired) {
2546
+ throw new Error(
2547
+ `Required phase "${failedRequired.name}" failed: ${failedRequired.summary}`
2548
+ );
2549
+ }
1949
2550
  } else {
1950
2551
  const taskPrompt = composeTaskPrompt(
1951
2552
  vaultContext,
@@ -2011,6 +2612,11 @@ ${err.stack.split("\n").slice(0, 3).join("\n")}`;
2011
2612
  } catch (dbErr) {
2012
2613
  console.error(`[agent] Failed to save error to DB:`, dbErr);
2013
2614
  }
2615
+ await cleanupOnTaskFailure({
2616
+ taskName: config.taskName,
2617
+ vaultDir,
2618
+ runContext: options?.runContext
2619
+ });
2014
2620
  return {
2015
2621
  runId,
2016
2622
  status: STATUS_FAILED,
@@ -2019,10 +2625,29 @@ ${err.stack.split("\n").slice(0, 3).join("\n")}`;
2019
2625
  };
2020
2626
  }
2021
2627
  }
2628
+ async function cleanupOnTaskFailure(args) {
2629
+ if (args.taskName !== SKILL_GENERATE_TASK) return;
2630
+ if (!args.vaultDir) return;
2631
+ const candidateId = args.runContext?.candidate_id;
2632
+ if (!candidateId) return;
2633
+ try {
2634
+ const { cleanupStagedSkill: cleanupStagedSkill2 } = await import("./skill-staging-SWM7UC5D.js");
2635
+ cleanupStagedSkill2(args.vaultDir, candidateId);
2636
+ console.warn(
2637
+ `[agent] skill-generate failed \u2014 cleaned up staging for candidate ${candidateId}`
2638
+ );
2639
+ } catch (cleanupErr) {
2640
+ console.warn(
2641
+ `[agent] Failed to clean staging for candidate ${candidateId}:`,
2642
+ cleanupErr instanceof Error ? cleanupErr.message : cleanupErr
2643
+ );
2644
+ }
2645
+ }
2022
2646
  export {
2647
+ cleanupOnTaskFailure,
2023
2648
  composePhasePrompt,
2024
2649
  composeTaskPrompt,
2025
2650
  computeWaves,
2026
2651
  runAgent
2027
2652
  };
2028
- //# sourceMappingURL=executor-7XOKS6HS.js.map
2653
+ //# sourceMappingURL=executor-HP3Y64PD.js.map