@goondocks/myco 0.15.1 → 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 (181) hide show
  1. package/README.md +6 -4
  2. package/dist/{agent-run-T433ENJS.js → agent-run-S4MFWUSV.js} +8 -8
  3. package/dist/{agent-tasks-TAIU3V5I.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-2QMDRZPJ.js → chunk-C3GNF7RJ.js} +5 -5
  9. package/dist/{chunk-DJQOYEK3.js → chunk-CJ2KTRWI.js} +52 -4
  10. package/dist/chunk-CJ2KTRWI.js.map +1 -0
  11. package/dist/{chunk-3MEOYXOW.js → chunk-D2NTFSVO.js} +3 -3
  12. package/dist/{chunk-5YQ6VOFZ.js → chunk-DZWSHCAC.js} +2 -2
  13. package/dist/{chunk-CUADDHHU.js → chunk-E7NUADTQ.js} +9 -1
  14. package/dist/chunk-E7NUADTQ.js.map +1 -0
  15. package/dist/{chunk-4O3QNM5I.js → chunk-G6LM45FD.js} +3 -3
  16. package/dist/{chunk-SV6UCB2Z.js → chunk-GSKXOCFG.js} +3 -1
  17. package/dist/chunk-GSKXOCFG.js.map +1 -0
  18. package/dist/{chunk-RBFECYNA.js → chunk-I3S6L7QC.js} +2 -2
  19. package/dist/{chunk-SPJGJEFV.js → chunk-IRSNOBGD.js} +2 -2
  20. package/dist/{chunk-75J2BR4P.js → chunk-KYSLNB3C.js} +580 -415
  21. package/dist/chunk-KYSLNB3C.js.map +1 -0
  22. package/dist/{chunk-GYIA6XLB.js → chunk-MVBCON4D.js} +2 -2
  23. package/dist/chunk-MVBCON4D.js.map +1 -0
  24. package/dist/{chunk-6GG2IVNV.js → chunk-OH334Y3J.js} +4 -4
  25. package/dist/{chunk-Z7TZJ2SP.js → chunk-P6C6ADBU.js} +2 -2
  26. package/dist/{chunk-OKCSSDFC.js → chunk-RPILIIYT.js} +2 -2
  27. package/dist/{chunk-X5IXK5KO.js → chunk-TIAYBVSI.js} +153 -18
  28. package/dist/chunk-TIAYBVSI.js.map +1 -0
  29. package/dist/{chunk-BFM6AM6R.js → chunk-U255A3RE.js} +2 -2
  30. package/dist/chunk-U5EW2VIQ.js +86 -0
  31. package/dist/chunk-U5EW2VIQ.js.map +1 -0
  32. package/dist/{chunk-DTE3SHYK.js → chunk-UILSK6DK.js} +2 -2
  33. package/dist/{chunk-EYMKBNRP.js → chunk-V2ZBYKDU.js} +3 -3
  34. package/dist/{chunk-GCCBXCHF.js → chunk-VEQLNB7E.js} +3 -3
  35. package/dist/{chunk-TQO4PF5K.js → chunk-VQZPWCBH.js} +4 -4
  36. package/dist/{chunk-HHZ3RTEI.js → chunk-VWXDSDJU.js} +2 -2
  37. package/dist/{chunk-X4XFJG6I.js → chunk-W7ZOOZMK.js} +3 -3
  38. package/dist/{chunk-23FJUKCN.js → chunk-WKAYMCPR.js} +2 -2
  39. package/dist/{chunk-4BQ5QE76.js → chunk-XAXQ72L3.js} +9 -2
  40. package/dist/chunk-XAXQ72L3.js.map +1 -0
  41. package/dist/{cli-W37MRZHD.js → cli-U2FZT4GP.js} +42 -42
  42. package/dist/{client-YNTTC75R.js → client-MZL5SFQI.js} +5 -5
  43. package/dist/{config-MOWCOJJ4.js → config-VHHCGE4F.js} +4 -4
  44. package/dist/{detect-GFYKKHLJ.js → detect-6FNYONJF.js} +2 -2
  45. package/dist/{detect-providers-EU35RUL3.js → detect-providers-R7QOB3H6.js} +5 -5
  46. package/dist/{doctor-PAAQU5AS.js → doctor-OJW7SCDQ.js} +13 -13
  47. package/dist/{executor-4OXDK4ZA.js → executor-HP3Y64PD.js} +495 -157
  48. package/dist/executor-HP3Y64PD.js.map +1 -0
  49. package/dist/{init-PHQAQANR.js → init-XVAONLZ7.js} +18 -18
  50. package/dist/{init-wizard-RFD46XAJ.js → init-wizard-3OPLXLNA.js} +8 -8
  51. package/dist/{installer-BTUNKWOU.js → installer-AARSFXI6.js} +2 -2
  52. package/dist/llm-LS7U7BHC.js +17 -0
  53. package/dist/{loader-WGDVRGLM.js → loader-QDWQTBX4.js} +4 -4
  54. package/dist/{loader-WC4U5NM5.js → loader-YQDG5GI5.js} +4 -4
  55. package/dist/{logs-WFBX2I7C.js → logs-TMKNLSJY.js} +3 -3
  56. package/dist/{main-ADLCOYKM.js → main-VAU5UPY7.js} +608 -214
  57. package/dist/main-VAU5UPY7.js.map +1 -0
  58. package/dist/{open-3VPUP3HD.js → open-ES2AOXL5.js} +8 -8
  59. package/dist/{openai-embeddings-SEIV7AM3.js → openai-embeddings-FUW6CSN2.js} +5 -5
  60. package/dist/{openrouter-ELODIZRP.js → openrouter-YSIUSUQL.js} +5 -5
  61. package/dist/{post-compact-5NYLOC46.js → post-compact-CCSP4ZRC.js} +8 -8
  62. package/dist/{post-tool-use-SNNXSZ5Y.js → post-tool-use-5UIVOE7I.js} +7 -7
  63. package/dist/{post-tool-use-failure-POKVXQHY.js → post-tool-use-failure-SR2523FX.js} +8 -8
  64. package/dist/{pre-compact-ZUICBJEX.js → pre-compact-2G2UWGDZ.js} +8 -8
  65. package/dist/{provider-check-B66E5PWS.js → provider-check-VEYONGNU.js} +5 -5
  66. package/dist/{registry-DHWVHXWY.js → registry-5R3DLJQH.js} +5 -5
  67. package/dist/{remove-SVU2V4Q7.js → remove-L4HNBCSZ.js} +10 -10
  68. package/dist/{resolution-events-DBCRVZGU.js → resolution-events-CHOKR35X.js} +5 -5
  69. package/dist/{restart-NBB5CXJ4.js → restart-ZQ3QNRF4.js} +9 -9
  70. package/dist/{search-YUQZFRZX.js → search-ECJ76TU3.js} +9 -9
  71. package/dist/{server-NBRX56VL.js → server-4MUFDPDP.js} +5 -5
  72. package/dist/{session-2QP4HMZ5.js → session-ZHYO3BBY.js} +10 -10
  73. package/dist/{session-end-NNFBW7CQ.js → session-end-UJM3UODF.js} +7 -7
  74. package/dist/{session-start-NPNP4IXX.js → session-start-UXLG36AZ.js} +12 -12
  75. package/dist/{setup-llm-C3IGFLRN.js → setup-llm-NEN5XPNY.js} +9 -9
  76. package/dist/skill-staging-SWM7UC5D.js +25 -0
  77. package/dist/src/agent/definitions/tasks/full-intelligence.yaml +1 -1
  78. package/dist/src/agent/definitions/tasks/skill-generate.yaml +55 -21
  79. package/dist/src/cli.js +1 -1
  80. package/dist/src/daemon/main.js +1 -1
  81. package/dist/src/hooks/post-tool-use.js +1 -1
  82. package/dist/src/hooks/session-end.js +1 -1
  83. package/dist/src/hooks/session-start.js +1 -1
  84. package/dist/src/hooks/stop.js +1 -1
  85. package/dist/src/hooks/user-prompt-submit.js +1 -1
  86. package/dist/src/mcp/server.js +1 -1
  87. package/dist/src/symbionts/manifests/codex.yaml +1 -0
  88. package/dist/src/symbionts/templates/codex/settings.json +5 -0
  89. package/dist/src/worker/src/schema.ts +16 -0
  90. package/dist/{stats-FEEXIRMS.js → stats-AYRSUFHR.js} +10 -10
  91. package/dist/{stop-FGDGWXTK.js → stop-5WD22XAH.js} +7 -7
  92. package/dist/{stop-failure-5YAGH2TQ.js → stop-failure-FLFIOPJY.js} +8 -8
  93. package/dist/{subagent-start-UCKVJDR4.js → subagent-start-AMCPECUD.js} +8 -8
  94. package/dist/{subagent-stop-H25B3QEC.js → subagent-stop-4M4BUENR.js} +8 -8
  95. package/dist/{task-completed-2JGZN2CF.js → task-completed-QJOEVDXZ.js} +8 -8
  96. package/dist/{team-TG5WZXWO.js → team-FWEVWYIY.js} +6 -6
  97. package/dist/ui/assets/index-Bjv_ck3c.css +1 -0
  98. package/dist/ui/assets/index-RYHXSJv1.js +842 -0
  99. package/dist/ui/index.html +2 -2
  100. package/dist/{update-EG3N2EXI.js → update-F2LPJMUE.js} +10 -10
  101. package/dist/{user-prompt-submit-7FFQ3ORA.js → user-prompt-submit-UOAIU3JW.js} +7 -7
  102. package/dist/{verify-2M3DYHEY.js → verify-ITBMLK67.js} +9 -9
  103. package/dist/{version-JUQU5W22.js → version-2NJN3WW6.js} +2 -2
  104. package/dist/version-2NJN3WW6.js.map +1 -0
  105. package/package.json +1 -1
  106. package/dist/chunk-4BQ5QE76.js.map +0 -1
  107. package/dist/chunk-75J2BR4P.js.map +0 -1
  108. package/dist/chunk-CUADDHHU.js.map +0 -1
  109. package/dist/chunk-DJQOYEK3.js.map +0 -1
  110. package/dist/chunk-GYIA6XLB.js.map +0 -1
  111. package/dist/chunk-SV6UCB2Z.js.map +0 -1
  112. package/dist/chunk-TRA3R4EC.js.map +0 -1
  113. package/dist/chunk-X5IXK5KO.js.map +0 -1
  114. package/dist/executor-4OXDK4ZA.js.map +0 -1
  115. package/dist/llm-D4VWYUK7.js +0 -17
  116. package/dist/main-ADLCOYKM.js.map +0 -1
  117. package/dist/ui/assets/index-7Vimyg7g.js +0 -837
  118. package/dist/ui/assets/index-DlEQ8A8Y.css +0 -1
  119. /package/dist/{agent-run-T433ENJS.js.map → agent-run-S4MFWUSV.js.map} +0 -0
  120. /package/dist/{agent-tasks-TAIU3V5I.js.map → agent-tasks-JZ77AZSK.js.map} +0 -0
  121. /package/dist/{chunk-LF5Z62X6.js.map → chunk-4JVHWBZF.js.map} +0 -0
  122. /package/dist/{chunk-GDCSPMH4.js.map → chunk-6JZEAOLG.js.map} +0 -0
  123. /package/dist/{chunk-2QMDRZPJ.js.map → chunk-C3GNF7RJ.js.map} +0 -0
  124. /package/dist/{chunk-3MEOYXOW.js.map → chunk-D2NTFSVO.js.map} +0 -0
  125. /package/dist/{chunk-5YQ6VOFZ.js.map → chunk-DZWSHCAC.js.map} +0 -0
  126. /package/dist/{chunk-4O3QNM5I.js.map → chunk-G6LM45FD.js.map} +0 -0
  127. /package/dist/{chunk-RBFECYNA.js.map → chunk-I3S6L7QC.js.map} +0 -0
  128. /package/dist/{chunk-SPJGJEFV.js.map → chunk-IRSNOBGD.js.map} +0 -0
  129. /package/dist/{chunk-6GG2IVNV.js.map → chunk-OH334Y3J.js.map} +0 -0
  130. /package/dist/{chunk-Z7TZJ2SP.js.map → chunk-P6C6ADBU.js.map} +0 -0
  131. /package/dist/{chunk-OKCSSDFC.js.map → chunk-RPILIIYT.js.map} +0 -0
  132. /package/dist/{chunk-BFM6AM6R.js.map → chunk-U255A3RE.js.map} +0 -0
  133. /package/dist/{chunk-DTE3SHYK.js.map → chunk-UILSK6DK.js.map} +0 -0
  134. /package/dist/{chunk-EYMKBNRP.js.map → chunk-V2ZBYKDU.js.map} +0 -0
  135. /package/dist/{chunk-GCCBXCHF.js.map → chunk-VEQLNB7E.js.map} +0 -0
  136. /package/dist/{chunk-TQO4PF5K.js.map → chunk-VQZPWCBH.js.map} +0 -0
  137. /package/dist/{chunk-HHZ3RTEI.js.map → chunk-VWXDSDJU.js.map} +0 -0
  138. /package/dist/{chunk-X4XFJG6I.js.map → chunk-W7ZOOZMK.js.map} +0 -0
  139. /package/dist/{chunk-23FJUKCN.js.map → chunk-WKAYMCPR.js.map} +0 -0
  140. /package/dist/{cli-W37MRZHD.js.map → cli-U2FZT4GP.js.map} +0 -0
  141. /package/dist/{client-YNTTC75R.js.map → client-MZL5SFQI.js.map} +0 -0
  142. /package/dist/{config-MOWCOJJ4.js.map → config-VHHCGE4F.js.map} +0 -0
  143. /package/dist/{detect-GFYKKHLJ.js.map → detect-6FNYONJF.js.map} +0 -0
  144. /package/dist/{detect-providers-EU35RUL3.js.map → detect-providers-R7QOB3H6.js.map} +0 -0
  145. /package/dist/{doctor-PAAQU5AS.js.map → doctor-OJW7SCDQ.js.map} +0 -0
  146. /package/dist/{init-PHQAQANR.js.map → init-XVAONLZ7.js.map} +0 -0
  147. /package/dist/{init-wizard-RFD46XAJ.js.map → init-wizard-3OPLXLNA.js.map} +0 -0
  148. /package/dist/{installer-BTUNKWOU.js.map → installer-AARSFXI6.js.map} +0 -0
  149. /package/dist/{llm-D4VWYUK7.js.map → llm-LS7U7BHC.js.map} +0 -0
  150. /package/dist/{loader-WC4U5NM5.js.map → loader-QDWQTBX4.js.map} +0 -0
  151. /package/dist/{loader-WGDVRGLM.js.map → loader-YQDG5GI5.js.map} +0 -0
  152. /package/dist/{logs-WFBX2I7C.js.map → logs-TMKNLSJY.js.map} +0 -0
  153. /package/dist/{open-3VPUP3HD.js.map → open-ES2AOXL5.js.map} +0 -0
  154. /package/dist/{openai-embeddings-SEIV7AM3.js.map → openai-embeddings-FUW6CSN2.js.map} +0 -0
  155. /package/dist/{openrouter-ELODIZRP.js.map → openrouter-YSIUSUQL.js.map} +0 -0
  156. /package/dist/{post-compact-5NYLOC46.js.map → post-compact-CCSP4ZRC.js.map} +0 -0
  157. /package/dist/{post-tool-use-SNNXSZ5Y.js.map → post-tool-use-5UIVOE7I.js.map} +0 -0
  158. /package/dist/{post-tool-use-failure-POKVXQHY.js.map → post-tool-use-failure-SR2523FX.js.map} +0 -0
  159. /package/dist/{pre-compact-ZUICBJEX.js.map → pre-compact-2G2UWGDZ.js.map} +0 -0
  160. /package/dist/{provider-check-B66E5PWS.js.map → provider-check-VEYONGNU.js.map} +0 -0
  161. /package/dist/{registry-DHWVHXWY.js.map → registry-5R3DLJQH.js.map} +0 -0
  162. /package/dist/{remove-SVU2V4Q7.js.map → remove-L4HNBCSZ.js.map} +0 -0
  163. /package/dist/{resolution-events-DBCRVZGU.js.map → resolution-events-CHOKR35X.js.map} +0 -0
  164. /package/dist/{restart-NBB5CXJ4.js.map → restart-ZQ3QNRF4.js.map} +0 -0
  165. /package/dist/{search-YUQZFRZX.js.map → search-ECJ76TU3.js.map} +0 -0
  166. /package/dist/{server-NBRX56VL.js.map → server-4MUFDPDP.js.map} +0 -0
  167. /package/dist/{session-2QP4HMZ5.js.map → session-ZHYO3BBY.js.map} +0 -0
  168. /package/dist/{session-end-NNFBW7CQ.js.map → session-end-UJM3UODF.js.map} +0 -0
  169. /package/dist/{session-start-NPNP4IXX.js.map → session-start-UXLG36AZ.js.map} +0 -0
  170. /package/dist/{setup-llm-C3IGFLRN.js.map → setup-llm-NEN5XPNY.js.map} +0 -0
  171. /package/dist/{version-JUQU5W22.js.map → skill-staging-SWM7UC5D.js.map} +0 -0
  172. /package/dist/{stats-FEEXIRMS.js.map → stats-AYRSUFHR.js.map} +0 -0
  173. /package/dist/{stop-FGDGWXTK.js.map → stop-5WD22XAH.js.map} +0 -0
  174. /package/dist/{stop-failure-5YAGH2TQ.js.map → stop-failure-FLFIOPJY.js.map} +0 -0
  175. /package/dist/{subagent-start-UCKVJDR4.js.map → subagent-start-AMCPECUD.js.map} +0 -0
  176. /package/dist/{subagent-stop-H25B3QEC.js.map → subagent-stop-4M4BUENR.js.map} +0 -0
  177. /package/dist/{task-completed-2JGZN2CF.js.map → task-completed-QJOEVDXZ.js.map} +0 -0
  178. /package/dist/{team-TG5WZXWO.js.map → team-FWEVWYIY.js.map} +0 -0
  179. /package/dist/{update-EG3N2EXI.js.map → update-F2LPJMUE.js.map} +0 -0
  180. /package/dist/{user-prompt-submit-7FFQ3ORA.js.map → user-prompt-submit-UOAIU3JW.js.map} +0 -0
  181. /package/dist/{verify-2M3DYHEY.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-BFM6AM6R.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 {
@@ -836,6 +846,200 @@ function validateSkillContent(content, dirName) {
836
846
  // src/agent/tools/skill-tools.ts
837
847
  function createSkillTools(deps) {
838
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
+ }
839
1043
  const vaultSkillCandidates = tool4(
840
1044
  "vault_skill_candidates",
841
1045
  "Manage skill candidates (identified topics that may become skills). Supports list, get, create, and update actions.",
@@ -845,7 +1049,9 @@ function createSkillTools(deps) {
845
1049
  topic: external_exports.string().optional().describe("Skill topic (required for create)"),
846
1050
  rationale: external_exports.string().optional().describe("Why this should be a skill (required for create)"),
847
1051
  confidence: external_exports.number().optional().describe("Confidence score 0-1"),
848
- 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
+ ),
849
1055
  source_ids: external_exports.string().optional().describe("JSON array of source spore/entity IDs"),
850
1056
  skill_id: external_exports.string().optional().describe("Associated skill record ID (after materialization)"),
851
1057
  limit: external_exports.number().optional().describe("Maximum candidates to return (for list)")
@@ -884,6 +1090,19 @@ function createSkillTools(deps) {
884
1090
  overlapping_skills: overlapping.map((s) => ({ name: s.name, display_name: s.display_name, description: s.description }))
885
1091
  });
886
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
+ }
887
1106
  const now = epochSeconds();
888
1107
  const candidate = insertCandidate({
889
1108
  id: crypto2.randomUUID(),
@@ -991,7 +1210,7 @@ function createSkillTools(deps) {
991
1210
  console.warn("[vault_skill_records] Failed to remove skill directory:", err instanceof Error ? err.message : err);
992
1211
  }
993
1212
  try {
994
- const { syncSkillSymlinks } = await import("./installer-BTUNKWOU.js");
1213
+ const { syncSkillSymlinks } = await import("./installer-AARSFXI6.js");
995
1214
  syncSkillSymlinks(root, result.name, { remove: true });
996
1215
  } catch (err) {
997
1216
  console.warn("[vault_skill_records] Failed to remove symlinks:", err instanceof Error ? err.message : err);
@@ -1032,51 +1251,18 @@ function createSkillTools(deps) {
1032
1251
  error: 'Invalid skill name: must be a simple directory name without path separators or ".."'
1033
1252
  });
1034
1253
  }
1035
- const existingSameName = getSkillRecordByName(args.name);
1036
- if (!existingSameName) {
1037
- if (args.candidate_id) {
1038
- const candidate = getCandidate(args.candidate_id);
1039
- if (candidate?.skill_id) {
1040
- const linkedSkill = getSkillRecord(candidate.skill_id);
1041
- if (linkedSkill && linkedSkill.name !== args.name) {
1042
- recordTurn("vault_write_skill", args);
1043
- return textResult({
1044
- 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.`,
1045
- existing_skill: {
1046
- id: linkedSkill.id,
1047
- name: linkedSkill.name,
1048
- description: linkedSkill.description,
1049
- path: linkedSkill.path
1050
- }
1051
- });
1052
- }
1053
- }
1054
- }
1055
- const activeSkills = listSkillRecords({ agent_id: agentId, status: "active", limit: 200 });
1056
- let bestMatch = null;
1057
- for (const skill of activeSkills) {
1058
- const score = descriptionSimilarity(args.description, skill.description);
1059
- if (score >= DESCRIPTION_DUPLICATE_THRESHOLD && (!bestMatch || score > bestMatch.score)) {
1060
- bestMatch = { skill, score };
1061
- }
1062
- }
1063
- if (bestMatch) {
1064
- recordTurn("vault_write_skill", args);
1065
- return textResult({
1066
- 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.`,
1067
- overlapping_skill: {
1068
- id: bestMatch.skill.id,
1069
- name: bestMatch.skill.name,
1070
- description: bestMatch.skill.description,
1071
- path: bestMatch.skill.path
1072
- },
1073
- similarity: bestMatch.score
1074
- });
1075
- }
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);
1076
1262
  }
1263
+ const existing = getSkillRecordByName(args.name);
1077
1264
  const root = projectRoot ?? process.cwd();
1078
- const skillDir = resolve(root, ".agents", "skills", args.name);
1079
- const skillPath = resolve(skillDir, "SKILL.md");
1265
+ const skillPath = resolve(root, ".agents", "skills", args.name, "SKILL.md");
1080
1266
  if (existsSync(skillPath)) {
1081
1267
  const existingContent = readFileSync(skillPath, "utf-8");
1082
1268
  const violations = checkFrontmatterPreservation(existingContent, args.content);
@@ -1088,114 +1274,95 @@ function createSkillTools(deps) {
1088
1274
  });
1089
1275
  }
1090
1276
  }
1091
- const skillDirPreexisted = existsSync(skillDir);
1092
- let priorSkillContent = null;
1093
- if (existsSync(skillPath)) {
1094
- try {
1095
- priorSkillContent = readFileSync(skillPath, "utf-8");
1096
- } catch {
1097
- priorSkillContent = null;
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
+ }
1098
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);
1099
1324
  }
1325
+ const priorSkillContent = readFileSync(skillPath, "utf-8");
1100
1326
  try {
1101
- mkdirSync(skillDir, { recursive: true });
1102
1327
  writeFileSync(skillPath, args.content, "utf-8");
1103
1328
  } catch (err) {
1104
1329
  return textResult({ error: `Failed to write skill file: ${err instanceof Error ? err.message : String(err)}` });
1105
1330
  }
1106
1331
  try {
1107
- const { syncSkillSymlinks } = await import("./installer-BTUNKWOU.js");
1332
+ const { syncSkillSymlinks } = await import("./installer-AARSFXI6.js");
1108
1333
  syncSkillSymlinks(root, args.name);
1109
1334
  } catch (err) {
1110
1335
  console.warn("[vault_write_skill] syncSkillSymlinks failed:", err instanceof Error ? err.message : err);
1111
1336
  }
1112
1337
  const now = epochSeconds();
1113
1338
  const relativePath = `.agents/skills/${args.name}/SKILL.md`;
1114
- const existing = getSkillRecordByName(args.name);
1115
- let recordId = "";
1116
- let generation = 0;
1339
+ const generation = existing.generation + 1;
1340
+ const recordId = existing.id;
1117
1341
  const txDb = getDatabase();
1118
1342
  try {
1119
1343
  txDb.transaction(() => {
1120
- if (existing) {
1121
- generation = existing.generation + 1;
1122
- recordId = existing.id;
1123
- updateSkillRecord(existing.id, {
1124
- display_name: args.display_name,
1125
- description: args.description,
1126
- generation,
1127
- ...args.source_ids !== void 0 ? { source_ids: args.source_ids } : {},
1128
- path: relativePath,
1129
- updated_at: now
1130
- });
1131
- insertLineage({
1132
- id: crypto2.randomUUID(),
1133
- skill_id: existing.id,
1134
- generation,
1135
- action: "updated",
1136
- rationale: args.rationale ?? "Skill content updated",
1137
- source_ids_added: args.source_ids,
1138
- content_snapshot: args.content,
1139
- created_at: now
1140
- });
1141
- } else {
1142
- recordId = crypto2.randomUUID();
1143
- generation = 1;
1144
- insertSkillRecord({
1145
- id: recordId,
1146
- agent_id: agentId,
1147
- machine_id: machineId,
1148
- name: args.name,
1149
- display_name: args.display_name,
1150
- description: args.description,
1151
- candidate_id: args.candidate_id ?? null,
1152
- source_ids: args.source_ids,
1153
- path: relativePath,
1154
- created_at: now,
1155
- updated_at: now
1156
- });
1157
- insertLineage({
1158
- id: crypto2.randomUUID(),
1159
- skill_id: recordId,
1160
- generation,
1161
- action: "created",
1162
- rationale: args.rationale ?? "Initial skill creation",
1163
- source_ids_added: args.source_ids,
1164
- content_snapshot: args.content,
1165
- created_at: now
1166
- });
1167
- const approvedCandidates = listCandidates({ status: "approved", limit: 10 });
1168
- let linkedCandidate = false;
1169
- if (args.candidate_id && !linkedCandidate) {
1170
- const exact = updateCandidate(args.candidate_id, {
1171
- status: "generated",
1172
- skill_id: recordId,
1173
- updated_at: now
1174
- });
1175
- if (exact) linkedCandidate = true;
1176
- }
1177
- if (args.candidate_id && !linkedCandidate) {
1178
- const prefixMatch = approvedCandidates.find((c) => c.id.startsWith(args.candidate_id));
1179
- if (prefixMatch) {
1180
- updateCandidate(prefixMatch.id, {
1181
- status: "generated",
1182
- skill_id: recordId,
1183
- updated_at: now
1184
- });
1185
- linkedCandidate = true;
1186
- }
1187
- }
1188
- }
1344
+ updateSkillRecord(existing.id, {
1345
+ display_name: args.display_name,
1346
+ description: args.description,
1347
+ generation,
1348
+ ...args.source_ids !== void 0 ? { source_ids: args.source_ids } : {},
1349
+ path: relativePath,
1350
+ updated_at: now
1351
+ });
1352
+ insertLineage({
1353
+ id: crypto2.randomUUID(),
1354
+ skill_id: existing.id,
1355
+ generation,
1356
+ action: "updated",
1357
+ rationale: args.rationale ?? "Skill content updated",
1358
+ source_ids_added: args.source_ids,
1359
+ content_snapshot: args.content,
1360
+ created_at: now
1361
+ });
1189
1362
  })();
1190
1363
  } catch (err) {
1191
1364
  try {
1192
- if (priorSkillContent !== null) {
1193
- writeFileSync(skillPath, priorSkillContent, "utf-8");
1194
- } else if (!skillDirPreexisted) {
1195
- rmSync(skillDir, { recursive: true, force: true });
1196
- } else {
1197
- rmSync(skillPath, { force: true });
1198
- }
1365
+ writeFileSync(skillPath, priorSkillContent, "utf-8");
1199
1366
  } catch (rollbackErr) {
1200
1367
  console.warn(
1201
1368
  "[vault_write_skill] file rollback after DB failure also failed:",
@@ -1207,14 +1374,12 @@ function createSkillTools(deps) {
1207
1374
  error: `Skill write aborted: database transaction failed and on-disk state was rolled back. ${err instanceof Error ? err.message : String(err)}`
1208
1375
  });
1209
1376
  }
1210
- const isNew = generation === 1;
1211
- notify(vaultDir, {
1212
- domain: "skills",
1213
- type: isNew ? "skill.created" : "skill.evolved",
1214
- title: isNew ? `Skill created: ${args.display_name}` : `Skill evolved: ${args.display_name}`,
1215
- message: args.description.slice(0, 120),
1216
- link: `/skills?skill=${encodeURIComponent(args.name)}`,
1217
- metadata: { skillId: recordId, name: args.name, generation }
1377
+ emitSkillNotification("evolved", {
1378
+ name: args.name,
1379
+ display_name: args.display_name,
1380
+ description: args.description,
1381
+ recordId,
1382
+ generation
1218
1383
  });
1219
1384
  recordTurn("vault_write_skill", args);
1220
1385
  return textResult({
@@ -1226,10 +1391,145 @@ function createSkillTools(deps) {
1226
1391
  },
1227
1392
  { annotations: { openWorldHint: true } }
1228
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
+ );
1229
1527
  return [
1230
1528
  vaultSkillCandidates,
1231
1529
  vaultSkillRecords,
1232
- vaultWriteSkill
1530
+ vaultWriteSkill,
1531
+ vaultStageSkill,
1532
+ vaultFinalizeSkill
1233
1533
  ];
1234
1534
  }
1235
1535
 
@@ -1259,8 +1559,11 @@ var OBSERVABILITY_TOOL_NAMES = /* @__PURE__ */ new Set(["vault_report"]);
1259
1559
  var SKILL_TOOL_NAMES = /* @__PURE__ */ new Set([
1260
1560
  "vault_skill_candidates",
1261
1561
  "vault_skill_records",
1262
- "vault_write_skill"
1562
+ "vault_write_skill",
1563
+ "vault_stage_skill",
1564
+ "vault_finalize_skill"
1263
1565
  ]);
1566
+ var VAULT_TOOL_COUNT = READ_TOOL_NAMES.size + WRITE_TOOL_NAMES.size + OBSERVABILITY_TOOL_NAMES.size + SKILL_TOOL_NAMES.size;
1264
1567
  function setsOverlap(a, b) {
1265
1568
  for (const item of a) {
1266
1569
  if (b.has(item)) return true;
@@ -2233,6 +2536,17 @@ async function runAgent(vaultDir, options) {
2233
2536
  tokensUsed = result.tokensUsed;
2234
2537
  costUsd = result.costUsd;
2235
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
+ }
2236
2550
  } else {
2237
2551
  const taskPrompt = composeTaskPrompt(
2238
2552
  vaultContext,
@@ -2298,6 +2612,11 @@ ${err.stack.split("\n").slice(0, 3).join("\n")}`;
2298
2612
  } catch (dbErr) {
2299
2613
  console.error(`[agent] Failed to save error to DB:`, dbErr);
2300
2614
  }
2615
+ await cleanupOnTaskFailure({
2616
+ taskName: config.taskName,
2617
+ vaultDir,
2618
+ runContext: options?.runContext
2619
+ });
2301
2620
  return {
2302
2621
  runId,
2303
2622
  status: STATUS_FAILED,
@@ -2306,10 +2625,29 @@ ${err.stack.split("\n").slice(0, 3).join("\n")}`;
2306
2625
  };
2307
2626
  }
2308
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
+ }
2309
2646
  export {
2647
+ cleanupOnTaskFailure,
2310
2648
  composePhasePrompt,
2311
2649
  composeTaskPrompt,
2312
2650
  computeWaves,
2313
2651
  runAgent
2314
2652
  };
2315
- //# sourceMappingURL=executor-4OXDK4ZA.js.map
2653
+ //# sourceMappingURL=executor-HP3Y64PD.js.map