safeword 0.78.2 → 0.78.3

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 (218) hide show
  1. package/codex-plugin/.codex-plugin/plugin.json +1 -1
  2. package/codex-plugin/hooks.json +5 -5
  3. package/codex-plugin/skills/bdd/references/SCENARIOS.md +3 -0
  4. package/codex-plugin/skills/bdd/references/TDD.md +11 -5
  5. package/codex-plugin/skills/closeout/SKILL.md +24 -16
  6. package/codex-plugin/skills/finish-review/SKILL.md +4 -3
  7. package/codex-plugin/skills/lint/SKILL.md +1 -1
  8. package/codex-plugin/skills/review-spec/SKILL.md +5 -1
  9. package/codex-plugin/skills/self-review/SKILL.md +2 -2
  10. package/codex-plugin/skills/ticket-system/SKILL.md +3 -0
  11. package/dist/{architecture-N5ZC4CDU.js → architecture-RQ377ZER.js} +6 -6
  12. package/dist/{architecture-document-UBA5UT4J.js → architecture-document-KS3CLSYN.js} +6 -6
  13. package/dist/{architecture-monorepo-E4JUYJGV.js → architecture-monorepo-NJWWU65G.js} +4 -4
  14. package/dist/{boundary-HLJILY7R.js → boundary-OPTC2RVJ.js} +6 -6
  15. package/dist/{chunk-NOTBZCKG.js → chunk-2XR7J5WL.js} +93 -42
  16. package/dist/chunk-2XR7J5WL.js.map +1 -0
  17. package/dist/{chunk-4CZS4YJV.js → chunk-3OKZ3X4O.js} +3 -3
  18. package/dist/{chunk-J7UDLLTK.js → chunk-76XHADUG.js} +39 -25
  19. package/dist/chunk-76XHADUG.js.map +1 -0
  20. package/dist/{chunk-4CKCRZDO.js → chunk-7ULZND56.js} +2 -2
  21. package/dist/{chunk-34NCOTP5.js → chunk-7XELS325.js} +2 -2
  22. package/dist/{chunk-USPVNZ63.js → chunk-BZWO3BVD.js} +11 -7
  23. package/dist/chunk-BZWO3BVD.js.map +1 -0
  24. package/dist/{chunk-GZ36UZJG.js → chunk-CFME47IY.js} +6 -6
  25. package/dist/{chunk-SME5SU3O.js → chunk-CNBZ3S5U.js} +255 -45
  26. package/dist/chunk-CNBZ3S5U.js.map +1 -0
  27. package/dist/{chunk-AV5LTEME.js → chunk-CRGPYC35.js} +8 -6
  28. package/dist/{chunk-AV5LTEME.js.map → chunk-CRGPYC35.js.map} +1 -1
  29. package/dist/{chunk-BQTC6YGN.js → chunk-D5ENZR6N.js} +2 -2
  30. package/dist/{chunk-V63TSN3C.js → chunk-FOYGZ4MK.js} +31 -10
  31. package/dist/chunk-FOYGZ4MK.js.map +1 -0
  32. package/dist/{chunk-ZRV7TOCX.js → chunk-G5AFQXXW.js} +2 -2
  33. package/dist/{chunk-S2T356DA.js → chunk-HF3DXZEA.js} +7 -10
  34. package/dist/chunk-HF3DXZEA.js.map +1 -0
  35. package/dist/{chunk-NSZGI7HK.js → chunk-HNCUIXDY.js} +9 -9
  36. package/dist/{chunk-NSZGI7HK.js.map → chunk-HNCUIXDY.js.map} +1 -1
  37. package/dist/{chunk-KXHVFCDT.js → chunk-IDRLACDS.js} +145 -19
  38. package/dist/chunk-IDRLACDS.js.map +1 -0
  39. package/dist/{chunk-BSOUMHMQ.js → chunk-IPTM5VE7.js} +101 -1094
  40. package/dist/chunk-IPTM5VE7.js.map +1 -0
  41. package/dist/chunk-JBOLNV2L.js +21 -0
  42. package/dist/chunk-JBOLNV2L.js.map +1 -0
  43. package/dist/chunk-JSR3HXFT.js +1231 -0
  44. package/dist/chunk-JSR3HXFT.js.map +1 -0
  45. package/dist/{chunk-I4KBHRZW.js → chunk-LH2FAQTI.js} +4 -4
  46. package/dist/{chunk-WVDRXD67.js → chunk-MLBZFB5A.js} +1 -1
  47. package/dist/chunk-MLBZFB5A.js.map +1 -0
  48. package/dist/{chunk-KFVEPQ7X.js → chunk-MVFHTUQ3.js} +2 -2
  49. package/dist/{chunk-BAVDDAN3.js → chunk-MZKTOX6X.js} +7 -7
  50. package/dist/{chunk-TJ627SKI.js → chunk-O3GZ77F7.js} +15 -1
  51. package/dist/chunk-O3GZ77F7.js.map +1 -0
  52. package/dist/{chunk-Q5KLE6ZF.js → chunk-OKPRQGFG.js} +2 -2
  53. package/dist/{chunk-QUDIOBHP.js → chunk-PD7ZXRHV.js} +139 -18
  54. package/dist/chunk-PD7ZXRHV.js.map +1 -0
  55. package/dist/chunk-QAHJSG53.js +30 -0
  56. package/dist/chunk-QAHJSG53.js.map +1 -0
  57. package/dist/{chunk-QTG2EXFT.js → chunk-QLOUSTQE.js} +2 -2
  58. package/dist/{chunk-QTG2EXFT.js.map → chunk-QLOUSTQE.js.map} +1 -1
  59. package/dist/{chunk-3SOJBMM6.js → chunk-R6BSRNU4.js} +6 -6
  60. package/dist/{chunk-3SOJBMM6.js.map → chunk-R6BSRNU4.js.map} +1 -1
  61. package/dist/{chunk-664YNAMR.js → chunk-RTFPKJJV.js} +5 -5
  62. package/dist/{chunk-GKR44BCH.js → chunk-RVDXBHUG.js} +2 -2
  63. package/dist/chunk-THLEVFEX.js +22 -0
  64. package/dist/chunk-THLEVFEX.js.map +1 -0
  65. package/dist/{chunk-A7Z27COA.js → chunk-VFWAZLEF.js} +3 -3
  66. package/dist/{chunk-3EJNDIAO.js → chunk-VZ5B2SIO.js} +2 -2
  67. package/dist/chunk-WAMQTZAH.js +19 -0
  68. package/dist/chunk-WAMQTZAH.js.map +1 -0
  69. package/dist/{chunk-FDHJ63FZ.js → chunk-WVA4Q6BX.js} +1 -1
  70. package/dist/{chunk-FDHJ63FZ.js.map → chunk-WVA4Q6BX.js.map} +1 -1
  71. package/dist/{cleanup-X556LNWM.js → cleanup-ELVHZPRC.js} +9 -9
  72. package/dist/{cleanup-command-RV2Q7G72.js → cleanup-command-5BY4FWAT.js} +12 -12
  73. package/dist/cli.js +143 -108
  74. package/dist/cli.js.map +1 -1
  75. package/dist/{clients-HP23HRN2.js → clients-WRPBRO6V.js} +2 -2
  76. package/dist/{codex-bootstrap-MGY2D6HU.js → codex-bootstrap-GYG7N4XU.js} +77 -34
  77. package/dist/codex-bootstrap-GYG7N4XU.js.map +1 -0
  78. package/dist/{codex-hook-IPX3PDXX.js → codex-hook-7ILIPLYM.js} +56 -48
  79. package/dist/codex-hook-7ILIPLYM.js.map +1 -0
  80. package/dist/{codify-J5ZLJ3HK.js → codify-HSDVRZFI.js} +11 -8
  81. package/dist/codify-HSDVRZFI.js.map +1 -0
  82. package/dist/{commands-L4AINARC.js → commands-X4KBCMHS.js} +115 -60
  83. package/dist/commands-X4KBCMHS.js.map +1 -0
  84. package/dist/{config-LUSJAK6B.js → config-TXJRCN3W.js} +2 -2
  85. package/dist/{configured-paths-SPQ5XWOM.js → configured-paths-4CRHLBIF.js} +4 -4
  86. package/dist/contract-B7NDKO5B.js +8 -0
  87. package/dist/{coordinator-SA75UB3C.js → coordinator-ZMD3467F.js} +231 -144
  88. package/dist/coordinator-ZMD3467F.js.map +1 -0
  89. package/dist/corpus-PUBXCCSN.js +15 -0
  90. package/dist/{doctor-DSC36R4U.js → doctor-RPXETRSJ.js} +18 -18
  91. package/dist/{feature-directories-VOHVV26X.js → feature-directories-ASNDEM7J.js} +6 -6
  92. package/dist/{finalization-7SMLMVLV.js → finalization-BGPOEBAL.js} +2 -2
  93. package/dist/{gh-cli-ZMO7D2KE.js → gh-cli-OTX7PORT.js} +2 -2
  94. package/dist/{github-rest-KGKS4V2H.js → github-rest-JCW32377.js} +2 -2
  95. package/dist/index.js +2 -2
  96. package/dist/{job-CDADLJDF.js → job-NXXG4U5K.js} +20 -21
  97. package/dist/job-NXXG4U5K.js.map +1 -0
  98. package/dist/{learning-sync-6BTHM6ZC.js → learning-sync-2WIAOTW3.js} +4 -4
  99. package/dist/{legacy-global-guidance-2JL4Y5EB.js → legacy-global-guidance-WTDI53H7.js} +5 -5
  100. package/dist/{lint-gherkin-COSHVS6Z.js → lint-gherkin-LS3CHW3J.js} +7 -7
  101. package/dist/{operations-XVRQSLJG.js → operations-OZKEQCS4.js} +12 -12
  102. package/dist/{output-3LB2OEHN.js → output-ZYTEW6SC.js} +2 -2
  103. package/dist/{packet-5WT5M3OS.js → packet-7S6CZJQR.js} +2 -2
  104. package/dist/presets/typescript/index.js +2 -2
  105. package/dist/{profile-UMKQ5JVO.js → profile-SDFSBKH5.js} +7 -7
  106. package/dist/{prompt-GWYDFMLO.js → prompt-LYPODDC3.js} +2 -2
  107. package/dist/remove-P5JQNS5P.js +23 -0
  108. package/dist/{retro-IFSVU5KS.js → retro-EQK2Z2Z6.js} +8 -8
  109. package/dist/{retro-draft-spool-4S3TCHWL.js → retro-draft-spool-MDPBVM6J.js} +2 -2
  110. package/dist/{retro-extract-OSBFAXLU.js → retro-extract-CZMDD4WB.js} +2 -2
  111. package/dist/{review-pr-6S2352CK.js → review-pr-CWRD446M.js} +2 -2
  112. package/dist/{review-pr-publication-CGHCRRUI.js → review-pr-publication-AIX2U3VM.js} +2 -2
  113. package/dist/{run-5JESKXUR.js → run-T45X6Z6C.js} +4 -4
  114. package/dist/{self-report-VPHBPPSJ.js → self-report-V7PHKTS6.js} +2 -2
  115. package/dist/status-F5TFEIZ6.js +21 -0
  116. package/dist/{status-EX4L3ELO.js → status-S24PDFXB.js} +18 -18
  117. package/dist/{sync-config-O7J7IDGE.js → sync-config-EUVBPUUG.js} +5 -5
  118. package/dist/{sync-tracker-U2GYOFUO.js → sync-tracker-5FSIIAMS.js} +6 -6
  119. package/dist/{test-execution-6LIPMBIH.js → test-execution-HASVO6EL.js} +8 -7
  120. package/dist/{test-execution-6LIPMBIH.js.map → test-execution-HASVO6EL.js.map} +1 -1
  121. package/dist/{test-plan-VASHZXJ7.js → test-plan-EM3PVGIX.js} +7 -6
  122. package/dist/{test-plan-VASHZXJ7.js.map → test-plan-EM3PVGIX.js.map} +1 -1
  123. package/dist/{ticket-new-3YXDIWPC.js → ticket-new-H6ZNIRS4.js} +11 -11
  124. package/dist/ticket-new-H6ZNIRS4.js.map +1 -0
  125. package/dist/{ticket-sync-LLUBXZ2R.js → ticket-sync-7QRH6PVT.js} +5 -5
  126. package/dist/{tracker-map-HR4MBYG5.js → tracker-map-7AGCTWBZ.js} +2 -2
  127. package/dist/{tracker-sync-YDICZN66.js → tracker-sync-MLVYGFQJ.js} +2 -2
  128. package/package.json +1 -1
  129. package/templates/SAFEWORD.md +2 -2
  130. package/templates/commands/lint.md +1 -1
  131. package/templates/scripts/closeout-cleanup.ts +127 -31
  132. package/templates/skills/bdd/SCENARIOS.md +3 -0
  133. package/templates/skills/bdd/TDD.md +11 -5
  134. package/templates/skills/closeout/SKILL.md +25 -17
  135. package/templates/skills/finish-review/SKILL.md +4 -3
  136. package/templates/skills/lint/SKILL.md +1 -1
  137. package/templates/skills/review-spec/SKILL.md +5 -1
  138. package/templates/skills/self-review/SKILL.md +2 -2
  139. package/templates/skills/ticket-system/SKILL.md +3 -0
  140. package/dist/chunk-6S7CTKGC.js +0 -128
  141. package/dist/chunk-6S7CTKGC.js.map +0 -1
  142. package/dist/chunk-BSOUMHMQ.js.map +0 -1
  143. package/dist/chunk-J7UDLLTK.js.map +0 -1
  144. package/dist/chunk-KXHVFCDT.js.map +0 -1
  145. package/dist/chunk-NOTBZCKG.js.map +0 -1
  146. package/dist/chunk-QUDIOBHP.js.map +0 -1
  147. package/dist/chunk-S2T356DA.js.map +0 -1
  148. package/dist/chunk-SME5SU3O.js.map +0 -1
  149. package/dist/chunk-TJ627SKI.js.map +0 -1
  150. package/dist/chunk-USPVNZ63.js.map +0 -1
  151. package/dist/chunk-V63TSN3C.js.map +0 -1
  152. package/dist/chunk-WRUXO3WA.js +0 -48
  153. package/dist/chunk-WRUXO3WA.js.map +0 -1
  154. package/dist/chunk-WVDRXD67.js.map +0 -1
  155. package/dist/codex-bootstrap-MGY2D6HU.js.map +0 -1
  156. package/dist/codex-hook-IPX3PDXX.js.map +0 -1
  157. package/dist/codify-J5ZLJ3HK.js.map +0 -1
  158. package/dist/commands-L4AINARC.js.map +0 -1
  159. package/dist/contract-KEZY6YC3.js +0 -8
  160. package/dist/coordinator-SA75UB3C.js.map +0 -1
  161. package/dist/corpus-PPOBTF4J.js +0 -15
  162. package/dist/job-CDADLJDF.js.map +0 -1
  163. package/dist/remove-E6HGNHOU.js +0 -22
  164. package/dist/status-7VIGNTUI.js +0 -21
  165. package/dist/ticket-new-3YXDIWPC.js.map +0 -1
  166. /package/dist/{architecture-N5ZC4CDU.js.map → architecture-RQ377ZER.js.map} +0 -0
  167. /package/dist/{architecture-document-UBA5UT4J.js.map → architecture-document-KS3CLSYN.js.map} +0 -0
  168. /package/dist/{architecture-monorepo-E4JUYJGV.js.map → architecture-monorepo-NJWWU65G.js.map} +0 -0
  169. /package/dist/{boundary-HLJILY7R.js.map → boundary-OPTC2RVJ.js.map} +0 -0
  170. /package/dist/{chunk-4CZS4YJV.js.map → chunk-3OKZ3X4O.js.map} +0 -0
  171. /package/dist/{chunk-4CKCRZDO.js.map → chunk-7ULZND56.js.map} +0 -0
  172. /package/dist/{chunk-34NCOTP5.js.map → chunk-7XELS325.js.map} +0 -0
  173. /package/dist/{chunk-GZ36UZJG.js.map → chunk-CFME47IY.js.map} +0 -0
  174. /package/dist/{chunk-BQTC6YGN.js.map → chunk-D5ENZR6N.js.map} +0 -0
  175. /package/dist/{chunk-ZRV7TOCX.js.map → chunk-G5AFQXXW.js.map} +0 -0
  176. /package/dist/{chunk-I4KBHRZW.js.map → chunk-LH2FAQTI.js.map} +0 -0
  177. /package/dist/{chunk-KFVEPQ7X.js.map → chunk-MVFHTUQ3.js.map} +0 -0
  178. /package/dist/{chunk-BAVDDAN3.js.map → chunk-MZKTOX6X.js.map} +0 -0
  179. /package/dist/{chunk-Q5KLE6ZF.js.map → chunk-OKPRQGFG.js.map} +0 -0
  180. /package/dist/{chunk-664YNAMR.js.map → chunk-RTFPKJJV.js.map} +0 -0
  181. /package/dist/{chunk-GKR44BCH.js.map → chunk-RVDXBHUG.js.map} +0 -0
  182. /package/dist/{chunk-A7Z27COA.js.map → chunk-VFWAZLEF.js.map} +0 -0
  183. /package/dist/{chunk-3EJNDIAO.js.map → chunk-VZ5B2SIO.js.map} +0 -0
  184. /package/dist/{cleanup-X556LNWM.js.map → cleanup-ELVHZPRC.js.map} +0 -0
  185. /package/dist/{cleanup-command-RV2Q7G72.js.map → cleanup-command-5BY4FWAT.js.map} +0 -0
  186. /package/dist/{clients-HP23HRN2.js.map → clients-WRPBRO6V.js.map} +0 -0
  187. /package/dist/{config-LUSJAK6B.js.map → config-TXJRCN3W.js.map} +0 -0
  188. /package/dist/{configured-paths-SPQ5XWOM.js.map → configured-paths-4CRHLBIF.js.map} +0 -0
  189. /package/dist/{contract-KEZY6YC3.js.map → contract-B7NDKO5B.js.map} +0 -0
  190. /package/dist/{corpus-PPOBTF4J.js.map → corpus-PUBXCCSN.js.map} +0 -0
  191. /package/dist/{doctor-DSC36R4U.js.map → doctor-RPXETRSJ.js.map} +0 -0
  192. /package/dist/{feature-directories-VOHVV26X.js.map → feature-directories-ASNDEM7J.js.map} +0 -0
  193. /package/dist/{finalization-7SMLMVLV.js.map → finalization-BGPOEBAL.js.map} +0 -0
  194. /package/dist/{gh-cli-ZMO7D2KE.js.map → gh-cli-OTX7PORT.js.map} +0 -0
  195. /package/dist/{github-rest-KGKS4V2H.js.map → github-rest-JCW32377.js.map} +0 -0
  196. /package/dist/{learning-sync-6BTHM6ZC.js.map → learning-sync-2WIAOTW3.js.map} +0 -0
  197. /package/dist/{legacy-global-guidance-2JL4Y5EB.js.map → legacy-global-guidance-WTDI53H7.js.map} +0 -0
  198. /package/dist/{lint-gherkin-COSHVS6Z.js.map → lint-gherkin-LS3CHW3J.js.map} +0 -0
  199. /package/dist/{operations-XVRQSLJG.js.map → operations-OZKEQCS4.js.map} +0 -0
  200. /package/dist/{output-3LB2OEHN.js.map → output-ZYTEW6SC.js.map} +0 -0
  201. /package/dist/{packet-5WT5M3OS.js.map → packet-7S6CZJQR.js.map} +0 -0
  202. /package/dist/{profile-UMKQ5JVO.js.map → profile-SDFSBKH5.js.map} +0 -0
  203. /package/dist/{prompt-GWYDFMLO.js.map → prompt-LYPODDC3.js.map} +0 -0
  204. /package/dist/{remove-E6HGNHOU.js.map → remove-P5JQNS5P.js.map} +0 -0
  205. /package/dist/{retro-IFSVU5KS.js.map → retro-EQK2Z2Z6.js.map} +0 -0
  206. /package/dist/{retro-draft-spool-4S3TCHWL.js.map → retro-draft-spool-MDPBVM6J.js.map} +0 -0
  207. /package/dist/{retro-extract-OSBFAXLU.js.map → retro-extract-CZMDD4WB.js.map} +0 -0
  208. /package/dist/{review-pr-6S2352CK.js.map → review-pr-CWRD446M.js.map} +0 -0
  209. /package/dist/{review-pr-publication-CGHCRRUI.js.map → review-pr-publication-AIX2U3VM.js.map} +0 -0
  210. /package/dist/{run-5JESKXUR.js.map → run-T45X6Z6C.js.map} +0 -0
  211. /package/dist/{self-report-VPHBPPSJ.js.map → self-report-V7PHKTS6.js.map} +0 -0
  212. /package/dist/{status-7VIGNTUI.js.map → status-F5TFEIZ6.js.map} +0 -0
  213. /package/dist/{status-EX4L3ELO.js.map → status-S24PDFXB.js.map} +0 -0
  214. /package/dist/{sync-config-O7J7IDGE.js.map → sync-config-EUVBPUUG.js.map} +0 -0
  215. /package/dist/{sync-tracker-U2GYOFUO.js.map → sync-tracker-5FSIIAMS.js.map} +0 -0
  216. /package/dist/{ticket-sync-LLUBXZ2R.js.map → ticket-sync-7QRH6PVT.js.map} +0 -0
  217. /package/dist/{tracker-map-HR4MBYG5.js.map → tracker-map-7AGCTWBZ.js.map} +0 -0
  218. /package/dist/{tracker-sync-YDICZN66.js.map → tracker-sync-MLVYGFQJ.js.map} +0 -0
@@ -1,15 +1,15 @@
1
1
  import {
2
2
  WORKSPACE_ROOTS
3
- } from "./chunk-Q5KLE6ZF.js";
3
+ } from "./chunk-OKPRQGFG.js";
4
4
  import {
5
5
  resolveConfiguredLaneDirectory,
6
6
  resolveTicketsDirectory,
7
7
  toRepoDirectory,
8
8
  toRepoPath
9
- } from "./chunk-GKR44BCH.js";
9
+ } from "./chunk-RVDXBHUG.js";
10
10
  import {
11
11
  readFileSafe
12
- } from "./chunk-S2T356DA.js";
12
+ } from "./chunk-HF3DXZEA.js";
13
13
 
14
14
  // src/utils/feature-source.ts
15
15
  import { existsSync, readdirSync } from "fs";
@@ -39,9 +39,12 @@ function slugForTicket(cwd, ticketFolder) {
39
39
  const dashIndex = ticketFolder.indexOf("-");
40
40
  return dashIndex === -1 ? ticketFolder : ticketFolder.slice(dashIndex + 1);
41
41
  }
42
- function findFeatureSourcePath(cwd, ticketFolder) {
43
- const fileName = `${slugForTicket(cwd, ticketFolder)}.feature`;
44
- return collectExecutableFeatureFiles(cwd, fileName)[0];
42
+ function featureSourceFileName(cwd, ticketFolder) {
43
+ return `${slugForTicket(cwd, ticketFolder)}.feature`;
44
+ }
45
+ function findFeatureSourcePath(cwd, ticketFolder, featureFiles = collectExecutableFeatureFiles(cwd)) {
46
+ const fileName = featureSourceFileName(cwd, ticketFolder);
47
+ return featureFiles.find((path) => nodePath.basename(path) === fileName);
45
48
  }
46
49
  function createPhaseAnchorEnvironment(cwd, configuredFeatures) {
47
50
  const featureRoots = ["features"];
@@ -124,6 +127,7 @@ function isMatchingFeatureFile(entryName, fileName) {
124
127
 
125
128
  export {
126
129
  readFrontmatterScalar,
130
+ featureSourceFileName,
127
131
  findFeatureSourcePath,
128
132
  createPhaseAnchorEnvironment,
129
133
  createPhaseAnchorScope,
@@ -131,4 +135,4 @@ export {
131
135
  hasDefaultExecutableFeatureFiles,
132
136
  collectExecutableFeatureDirectories
133
137
  };
134
- //# sourceMappingURL=chunk-USPVNZ63.js.map
138
+ //# sourceMappingURL=chunk-BZWO3BVD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/feature-source.ts","../src/utils/frontmatter.ts"],"sourcesContent":["import { existsSync, readdirSync } from 'node:fs';\nimport nodePath from 'node:path';\n\nimport type { PhaseAnchorScope } from '../../templates/hooks/lib/phase-provenance.js';\nimport { resolveConfiguredLaneDirectory, resolveTicketsDirectory } from './configured-paths.js';\nimport { readFrontmatterScalar } from './frontmatter.js';\nimport { readFileSafe } from './fs.js';\nimport { toRepoDirectory, toRepoPath } from './repo-path.js';\nimport { WORKSPACE_ROOTS } from './workspaces.js';\n\n/** Read the authoritative ticket slug, falling back for legacy folders. */\nfunction slugForTicket(cwd: string, ticketFolder: string): string {\n const content = readFileSafe(\n nodePath.join(resolveTicketsDirectory(cwd), ticketFolder, 'ticket.md'),\n );\n const slug = readFrontmatterScalar(content, 'slug');\n if (slug !== undefined && slug !== '') return slug;\n\n const dashIndex = ticketFolder.indexOf('-');\n return dashIndex === -1 ? ticketFolder : ticketFolder.slice(dashIndex + 1);\n}\n\n/**\n * Find the executable `.feature` file for a ticket by slug. The search mirrors\n * the Cucumber lane: all feature files under root `features/`, then under each\n * direct workspace package's `features/` directory in stable order.\n */\nexport function featureSourceFileName(cwd: string, ticketFolder: string): string {\n return `${slugForTicket(cwd, ticketFolder)}.feature`;\n}\n\nexport function findFeatureSourcePath(\n cwd: string,\n ticketFolder: string,\n featureFiles: readonly string[] = collectExecutableFeatureFiles(cwd),\n): string | undefined {\n const fileName = featureSourceFileName(cwd, ticketFolder);\n return featureFiles.find(path => nodePath.basename(path) === fileName);\n}\n\n/** Build the ticket-invariant feature ownership policy once per tree/config. */\nexport function createPhaseAnchorEnvironment(\n cwd: string,\n configuredFeatures?: string,\n): Omit<PhaseAnchorScope, 'ticketPath'> {\n const featureRoots = ['features'];\n if (configuredFeatures !== undefined) {\n const configuredRoot = toRepoDirectory(cwd, configuredFeatures);\n if (configuredRoot !== undefined && !featureRoots.includes(configuredRoot)) {\n featureRoots.push(configuredRoot);\n }\n }\n return { featureRoots, workspaceRoots: [...WORKSPACE_ROOTS] };\n}\n\n/** Build feature ownership policy without consulting feature-file existence. */\nexport function createPhaseAnchorScope(\n cwd: string,\n ticketPath: string,\n configuredFeatures?: string,\n): PhaseAnchorScope {\n return {\n ticketPath: toRepoPath(ticketPath),\n ...createPhaseAnchorEnvironment(cwd, configuredFeatures),\n };\n}\n\nexport function collectExecutableFeatureFiles(cwd: string, fileName?: string): string[] {\n return collectExecutableFeatureDirectories(cwd).flatMap(directory =>\n findFeatureFiles(directory, fileName),\n );\n}\n\n/** Whether Safeword's built-in feature locations already contain a real suite. */\nexport function hasDefaultExecutableFeatureFiles(cwd: string): boolean {\n return collectDefaultFeatureDirectories(cwd).some(directory => containsFeatureFile(directory));\n}\n\n/**\n * Return every directory that contributes executable feature files.\n *\n * Keep consumers that need feature-lane coverage (for example, audit checks)\n * on this shared enumeration rather than reconstructing workspace discovery.\n */\nexport function collectExecutableFeatureDirectories(cwd: string): string[] {\n const directories = collectDefaultFeatureDirectories(cwd);\n\n // paths.features AUGMENTS the defaults (ticket 56JCFZ) — relocated/host\n // lanes become readable without abandoning root features/.\n const configured = resolveConfiguredLaneDirectory(cwd, 'features');\n if (configured !== undefined && !directories.includes(configured)) {\n directories.push(configured);\n }\n return directories;\n}\n\nfunction collectDefaultFeatureDirectories(cwd: string): string[] {\n return [\n nodePath.join(cwd, 'features'),\n ...WORKSPACE_ROOTS.flatMap(root =>\n collectWorkspaceFeatureDirectories(nodePath.join(cwd, root)),\n ),\n ];\n}\n\nfunction collectWorkspaceFeatureDirectories(workspaceDirectory: string): string[] {\n if (!existsSync(workspaceDirectory)) return [];\n return readdirSync(workspaceDirectory, { withFileTypes: true })\n .filter(entry => entry.isDirectory())\n .map(entry => nodePath.join(workspaceDirectory, entry.name, 'features'));\n}\n\nfunction findFeatureFiles(directory: string, fileName?: string): string[] {\n let entries;\n try {\n entries = readdirSync(directory, { withFileTypes: true });\n } catch {\n return [];\n }\n\n const matches: string[] = [];\n for (const entry of entries) {\n const absolute = nodePath.join(directory, entry.name);\n if (entry.isDirectory()) {\n matches.push(...findFeatureFiles(absolute, fileName));\n } else if (entry.isFile() && isMatchingFeatureFile(entry.name, fileName)) {\n matches.push(absolute);\n }\n }\n return matches.toSorted((a, b) => a.localeCompare(b));\n}\n\nfunction containsFeatureFile(directory: string): boolean {\n let entries;\n try {\n entries = readdirSync(directory, { withFileTypes: true });\n } catch {\n return false;\n }\n return entries.some(entry => {\n const absolute = nodePath.join(directory, entry.name);\n return entry.isDirectory()\n ? containsFeatureFile(absolute)\n : entry.isFile() && entry.name.endsWith('.feature');\n });\n}\n\nfunction isMatchingFeatureFile(entryName: string, fileName: string | undefined): boolean {\n if (!entryName.endsWith('.feature')) return false;\n return fileName === undefined || entryName === fileName;\n}\n","/** Read one scalar field from a `---`-fenced YAML frontmatter block. */\nexport function readFrontmatterScalar(\n content: string | undefined,\n field: string,\n): string | undefined {\n const lines = content?.split(/\\r?\\n/) ?? [];\n if (lines[0] !== '---') return undefined;\n const prefix = `${field}:`;\n for (const line of lines.slice(1)) {\n if (line === '---') return undefined;\n if (!line.startsWith(prefix)) continue;\n const value = line.slice(prefix.length).trim();\n return value === '' ? undefined : value;\n }\n return undefined;\n}\n"],"mappings":"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","names":[]}
@@ -2,10 +2,10 @@ import {
2
2
  assertSafeClaudeCleanupTarget,
3
3
  containedClaudeCleanupPath,
4
4
  observeClaudeLegacy
5
- } from "./chunk-4CKCRZDO.js";
5
+ } from "./chunk-7ULZND56.js";
6
6
  import {
7
7
  canonicalClaudeProjectRoot
8
- } from "./chunk-34NCOTP5.js";
8
+ } from "./chunk-7XELS325.js";
9
9
  import {
10
10
  CLAUDE_MIGRATION_SCHEMA,
11
11
  advisoryStateDigest,
@@ -14,19 +14,19 @@ import {
14
14
  readClaudePluginMode,
15
15
  writeClaudeMigrationAttention,
16
16
  writeClaudePluginMode
17
- } from "./chunk-FDHJ63FZ.js";
17
+ } from "./chunk-WVA4Q6BX.js";
18
18
  import {
19
19
  cataloguedClaudeLegacyPaths,
20
20
  isAcceptedHistoricalFile,
21
21
  isAcceptedHistoricalHook
22
- } from "./chunk-NSZGI7HK.js";
22
+ } from "./chunk-HNCUIXDY.js";
23
23
  import {
24
24
  writeDurableFile,
25
25
  writeDurableFileExclusive
26
26
  } from "./chunk-QOLHGAD5.js";
27
27
  import {
28
28
  createResult
29
- } from "./chunk-V63TSN3C.js";
29
+ } from "./chunk-FOYGZ4MK.js";
30
30
 
31
31
  // src/claude-plugin/cleanup.ts
32
32
  import { createHash, randomUUID } from "crypto";
@@ -833,4 +833,4 @@ export {
833
833
  migrateClaudeLegacyAutomatically,
834
834
  recoverClaudeCleanup
835
835
  };
836
- //# sourceMappingURL=chunk-GZ36UZJG.js.map
836
+ //# sourceMappingURL=chunk-CFME47IY.js.map
@@ -1,7 +1,9 @@
1
1
  import {
2
- SAFEWORD_SCHEMA,
2
+ SAFEWORD_SCHEMA
3
+ } from "./chunk-IPTM5VE7.js";
4
+ import {
3
5
  parse
4
- } from "./chunk-BSOUMHMQ.js";
6
+ } from "./chunk-JSR3HXFT.js";
5
7
  import {
6
8
  CODEX_MIGRATION_SCHEMA
7
9
  } from "./chunk-AVAUUIDB.js";
@@ -120,10 +122,13 @@ import {
120
122
  constants as fsConstants,
121
123
  existsSync,
122
124
  fstatSync,
125
+ lstatSync as lstatSync2,
123
126
  openSync,
127
+ readdirSync,
124
128
  readFileSync as readFileSync2,
125
129
  readSync,
126
130
  realpathSync,
131
+ rmdirSync,
127
132
  rmSync
128
133
  } from "fs";
129
134
  import { homedir } from "os";
@@ -210,15 +215,17 @@ function observeCodexHostProcesses() {
210
215
  if (result.status !== 0) return { available: false, running: [], current: null };
211
216
  return codexHostsFromProcessTable(result.stdout, process.ppid);
212
217
  }
213
- function observeRunningCodexHosts() {
214
- const observation = observeCodexHostProcesses();
215
- return observation.available ? observation.running : null;
216
- }
217
218
  function sameCodexHost(left, right) {
218
219
  return left.pid === right.pid && left.started_at === right.started_at;
219
220
  }
220
221
 
221
222
  // src/codex-plugin/profile-proof.ts
223
+ var SHA256_PATTERN = /^[a-f\d]{64}$/u;
224
+ var NUMERIC_VERSION_PART_PATTERN = /^\d+$/u;
225
+ var VERSION_IDENTIFIER_PATTERN = /^[\dA-Za-z-]+$/u;
226
+ var MAX_PLUGIN_VERSION_LENGTH = 128;
227
+ var MAX_RETAINED_SESSION_PROOFS_PER_PROJECT = 256;
228
+ var MAX_RETAINED_SESSION_PROOF_PROJECTS = 64;
222
229
  var CODEX_PLUGIN_HOOK_EVENTS = [
223
230
  "session-start",
224
231
  "pre-tool-use",
@@ -227,7 +234,8 @@ var CODEX_PLUGIN_HOOK_EVENTS = [
227
234
  "stop"
228
235
  ];
229
236
  function codexProfileDirectory(environment = process.env) {
230
- return environment.CODEX_HOME ?? nodePath2.join(homedir(), ".codex");
237
+ const configuredHome = environment.CODEX_HOME?.trim();
238
+ return configuredHome || nodePath2.join(homedir(), ".codex");
231
239
  }
232
240
  function codexProofPath(environment = process.env, event = "session-start") {
233
241
  return nodePath2.join(
@@ -267,7 +275,13 @@ function legacyCodexRestartMarkerPath(environment = process.env) {
267
275
  return nodePath2.join(codexProfileDirectory(environment), "safeword/restart-pending-v1.json");
268
276
  }
269
277
  function codexActivationIsPending(environment = process.env) {
270
- const identity = currentCodexPluginIdentity();
278
+ try {
279
+ return activationIsPendingForIdentity(environment, currentCodexPluginIdentity());
280
+ } catch {
281
+ return true;
282
+ }
283
+ }
284
+ function activationIsPendingForIdentity(environment, identity) {
271
285
  return (
272
286
  // The canonical marker is written atomically. If it exists but cannot be
273
287
  // parsed or matched, fail closed instead of accepting unbound hook proof.
@@ -276,6 +290,23 @@ function codexActivationIsPending(environment = process.env) {
276
290
  )
277
291
  );
278
292
  }
293
+ function codexActivationMarkerIssue(environment = process.env) {
294
+ const markerPath = codexActivationMarkerPath(environment);
295
+ if (!existsSync(markerPath)) return void 0;
296
+ let identity;
297
+ try {
298
+ identity = currentCodexPluginIdentity();
299
+ } catch {
300
+ return "package-unavailable";
301
+ }
302
+ try {
303
+ const marker = readTrustedHookProofJson(markerPath);
304
+ if (!isActivationMarkerV2(marker)) return "malformed";
305
+ return matchesCodexPluginIdentity(marker, identity) ? void 0 : "identity-mismatch";
306
+ } catch {
307
+ return "malformed";
308
+ }
309
+ }
279
310
  function packagedHookManifestPath() {
280
311
  const candidates = [
281
312
  nodePath2.resolve(import.meta.dirname, "../codex-plugin/hooks.json"),
@@ -287,19 +318,25 @@ function packagedHookManifestPath() {
287
318
  }
288
319
  return manifest;
289
320
  }
290
- function currentCodexPluginIdentity() {
291
- const manifest = readFileSync2(packagedHookManifestPath());
292
- return {
293
- plugin_version: SAFEWORD_SCHEMA.version,
294
- manifest_sha256: createHash("sha256").update(manifest).digest("hex")
321
+ var currentCodexPluginIdentity = /* @__PURE__ */ (() => {
322
+ let cachedIdentity;
323
+ return () => {
324
+ cachedIdentity ??= (() => {
325
+ const manifest = readFileSync2(packagedHookManifestPath());
326
+ return {
327
+ plugin_version: SAFEWORD_SCHEMA.version,
328
+ manifest_sha256: createHash("sha256").update(manifest).digest("hex")
329
+ };
330
+ })();
331
+ return cachedIdentity;
295
332
  };
296
- }
333
+ })();
297
334
  function matchesCodexPluginIdentity(value, identity) {
298
335
  return value.plugin_version === identity.plugin_version && value.manifest_sha256 === identity.manifest_sha256;
299
336
  }
300
337
  function writeCodexActivationMarker(environment = process.env, now = /* @__PURE__ */ new Date(), options = {}) {
301
338
  const path = codexActivationMarkerPath(environment);
302
- const activeHosts = options.activeHosts === void 0 ? observeRunningCodexHosts() : options.activeHosts;
339
+ const activeHosts = activeHostsForMarker(options);
303
340
  const marker = {
304
341
  schema_version: 2,
305
342
  ...currentCodexPluginIdentity(),
@@ -315,15 +352,24 @@ function writeCodexActivationMarker(environment = process.env, now = /* @__PURE_
315
352
  recursive: true,
316
353
  force: true
317
354
  });
318
- rmSync(nodePath2.join(codexProfileDirectory(environment), "safeword/session-proof-v1"), {
319
- recursive: true,
320
- force: true
321
- });
322
355
  rmSync(codexActivationReceiptPath(environment), { force: true });
323
356
  rmSync(legacyCodexActivationMarkerPath(environment), { force: true });
324
357
  rmSync(legacyCodexRestartMarkerPath(environment), { force: true });
325
358
  return marker;
326
359
  }
360
+ function activeHostsForMarker(options) {
361
+ if (options.activeHosts !== void 0) {
362
+ return options.activeHosts === null || options.activeHosts.length === 0 ? null : options.activeHosts;
363
+ }
364
+ const observation = options.hostObservation ?? observeCodexHostProcesses();
365
+ if (!observation.available) return null;
366
+ const activeHosts = [...observation.running];
367
+ const currentHost = observation.current;
368
+ if (currentHost !== null && activeHosts.every((host) => !sameCodexHost(host, currentHost))) {
369
+ activeHosts.push(currentHost);
370
+ }
371
+ return activeHosts.length === 0 ? null : activeHosts;
372
+ }
327
373
  function hostObservationForOverride(current = null) {
328
374
  return {
329
375
  available: true,
@@ -333,7 +379,7 @@ function hostObservationForOverride(current = null) {
333
379
  }
334
380
  function activationReceiptForRestart(marker, hostObservation, identity, now) {
335
381
  const currentHost = hostObservation.current;
336
- if (!hostObservation.available || currentHost === null || marker.host_observation !== "observed" || now.getTime() < Date.parse(marker.installed_at) || marker.active_hosts.some(
382
+ if (!hostObservation.available || currentHost === null || marker.host_observation !== "observed" || marker.active_hosts.length === 0 || now.getTime() < Date.parse(marker.installed_at) || marker.active_hosts.some((installedHost) => sameCodexHost(installedHost, currentHost)) || marker.active_hosts.some(
337
383
  (installedHost) => hostObservation.running.some((runningHost) => sameCodexHost(installedHost, runningHost))
338
384
  )) {
339
385
  return null;
@@ -361,36 +407,174 @@ function recordCodexHookProof(event, environment = process.env, now = /* @__PURE
361
407
  }
362
408
  const projectDirectory = writeOptions.projectDirectory === void 0 ? void 0 : canonicalProjectDirectory(writeOptions.projectDirectory);
363
409
  const sessionId = writeOptions.sessionId?.trim();
364
- const proof = {
365
- schema_version: projectDirectory !== void 0 && sessionId ? 3 : 2,
410
+ const activationId = activeCodexActivationId(marker, receipt);
411
+ const recordedAt = now.toISOString();
412
+ const proof = projectDirectory !== void 0 && sessionId ? {
413
+ schema_version: 3,
414
+ event,
415
+ ...identity,
416
+ activation_id: activationId,
417
+ project_directory: projectDirectory,
418
+ session_id: sessionId,
419
+ recorded_at: recordedAt
420
+ } : {
421
+ schema_version: 2,
366
422
  event,
367
423
  ...identity,
368
- activation_id: receipt?.activation_id ?? marker?.activation_id ?? null,
369
- ...projectDirectory !== void 0 && sessionId && { project_directory: projectDirectory, session_id: sessionId },
370
- recorded_at: now.toISOString()
424
+ activation_id: activationId,
425
+ recorded_at: recordedAt
371
426
  };
372
427
  writeAtomicJson(codexProofPath(environment, event), proof, writeOptions);
373
428
  if (event === "session-start" && projectDirectory !== void 0 && sessionId) {
374
429
  const sessionProof = {
375
- schema_version: 1,
430
+ schema_version: 2,
376
431
  ...identity,
432
+ activation_id: activationId,
377
433
  project_directory: projectDirectory,
378
434
  session_id: sessionId,
379
- recorded_at: now.toISOString()
435
+ recorded_at: recordedAt
380
436
  };
381
- writeAtomicJson(codexSessionProofPath(projectDirectory, sessionId, environment), sessionProof);
437
+ const path = codexSessionProofPath(projectDirectory, sessionId, environment);
438
+ writeAtomicJson(path, sessionProof);
439
+ pruneCodexSessionProofs(path);
382
440
  }
383
441
  return proof;
384
442
  }
385
- function codexSessionProofIsCurrent(projectDirectory, sessionId, environment = process.env) {
443
+ function validSessionProof(value, canonicalProject, sessionId) {
444
+ if (typeof value !== "object" || value === null) return false;
445
+ const proof = value;
446
+ return sessionProofBindingMatches(proof, canonicalProject, sessionId) && sessionProofIdentityIsWellFormed(proof) && sessionProofTimeIsWellFormed(proof);
447
+ }
448
+ function sessionProofBindingMatches(proof, canonicalProject, sessionId) {
449
+ return (proof.schema_version === 1 || proof.schema_version === 2) && proof.project_directory === canonicalProject && proof.session_id === sessionId && (proof.schema_version === 1 || typeof proof.activation_id === "string" || proof.activation_id === null);
450
+ }
451
+ function sessionProofIdentityIsWellFormed(proof) {
452
+ return pluginVersionIsWellFormed(proof.plugin_version) && typeof proof.manifest_sha256 === "string" && SHA256_PATTERN.test(proof.manifest_sha256);
453
+ }
454
+ function numericVersionPartIsWellFormed(part) {
455
+ return part.length > 0 && NUMERIC_VERSION_PART_PATTERN.test(part) && (part === "0" || !part.startsWith("0"));
456
+ }
457
+ function versionIdentifiersAreWellFormed(value) {
458
+ return value === void 0 || value.split(".").every(
459
+ (identifier) => identifier.length > 0 ? VERSION_IDENTIFIER_PATTERN.test(identifier) : false
460
+ );
461
+ }
462
+ function pluginVersionIsWellFormed(value) {
463
+ if (typeof value !== "string" || value.length > MAX_PLUGIN_VERSION_LENGTH) return false;
464
+ const buildSplit = value.split("+");
465
+ if (buildSplit.length > 2) return false;
466
+ const coreAndPrerelease = buildSplit[0] ?? "";
467
+ const prereleaseSeparator = coreAndPrerelease.indexOf("-");
468
+ const coreText = prereleaseSeparator === -1 ? coreAndPrerelease : coreAndPrerelease.slice(0, prereleaseSeparator);
469
+ const prerelease = prereleaseSeparator === -1 ? void 0 : coreAndPrerelease.slice(prereleaseSeparator + 1);
470
+ const core = coreText.split(".");
471
+ return core.length === 3 && core.every((part) => numericVersionPartIsWellFormed(part)) && versionIdentifiersAreWellFormed(prerelease) && versionIdentifiersAreWellFormed(buildSplit[1]);
472
+ }
473
+ function sessionProofTimeIsWellFormed(proof) {
474
+ return typeof proof.recorded_at === "string" && !Number.isNaN(Date.parse(proof.recorded_at));
475
+ }
476
+ function classifySessionProof(proof, identity, marker, receipt, activationPending) {
477
+ if (proof.schema_version === 1) return "prior-observed";
478
+ if (!matchesCodexPluginIdentity(proof, identity)) return "prior-observed";
479
+ if (marker !== null && Date.parse(proof.recorded_at) < Date.parse(marker.installed_at)) {
480
+ return "prior-observed";
481
+ }
482
+ if (activationPending) return "activation-pending";
483
+ if (receipt === null) return proof.activation_id === null ? "current" : "prior-observed";
484
+ return proof.activation_id === receipt.activation_id && Date.parse(proof.recorded_at) >= Date.parse(receipt.activated_at) ? "current" : "prior-observed";
485
+ }
486
+ function observeCodexSessionProof(projectDirectory, sessionId, environment = process.env) {
386
487
  const canonicalProject = canonicalProjectDirectory(projectDirectory);
488
+ const path = codexSessionProofPath(canonicalProject, sessionId, environment);
489
+ if (!existsSync(path)) {
490
+ return {
491
+ status: "missing",
492
+ plugin_version: null,
493
+ manifest_sha256: null,
494
+ recorded_at: null
495
+ };
496
+ }
387
497
  try {
388
- const value = JSON.parse(
389
- readFileSync2(codexSessionProofPath(canonicalProject, sessionId, environment), "utf8")
498
+ const value = readTrustedHookProofJson(path);
499
+ if (!validSessionProof(value, canonicalProject, sessionId)) throw new Error("Invalid proof.");
500
+ const identity = currentCodexPluginIdentity();
501
+ const marker = readActivationMarkerV2(environment, identity);
502
+ const receipt = readActivationReceipt(environment, identity);
503
+ const status = classifySessionProof(
504
+ value,
505
+ identity,
506
+ marker,
507
+ receipt,
508
+ activationIsPendingForIdentity(environment, identity)
390
509
  );
391
- return value.schema_version === 1 && value.project_directory === canonicalProject && value.session_id === sessionId && typeof value.recorded_at === "string" && !Number.isNaN(Date.parse(value.recorded_at)) && matchesCodexPluginIdentity(value, currentCodexPluginIdentity());
510
+ return {
511
+ status,
512
+ plugin_version: value.plugin_version,
513
+ manifest_sha256: value.manifest_sha256,
514
+ recorded_at: value.recorded_at
515
+ };
392
516
  } catch {
393
- return false;
517
+ return {
518
+ status: "untrusted",
519
+ plugin_version: null,
520
+ manifest_sha256: null,
521
+ recorded_at: null
522
+ };
523
+ }
524
+ }
525
+ function sessionProofProjectDirectories(root) {
526
+ try {
527
+ return readdirSync(root, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.isSymbolicLink()).flatMap((projectEntry) => {
528
+ const path = nodePath2.join(root, projectEntry.name);
529
+ try {
530
+ if (sessionProofProjectFiles(path).length === 0) return [];
531
+ return [{ modifiedAt: lstatSync2(path).mtimeMs, path }];
532
+ } catch {
533
+ return [];
534
+ }
535
+ });
536
+ } catch {
537
+ return [];
538
+ }
539
+ }
540
+ function sessionProofProjectFiles(projectPath) {
541
+ try {
542
+ return readdirSync(projectPath, { withFileTypes: true }).flatMap((entry) => {
543
+ if (!entry.isFile() || entry.isSymbolicLink() || entry.name.startsWith(".") || !entry.name.endsWith(".json"))
544
+ return [];
545
+ const path = nodePath2.join(projectPath, entry.name);
546
+ try {
547
+ return [{ modifiedAt: lstatSync2(path).mtimeMs, path }];
548
+ } catch {
549
+ return [];
550
+ }
551
+ });
552
+ } catch {
553
+ return [];
554
+ }
555
+ }
556
+ function pruneCodexSessionProofs(retainedPath) {
557
+ const retainedProjectPath = nodePath2.dirname(retainedPath);
558
+ const files = sessionProofProjectFiles(retainedProjectPath).toSorted((left, right) => {
559
+ if (left.path === retainedPath) return -1;
560
+ if (right.path === retainedPath) return 1;
561
+ return right.modifiedAt - left.modifiedAt || left.path.localeCompare(right.path);
562
+ });
563
+ for (const stale of files.slice(MAX_RETAINED_SESSION_PROOFS_PER_PROJECT)) {
564
+ rmSync(stale.path, { force: true });
565
+ }
566
+ const root = nodePath2.dirname(retainedProjectPath);
567
+ const projects = sessionProofProjectDirectories(root).toSorted((left, right) => {
568
+ if (left.path === retainedProjectPath) return -1;
569
+ if (right.path === retainedProjectPath) return 1;
570
+ return right.modifiedAt - left.modifiedAt || left.path.localeCompare(right.path);
571
+ });
572
+ for (const stale of projects.slice(MAX_RETAINED_SESSION_PROOF_PROJECTS)) {
573
+ for (const proof of sessionProofProjectFiles(stale.path)) rmSync(proof.path, { force: true });
574
+ try {
575
+ rmdirSync(stale.path);
576
+ } catch {
577
+ }
394
578
  }
395
579
  }
396
580
  function writeAtomicJson(path, value, options = {}) {
@@ -423,10 +607,13 @@ function readActivationReceipt(environment, identity) {
423
607
  isActivationReceiptV1
424
608
  );
425
609
  }
610
+ function activeCodexActivationId(marker, receipt) {
611
+ return receipt?.activation_id ?? marker?.activation_id ?? null;
612
+ }
426
613
  function readIdentityBoundJson(path, identity, validator) {
427
614
  if (!existsSync(path)) return null;
428
615
  try {
429
- const value = JSON.parse(readFileSync2(path, "utf8"));
616
+ const value = readTrustedHookProofJson(path);
430
617
  return validator(value) && matchesCodexPluginIdentity(value, identity) ? value : null;
431
618
  } catch {
432
619
  return null;
@@ -442,8 +629,8 @@ function readEventProof(path, expectedEvent) {
442
629
  }
443
630
  var MAX_CODEX_HOOK_PROOF_BYTES = 64 * 1024;
444
631
  function assertTrustedHookProofMetadata(metadata) {
445
- const ownerMatches = process.getuid === void 0 || metadata.uid === process.getuid();
446
- if (!metadata.isFile() || metadata.nlink !== 1 || !ownerMatches || (metadata.mode & 63) !== 0 || metadata.size > MAX_CODEX_HOOK_PROOF_BYTES) {
632
+ const posixPermissionsAreTrusted = process.getuid === void 0 || metadata.uid === process.getuid() && (metadata.mode & 63) === 0;
633
+ if (!metadata.isFile() || metadata.nlink !== 1 || !posixPermissionsAreTrusted || metadata.size > MAX_CODEX_HOOK_PROOF_BYTES) {
447
634
  throw new Error("Untrusted Codex hook proof file.");
448
635
  }
449
636
  }
@@ -496,15 +683,25 @@ function observeCodexHookProof(environment = process.env, binding) {
496
683
  const parsed = existing.map((item) => readEventProof(item.path, item.event));
497
684
  if (parsed.includes(null)) return malformedObservation();
498
685
  const validProofs = parsed.filter((proof) => proof !== null);
499
- const identity = currentCodexPluginIdentity();
500
- const activationId = readActivationMarkerV2(environment, identity)?.activation_id ?? readActivationReceipt(environment, identity)?.activation_id ?? null;
686
+ let identity;
687
+ try {
688
+ identity = currentCodexPluginIdentity();
689
+ } catch {
690
+ return malformedObservation();
691
+ }
692
+ const marker = readActivationMarkerV2(environment, identity);
693
+ const receipt = readActivationReceipt(environment, identity);
694
+ const activationId = activeCodexActivationId(marker, receipt);
695
+ if (activationIsPendingForIdentity(environment, identity)) {
696
+ return staleHookObservation(validProofs, activationId);
697
+ }
501
698
  const canonicalProject = binding === void 0 ? void 0 : canonicalProjectDirectory(binding.projectDirectory);
502
699
  const current = validProofs.filter(
503
- (proof) => matchesCodexPluginIdentity(proof, identity) && (activationId === null || proof.schema_version !== 1 && proof.activation_id === activationId) && (binding === void 0 || proof.schema_version === 3 && proof.project_directory === canonicalProject && proof.session_id === binding.sessionId)
700
+ (proof) => matchesCodexPluginIdentity(proof, identity) && (activationId === null ? proof.activation_id === null : proof.activation_id === activationId) && (receipt === null || Date.parse(proof.recorded_at) >= Date.parse(receipt.activated_at)) && (binding === void 0 || proof.schema_version === 3 && proof.project_directory === canonicalProject && proof.session_id === binding.sessionId)
504
701
  );
505
702
  const events = current.map((proof) => proof.event);
506
703
  const missingEvents = CODEX_PLUGIN_HOOK_EVENTS.filter((event) => !events.includes(event));
507
- const latest = current.toSorted((left, right) => right.recorded_at.localeCompare(left.recorded_at)).at(0);
704
+ const latest = current.toSorted((left, right) => Date.parse(right.recorded_at) - Date.parse(left.recorded_at)).at(0);
508
705
  let status = "stale";
509
706
  if (current.length === CODEX_PLUGIN_HOOK_EVENTS.length) status = "current";
510
707
  else if (current.length > 0) status = "partial";
@@ -518,6 +715,17 @@ function observeCodexHookProof(environment = process.env, binding) {
518
715
  missing_events: missingEvents
519
716
  };
520
717
  }
718
+ function staleHookObservation(proofs, activationId) {
719
+ return {
720
+ status: "stale",
721
+ plugin_version: proofs[0]?.plugin_version ?? null,
722
+ manifest_sha256: proofs[0]?.manifest_sha256 ?? null,
723
+ recorded_at: null,
724
+ activation_id: activationId,
725
+ events: [],
726
+ missing_events: CODEX_PLUGIN_HOOK_EVENTS
727
+ };
728
+ }
521
729
  function malformedObservation() {
522
730
  return {
523
731
  status: "malformed",
@@ -532,17 +740,17 @@ function malformedObservation() {
532
740
  function isCodexHookProof(value) {
533
741
  if (typeof value !== "object" || value === null) return false;
534
742
  const proof = value;
535
- return [1, 2, 3].includes(proof.schema_version) && isCodexPluginHookEvent(proof.event) && typeof proof.plugin_version === "string" && typeof proof.manifest_sha256 === "string" && /^[\da-f]{64}$/u.test(proof.manifest_sha256) && (proof.schema_version === 1 || "activation_id" in proof && (typeof proof.activation_id === "string" || proof.activation_id === null)) && (proof.schema_version !== 3 || typeof proof.project_directory === "string" && typeof proof.session_id === "string" && proof.session_id.trim() !== "") && typeof proof.recorded_at === "string" && !Number.isNaN(Date.parse(proof.recorded_at));
743
+ return (proof.schema_version === 2 || proof.schema_version === 3) && isCodexPluginHookEvent(proof.event) && pluginVersionIsWellFormed(proof.plugin_version) && typeof proof.manifest_sha256 === "string" && SHA256_PATTERN.test(proof.manifest_sha256) && "activation_id" in proof && (typeof proof.activation_id === "string" || proof.activation_id === null) && (proof.schema_version !== 3 || typeof proof.project_directory === "string" && typeof proof.session_id === "string" && proof.session_id.trim() !== "") && typeof proof.recorded_at === "string" && !Number.isNaN(Date.parse(proof.recorded_at));
536
744
  }
537
745
  function isActivationMarkerV2(value) {
538
746
  if (typeof value !== "object" || value === null) return false;
539
747
  const marker = value;
540
- return marker.schema_version === 2 && typeof marker.plugin_version === "string" && typeof marker.manifest_sha256 === "string" && /^[\da-f]{64}$/u.test(marker.manifest_sha256) && typeof marker.activation_id === "string" && typeof marker.installed_at === "string" && !Number.isNaN(Date.parse(marker.installed_at)) && (marker.host_observation === "observed" || marker.host_observation === "unavailable") && Array.isArray(marker.active_hosts) && marker.active_hosts.every(isCodexHostProcessIdentity);
748
+ return marker.schema_version === 2 && pluginVersionIsWellFormed(marker.plugin_version) && typeof marker.manifest_sha256 === "string" && SHA256_PATTERN.test(marker.manifest_sha256) && typeof marker.activation_id === "string" && typeof marker.installed_at === "string" && !Number.isNaN(Date.parse(marker.installed_at)) && (marker.host_observation === "observed" || marker.host_observation === "unavailable") && Array.isArray(marker.active_hosts) && marker.active_hosts.every(isCodexHostProcessIdentity) && (marker.host_observation === "observed" ? marker.active_hosts.length > 0 : marker.active_hosts.length === 0);
541
749
  }
542
750
  function isActivationReceiptV1(value) {
543
751
  if (typeof value !== "object" || value === null) return false;
544
752
  const receipt = value;
545
- return receipt.schema_version === 1 && typeof receipt.plugin_version === "string" && typeof receipt.manifest_sha256 === "string" && /^[\da-f]{64}$/u.test(receipt.manifest_sha256) && typeof receipt.activation_id === "string" && typeof receipt.activated_at === "string" && !Number.isNaN(Date.parse(receipt.activated_at)) && isCodexHostProcessIdentity(receipt.host);
753
+ return receipt.schema_version === 1 && pluginVersionIsWellFormed(receipt.plugin_version) && typeof receipt.manifest_sha256 === "string" && SHA256_PATTERN.test(receipt.manifest_sha256) && typeof receipt.activation_id === "string" && typeof receipt.activated_at === "string" && !Number.isNaN(Date.parse(receipt.activated_at)) && isCodexHostProcessIdentity(receipt.host);
546
754
  }
547
755
  function isCodexHostProcessIdentity(value) {
548
756
  if (typeof value !== "object" || value === null) return false;
@@ -556,10 +764,12 @@ function isCodexPluginHookEvent(value) {
556
764
  export {
557
765
  legacyCommandIdentity,
558
766
  legacyCodexEventIsViable,
767
+ CODEX_PLUGIN_HOOK_EVENTS,
559
768
  codexActivationIsPending,
769
+ codexActivationMarkerIssue,
560
770
  writeCodexActivationMarker,
561
771
  recordCodexHookProof,
562
- codexSessionProofIsCurrent,
772
+ observeCodexSessionProof,
563
773
  observeCodexHookProof
564
774
  };
565
- //# sourceMappingURL=chunk-SME5SU3O.js.map
775
+ //# sourceMappingURL=chunk-CNBZ3S5U.js.map