safeword 0.75.0 → 0.76.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 (145) hide show
  1. package/codex-plugin/.codex-plugin/plugin.json +1 -1
  2. package/codex-plugin/hooks.json +5 -5
  3. package/dist/{architecture-KKL2LBNN.js → architecture-MLLLP2QN.js} +2 -2
  4. package/dist/{architecture-document-3IZGQHGU.js → architecture-document-OFBN5ZAO.js} +2 -2
  5. package/dist/{architecture-monorepo-RKR4FT2Y.js → architecture-monorepo-MYENEFSR.js} +2 -2
  6. package/dist/{boundary-4ETEMTY3.js → boundary-OWTJP7DH.js} +2 -2
  7. package/dist/{chunk-C7HR7OK2.js → chunk-2MBSWVT2.js} +164 -90
  8. package/dist/chunk-2MBSWVT2.js.map +1 -0
  9. package/dist/{chunk-Q6TQYWG5.js → chunk-37ZTQ3NL.js} +4 -4
  10. package/dist/{chunk-C7ITU7HB.js → chunk-6DKB2H5U.js} +2 -2
  11. package/dist/{chunk-C7ITU7HB.js.map → chunk-6DKB2H5U.js.map} +1 -1
  12. package/dist/{chunk-2CRRUMI2.js → chunk-7ASEPZRH.js} +64 -27
  13. package/dist/chunk-7ASEPZRH.js.map +1 -0
  14. package/dist/{chunk-DGZ4ULMX.js → chunk-AMGSSOTT.js} +2 -2
  15. package/dist/{chunk-S7RI3VQ3.js → chunk-BPQESZJI.js} +4 -4
  16. package/dist/{chunk-2LZCGPTG.js → chunk-BQTC6YGN.js} +2 -2
  17. package/dist/chunk-CFWDNJLQ.js +200 -0
  18. package/dist/chunk-CFWDNJLQ.js.map +1 -0
  19. package/dist/{chunk-SX7PUJAL.js → chunk-MQIOSUHE.js} +6 -6
  20. package/dist/{chunk-RZDWJVAU.js → chunk-PFVH6RET.js} +6 -5
  21. package/dist/{chunk-IQQO3URI.js → chunk-U5BIKVUL.js} +5 -2
  22. package/dist/chunk-U5BIKVUL.js.map +1 -0
  23. package/dist/{chunk-YURMDDJT.js → chunk-V63TSN3C.js} +13 -4
  24. package/dist/chunk-V63TSN3C.js.map +1 -0
  25. package/dist/{chunk-57TP2GNA.js → chunk-VN2SBPDX.js} +3 -3
  26. package/dist/{chunk-M7E4TRRB.js → chunk-WRUXO3WA.js} +2 -2
  27. package/dist/{chunk-TA5H73XP.js → chunk-XD26W4MT.js} +2 -2
  28. package/dist/{chunk-MYZGBQGQ.js → chunk-ZRV7TOCX.js} +2 -2
  29. package/dist/{cleanup-PUBXGIJJ.js → cleanup-YLTC244U.js} +4 -4
  30. package/dist/{cleanup-command-RASRW2DA.js → cleanup-command-4WXNQCMP.js} +8 -8
  31. package/dist/cli.js +111 -62
  32. package/dist/cli.js.map +1 -1
  33. package/dist/{clients-2IL3MBIE.js → clients-54SNTYLV.js} +2 -2
  34. package/dist/{codex-bootstrap-F3U7BELN.js → codex-bootstrap-3XVVIOJ6.js} +8 -8
  35. package/dist/{codex-hook-2WRPYIQZ.js → codex-hook-6NBOO6BO.js} +4 -4
  36. package/dist/{codify-IJTPTPML.js → codify-DYMOC3I6.js} +3 -3
  37. package/dist/{commands-MW27XF42.js → commands-DZ3YICLQ.js} +391 -68
  38. package/dist/commands-DZ3YICLQ.js.map +1 -0
  39. package/dist/{config-TRF23XJ7.js → config-56DVFORI.js} +2 -2
  40. package/dist/{configured-paths-673TQWOO.js → configured-paths-FKHX74N4.js} +2 -2
  41. package/dist/{contract-VREO5Z5I.js → contract-ARHWUCBL.js} +2 -2
  42. package/dist/contract-ARHWUCBL.js.map +1 -0
  43. package/dist/{coordinator-CKC6MZZ7.js → coordinator-YNT636VE.js} +95 -204
  44. package/dist/coordinator-YNT636VE.js.map +1 -0
  45. package/dist/{corpus-W2XWU3V5.js → corpus-32QZRB5Z.js} +2 -2
  46. package/dist/{doctor-VVSMO463.js → doctor-JK7TUG5R.js} +9 -9
  47. package/dist/{feature-directories-BDUSAF25.js → feature-directories-5YGBVQFK.js} +2 -2
  48. package/dist/{finalization-DBQYEMSH.js → finalization-YZ3MACEB.js} +2 -2
  49. package/dist/{gh-cli-6RRODYFX.js → gh-cli-TCMPLTFK.js} +2 -2
  50. package/dist/{github-rest-FBK75SE2.js → github-rest-ONSGEF2E.js} +2 -2
  51. package/dist/index.js +1 -1
  52. package/dist/{learning-sync-QYZRE2F5.js → learning-sync-7E6WZGQ5.js} +2 -2
  53. package/dist/{legacy-global-guidance-YOLALLBG.js → legacy-global-guidance-SQJFZAKL.js} +5 -5
  54. package/dist/{lint-gherkin-5HZD7JNM.js → lint-gherkin-JE2I4DB7.js} +3 -3
  55. package/dist/{operations-3PLVK2ME.js → operations-3VCNJMVX.js} +8 -8
  56. package/dist/{output-ENM6VQ46.js → output-IPI7PJMA.js} +2 -2
  57. package/dist/packet-3XZYADS3.js +10 -0
  58. package/dist/presets/typescript/index.js +1 -1
  59. package/dist/{profile-6JFE7CN4.js → profile-SZMHCSBZ.js} +5 -5
  60. package/dist/{prompt-QRDN7OOW.js → prompt-LUTZ27E4.js} +2 -2
  61. package/dist/{remove-W7K627WA.js → remove-QRMVWCJJ.js} +7 -7
  62. package/dist/{retro-DSAE2JYV.js → retro-FZH6ERN2.js} +8 -8
  63. package/dist/{retro-draft-spool-CPVRVMDV.js → retro-draft-spool-7ZAURNYG.js} +2 -2
  64. package/dist/{retro-extract-ECWXSFML.js → retro-extract-OMAIIR5F.js} +2 -2
  65. package/dist/{review-pr-CDSF4ONC.js → review-pr-MO66DIXC.js} +2 -2
  66. package/dist/{review-pr-publication-IAA74JYT.js → review-pr-publication-64TR3FDG.js} +2 -2
  67. package/dist/{run-EXSRQCIU.js → run-DTVISMLB.js} +2 -2
  68. package/dist/{self-report-S2WIBZJY.js → self-report-LTYM6EWR.js} +2 -2
  69. package/dist/{status-6PGYYP3X.js → status-ECVVBPEH.js} +6 -6
  70. package/dist/{status-KNZRPSOF.js → status-MYY4XKEA.js} +9 -9
  71. package/dist/{sync-config-NPPOTR67.js → sync-config-3TDU5CY6.js} +2 -2
  72. package/dist/{sync-tracker-2D2POJU5.js → sync-tracker-7DFOGVOT.js} +2 -2
  73. package/dist/{test-execution-VRCJRWPU.js → test-execution-6ZJQQLYS.js} +3 -3
  74. package/dist/{test-plan-2UFKKWIZ.js → test-plan-R63XUAFZ.js} +3 -3
  75. package/dist/{ticket-new-KCEJV2IZ.js → ticket-new-M4UL4XP4.js} +3 -3
  76. package/dist/{ticket-sync-L3S4TGCW.js → ticket-sync-5R7VRCG5.js} +2 -2
  77. package/dist/{tracker-map-FSNOURGV.js → tracker-map-KFPGE4HB.js} +2 -2
  78. package/dist/{tracker-sync-4I7TAH5I.js → tracker-sync-KAVU4N7T.js} +2 -2
  79. package/dist/tracker-sync-KAVU4N7T.js.map +1 -0
  80. package/package.json +2 -2
  81. package/templates/SAFEWORD.md +2 -0
  82. package/templates/cucumber/cucumber.mjs +1 -1
  83. package/templates/hooks/lib/quality.ts +289 -57
  84. package/templates/hooks/session-version.ts +1 -1
  85. package/templates/hooks/stop-quality.ts +123 -27
  86. package/dist/chunk-2CRRUMI2.js.map +0 -1
  87. package/dist/chunk-C7HR7OK2.js.map +0 -1
  88. package/dist/chunk-IQQO3URI.js.map +0 -1
  89. package/dist/chunk-YURMDDJT.js.map +0 -1
  90. package/dist/commands-MW27XF42.js.map +0 -1
  91. package/dist/contract-VREO5Z5I.js.map +0 -1
  92. package/dist/coordinator-CKC6MZZ7.js.map +0 -1
  93. /package/dist/{architecture-KKL2LBNN.js.map → architecture-MLLLP2QN.js.map} +0 -0
  94. /package/dist/{architecture-document-3IZGQHGU.js.map → architecture-document-OFBN5ZAO.js.map} +0 -0
  95. /package/dist/{architecture-monorepo-RKR4FT2Y.js.map → architecture-monorepo-MYENEFSR.js.map} +0 -0
  96. /package/dist/{boundary-4ETEMTY3.js.map → boundary-OWTJP7DH.js.map} +0 -0
  97. /package/dist/{chunk-Q6TQYWG5.js.map → chunk-37ZTQ3NL.js.map} +0 -0
  98. /package/dist/{chunk-DGZ4ULMX.js.map → chunk-AMGSSOTT.js.map} +0 -0
  99. /package/dist/{chunk-S7RI3VQ3.js.map → chunk-BPQESZJI.js.map} +0 -0
  100. /package/dist/{chunk-2LZCGPTG.js.map → chunk-BQTC6YGN.js.map} +0 -0
  101. /package/dist/{chunk-SX7PUJAL.js.map → chunk-MQIOSUHE.js.map} +0 -0
  102. /package/dist/{chunk-RZDWJVAU.js.map → chunk-PFVH6RET.js.map} +0 -0
  103. /package/dist/{chunk-57TP2GNA.js.map → chunk-VN2SBPDX.js.map} +0 -0
  104. /package/dist/{chunk-M7E4TRRB.js.map → chunk-WRUXO3WA.js.map} +0 -0
  105. /package/dist/{chunk-TA5H73XP.js.map → chunk-XD26W4MT.js.map} +0 -0
  106. /package/dist/{chunk-MYZGBQGQ.js.map → chunk-ZRV7TOCX.js.map} +0 -0
  107. /package/dist/{cleanup-PUBXGIJJ.js.map → cleanup-YLTC244U.js.map} +0 -0
  108. /package/dist/{cleanup-command-RASRW2DA.js.map → cleanup-command-4WXNQCMP.js.map} +0 -0
  109. /package/dist/{clients-2IL3MBIE.js.map → clients-54SNTYLV.js.map} +0 -0
  110. /package/dist/{codex-bootstrap-F3U7BELN.js.map → codex-bootstrap-3XVVIOJ6.js.map} +0 -0
  111. /package/dist/{codex-hook-2WRPYIQZ.js.map → codex-hook-6NBOO6BO.js.map} +0 -0
  112. /package/dist/{codify-IJTPTPML.js.map → codify-DYMOC3I6.js.map} +0 -0
  113. /package/dist/{config-TRF23XJ7.js.map → config-56DVFORI.js.map} +0 -0
  114. /package/dist/{configured-paths-673TQWOO.js.map → configured-paths-FKHX74N4.js.map} +0 -0
  115. /package/dist/{corpus-W2XWU3V5.js.map → corpus-32QZRB5Z.js.map} +0 -0
  116. /package/dist/{doctor-VVSMO463.js.map → doctor-JK7TUG5R.js.map} +0 -0
  117. /package/dist/{feature-directories-BDUSAF25.js.map → feature-directories-5YGBVQFK.js.map} +0 -0
  118. /package/dist/{finalization-DBQYEMSH.js.map → finalization-YZ3MACEB.js.map} +0 -0
  119. /package/dist/{gh-cli-6RRODYFX.js.map → gh-cli-TCMPLTFK.js.map} +0 -0
  120. /package/dist/{github-rest-FBK75SE2.js.map → github-rest-ONSGEF2E.js.map} +0 -0
  121. /package/dist/{learning-sync-QYZRE2F5.js.map → learning-sync-7E6WZGQ5.js.map} +0 -0
  122. /package/dist/{legacy-global-guidance-YOLALLBG.js.map → legacy-global-guidance-SQJFZAKL.js.map} +0 -0
  123. /package/dist/{lint-gherkin-5HZD7JNM.js.map → lint-gherkin-JE2I4DB7.js.map} +0 -0
  124. /package/dist/{operations-3PLVK2ME.js.map → operations-3VCNJMVX.js.map} +0 -0
  125. /package/dist/{output-ENM6VQ46.js.map → output-IPI7PJMA.js.map} +0 -0
  126. /package/dist/{profile-6JFE7CN4.js.map → packet-3XZYADS3.js.map} +0 -0
  127. /package/dist/{prompt-QRDN7OOW.js.map → profile-SZMHCSBZ.js.map} +0 -0
  128. /package/dist/{remove-W7K627WA.js.map → prompt-LUTZ27E4.js.map} +0 -0
  129. /package/dist/{retro-draft-spool-CPVRVMDV.js.map → remove-QRMVWCJJ.js.map} +0 -0
  130. /package/dist/{retro-DSAE2JYV.js.map → retro-FZH6ERN2.js.map} +0 -0
  131. /package/dist/{retro-extract-ECWXSFML.js.map → retro-draft-spool-7ZAURNYG.js.map} +0 -0
  132. /package/dist/{self-report-S2WIBZJY.js.map → retro-extract-OMAIIR5F.js.map} +0 -0
  133. /package/dist/{review-pr-CDSF4ONC.js.map → review-pr-MO66DIXC.js.map} +0 -0
  134. /package/dist/{review-pr-publication-IAA74JYT.js.map → review-pr-publication-64TR3FDG.js.map} +0 -0
  135. /package/dist/{run-EXSRQCIU.js.map → run-DTVISMLB.js.map} +0 -0
  136. /package/dist/{status-6PGYYP3X.js.map → self-report-LTYM6EWR.js.map} +0 -0
  137. /package/dist/{status-KNZRPSOF.js.map → status-ECVVBPEH.js.map} +0 -0
  138. /package/dist/{sync-config-NPPOTR67.js.map → status-MYY4XKEA.js.map} +0 -0
  139. /package/dist/{ticket-sync-L3S4TGCW.js.map → sync-config-3TDU5CY6.js.map} +0 -0
  140. /package/dist/{sync-tracker-2D2POJU5.js.map → sync-tracker-7DFOGVOT.js.map} +0 -0
  141. /package/dist/{test-execution-VRCJRWPU.js.map → test-execution-6ZJQQLYS.js.map} +0 -0
  142. /package/dist/{test-plan-2UFKKWIZ.js.map → test-plan-R63XUAFZ.js.map} +0 -0
  143. /package/dist/{ticket-new-KCEJV2IZ.js.map → ticket-new-M4UL4XP4.js.map} +0 -0
  144. /package/dist/{tracker-map-FSNOURGV.js.map → ticket-sync-5R7VRCG5.js.map} +0 -0
  145. /package/dist/{tracker-sync-4I7TAH5I.js.map → tracker-map-KFPGE4HB.js.map} +0 -0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "safeword",
3
- "version": "0.75.0",
3
+ "version": "0.76.0",
4
4
  "description": "Safeword workflow guidance and gates for Codex.",
5
5
  "author": {
6
6
  "name": "Arcade AI"
@@ -6,7 +6,7 @@
6
6
  "hooks": [
7
7
  {
8
8
  "type": "command",
9
- "command": "bunx --bun safeword@0.75.0 hook codex session-start --plugin-hook",
9
+ "command": "bunx --bun safeword@0.76.0 hook codex session-start --plugin-hook",
10
10
  "timeout": 120,
11
11
  "statusMessage": "Loading Safeword standing instructions"
12
12
  }
@@ -19,7 +19,7 @@
19
19
  "hooks": [
20
20
  {
21
21
  "type": "command",
22
- "command": "bunx --bun safeword@0.75.0 hook codex pre-tool-use --plugin-hook",
22
+ "command": "bunx --bun safeword@0.76.0 hook codex pre-tool-use --plugin-hook",
23
23
  "timeout": 30,
24
24
  "statusMessage": "Checking Safeword edit gates"
25
25
  }
@@ -32,7 +32,7 @@
32
32
  "hooks": [
33
33
  {
34
34
  "type": "command",
35
- "command": "bunx --bun safeword@0.75.0 hook codex post-tool-use --plugin-hook",
35
+ "command": "bunx --bun safeword@0.76.0 hook codex post-tool-use --plugin-hook",
36
36
  "timeout": 30,
37
37
  "statusMessage": "Surfacing Safeword post-tool context"
38
38
  }
@@ -45,7 +45,7 @@
45
45
  "hooks": [
46
46
  {
47
47
  "type": "command",
48
- "command": "bunx --bun safeword@0.75.0 hook codex user-prompt-submit --plugin-hook",
48
+ "command": "bunx --bun safeword@0.76.0 hook codex user-prompt-submit --plugin-hook",
49
49
  "timeout": 30,
50
50
  "statusMessage": "Checking queued Safeword prompt context"
51
51
  }
@@ -58,7 +58,7 @@
58
58
  "hooks": [
59
59
  {
60
60
  "type": "command",
61
- "command": "bunx --bun safeword@0.75.0 hook codex stop --plugin-hook",
61
+ "command": "bunx --bun safeword@0.76.0 hook codex stop --plugin-hook",
62
62
  "timeout": 600,
63
63
  "statusMessage": "Checking Safeword stop continuation"
64
64
  }
@@ -25,7 +25,7 @@ import {
25
25
  readJson,
26
26
  writeJson
27
27
  } from "./chunk-7G6OSNIV.js";
28
- import "./chunk-C7ITU7HB.js";
28
+ import "./chunk-6DKB2H5U.js";
29
29
 
30
30
  // src/commands/architecture.ts
31
31
  import { execFileSync } from "child_process";
@@ -650,4 +650,4 @@ export {
650
650
  architectureStage,
651
651
  architectureStaged
652
652
  };
653
- //# sourceMappingURL=architecture-KKL2LBNN.js.map
653
+ //# sourceMappingURL=architecture-MLLLP2QN.js.map
@@ -11,7 +11,7 @@ import {
11
11
  import "./chunk-YP2V3YUV.js";
12
12
  import "./chunk-2MI7M2CO.js";
13
13
  import "./chunk-7G6OSNIV.js";
14
- import "./chunk-C7ITU7HB.js";
14
+ import "./chunk-6DKB2H5U.js";
15
15
  export {
16
16
  isSafewordOwned,
17
17
  isWouldChangeAction,
@@ -22,4 +22,4 @@ export {
22
22
  selfHealProject,
23
23
  selfHealProjectPreservingProse
24
24
  };
25
- //# sourceMappingURL=architecture-document-3IZGQHGU.js.map
25
+ //# sourceMappingURL=architecture-document-OFBN5ZAO.js.map
@@ -5,11 +5,11 @@ import {
5
5
  monorepoFingerprintOf
6
6
  } from "./chunk-YP2V3YUV.js";
7
7
  import "./chunk-7G6OSNIV.js";
8
- import "./chunk-C7ITU7HB.js";
8
+ import "./chunk-6DKB2H5U.js";
9
9
  export {
10
10
  discoverUnreadableWorkspaces,
11
11
  discoverWorkspaces,
12
12
  extractMonorepoArchitectureSnapshot,
13
13
  monorepoFingerprintOf
14
14
  };
15
- //# sourceMappingURL=architecture-monorepo-RKR4FT2Y.js.map
15
+ //# sourceMappingURL=architecture-monorepo-MYENEFSR.js.map
@@ -25,7 +25,7 @@ import {
25
25
  import {
26
26
  readFileSafe
27
27
  } from "./chunk-7G6OSNIV.js";
28
- import "./chunk-C7ITU7HB.js";
28
+ import "./chunk-6DKB2H5U.js";
29
29
 
30
30
  // src/commands/boundary.ts
31
31
  import { execFileSync as execFileSync2 } from "child_process";
@@ -628,4 +628,4 @@ function boundary(options) {
628
628
  export {
629
629
  boundary
630
630
  };
631
- //# sourceMappingURL=boundary-4ETEMTY3.js.map
631
+ //# sourceMappingURL=boundary-OWTJP7DH.js.map
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-URVJZ4NL.js";
5
5
  import {
6
6
  setupPythonTooling
7
- } from "./chunk-IQQO3URI.js";
7
+ } from "./chunk-U5BIKVUL.js";
8
8
  import {
9
9
  SAFEWORD_PRIMARY_LINT_SCRIPT,
10
10
  detect
@@ -876,69 +876,17 @@ function shouldRemoveTextPatchTarget(content, definition) {
876
876
  return definition.removeFileIfContentEquals?.some((candidate) => trimmed === candidate.trim()) ?? false;
877
877
  }
878
878
 
879
- // src/packs/config.ts
880
- import nodePath2 from "path";
881
- import process from "process";
882
- var CONFIG_PATH = ".safeword/config.json";
883
- function readConfig(cwd) {
884
- const configPath = nodePath2.join(cwd, CONFIG_PATH);
885
- const content = readFileSafe(configPath);
886
- if (!content) return void 0;
887
- return JSON.parse(content);
888
- }
889
- function writeConfig(cwd, config) {
890
- const configPath = nodePath2.join(cwd, CONFIG_PATH);
891
- writeFile(configPath, JSON.stringify(config, void 0, 2));
892
- }
893
- function getInstalledPacks(cwd) {
894
- const config = readConfig(cwd);
895
- return config?.installedPacks ?? [];
896
- }
897
- function isPackInstalled(cwd, packId) {
898
- return getInstalledPacks(cwd).includes(packId);
899
- }
900
- function addInstalledPack(cwd, packId) {
901
- const config = readConfig(cwd) ?? { installedPacks: [] };
902
- if (!config.installedPacks.includes(packId)) {
903
- config.installedPacks.push(packId);
904
- writeConfig(cwd, config);
905
- }
906
- }
907
-
908
- // src/packs/golang/setup.ts
909
- function setupGoTooling() {
910
- return { files: [] };
911
- }
912
-
913
- // src/packs/golang/index.ts
914
- var golangPack = {
915
- id: "golang",
916
- name: "Go",
917
- extensions: [".go"],
918
- detect(cwd) {
919
- return existsInTree(cwd, "go.mod");
920
- },
921
- setup(_cwd, _ctx) {
922
- return setupGoTooling();
923
- }
924
- };
925
-
926
- // src/packs/python/index.ts
927
- var pythonPack = {
928
- id: "python",
929
- name: "Python",
930
- extensions: [".py", ".pyi"],
931
- detect(cwd) {
932
- return existsInTree(cwd, "pyproject.toml") || existsInTree(cwd, "requirements.txt");
933
- },
934
- setup(_cwd, _ctx) {
935
- return setupPythonTooling();
936
- }
937
- };
938
-
939
879
  // src/packs/rust/setup.ts
940
- import { existsSync, readdirSync, readFileSync, writeFileSync } from "fs";
941
- import nodePath3 from "path";
880
+ import {
881
+ existsSync,
882
+ lstatSync as lstatSync2,
883
+ readdirSync,
884
+ readFileSync,
885
+ realpathSync,
886
+ writeFileSync
887
+ } from "fs";
888
+ import nodePath2 from "path";
889
+ import { parse } from "smol-toml";
942
890
  var SAFEWORD_CLIPPY_LINTS = `[lints.clippy]
943
891
  # Enable pedantic for stricter linting (priority -1 allows individual overrides)
944
892
  pedantic = { level = "deny", priority = -1 }
@@ -957,6 +905,25 @@ module_name_repetitions = "allow"
957
905
  # Deny unsafe code by default (LLMs shouldn't write unsafe)
958
906
  unsafe_code = "deny"
959
907
  `;
908
+ function isContainedPath(root, candidate) {
909
+ const relative = nodePath2.relative(root, candidate);
910
+ return relative === "" || !relative.startsWith(`..${nodePath2.sep}`) && !nodePath2.isAbsolute(relative);
911
+ }
912
+ function containedWorkspaceMember(cwd, member) {
913
+ if (nodePath2.isAbsolute(member)) return void 0;
914
+ const root = nodePath2.resolve(cwd);
915
+ const candidate = nodePath2.resolve(root, member);
916
+ if (!isContainedPath(root, candidate)) return void 0;
917
+ if (existsSync(candidate) && !isContainedPath(realpathSync(root), realpathSync(candidate))) {
918
+ return void 0;
919
+ }
920
+ return nodePath2.relative(root, candidate);
921
+ }
922
+ function isSafeCargoManifest(cwd, cargoPath) {
923
+ if (!existsSync(cargoPath)) return false;
924
+ const stat = lstatSync2(cargoPath);
925
+ return stat.isFile() && !stat.isSymbolicLink() && isContainedPath(realpathSync(cwd), realpathSync(cargoPath));
926
+ }
960
927
  var SAFEWORD_WORKSPACE_LINTS = `[workspace.lints.clippy]
961
928
  # Enable pedantic for stricter linting (priority -1 allows individual overrides)
962
929
  pedantic = { level = "deny", priority = -1 }
@@ -976,53 +943,97 @@ module_name_repetitions = "allow"
976
943
  unsafe_code = "deny"
977
944
  `;
978
945
  function detectWorkspaceType(cargoContent) {
979
- const hasWorkspace = cargoContent.includes("[workspace]");
980
- const hasPackage = cargoContent.includes("[package]");
946
+ const hasWorkspace = hasExactTableHeader(cargoContent, "workspace");
947
+ const hasPackage = hasExactTableHeader(cargoContent, "package");
981
948
  if (hasWorkspace && !hasPackage) return "virtual";
982
949
  if (hasWorkspace && hasPackage) return "root-package";
983
950
  return "single-crate";
984
951
  }
952
+ function hasExactTableHeader(content, table) {
953
+ let multilineDelimiter;
954
+ const header = `[${table}]`;
955
+ for (const line of content.split(/\r?\n/u)) {
956
+ if (multilineDelimiter !== void 0) {
957
+ if (hasOddOccurrences(line, multilineDelimiter)) multilineDelimiter = void 0;
958
+ continue;
959
+ }
960
+ const trimmed = line.trim();
961
+ if (trimmed === header || trimmed.startsWith(`${header} #`)) return true;
962
+ multilineDelimiter = openingMultilineDelimiter(line);
963
+ }
964
+ return false;
965
+ }
966
+ function hasOddOccurrences(line, delimiter) {
967
+ return line.split(delimiter).length % 2 === 0;
968
+ }
969
+ function openingMultilineDelimiter(line) {
970
+ const candidates = ['"""', "'''"];
971
+ return candidates.filter((delimiter) => hasOddOccurrences(line, delimiter)).toSorted((left, right) => line.indexOf(left) - line.indexOf(right))[0];
972
+ }
985
973
  function hasExistingLints(cargoContent) {
986
- return cargoContent.includes("[lints.clippy]") || cargoContent.includes("[lints.rust]") || cargoContent.includes("[lints]") || cargoContent.includes("[workspace.lints.clippy]") || cargoContent.includes("[workspace.lints.rust]");
974
+ const manifest = parseCargoManifest(cargoContent);
975
+ const workspace = isTomlTable(manifest?.workspace) ? manifest.workspace : void 0;
976
+ return isTomlTable(manifest?.lints) || isTomlTable(workspace?.lints);
977
+ }
978
+ function isTomlTable(value) {
979
+ return typeof value === "object" && value !== null && !Array.isArray(value);
980
+ }
981
+ function parseCargoManifest(content) {
982
+ try {
983
+ const manifest = parse(content);
984
+ return isTomlTable(manifest) ? manifest : void 0;
985
+ } catch {
986
+ return void 0;
987
+ }
988
+ }
989
+ function rustToolingTargets(cwd) {
990
+ const rootTarget = "Cargo.toml";
991
+ const cargoPath = nodePath2.join(cwd, rootTarget);
992
+ if (!isSafeCargoManifest(cwd, cargoPath)) return [];
993
+ const content = readFileSync(cargoPath, "utf8");
994
+ const targets = hasExistingLints(content) ? [] : [rootTarget];
995
+ if (detectWorkspaceType(content) === "single-crate") return targets;
996
+ for (const member of parseWorkspaceMembers(content, cwd)) {
997
+ const target = nodePath2.join(member, "Cargo.toml");
998
+ const memberPath = nodePath2.join(cwd, target);
999
+ if (!isSafeCargoManifest(cwd, memberPath)) continue;
1000
+ if (!hasExistingLints(readFileSync(memberPath, "utf8"))) targets.push(target);
1001
+ }
1002
+ return targets;
987
1003
  }
988
1004
  function expandMemberPattern(cwd, pattern) {
989
1005
  if (pattern.endsWith("/*")) {
990
- const baseDirectory = pattern.slice(0, -2);
991
- const fullPath = nodePath3.join(cwd, baseDirectory);
1006
+ const baseDirectory = containedWorkspaceMember(cwd, pattern.slice(0, -2));
1007
+ if (baseDirectory === void 0) return [];
1008
+ const fullPath = nodePath2.join(cwd, baseDirectory);
992
1009
  if (!existsSync(fullPath)) return [];
993
1010
  try {
994
1011
  const entries = readdirSync(fullPath, { withFileTypes: true });
995
1012
  return entries.filter((entry) => {
996
1013
  if (!entry.isDirectory()) return false;
997
- const cargoPath = nodePath3.join(fullPath, entry.name, "Cargo.toml");
1014
+ const cargoPath = nodePath2.join(fullPath, entry.name, "Cargo.toml");
998
1015
  return existsSync(cargoPath);
999
- }).map((entry) => nodePath3.join(baseDirectory, entry.name));
1016
+ }).map((entry) => nodePath2.join(baseDirectory, entry.name));
1000
1017
  } catch {
1001
1018
  return [];
1002
1019
  }
1003
1020
  }
1004
- return [pattern];
1021
+ const member = containedWorkspaceMember(cwd, pattern);
1022
+ return member === void 0 ? [] : [member];
1005
1023
  }
1006
1024
  function parseWorkspaceMembers(cargoContent, cwd) {
1007
- const membersMatch = /\[workspace\][^[]*members\s*=\s*\[([\s\S]*?)\]/.exec(cargoContent);
1008
- if (!membersMatch?.[1]) return [];
1009
- const membersBlock = membersMatch[1];
1010
- const rawMembers = [];
1011
- const stringRegex = /"([^"]+)"/g;
1012
- let match;
1013
- while ((match = stringRegex.exec(membersBlock)) !== null) {
1014
- if (match[1]) {
1015
- rawMembers.push(match[1]);
1016
- }
1017
- }
1025
+ const manifest = parseCargoManifest(cargoContent);
1026
+ const members = isTomlTable(manifest?.workspace) ? manifest.workspace.members : void 0;
1027
+ if (!Array.isArray(members)) return [];
1028
+ const rawMembers = members.filter((member) => typeof member === "string");
1018
1029
  const expandedMembers = [];
1019
1030
  for (const member of rawMembers) {
1020
1031
  expandedMembers.push(...expandMemberPattern(cwd, member));
1021
1032
  }
1022
1033
  return expandedMembers;
1023
1034
  }
1024
- function addWorkspaceLints(memberCargoPath) {
1025
- if (!existsSync(memberCargoPath)) return;
1035
+ function addWorkspaceLints(cwd, memberCargoPath) {
1036
+ if (!isSafeCargoManifest(cwd, memberCargoPath)) return;
1026
1037
  const content = readFileSync(memberCargoPath, "utf8");
1027
1038
  if (hasExistingLints(content)) return;
1028
1039
  const lintsSection = `[lints]
@@ -1044,13 +1055,13 @@ ${lintsToAdd}`;
1044
1055
  function addMemberLints(cwd, content) {
1045
1056
  const members = parseWorkspaceMembers(content, cwd);
1046
1057
  for (const member of members) {
1047
- const memberCargoPath = nodePath3.join(cwd, member, "Cargo.toml");
1048
- addWorkspaceLints(memberCargoPath);
1058
+ const memberCargoPath = nodePath2.join(cwd, member, "Cargo.toml");
1059
+ addWorkspaceLints(cwd, memberCargoPath);
1049
1060
  }
1050
1061
  }
1051
1062
  function setupRustTooling(cwd) {
1052
- const cargoPath = nodePath3.join(cwd, "Cargo.toml");
1053
- if (!existsSync(cargoPath)) return { files: [] };
1063
+ const cargoPath = nodePath2.join(cwd, "Cargo.toml");
1064
+ if (!isSafeCargoManifest(cwd, cargoPath)) return { files: [] };
1054
1065
  const content = readFileSync(cargoPath, "utf8");
1055
1066
  const workspaceType = detectWorkspaceType(content);
1056
1067
  const isWorkspace = workspaceType !== "single-crate";
@@ -1061,6 +1072,66 @@ function setupRustTooling(cwd) {
1061
1072
  return { files: [] };
1062
1073
  }
1063
1074
 
1075
+ // src/packs/config.ts
1076
+ import nodePath3 from "path";
1077
+ import process from "process";
1078
+ var CONFIG_PATH = ".safeword/config.json";
1079
+ function readConfig(cwd) {
1080
+ const configPath = nodePath3.join(cwd, CONFIG_PATH);
1081
+ const content = readFileSafe(configPath);
1082
+ if (!content) return void 0;
1083
+ return JSON.parse(content);
1084
+ }
1085
+ function writeConfig(cwd, config) {
1086
+ const configPath = nodePath3.join(cwd, CONFIG_PATH);
1087
+ writeFile(configPath, JSON.stringify(config, void 0, 2));
1088
+ }
1089
+ function getInstalledPacks(cwd) {
1090
+ const config = readConfig(cwd);
1091
+ return config?.installedPacks ?? [];
1092
+ }
1093
+ function isPackInstalled(cwd, packId) {
1094
+ return getInstalledPacks(cwd).includes(packId);
1095
+ }
1096
+ function addInstalledPack(cwd, packId) {
1097
+ const config = readConfig(cwd) ?? { installedPacks: [] };
1098
+ if (!config.installedPacks.includes(packId)) {
1099
+ config.installedPacks.push(packId);
1100
+ writeConfig(cwd, config);
1101
+ }
1102
+ }
1103
+
1104
+ // src/packs/golang/setup.ts
1105
+ function setupGoTooling() {
1106
+ return { files: [] };
1107
+ }
1108
+
1109
+ // src/packs/golang/index.ts
1110
+ var golangPack = {
1111
+ id: "golang",
1112
+ name: "Go",
1113
+ extensions: [".go"],
1114
+ detect(cwd) {
1115
+ return existsInTree(cwd, "go.mod");
1116
+ },
1117
+ setup(_cwd, _ctx) {
1118
+ return setupGoTooling();
1119
+ }
1120
+ };
1121
+
1122
+ // src/packs/python/index.ts
1123
+ var pythonPack = {
1124
+ id: "python",
1125
+ name: "Python",
1126
+ extensions: [".py", ".pyi"],
1127
+ detect(cwd) {
1128
+ return existsInTree(cwd, "pyproject.toml") || existsInTree(cwd, "requirements.txt");
1129
+ },
1130
+ setup(_cwd, _ctx) {
1131
+ return setupPythonTooling();
1132
+ }
1133
+ };
1134
+
1064
1135
  // src/packs/rust/index.ts
1065
1136
  var rustPack = {
1066
1137
  id: "rust",
@@ -1249,7 +1320,9 @@ var CUCUMBER_TEMPLATE_REVISION_HASHES = /* @__PURE__ */ new Set([
1249
1320
  // 56JCFZ — runtime paths.features/paths.steps config read (current)
1250
1321
  "929014fb4ce6ca1483b292757b6044be89932028f5bb21ba82c28ffd66b09e53",
1251
1322
  // 4DK9H4 — live profile excludes @manual while selecting @live scenarios
1252
- "1295e832b1b3bdd0faa963e218bd489c9c252a981fc8e315d4814d4208384a5f"
1323
+ "1295e832b1b3bdd0faa963e218bd489c9c252a981fc8e315d4814d4208384a5f",
1324
+ // ECGEP9 — automated lane distinguishes Vitest-backed proof from unfinished work
1325
+ "0ec4cb6d65b39e01f8aff27c4ca3841e0271a43b5435492676450f0fb3cfac27"
1253
1326
  ]);
1254
1327
  function isShippedCucumberTemplateRevision(content) {
1255
1328
  const hash = createHash("sha256").update(content).digest("hex");
@@ -1563,6 +1636,7 @@ function createProjectContext(cwd) {
1563
1636
  export {
1564
1637
  isPackInstalled,
1565
1638
  addInstalledPack,
1639
+ rustToolingTargets,
1566
1640
  LANGUAGE_PACKS,
1567
1641
  getMissingPacks,
1568
1642
  reconcile,
@@ -1572,4 +1646,4 @@ export {
1572
1646
  LEFTHOOK_CONFIGS,
1573
1647
  createProjectContext
1574
1648
  };
1575
- //# sourceMappingURL=chunk-C7HR7OK2.js.map
1649
+ //# sourceMappingURL=chunk-2MBSWVT2.js.map