hammoc 1.4.0 → 1.5.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 (252) hide show
  1. package/README.md +422 -405
  2. package/bin/hammoc.js +0 -6
  3. package/package.json +100 -94
  4. package/packages/client/dist/assets/agentExampleHighlight-BgwTm15v.js +1 -0
  5. package/packages/client/dist/assets/commandTokenHighlight-BljHwnrK.js +1 -0
  6. package/packages/client/dist/assets/index-CjyjnXB8.css +32 -0
  7. package/packages/client/dist/assets/index-D3LxqW3f.js +2 -0
  8. package/packages/client/dist/assets/index-NqJdhlek.js +1498 -0
  9. package/packages/client/dist/assets/snippetTokenHighlight-DWsaQXX0.js +1 -0
  10. package/packages/client/dist/index.html +2 -2
  11. package/packages/client/dist/sw.js +1 -1
  12. package/packages/server/dist/app.d.ts.map +1 -1
  13. package/packages/server/dist/app.js +13 -21
  14. package/packages/server/dist/app.js.map +1 -1
  15. package/packages/server/dist/controllers/claudeMdController.d.ts +26 -0
  16. package/packages/server/dist/controllers/claudeMdController.d.ts.map +1 -0
  17. package/packages/server/dist/controllers/claudeMdController.js +158 -0
  18. package/packages/server/dist/controllers/claudeMdController.js.map +1 -0
  19. package/packages/server/dist/controllers/harnessAgentController.d.ts +28 -0
  20. package/packages/server/dist/controllers/harnessAgentController.d.ts.map +1 -0
  21. package/packages/server/dist/controllers/harnessAgentController.js +339 -0
  22. package/packages/server/dist/controllers/harnessAgentController.js.map +1 -0
  23. package/packages/server/dist/controllers/harnessCommandController.d.ts +28 -0
  24. package/packages/server/dist/controllers/harnessCommandController.d.ts.map +1 -0
  25. package/packages/server/dist/controllers/harnessCommandController.js +382 -0
  26. package/packages/server/dist/controllers/harnessCommandController.js.map +1 -0
  27. package/packages/server/dist/controllers/harnessController.d.ts +21 -0
  28. package/packages/server/dist/controllers/harnessController.d.ts.map +1 -0
  29. package/packages/server/dist/controllers/harnessController.js +176 -0
  30. package/packages/server/dist/controllers/harnessController.js.map +1 -0
  31. package/packages/server/dist/controllers/harnessHookController.d.ts +32 -0
  32. package/packages/server/dist/controllers/harnessHookController.d.ts.map +1 -0
  33. package/packages/server/dist/controllers/harnessHookController.js +363 -0
  34. package/packages/server/dist/controllers/harnessHookController.js.map +1 -0
  35. package/packages/server/dist/controllers/harnessLintController.d.ts +18 -0
  36. package/packages/server/dist/controllers/harnessLintController.d.ts.map +1 -0
  37. package/packages/server/dist/controllers/harnessLintController.js +72 -0
  38. package/packages/server/dist/controllers/harnessLintController.js.map +1 -0
  39. package/packages/server/dist/controllers/harnessMcpController.d.ts +28 -0
  40. package/packages/server/dist/controllers/harnessMcpController.d.ts.map +1 -0
  41. package/packages/server/dist/controllers/harnessMcpController.js +310 -0
  42. package/packages/server/dist/controllers/harnessMcpController.js.map +1 -0
  43. package/packages/server/dist/controllers/harnessPluginController.d.ts +17 -0
  44. package/packages/server/dist/controllers/harnessPluginController.d.ts.map +1 -0
  45. package/packages/server/dist/controllers/harnessPluginController.js +115 -0
  46. package/packages/server/dist/controllers/harnessPluginController.js.map +1 -0
  47. package/packages/server/dist/controllers/harnessShareScopeController.d.ts +15 -0
  48. package/packages/server/dist/controllers/harnessShareScopeController.d.ts.map +1 -0
  49. package/packages/server/dist/controllers/harnessShareScopeController.js +73 -0
  50. package/packages/server/dist/controllers/harnessShareScopeController.js.map +1 -0
  51. package/packages/server/dist/controllers/harnessSkillController.d.ts +32 -0
  52. package/packages/server/dist/controllers/harnessSkillController.d.ts.map +1 -0
  53. package/packages/server/dist/controllers/harnessSkillController.js +453 -0
  54. package/packages/server/dist/controllers/harnessSkillController.js.map +1 -0
  55. package/packages/server/dist/controllers/projectController.d.ts.map +1 -1
  56. package/packages/server/dist/controllers/projectController.js +11 -0
  57. package/packages/server/dist/controllers/projectController.js.map +1 -1
  58. package/packages/server/dist/controllers/snippetController.d.ts +35 -0
  59. package/packages/server/dist/controllers/snippetController.d.ts.map +1 -0
  60. package/packages/server/dist/controllers/snippetController.js +294 -0
  61. package/packages/server/dist/controllers/snippetController.js.map +1 -0
  62. package/packages/server/dist/handlers/websocket.d.ts +15 -0
  63. package/packages/server/dist/handlers/websocket.d.ts.map +1 -1
  64. package/packages/server/dist/handlers/websocket.js +79 -0
  65. package/packages/server/dist/handlers/websocket.js.map +1 -1
  66. package/packages/server/dist/index.js +5 -0
  67. package/packages/server/dist/index.js.map +1 -1
  68. package/packages/server/dist/locales/en/server.json +37 -4
  69. package/packages/server/dist/locales/es/server.json +0 -4
  70. package/packages/server/dist/locales/ja/server.json +0 -4
  71. package/packages/server/dist/locales/ko/server.json +0 -4
  72. package/packages/server/dist/locales/pt/server.json +0 -4
  73. package/packages/server/dist/locales/zh-CN/server.json +0 -4
  74. package/packages/server/dist/routes/harness.d.ts +8 -0
  75. package/packages/server/dist/routes/harness.d.ts.map +1 -0
  76. package/packages/server/dist/routes/harness.js +92 -0
  77. package/packages/server/dist/routes/harness.js.map +1 -0
  78. package/packages/server/dist/routes/projects.d.ts.map +1 -1
  79. package/packages/server/dist/routes/projects.js +5 -60
  80. package/packages/server/dist/routes/projects.js.map +1 -1
  81. package/packages/server/dist/routes/snippets.d.ts +14 -0
  82. package/packages/server/dist/routes/snippets.d.ts.map +1 -0
  83. package/packages/server/dist/routes/snippets.js +27 -0
  84. package/packages/server/dist/routes/snippets.js.map +1 -0
  85. package/packages/server/dist/services/bmadStatusService.d.ts +6 -2
  86. package/packages/server/dist/services/bmadStatusService.d.ts.map +1 -1
  87. package/packages/server/dist/services/bmadStatusService.js +88 -32
  88. package/packages/server/dist/services/bmadStatusService.js.map +1 -1
  89. package/packages/server/dist/services/chatService.d.ts +3 -0
  90. package/packages/server/dist/services/chatService.d.ts.map +1 -1
  91. package/packages/server/dist/services/chatService.js +27 -6
  92. package/packages/server/dist/services/chatService.js.map +1 -1
  93. package/packages/server/dist/services/claudeMdService.d.ts +48 -0
  94. package/packages/server/dist/services/claudeMdService.d.ts.map +1 -0
  95. package/packages/server/dist/services/claudeMdService.js +240 -0
  96. package/packages/server/dist/services/claudeMdService.js.map +1 -0
  97. package/packages/server/dist/services/commandService.d.ts +10 -0
  98. package/packages/server/dist/services/commandService.d.ts.map +1 -1
  99. package/packages/server/dist/services/commandService.js +129 -4
  100. package/packages/server/dist/services/commandService.js.map +1 -1
  101. package/packages/server/dist/services/fileWatcherService.d.ts +24 -0
  102. package/packages/server/dist/services/fileWatcherService.d.ts.map +1 -1
  103. package/packages/server/dist/services/fileWatcherService.js +192 -1
  104. package/packages/server/dist/services/fileWatcherService.js.map +1 -1
  105. package/packages/server/dist/services/harnessAgentService.d.ts +79 -0
  106. package/packages/server/dist/services/harnessAgentService.d.ts.map +1 -0
  107. package/packages/server/dist/services/harnessAgentService.js +933 -0
  108. package/packages/server/dist/services/harnessAgentService.js.map +1 -0
  109. package/packages/server/dist/services/harnessCommandService.d.ts +60 -0
  110. package/packages/server/dist/services/harnessCommandService.d.ts.map +1 -0
  111. package/packages/server/dist/services/harnessCommandService.js +853 -0
  112. package/packages/server/dist/services/harnessCommandService.js.map +1 -0
  113. package/packages/server/dist/services/harnessHookService.d.ts +55 -0
  114. package/packages/server/dist/services/harnessHookService.d.ts.map +1 -0
  115. package/packages/server/dist/services/harnessHookService.js +1060 -0
  116. package/packages/server/dist/services/harnessHookService.js.map +1 -0
  117. package/packages/server/dist/services/harnessLintService.d.ts +49 -0
  118. package/packages/server/dist/services/harnessLintService.d.ts.map +1 -0
  119. package/packages/server/dist/services/harnessLintService.js +628 -0
  120. package/packages/server/dist/services/harnessLintService.js.map +1 -0
  121. package/packages/server/dist/services/harnessMcpService.d.ts +77 -0
  122. package/packages/server/dist/services/harnessMcpService.d.ts.map +1 -0
  123. package/packages/server/dist/services/harnessMcpService.js +814 -0
  124. package/packages/server/dist/services/harnessMcpService.js.map +1 -0
  125. package/packages/server/dist/services/harnessPluginService.d.ts +66 -0
  126. package/packages/server/dist/services/harnessPluginService.d.ts.map +1 -0
  127. package/packages/server/dist/services/harnessPluginService.js +559 -0
  128. package/packages/server/dist/services/harnessPluginService.js.map +1 -0
  129. package/packages/server/dist/services/harnessService.d.ts +40 -0
  130. package/packages/server/dist/services/harnessService.d.ts.map +1 -0
  131. package/packages/server/dist/services/harnessService.js +222 -0
  132. package/packages/server/dist/services/harnessService.js.map +1 -0
  133. package/packages/server/dist/services/harnessShareScopeService.d.ts +31 -0
  134. package/packages/server/dist/services/harnessShareScopeService.d.ts.map +1 -0
  135. package/packages/server/dist/services/harnessShareScopeService.js +93 -0
  136. package/packages/server/dist/services/harnessShareScopeService.js.map +1 -0
  137. package/packages/server/dist/services/harnessSkillService.d.ts +70 -0
  138. package/packages/server/dist/services/harnessSkillService.d.ts.map +1 -0
  139. package/packages/server/dist/services/harnessSkillService.js +636 -0
  140. package/packages/server/dist/services/harnessSkillService.js.map +1 -0
  141. package/packages/server/dist/services/issueService.d.ts.map +1 -1
  142. package/packages/server/dist/services/issueService.js +2 -1
  143. package/packages/server/dist/services/issueService.js.map +1 -1
  144. package/packages/server/dist/services/manualSyncService.d.ts +19 -0
  145. package/packages/server/dist/services/manualSyncService.d.ts.map +1 -0
  146. package/packages/server/dist/services/manualSyncService.js +110 -0
  147. package/packages/server/dist/services/manualSyncService.js.map +1 -0
  148. package/packages/server/dist/services/queueService.d.ts.map +1 -1
  149. package/packages/server/dist/services/queueService.js +45 -2
  150. package/packages/server/dist/services/queueService.js.map +1 -1
  151. package/packages/server/dist/services/snippetService.d.ts +54 -0
  152. package/packages/server/dist/services/snippetService.d.ts.map +1 -0
  153. package/packages/server/dist/services/snippetService.js +371 -0
  154. package/packages/server/dist/services/snippetService.js.map +1 -0
  155. package/packages/server/dist/services/utils/applyYamlFrontmatterPatch.d.ts +46 -0
  156. package/packages/server/dist/services/utils/applyYamlFrontmatterPatch.d.ts.map +1 -0
  157. package/packages/server/dist/services/utils/applyYamlFrontmatterPatch.js +125 -0
  158. package/packages/server/dist/services/utils/applyYamlFrontmatterPatch.js.map +1 -0
  159. package/packages/server/dist/snippets/split-commit +9 -0
  160. package/packages/server/dist/utils/applySecretsPolicy.d.ts +53 -0
  161. package/packages/server/dist/utils/applySecretsPolicy.d.ts.map +1 -0
  162. package/packages/server/dist/utils/applySecretsPolicy.js +204 -0
  163. package/packages/server/dist/utils/applySecretsPolicy.js.map +1 -0
  164. package/packages/server/dist/utils/assertNoSecretOnShared.d.ts +40 -0
  165. package/packages/server/dist/utils/assertNoSecretOnShared.d.ts.map +1 -0
  166. package/packages/server/dist/utils/assertNoSecretOnShared.js +47 -0
  167. package/packages/server/dist/utils/assertNoSecretOnShared.js.map +1 -0
  168. package/packages/server/dist/utils/gitignoreFilter.d.ts +23 -0
  169. package/packages/server/dist/utils/gitignoreFilter.d.ts.map +1 -0
  170. package/packages/server/dist/utils/gitignoreFilter.js +42 -0
  171. package/packages/server/dist/utils/gitignoreFilter.js.map +1 -0
  172. package/packages/server/dist/utils/harnessBundleSchema.d.ts +105 -0
  173. package/packages/server/dist/utils/harnessBundleSchema.d.ts.map +1 -0
  174. package/packages/server/dist/utils/harnessBundleSchema.js +79 -0
  175. package/packages/server/dist/utils/harnessBundleSchema.js.map +1 -0
  176. package/packages/server/dist/utils/harnessPaths.d.ts +34 -0
  177. package/packages/server/dist/utils/harnessPaths.d.ts.map +1 -0
  178. package/packages/server/dist/utils/harnessPaths.js +124 -0
  179. package/packages/server/dist/utils/harnessPaths.js.map +1 -0
  180. package/packages/server/dist/utils/secretHeuristic.d.ts +72 -0
  181. package/packages/server/dist/utils/secretHeuristic.d.ts.map +1 -0
  182. package/packages/server/dist/utils/secretHeuristic.js +163 -0
  183. package/packages/server/dist/utils/secretHeuristic.js.map +1 -0
  184. package/packages/server/dist/utils/secretPlaceholderNamer.d.ts +41 -0
  185. package/packages/server/dist/utils/secretPlaceholderNamer.d.ts.map +1 -0
  186. package/packages/server/dist/utils/secretPlaceholderNamer.js +81 -0
  187. package/packages/server/dist/utils/secretPlaceholderNamer.js.map +1 -0
  188. package/packages/server/dist/utils/serverPathResolver.d.ts +29 -0
  189. package/packages/server/dist/utils/serverPathResolver.d.ts.map +1 -0
  190. package/packages/server/dist/utils/serverPathResolver.js +59 -0
  191. package/packages/server/dist/utils/serverPathResolver.js.map +1 -0
  192. package/packages/server/dist/utils/snippetPaths.d.ts +61 -0
  193. package/packages/server/dist/utils/snippetPaths.d.ts.map +1 -0
  194. package/packages/server/dist/utils/snippetPaths.js +123 -0
  195. package/packages/server/dist/utils/snippetPaths.js.map +1 -0
  196. package/packages/server/dist/utils/structuredEditor.d.ts +34 -0
  197. package/packages/server/dist/utils/structuredEditor.d.ts.map +1 -0
  198. package/packages/server/dist/utils/structuredEditor.js +111 -0
  199. package/packages/server/dist/utils/structuredEditor.js.map +1 -0
  200. package/packages/server/package.json +4 -1
  201. package/packages/server/resources/internals/INDEX.md +23 -0
  202. package/packages/server/resources/internals/harness-files.md +63 -0
  203. package/packages/server/resources/internals/image-storage.md +43 -0
  204. package/packages/server/resources/manual/01-getting-started.md +104 -0
  205. package/packages/server/resources/manual/02-chat.md +285 -0
  206. package/packages/server/resources/manual/03-sessions.md +48 -0
  207. package/packages/server/resources/manual/04-slash-commands-favorites.md +152 -0
  208. package/packages/server/resources/manual/05-projects.md +74 -0
  209. package/packages/server/resources/manual/06-file-explorer-editor.md +90 -0
  210. package/packages/server/resources/manual/07-git.md +94 -0
  211. package/packages/server/resources/manual/08-terminal.md +59 -0
  212. package/packages/server/resources/manual/09-queue-runner.md +262 -0
  213. package/packages/server/resources/manual/10-project-board.md +193 -0
  214. package/packages/server/resources/manual/11-bmad-method-integration.md +128 -0
  215. package/packages/server/resources/manual/12-harness-workbench.md +175 -0
  216. package/packages/server/resources/manual/13-settings.md +241 -0
  217. package/packages/server/resources/manual/14-keyboard-shortcuts.md +68 -0
  218. package/packages/server/resources/manual/15-environment-variables.md +28 -0
  219. package/packages/server/resources/manual/16-troubleshooting.md +110 -0
  220. package/packages/server/resources/manual/INDEX.md +60 -0
  221. package/packages/shared/dist/index.d.ts +3 -0
  222. package/packages/shared/dist/index.d.ts.map +1 -1
  223. package/packages/shared/dist/index.js +6 -0
  224. package/packages/shared/dist/index.js.map +1 -1
  225. package/packages/shared/dist/types/command.d.ts +3 -3
  226. package/packages/shared/dist/types/command.d.ts.map +1 -1
  227. package/packages/shared/dist/types/harness.d.ts +1211 -0
  228. package/packages/shared/dist/types/harness.d.ts.map +1 -0
  229. package/packages/shared/dist/types/harness.js +107 -0
  230. package/packages/shared/dist/types/harness.js.map +1 -0
  231. package/packages/shared/dist/types/harnessBundle.d.ts +170 -0
  232. package/packages/shared/dist/types/harnessBundle.d.ts.map +1 -0
  233. package/packages/shared/dist/types/harnessBundle.js +18 -0
  234. package/packages/shared/dist/types/harnessBundle.js.map +1 -0
  235. package/packages/shared/dist/types/preferences.d.ts +2 -0
  236. package/packages/shared/dist/types/preferences.d.ts.map +1 -1
  237. package/packages/shared/dist/types/preferences.js.map +1 -1
  238. package/packages/shared/dist/types/queue.d.ts +9 -0
  239. package/packages/shared/dist/types/queue.d.ts.map +1 -1
  240. package/packages/shared/dist/types/websocket.d.ts +10 -0
  241. package/packages/shared/dist/types/websocket.d.ts.map +1 -1
  242. package/packages/shared/dist/utils/markdownSections.d.ts +50 -0
  243. package/packages/shared/dist/utils/markdownSections.d.ts.map +1 -0
  244. package/packages/shared/dist/utils/markdownSections.js +111 -0
  245. package/packages/shared/dist/utils/markdownSections.js.map +1 -0
  246. package/packages/shared/dist/utils/queueParser.d.ts.map +1 -1
  247. package/packages/shared/dist/utils/queueParser.js +104 -0
  248. package/packages/shared/dist/utils/queueParser.js.map +1 -1
  249. package/scripts/build-manual-shards.mjs +100 -0
  250. package/packages/client/dist/assets/index-6jREnVYd.js +0 -2
  251. package/packages/client/dist/assets/index-BFF0iqyW.css +0 -32
  252. package/packages/client/dist/assets/index-BcI4y-fU.js +0 -1454
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Story 28.0.5: Harness workbench path resolver.
3
+ *
4
+ * Resolves `~/.claude` (user scope) and `<projectRoot>/.claude` (project scope)
5
+ * into absolute paths and enforces that any relative path requested by the
6
+ * caller stays inside the resolved root. Every entry point into `harnessService`
7
+ * MUST route through `resolveHarnessPath` so that Windows/POSIX separator
8
+ * mixing, drive letters, UNC paths, and null-byte inputs cannot escape the
9
+ * subtree.
10
+ */
11
+ import { type HarnessPathRef } from '@hammoc/shared';
12
+ /** Return the absolute path to the user-scope harness root (`~/.claude`). */
13
+ export declare function getUserHarnessRoot(): string;
14
+ /** Return the absolute path to the project-scope harness root (`<project>/.claude`). */
15
+ export declare function getProjectHarnessRoot(projectSlug: string): Promise<string>;
16
+ export interface ResolvedHarnessPath {
17
+ resolvedRoot: string;
18
+ absolutePath: string;
19
+ }
20
+ /**
21
+ * Resolve a `HarnessPathRef` to an absolute path, guaranteed to sit inside the
22
+ * resolved root. Throws `HARNESS_PATH_DENIED` for any traversal attempt.
23
+ */
24
+ export declare function resolveHarnessPath(ref: HarnessPathRef): Promise<ResolvedHarnessPath>;
25
+ /**
26
+ * Story 29.1 (AC6): resolve the project-root `<projectRoot>/CLAUDE.md` path —
27
+ * the single file in the harness workbench whose location sits OUTSIDE the
28
+ * project's `.claude/` subtree, so `resolveHarnessPath` would reject it as a
29
+ * traversal. This helper accepts only `projectSlug` (no caller-supplied
30
+ * relative path) and returns the canonical CLAUDE.md location alongside the
31
+ * project root, so traversal is impossible by construction.
32
+ */
33
+ export declare function resolveProjectClaudeMdPath(projectSlug: string): Promise<ResolvedHarnessPath>;
34
+ //# sourceMappingURL=harnessPaths.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"harnessPaths.d.ts","sourceRoot":"","sources":["../../src/utils/harnessPaths.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAKH,OAAO,EAAkB,KAAK,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAWrE,6EAA6E;AAC7E,wBAAgB,kBAAkB,IAAI,MAAM,CAO3C;AAED,wFAAwF;AACxF,wBAAsB,qBAAqB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAkBhF;AAED,MAAM,WAAW,mBAAmB;IAClC,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAwC1F;AAED;;;;;;;GAOG;AACH,wBAAsB,0BAA0B,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAyBlG"}
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Story 28.0.5: Harness workbench path resolver.
3
+ *
4
+ * Resolves `~/.claude` (user scope) and `<projectRoot>/.claude` (project scope)
5
+ * into absolute paths and enforces that any relative path requested by the
6
+ * caller stays inside the resolved root. Every entry point into `harnessService`
7
+ * MUST route through `resolveHarnessPath` so that Windows/POSIX separator
8
+ * mixing, drive letters, UNC paths, and null-byte inputs cannot escape the
9
+ * subtree.
10
+ */
11
+ import os from 'os';
12
+ import path from 'path';
13
+ import { projectService } from '../services/projectService.js';
14
+ import { HARNESS_ERRORS } from '@hammoc/shared';
15
+ /**
16
+ * Test-only dependency-injection hook. When set, `getUserHarnessRoot()` returns
17
+ * this value instead of `~/.claude`. Production configurations leave it unset
18
+ * (no effect); unit tests in `harnessPaths.test.ts` / `harnessService.test.ts`
19
+ * redirect the user scope to a temp directory so they never touch the real
20
+ * home directory.
21
+ */
22
+ const HOME_OVERRIDE_ENV = 'HAMMOC_HARNESS_HOME_OVERRIDE';
23
+ /** Return the absolute path to the user-scope harness root (`~/.claude`). */
24
+ export function getUserHarnessRoot() {
25
+ const override = process.env[HOME_OVERRIDE_ENV];
26
+ if (override && override.length > 0) {
27
+ return override;
28
+ }
29
+ // Node resolves %USERPROFILE% on Windows and $HOME on POSIX.
30
+ return path.join(os.homedir(), '.claude');
31
+ }
32
+ /** Return the absolute path to the project-scope harness root (`<project>/.claude`). */
33
+ export async function getProjectHarnessRoot(projectSlug) {
34
+ if (!projectSlug) {
35
+ const err = new Error('projectSlug is required for project scope');
36
+ err.code = HARNESS_ERRORS.HARNESS_ROOT_MISSING.code;
37
+ throw err;
38
+ }
39
+ try {
40
+ const projectRoot = await projectService.resolveOriginalPath(projectSlug);
41
+ return path.join(projectRoot, '.claude');
42
+ }
43
+ catch (error) {
44
+ // Any failure to resolve the project (unknown slug, missing index, etc.)
45
+ // maps to HARNESS_ROOT_MISSING so the controller can return a uniform 404.
46
+ const wrapped = new Error(`Unable to resolve harness root for project "${projectSlug}": ${error.message}`);
47
+ wrapped.code = HARNESS_ERRORS.HARNESS_ROOT_MISSING.code;
48
+ throw wrapped;
49
+ }
50
+ }
51
+ /**
52
+ * Resolve a `HarnessPathRef` to an absolute path, guaranteed to sit inside the
53
+ * resolved root. Throws `HARNESS_PATH_DENIED` for any traversal attempt.
54
+ */
55
+ export async function resolveHarnessPath(ref) {
56
+ const rel = ref.relativePath ?? '';
57
+ // Reject absolute paths and null bytes before touching `path.join`, since
58
+ // join would otherwise discard the root prefix (POSIX absolute) or let a
59
+ // null byte slip through to fs APIs.
60
+ if (rel.includes('\0')) {
61
+ const err = new Error('null byte in relative path');
62
+ err.code = HARNESS_ERRORS.HARNESS_PATH_DENIED.code;
63
+ throw err;
64
+ }
65
+ if (path.isAbsolute(rel)) {
66
+ const err = new Error('absolute path not allowed');
67
+ err.code = HARNESS_ERRORS.HARNESS_PATH_DENIED.code;
68
+ throw err;
69
+ }
70
+ // UNC inputs (`\\server\share`) on Windows: path.isAbsolute flags some of
71
+ // these, but on POSIX it would not — reject the prefix defensively.
72
+ if (rel.startsWith('\\\\') || rel.startsWith('//')) {
73
+ const err = new Error('UNC path not allowed');
74
+ err.code = HARNESS_ERRORS.HARNESS_PATH_DENIED.code;
75
+ throw err;
76
+ }
77
+ const resolvedRoot = ref.scope === 'user'
78
+ ? path.resolve(getUserHarnessRoot())
79
+ : path.resolve(await getProjectHarnessRoot(ref.projectSlug ?? ''));
80
+ const absolutePath = path.resolve(resolvedRoot, rel);
81
+ // Containment check: absolutePath must be the root itself or sit beneath it.
82
+ // `startsWith(root + sep)` avoids the false match where root="/a" and
83
+ // absolutePath="/abc".
84
+ if (absolutePath !== resolvedRoot && !absolutePath.startsWith(resolvedRoot + path.sep)) {
85
+ const err = new Error('path escapes harness root');
86
+ err.code = HARNESS_ERRORS.HARNESS_PATH_DENIED.code;
87
+ throw err;
88
+ }
89
+ return { resolvedRoot, absolutePath };
90
+ }
91
+ /**
92
+ * Story 29.1 (AC6): resolve the project-root `<projectRoot>/CLAUDE.md` path —
93
+ * the single file in the harness workbench whose location sits OUTSIDE the
94
+ * project's `.claude/` subtree, so `resolveHarnessPath` would reject it as a
95
+ * traversal. This helper accepts only `projectSlug` (no caller-supplied
96
+ * relative path) and returns the canonical CLAUDE.md location alongside the
97
+ * project root, so traversal is impossible by construction.
98
+ */
99
+ export async function resolveProjectClaudeMdPath(projectSlug) {
100
+ if (!projectSlug || projectSlug.includes('\0')) {
101
+ const err = new Error('invalid projectSlug');
102
+ err.code = HARNESS_ERRORS.HARNESS_PATH_DENIED.code;
103
+ throw err;
104
+ }
105
+ // Reject traversal-bearing slugs before they reach projectService — defense in depth.
106
+ if (projectSlug.includes('..') || projectSlug.includes('/') || projectSlug.includes('\\')) {
107
+ const err = new Error('projectSlug must not contain path separators');
108
+ err.code = HARNESS_ERRORS.HARNESS_PATH_DENIED.code;
109
+ throw err;
110
+ }
111
+ let projectRoot;
112
+ try {
113
+ projectRoot = await projectService.resolveOriginalPath(projectSlug);
114
+ }
115
+ catch (error) {
116
+ const wrapped = new Error(`Unable to resolve project root for "${projectSlug}": ${error.message}`);
117
+ wrapped.code = HARNESS_ERRORS.HARNESS_ROOT_MISSING.code;
118
+ throw wrapped;
119
+ }
120
+ const resolvedRoot = path.resolve(projectRoot);
121
+ const absolutePath = path.join(resolvedRoot, 'CLAUDE.md');
122
+ return { resolvedRoot, absolutePath };
123
+ }
124
+ //# sourceMappingURL=harnessPaths.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"harnessPaths.js","sourceRoot":"","sources":["../../src/utils/harnessPaths.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Story 30.1 (Task 1): single source of truth for secret-pattern heuristics
3
+ * used by the harness write paths.
4
+ *
5
+ * Replaces four drifted SECRET_PATTERNS definitions (harnessAgentService,
6
+ * harnessCommandService, harnessHookService, harnessMcpService). Drift
7
+ * before this module:
8
+ *
9
+ * - agent / command / hook : 32-char base64 unanchored, Bearer unanchored
10
+ * - mcp : 40-char base64 anchored, Bearer anchored
11
+ *
12
+ * Canonical chosen here (Story 30.1 sub-spike 1.0, option (b)):
13
+ * - 32-char base64 unanchored, Bearer unanchored
14
+ *
15
+ * Rationale: detection-rate first; Story 30.1 AC4.c provides a per-save
16
+ * "mark not a secret" opt-out that absorbs false positives, whereas missed
17
+ * detections silently leak credentials. MCP's stricter anchors were a local
18
+ * accident, not a deliberate spec decision.
19
+ *
20
+ * Two entry points are exposed because the four services walk two distinct
21
+ * shapes:
22
+ *
23
+ * - detectSecretsInText → string body (agent / command / hook commands)
24
+ * returns matched line numbers (1-based)
25
+ * - detectSecretsInValue → arbitrary JSON-like value (mcp config)
26
+ * returns matched dot-paths
27
+ *
28
+ * Both strip `${ENV_VAR}` references first so legitimate env-var indirection
29
+ * (`Authorization: 'Bearer ${GH_TOKEN}'`) does not raise false positives.
30
+ */
31
+ export interface SecretPattern {
32
+ name: string;
33
+ re: RegExp;
34
+ /**
35
+ * Optional minimum Shannon entropy (bits/char) gate for matched substrings.
36
+ * Story 30.3 spike #2: applies to the loose base64 pattern only. Natural
37
+ * English compounds (PascalCase identifiers, prose) sit at ~3.5–3.9; real
38
+ * base64-encoded tokens sit at ≥ 4.0 due to a-z/A-Z/0-9 uniform distribution.
39
+ * Other patterns keep their anchored prefixes (Bearer/sk-/AKIA/xox-) which
40
+ * already gate false positives — no entropy check needed.
41
+ */
42
+ minEntropy?: number;
43
+ }
44
+ export declare const SECRET_PATTERNS: readonly SecretPattern[];
45
+ export declare const ENV_REF_RE: RegExp;
46
+ /**
47
+ * Shannon entropy in bits/char of an arbitrary string. Used by patterns that
48
+ * opt in via `minEntropy` (currently the loose base64 pattern only).
49
+ */
50
+ export declare function shannonEntropy(s: string): number;
51
+ export interface DetectSecretsTextResult {
52
+ matched: boolean;
53
+ patternNames: string[];
54
+ lines: number[];
55
+ }
56
+ export interface DetectSecretsValueResult {
57
+ matched: boolean;
58
+ patternNames: string[];
59
+ paths: string[];
60
+ }
61
+ /**
62
+ * Line-based scanner for textual command / prompt bodies (agent system
63
+ * prompts, slash command bodies, hook command/prompt strings).
64
+ */
65
+ export declare function detectSecretsInText(text: string): DetectSecretsTextResult;
66
+ /**
67
+ * Object-walk scanner for JSON-like values (mcp server config). Each string
68
+ * leaf is evaluated against the canonical patterns; matches are reported as
69
+ * dot-paths (`env.GITHUB_TOKEN`, `headers.Authorization`, ...).
70
+ */
71
+ export declare function detectSecretsInValue(value: unknown, basePath?: string[]): DetectSecretsValueResult;
72
+ //# sourceMappingURL=secretHeuristic.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secretHeuristic.d.ts","sourceRoot":"","sources":["../../src/utils/secretHeuristic.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,eAAO,MAAM,eAAe,EAAE,SAAS,aAAa,EAMnD,CAAC;AAEF,eAAO,MAAM,UAAU,QAAkC,CAAC;AAE1D;;;GAGG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAWhD;AAkCD,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAQD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,uBAAuB,CA0BzE;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,OAAO,EACd,QAAQ,GAAE,MAAM,EAAO,GACtB,wBAAwB,CAgC1B"}
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Story 30.1 (Task 1): single source of truth for secret-pattern heuristics
3
+ * used by the harness write paths.
4
+ *
5
+ * Replaces four drifted SECRET_PATTERNS definitions (harnessAgentService,
6
+ * harnessCommandService, harnessHookService, harnessMcpService). Drift
7
+ * before this module:
8
+ *
9
+ * - agent / command / hook : 32-char base64 unanchored, Bearer unanchored
10
+ * - mcp : 40-char base64 anchored, Bearer anchored
11
+ *
12
+ * Canonical chosen here (Story 30.1 sub-spike 1.0, option (b)):
13
+ * - 32-char base64 unanchored, Bearer unanchored
14
+ *
15
+ * Rationale: detection-rate first; Story 30.1 AC4.c provides a per-save
16
+ * "mark not a secret" opt-out that absorbs false positives, whereas missed
17
+ * detections silently leak credentials. MCP's stricter anchors were a local
18
+ * accident, not a deliberate spec decision.
19
+ *
20
+ * Two entry points are exposed because the four services walk two distinct
21
+ * shapes:
22
+ *
23
+ * - detectSecretsInText → string body (agent / command / hook commands)
24
+ * returns matched line numbers (1-based)
25
+ * - detectSecretsInValue → arbitrary JSON-like value (mcp config)
26
+ * returns matched dot-paths
27
+ *
28
+ * Both strip `${ENV_VAR}` references first so legitimate env-var indirection
29
+ * (`Authorization: 'Bearer ${GH_TOKEN}'`) does not raise false positives.
30
+ */
31
+ export const SECRET_PATTERNS = [
32
+ { name: 'bearer', re: /Bearer\s+[A-Za-z0-9._-]{16,}/ },
33
+ { name: 'sk', re: /sk-[A-Za-z0-9]{20,}/ },
34
+ { name: 'aws', re: /AKIA[0-9A-Z]{16}/ },
35
+ { name: 'slack', re: /xox[baprs]-[A-Za-z0-9-]{10,}/ },
36
+ { name: 'base64', re: /[A-Za-z0-9+/=]{32,}/g, minEntropy: 4.0 },
37
+ ];
38
+ export const ENV_REF_RE = /\$\{[A-Za-z_][A-Za-z0-9_]*\}/g;
39
+ /**
40
+ * Shannon entropy in bits/char of an arbitrary string. Used by patterns that
41
+ * opt in via `minEntropy` (currently the loose base64 pattern only).
42
+ */
43
+ export function shannonEntropy(s) {
44
+ if (!s)
45
+ return 0;
46
+ const counts = new Map();
47
+ for (const ch of s)
48
+ counts.set(ch, (counts.get(ch) ?? 0) + 1);
49
+ const len = s.length;
50
+ let h = 0;
51
+ for (const c of counts.values()) {
52
+ const p = c / len;
53
+ h -= p * Math.log2(p);
54
+ }
55
+ return h;
56
+ }
57
+ // Story 30.3 spike #2 — the loose base64 pattern adds two combined gates so
58
+ // natural English compounds (e.g. PascalCase identifiers) cannot trigger a
59
+ // false positive:
60
+ // (i) Shannon entropy ≥ 4.0 bits/char — filters single-symbol runs
61
+ // (ii) ≥ 1 character outside [A-Za-z] — filters English compounds that
62
+ // otherwise meet the entropy bar (real base64 of random bytes almost
63
+ // always contains digits or '+'/'/'/'=' over 32+ chars)
64
+ // Both gates run only for patterns that opt in via `minEntropy`.
65
+ const BASE64_NON_ALPHA_RE = /[0-9+/=]/;
66
+ /**
67
+ * Tests whether `text` matches a single SecretPattern, applying the optional
68
+ * `minEntropy` + non-alpha gates to each individual regex match. The pattern's
69
+ * `re` must be either non-global (single test) or global (per-match gating).
70
+ */
71
+ function patternMatches(text, pat) {
72
+ if (pat.minEntropy === undefined) {
73
+ return pat.re.test(text);
74
+ }
75
+ const re = pat.re.global ? pat.re : new RegExp(pat.re.source, pat.re.flags + 'g');
76
+ re.lastIndex = 0;
77
+ let m;
78
+ while ((m = re.exec(text)) !== null) {
79
+ const s = m[0];
80
+ if (shannonEntropy(s) >= pat.minEntropy && BASE64_NON_ALPHA_RE.test(s)) {
81
+ return true;
82
+ }
83
+ if (m.index === re.lastIndex)
84
+ re.lastIndex += 1;
85
+ }
86
+ return false;
87
+ }
88
+ function stripEnvRefs(text) {
89
+ // Reset is required because the regex is /g — but `replace` with a /g regex
90
+ // re-creates state each call. Still, clone-replace pattern is safest.
91
+ return text.replace(ENV_REF_RE, '');
92
+ }
93
+ /**
94
+ * Line-based scanner for textual command / prompt bodies (agent system
95
+ * prompts, slash command bodies, hook command/prompt strings).
96
+ */
97
+ export function detectSecretsInText(text) {
98
+ if (!text || typeof text !== 'string') {
99
+ return { matched: false, patternNames: [], lines: [] };
100
+ }
101
+ const stripped = stripEnvRefs(text);
102
+ const matchedNames = new Set();
103
+ for (const pat of SECRET_PATTERNS) {
104
+ if (patternMatches(stripped, pat)) {
105
+ matchedNames.add(pat.name);
106
+ }
107
+ }
108
+ if (matchedNames.size === 0) {
109
+ return { matched: false, patternNames: [], lines: [] };
110
+ }
111
+ const lines = [];
112
+ const split = text.split(/\r?\n/);
113
+ for (let i = 0; i < split.length; i += 1) {
114
+ const lineStripped = stripEnvRefs(split[i]);
115
+ for (const pat of SECRET_PATTERNS) {
116
+ if (patternMatches(lineStripped, pat)) {
117
+ lines.push(i + 1);
118
+ break;
119
+ }
120
+ }
121
+ }
122
+ return { matched: true, patternNames: Array.from(matchedNames), lines };
123
+ }
124
+ /**
125
+ * Object-walk scanner for JSON-like values (mcp server config). Each string
126
+ * leaf is evaluated against the canonical patterns; matches are reported as
127
+ * dot-paths (`env.GITHUB_TOKEN`, `headers.Authorization`, ...).
128
+ */
129
+ export function detectSecretsInValue(value, basePath = []) {
130
+ const paths = [];
131
+ const matchedNames = new Set();
132
+ const walk = (v, p) => {
133
+ if (typeof v === 'string') {
134
+ const stripped = stripEnvRefs(v);
135
+ if (!stripped)
136
+ return;
137
+ for (const pat of SECRET_PATTERNS) {
138
+ if (patternMatches(stripped, pat)) {
139
+ paths.push(p.join('.'));
140
+ matchedNames.add(pat.name);
141
+ return;
142
+ }
143
+ }
144
+ return;
145
+ }
146
+ if (Array.isArray(v)) {
147
+ v.forEach((item, i) => walk(item, [...p, String(i)]));
148
+ return;
149
+ }
150
+ if (v && typeof v === 'object') {
151
+ for (const [k, child] of Object.entries(v)) {
152
+ walk(child, [...p, k]);
153
+ }
154
+ }
155
+ };
156
+ walk(value, basePath);
157
+ return {
158
+ matched: paths.length > 0,
159
+ patternNames: Array.from(matchedNames),
160
+ paths,
161
+ };
162
+ }
163
+ //# sourceMappingURL=secretHeuristic.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secretHeuristic.js","sourceRoot":"","sources":["../../src/utils/secretHeuristic.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Story 30.3 (Task 2.1): single source of truth for placeholder ENV names
3
+ * used when `secretsPolicy === 'placeholder'`.
4
+ *
5
+ * The export pipeline walks each domain's payload, calls `detectSecretsInValue`
6
+ * / `detectSecretsInText` from secretHeuristic.ts to locate every secret, and
7
+ * for each match asks this module to produce a stable ENV reference name.
8
+ * The name is what gets written to disk as `${ENV_REF_NAME}` so the importer
9
+ * can wire it up via environment variables.
10
+ *
11
+ * Naming policy (AC2.c):
12
+ * - mcp `env.<KEY>` → `<UPPER_NAME>_<UPPER_KEY>`
13
+ * - mcp `headers.Authorization` → `BEARER_TOKEN_<UPPER_NAME>`
14
+ * - mcp other paths → `<UPPER_NAME>_<UPPER_LAST_PATH_SEG>`
15
+ * - hook `command` / `prompt` → `HOOK_<EVENT>_TOKEN`
16
+ * - command body match → `COMMAND_<UPPER_SLASH_PATH>_TOKEN`
17
+ * - agent body match → `AGENT_<UPPER_NAME>_TOKEN`
18
+ * - claude-md body match → `CLAUDE_MD_TOKEN_<INDEX>`
19
+ *
20
+ * The names are intentionally not collision-free across multiple secrets in
21
+ * the same card — the export pipeline appends a numeric suffix when the same
22
+ * name is already emitted in that bundle (handled by the caller, not here),
23
+ * so this module stays a pure-function lookup table.
24
+ */
25
+ import type { BundleItemDomain } from '@hammoc/shared';
26
+ export interface NamePlaceholderInput {
27
+ /** Card domain — disambiguates the naming rule. */
28
+ domain: BundleItemDomain;
29
+ /**
30
+ * For mcp/hook: the dot-path returned by `detectSecretsInValue` (e.g.
31
+ * `mcpServers.context7.env.API_KEY`, `mcpServers.gh.headers.Authorization`).
32
+ * For command/agent/claude-md: an empty string or `body:<line>`.
33
+ */
34
+ fieldPath: string;
35
+ /** Active card name (skill: skillName, mcp: serverName, agent: name, etc.). */
36
+ cardName: string;
37
+ /** For hook entries — the matched HarnessHookEvent. */
38
+ hookEvent?: string;
39
+ }
40
+ export declare function namePlaceholder(input: NamePlaceholderInput): string;
41
+ //# sourceMappingURL=secretPlaceholderNamer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secretPlaceholderNamer.d.ts","sourceRoot":"","sources":["../../src/utils/secretPlaceholderNamer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAEvD,MAAM,WAAW,oBAAoB;IACnC,mDAAmD;IACnD,MAAM,EAAE,gBAAgB,CAAC;IACzB;;;;OAIG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB,+EAA+E;IAC/E,QAAQ,EAAE,MAAM,CAAC;IACjB,uDAAuD;IACvD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAiBD,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,GAAG,MAAM,CA4CnE"}
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Story 30.3 (Task 2.1): single source of truth for placeholder ENV names
3
+ * used when `secretsPolicy === 'placeholder'`.
4
+ *
5
+ * The export pipeline walks each domain's payload, calls `detectSecretsInValue`
6
+ * / `detectSecretsInText` from secretHeuristic.ts to locate every secret, and
7
+ * for each match asks this module to produce a stable ENV reference name.
8
+ * The name is what gets written to disk as `${ENV_REF_NAME}` so the importer
9
+ * can wire it up via environment variables.
10
+ *
11
+ * Naming policy (AC2.c):
12
+ * - mcp `env.<KEY>` → `<UPPER_NAME>_<UPPER_KEY>`
13
+ * - mcp `headers.Authorization` → `BEARER_TOKEN_<UPPER_NAME>`
14
+ * - mcp other paths → `<UPPER_NAME>_<UPPER_LAST_PATH_SEG>`
15
+ * - hook `command` / `prompt` → `HOOK_<EVENT>_TOKEN`
16
+ * - command body match → `COMMAND_<UPPER_SLASH_PATH>_TOKEN`
17
+ * - agent body match → `AGENT_<UPPER_NAME>_TOKEN`
18
+ * - claude-md body match → `CLAUDE_MD_TOKEN_<INDEX>`
19
+ *
20
+ * The names are intentionally not collision-free across multiple secrets in
21
+ * the same card — the export pipeline appends a numeric suffix when the same
22
+ * name is already emitted in that bundle (handled by the caller, not here),
23
+ * so this module stays a pure-function lookup table.
24
+ */
25
+ const NON_ALNUM_RE = /[^A-Za-z0-9]+/g;
26
+ function toUpperSnake(input) {
27
+ return input
28
+ .trim()
29
+ .replace(NON_ALNUM_RE, '_')
30
+ .replace(/^_+|_+$/g, '')
31
+ .toUpperCase();
32
+ }
33
+ function lastPathSegment(fieldPath) {
34
+ const segments = fieldPath.split('.').filter((s) => s.length > 0);
35
+ return segments[segments.length - 1] ?? '';
36
+ }
37
+ export function namePlaceholder(input) {
38
+ const card = toUpperSnake(input.cardName) || 'UNNAMED';
39
+ switch (input.domain) {
40
+ case 'mcp': {
41
+ const fp = input.fieldPath;
42
+ // Authorization header → BEARER_TOKEN_<CARD>
43
+ if (/(^|\.)headers\.Authorization$/i.test(fp)) {
44
+ return `BEARER_TOKEN_${card}`;
45
+ }
46
+ // env.<KEY> → <CARD>_<KEY>
47
+ const envMatch = fp.match(/(^|\.)env\.([^.]+)$/);
48
+ if (envMatch) {
49
+ return `${card}_${toUpperSnake(envMatch[2])}`;
50
+ }
51
+ // Fallback: last segment
52
+ const seg = toUpperSnake(lastPathSegment(fp)) || 'SECRET';
53
+ return `${card}_${seg}`;
54
+ }
55
+ case 'hook': {
56
+ const evt = toUpperSnake(input.hookEvent ?? 'GENERIC');
57
+ return `HOOK_${evt}_${card}_TOKEN`;
58
+ }
59
+ case 'command': {
60
+ return `COMMAND_${card}_TOKEN`;
61
+ }
62
+ case 'agent': {
63
+ return `AGENT_${card}_TOKEN`;
64
+ }
65
+ case 'claude-md': {
66
+ return `CLAUDE_MD_${card || 'TOKEN'}_${toUpperSnake(input.fieldPath) || 'TOKEN'}`
67
+ .replace(/_+$/, '');
68
+ }
69
+ case 'skill': {
70
+ return `SKILL_${card}_TOKEN`;
71
+ }
72
+ case 'bmad': {
73
+ return `BMAD_${card}_TOKEN`;
74
+ }
75
+ default: {
76
+ const _exhaustive = input.domain;
77
+ return _exhaustive;
78
+ }
79
+ }
80
+ }
81
+ //# sourceMappingURL=secretPlaceholderNamer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secretPlaceholderNamer.js","sourceRoot":"","sources":["../../src/utils/secretPlaceholderNamer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAmBH,MAAM,YAAY,GAAG,gBAAgB,CAAC;AAEtC,SAAS,YAAY,CAAC,KAAa;IACjC,OAAO,KAAK;SACT,IAAI,EAAE;SACN,OAAO,CAAC,YAAY,EAAE,GAAG,CAAC;SAC1B,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC;SACvB,WAAW,EAAE,CAAC;AACnB,CAAC;AAED,SAAS,eAAe,CAAC,SAAiB;IACxC,MAAM,QAAQ,GAAG,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAClE,OAAO,QAAQ,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;AAC7C,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAA2B;IACzD,MAAM,IAAI,GAAG,YAAY,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,SAAS,CAAC;IAEvD,QAAQ,KAAK,CAAC,MAAM,EAAE,CAAC;QACrB,KAAK,KAAK,CAAC,CAAC,CAAC;YACX,MAAM,EAAE,GAAG,KAAK,CAAC,SAAS,CAAC;YAC3B,6CAA6C;YAC7C,IAAI,gCAAgC,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;gBAC9C,OAAO,gBAAgB,IAAI,EAAE,CAAC;YAChC,CAAC;YACD,2BAA2B;YAC3B,MAAM,QAAQ,GAAG,EAAE,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC;YACjD,IAAI,QAAQ,EAAE,CAAC;gBACb,OAAO,GAAG,IAAI,IAAI,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAChD,CAAC;YACD,yBAAyB;YACzB,MAAM,GAAG,GAAG,YAAY,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC,IAAI,QAAQ,CAAC;YAC1D,OAAO,GAAG,IAAI,IAAI,GAAG,EAAE,CAAC;QAC1B,CAAC;QACD,KAAK,MAAM,CAAC,CAAC,CAAC;YACZ,MAAM,GAAG,GAAG,YAAY,CAAC,KAAK,CAAC,SAAS,IAAI,SAAS,CAAC,CAAC;YACvD,OAAO,QAAQ,GAAG,IAAI,IAAI,QAAQ,CAAC;QACrC,CAAC;QACD,KAAK,SAAS,CAAC,CAAC,CAAC;YACf,OAAO,WAAW,IAAI,QAAQ,CAAC;QACjC,CAAC;QACD,KAAK,OAAO,CAAC,CAAC,CAAC;YACb,OAAO,SAAS,IAAI,QAAQ,CAAC;QAC/B,CAAC;QACD,KAAK,WAAW,CAAC,CAAC,CAAC;YACjB,OAAO,aAAa,IAAI,IAAI,OAAO,IAAI,YAAY,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,OAAO,EAAE;iBAC9E,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACxB,CAAC;QACD,KAAK,OAAO,CAAC,CAAC,CAAC;YACb,OAAO,SAAS,IAAI,QAAQ,CAAC;QAC/B,CAAC;QACD,KAAK,MAAM,CAAC,CAAC,CAAC;YACZ,OAAO,QAAQ,IAAI,QAAQ,CAAC;QAC9B,CAAC;QACD,OAAO,CAAC,CAAC,CAAC;YACR,MAAM,WAAW,GAAU,KAAK,CAAC,MAAM,CAAC;YACxC,OAAO,WAAW,CAAC;QACrB,CAAC;IACH,CAAC;AACH,CAAC"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Story 30.2 (Task 1.5): server-side PATH resolver for the
3
+ * `mcp/command-not-on-path` lint rule.
4
+ *
5
+ * Mirrors the `which`/`where` + 5s timeout pattern used by
6
+ * serverController.ts:14-31 for npm path resolution. Absolute paths skip the
7
+ * shell call entirely and fall through to a simple `fs.existsSync` check —
8
+ * faster and avoids spurious `which: not found` lines on stderr for inputs
9
+ * that obviously don't need PATH resolution.
10
+ *
11
+ * The result is intentionally shaped as `{ resolved: string | null }` (not a
12
+ * boolean) so the caller can surface the resolved absolute path in tooltips
13
+ * without re-running the lookup.
14
+ */
15
+ export interface ResolveCommandResult {
16
+ resolved: string | null;
17
+ }
18
+ /**
19
+ * Resolve `cmd` against the current process PATH.
20
+ *
21
+ * - Absolute path → `fs.existsSync` only (no shell).
22
+ * - Relative path containing a separator → resolved relative to `cwd`
23
+ * (uncommon for MCP `command` fields but covered for completeness).
24
+ * - Bare name → `where.exe` on Windows / `which` elsewhere.
25
+ *
26
+ * Never throws — failures collapse to `{ resolved: null }`.
27
+ */
28
+ export declare function resolveCommandOnServerPath(cmd: string): ResolveCommandResult;
29
+ //# sourceMappingURL=serverPathResolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serverPathResolver.d.ts","sourceRoot":"","sources":["../../src/utils/serverPathResolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAMH,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAID;;;;;;;;;GASG;AACH,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,MAAM,GAAG,oBAAoB,CA8B5E"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Story 30.2 (Task 1.5): server-side PATH resolver for the
3
+ * `mcp/command-not-on-path` lint rule.
4
+ *
5
+ * Mirrors the `which`/`where` + 5s timeout pattern used by
6
+ * serverController.ts:14-31 for npm path resolution. Absolute paths skip the
7
+ * shell call entirely and fall through to a simple `fs.existsSync` check —
8
+ * faster and avoids spurious `which: not found` lines on stderr for inputs
9
+ * that obviously don't need PATH resolution.
10
+ *
11
+ * The result is intentionally shaped as `{ resolved: string | null }` (not a
12
+ * boolean) so the caller can surface the resolved absolute path in tooltips
13
+ * without re-running the lookup.
14
+ */
15
+ import { execFileSync } from 'child_process';
16
+ import fs from 'fs';
17
+ import path from 'path';
18
+ const RESOLVE_TIMEOUT_MS = 5000;
19
+ /**
20
+ * Resolve `cmd` against the current process PATH.
21
+ *
22
+ * - Absolute path → `fs.existsSync` only (no shell).
23
+ * - Relative path containing a separator → resolved relative to `cwd`
24
+ * (uncommon for MCP `command` fields but covered for completeness).
25
+ * - Bare name → `where.exe` on Windows / `which` elsewhere.
26
+ *
27
+ * Never throws — failures collapse to `{ resolved: null }`.
28
+ */
29
+ export function resolveCommandOnServerPath(cmd) {
30
+ if (!cmd || typeof cmd !== 'string')
31
+ return { resolved: null };
32
+ const trimmed = cmd.trim();
33
+ if (!trimmed)
34
+ return { resolved: null };
35
+ if (path.isAbsolute(trimmed)) {
36
+ return { resolved: fs.existsSync(trimmed) ? trimmed : null };
37
+ }
38
+ // Path with a separator (e.g. "./bin/foo" or "scripts/run.sh") — resolve
39
+ // against cwd. Server PATH is irrelevant in this case.
40
+ if (trimmed.includes('/') || trimmed.includes('\\')) {
41
+ const abs = path.resolve(process.cwd(), trimmed);
42
+ return { resolved: fs.existsSync(abs) ? abs : null };
43
+ }
44
+ const finder = process.platform === 'win32' ? 'where.exe' : 'which';
45
+ try {
46
+ const stdout = execFileSync(finder, [trimmed], {
47
+ encoding: 'utf-8',
48
+ timeout: RESOLVE_TIMEOUT_MS,
49
+ stdio: ['ignore', 'pipe', 'ignore'],
50
+ });
51
+ const first = stdout.trim().split(/\r?\n/)[0]?.trim();
52
+ return { resolved: first ? first : null };
53
+ }
54
+ catch {
55
+ // Non-zero exit (not found) or timeout — both surface as "unresolved".
56
+ return { resolved: null };
57
+ }
58
+ }
59
+ //# sourceMappingURL=serverPathResolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serverPathResolver.js","sourceRoot":"","sources":["../../src/utils/serverPathResolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AAMxB,MAAM,kBAAkB,GAAG,IAAI,CAAC;AAEhC;;;;;;;;;GASG;AACH,MAAM,UAAU,0BAA0B,CAAC,GAAW;IACpD,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAE/D,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IAC3B,IAAI,CAAC,OAAO;QAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAExC,IAAI,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QAC7B,OAAO,EAAE,QAAQ,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAC/D,CAAC;IAED,yEAAyE;IACzE,uDAAuD;IACvD,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACpD,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,OAAO,CAAC,CAAC;QACjD,OAAO,EAAE,QAAQ,EAAE,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACvD,CAAC;IAED,MAAM,MAAM,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC;IACpE,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,YAAY,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE;YAC7C,QAAQ,EAAE,OAAO;YACjB,OAAO,EAAE,kBAAkB;YAC3B,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC;SACpC,CAAC,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;QACtD,OAAO,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAC5C,CAAC;IAAC,MAAM,CAAC;QACP,uEAAuE;QACvE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC5B,CAAC;AACH,CAAC"}