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.
- package/README.md +422 -405
- package/bin/hammoc.js +0 -6
- package/package.json +100 -94
- package/packages/client/dist/assets/agentExampleHighlight-BgwTm15v.js +1 -0
- package/packages/client/dist/assets/commandTokenHighlight-BljHwnrK.js +1 -0
- package/packages/client/dist/assets/index-CjyjnXB8.css +32 -0
- package/packages/client/dist/assets/index-D3LxqW3f.js +2 -0
- package/packages/client/dist/assets/index-NqJdhlek.js +1498 -0
- package/packages/client/dist/assets/snippetTokenHighlight-DWsaQXX0.js +1 -0
- package/packages/client/dist/index.html +2 -2
- package/packages/client/dist/sw.js +1 -1
- package/packages/server/dist/app.d.ts.map +1 -1
- package/packages/server/dist/app.js +13 -21
- package/packages/server/dist/app.js.map +1 -1
- package/packages/server/dist/controllers/claudeMdController.d.ts +26 -0
- package/packages/server/dist/controllers/claudeMdController.d.ts.map +1 -0
- package/packages/server/dist/controllers/claudeMdController.js +158 -0
- package/packages/server/dist/controllers/claudeMdController.js.map +1 -0
- package/packages/server/dist/controllers/harnessAgentController.d.ts +28 -0
- package/packages/server/dist/controllers/harnessAgentController.d.ts.map +1 -0
- package/packages/server/dist/controllers/harnessAgentController.js +339 -0
- package/packages/server/dist/controllers/harnessAgentController.js.map +1 -0
- package/packages/server/dist/controllers/harnessCommandController.d.ts +28 -0
- package/packages/server/dist/controllers/harnessCommandController.d.ts.map +1 -0
- package/packages/server/dist/controllers/harnessCommandController.js +382 -0
- package/packages/server/dist/controllers/harnessCommandController.js.map +1 -0
- package/packages/server/dist/controllers/harnessController.d.ts +21 -0
- package/packages/server/dist/controllers/harnessController.d.ts.map +1 -0
- package/packages/server/dist/controllers/harnessController.js +176 -0
- package/packages/server/dist/controllers/harnessController.js.map +1 -0
- package/packages/server/dist/controllers/harnessHookController.d.ts +32 -0
- package/packages/server/dist/controllers/harnessHookController.d.ts.map +1 -0
- package/packages/server/dist/controllers/harnessHookController.js +363 -0
- package/packages/server/dist/controllers/harnessHookController.js.map +1 -0
- package/packages/server/dist/controllers/harnessLintController.d.ts +18 -0
- package/packages/server/dist/controllers/harnessLintController.d.ts.map +1 -0
- package/packages/server/dist/controllers/harnessLintController.js +72 -0
- package/packages/server/dist/controllers/harnessLintController.js.map +1 -0
- package/packages/server/dist/controllers/harnessMcpController.d.ts +28 -0
- package/packages/server/dist/controllers/harnessMcpController.d.ts.map +1 -0
- package/packages/server/dist/controllers/harnessMcpController.js +310 -0
- package/packages/server/dist/controllers/harnessMcpController.js.map +1 -0
- package/packages/server/dist/controllers/harnessPluginController.d.ts +17 -0
- package/packages/server/dist/controllers/harnessPluginController.d.ts.map +1 -0
- package/packages/server/dist/controllers/harnessPluginController.js +115 -0
- package/packages/server/dist/controllers/harnessPluginController.js.map +1 -0
- package/packages/server/dist/controllers/harnessShareScopeController.d.ts +15 -0
- package/packages/server/dist/controllers/harnessShareScopeController.d.ts.map +1 -0
- package/packages/server/dist/controllers/harnessShareScopeController.js +73 -0
- package/packages/server/dist/controllers/harnessShareScopeController.js.map +1 -0
- package/packages/server/dist/controllers/harnessSkillController.d.ts +32 -0
- package/packages/server/dist/controllers/harnessSkillController.d.ts.map +1 -0
- package/packages/server/dist/controllers/harnessSkillController.js +453 -0
- package/packages/server/dist/controllers/harnessSkillController.js.map +1 -0
- package/packages/server/dist/controllers/projectController.d.ts.map +1 -1
- package/packages/server/dist/controllers/projectController.js +11 -0
- package/packages/server/dist/controllers/projectController.js.map +1 -1
- package/packages/server/dist/controllers/snippetController.d.ts +35 -0
- package/packages/server/dist/controllers/snippetController.d.ts.map +1 -0
- package/packages/server/dist/controllers/snippetController.js +294 -0
- package/packages/server/dist/controllers/snippetController.js.map +1 -0
- package/packages/server/dist/handlers/websocket.d.ts +15 -0
- package/packages/server/dist/handlers/websocket.d.ts.map +1 -1
- package/packages/server/dist/handlers/websocket.js +79 -0
- package/packages/server/dist/handlers/websocket.js.map +1 -1
- package/packages/server/dist/index.js +5 -0
- package/packages/server/dist/index.js.map +1 -1
- package/packages/server/dist/locales/en/server.json +37 -4
- package/packages/server/dist/locales/es/server.json +0 -4
- package/packages/server/dist/locales/ja/server.json +0 -4
- package/packages/server/dist/locales/ko/server.json +0 -4
- package/packages/server/dist/locales/pt/server.json +0 -4
- package/packages/server/dist/locales/zh-CN/server.json +0 -4
- package/packages/server/dist/routes/harness.d.ts +8 -0
- package/packages/server/dist/routes/harness.d.ts.map +1 -0
- package/packages/server/dist/routes/harness.js +92 -0
- package/packages/server/dist/routes/harness.js.map +1 -0
- package/packages/server/dist/routes/projects.d.ts.map +1 -1
- package/packages/server/dist/routes/projects.js +5 -60
- package/packages/server/dist/routes/projects.js.map +1 -1
- package/packages/server/dist/routes/snippets.d.ts +14 -0
- package/packages/server/dist/routes/snippets.d.ts.map +1 -0
- package/packages/server/dist/routes/snippets.js +27 -0
- package/packages/server/dist/routes/snippets.js.map +1 -0
- package/packages/server/dist/services/bmadStatusService.d.ts +6 -2
- package/packages/server/dist/services/bmadStatusService.d.ts.map +1 -1
- package/packages/server/dist/services/bmadStatusService.js +88 -32
- package/packages/server/dist/services/bmadStatusService.js.map +1 -1
- package/packages/server/dist/services/chatService.d.ts +3 -0
- package/packages/server/dist/services/chatService.d.ts.map +1 -1
- package/packages/server/dist/services/chatService.js +27 -6
- package/packages/server/dist/services/chatService.js.map +1 -1
- package/packages/server/dist/services/claudeMdService.d.ts +48 -0
- package/packages/server/dist/services/claudeMdService.d.ts.map +1 -0
- package/packages/server/dist/services/claudeMdService.js +240 -0
- package/packages/server/dist/services/claudeMdService.js.map +1 -0
- package/packages/server/dist/services/commandService.d.ts +10 -0
- package/packages/server/dist/services/commandService.d.ts.map +1 -1
- package/packages/server/dist/services/commandService.js +129 -4
- package/packages/server/dist/services/commandService.js.map +1 -1
- package/packages/server/dist/services/fileWatcherService.d.ts +24 -0
- package/packages/server/dist/services/fileWatcherService.d.ts.map +1 -1
- package/packages/server/dist/services/fileWatcherService.js +192 -1
- package/packages/server/dist/services/fileWatcherService.js.map +1 -1
- package/packages/server/dist/services/harnessAgentService.d.ts +79 -0
- package/packages/server/dist/services/harnessAgentService.d.ts.map +1 -0
- package/packages/server/dist/services/harnessAgentService.js +933 -0
- package/packages/server/dist/services/harnessAgentService.js.map +1 -0
- package/packages/server/dist/services/harnessCommandService.d.ts +60 -0
- package/packages/server/dist/services/harnessCommandService.d.ts.map +1 -0
- package/packages/server/dist/services/harnessCommandService.js +853 -0
- package/packages/server/dist/services/harnessCommandService.js.map +1 -0
- package/packages/server/dist/services/harnessHookService.d.ts +55 -0
- package/packages/server/dist/services/harnessHookService.d.ts.map +1 -0
- package/packages/server/dist/services/harnessHookService.js +1060 -0
- package/packages/server/dist/services/harnessHookService.js.map +1 -0
- package/packages/server/dist/services/harnessLintService.d.ts +49 -0
- package/packages/server/dist/services/harnessLintService.d.ts.map +1 -0
- package/packages/server/dist/services/harnessLintService.js +628 -0
- package/packages/server/dist/services/harnessLintService.js.map +1 -0
- package/packages/server/dist/services/harnessMcpService.d.ts +77 -0
- package/packages/server/dist/services/harnessMcpService.d.ts.map +1 -0
- package/packages/server/dist/services/harnessMcpService.js +814 -0
- package/packages/server/dist/services/harnessMcpService.js.map +1 -0
- package/packages/server/dist/services/harnessPluginService.d.ts +66 -0
- package/packages/server/dist/services/harnessPluginService.d.ts.map +1 -0
- package/packages/server/dist/services/harnessPluginService.js +559 -0
- package/packages/server/dist/services/harnessPluginService.js.map +1 -0
- package/packages/server/dist/services/harnessService.d.ts +40 -0
- package/packages/server/dist/services/harnessService.d.ts.map +1 -0
- package/packages/server/dist/services/harnessService.js +222 -0
- package/packages/server/dist/services/harnessService.js.map +1 -0
- package/packages/server/dist/services/harnessShareScopeService.d.ts +31 -0
- package/packages/server/dist/services/harnessShareScopeService.d.ts.map +1 -0
- package/packages/server/dist/services/harnessShareScopeService.js +93 -0
- package/packages/server/dist/services/harnessShareScopeService.js.map +1 -0
- package/packages/server/dist/services/harnessSkillService.d.ts +70 -0
- package/packages/server/dist/services/harnessSkillService.d.ts.map +1 -0
- package/packages/server/dist/services/harnessSkillService.js +636 -0
- package/packages/server/dist/services/harnessSkillService.js.map +1 -0
- package/packages/server/dist/services/issueService.d.ts.map +1 -1
- package/packages/server/dist/services/issueService.js +2 -1
- package/packages/server/dist/services/issueService.js.map +1 -1
- package/packages/server/dist/services/manualSyncService.d.ts +19 -0
- package/packages/server/dist/services/manualSyncService.d.ts.map +1 -0
- package/packages/server/dist/services/manualSyncService.js +110 -0
- package/packages/server/dist/services/manualSyncService.js.map +1 -0
- package/packages/server/dist/services/queueService.d.ts.map +1 -1
- package/packages/server/dist/services/queueService.js +45 -2
- package/packages/server/dist/services/queueService.js.map +1 -1
- package/packages/server/dist/services/snippetService.d.ts +54 -0
- package/packages/server/dist/services/snippetService.d.ts.map +1 -0
- package/packages/server/dist/services/snippetService.js +371 -0
- package/packages/server/dist/services/snippetService.js.map +1 -0
- package/packages/server/dist/services/utils/applyYamlFrontmatterPatch.d.ts +46 -0
- package/packages/server/dist/services/utils/applyYamlFrontmatterPatch.d.ts.map +1 -0
- package/packages/server/dist/services/utils/applyYamlFrontmatterPatch.js +125 -0
- package/packages/server/dist/services/utils/applyYamlFrontmatterPatch.js.map +1 -0
- package/packages/server/dist/snippets/split-commit +9 -0
- package/packages/server/dist/utils/applySecretsPolicy.d.ts +53 -0
- package/packages/server/dist/utils/applySecretsPolicy.d.ts.map +1 -0
- package/packages/server/dist/utils/applySecretsPolicy.js +204 -0
- package/packages/server/dist/utils/applySecretsPolicy.js.map +1 -0
- package/packages/server/dist/utils/assertNoSecretOnShared.d.ts +40 -0
- package/packages/server/dist/utils/assertNoSecretOnShared.d.ts.map +1 -0
- package/packages/server/dist/utils/assertNoSecretOnShared.js +47 -0
- package/packages/server/dist/utils/assertNoSecretOnShared.js.map +1 -0
- package/packages/server/dist/utils/gitignoreFilter.d.ts +23 -0
- package/packages/server/dist/utils/gitignoreFilter.d.ts.map +1 -0
- package/packages/server/dist/utils/gitignoreFilter.js +42 -0
- package/packages/server/dist/utils/gitignoreFilter.js.map +1 -0
- package/packages/server/dist/utils/harnessBundleSchema.d.ts +105 -0
- package/packages/server/dist/utils/harnessBundleSchema.d.ts.map +1 -0
- package/packages/server/dist/utils/harnessBundleSchema.js +79 -0
- package/packages/server/dist/utils/harnessBundleSchema.js.map +1 -0
- package/packages/server/dist/utils/harnessPaths.d.ts +34 -0
- package/packages/server/dist/utils/harnessPaths.d.ts.map +1 -0
- package/packages/server/dist/utils/harnessPaths.js +124 -0
- package/packages/server/dist/utils/harnessPaths.js.map +1 -0
- package/packages/server/dist/utils/secretHeuristic.d.ts +72 -0
- package/packages/server/dist/utils/secretHeuristic.d.ts.map +1 -0
- package/packages/server/dist/utils/secretHeuristic.js +163 -0
- package/packages/server/dist/utils/secretHeuristic.js.map +1 -0
- package/packages/server/dist/utils/secretPlaceholderNamer.d.ts +41 -0
- package/packages/server/dist/utils/secretPlaceholderNamer.d.ts.map +1 -0
- package/packages/server/dist/utils/secretPlaceholderNamer.js +81 -0
- package/packages/server/dist/utils/secretPlaceholderNamer.js.map +1 -0
- package/packages/server/dist/utils/serverPathResolver.d.ts +29 -0
- package/packages/server/dist/utils/serverPathResolver.d.ts.map +1 -0
- package/packages/server/dist/utils/serverPathResolver.js +59 -0
- package/packages/server/dist/utils/serverPathResolver.js.map +1 -0
- package/packages/server/dist/utils/snippetPaths.d.ts +61 -0
- package/packages/server/dist/utils/snippetPaths.d.ts.map +1 -0
- package/packages/server/dist/utils/snippetPaths.js +123 -0
- package/packages/server/dist/utils/snippetPaths.js.map +1 -0
- package/packages/server/dist/utils/structuredEditor.d.ts +34 -0
- package/packages/server/dist/utils/structuredEditor.d.ts.map +1 -0
- package/packages/server/dist/utils/structuredEditor.js +111 -0
- package/packages/server/dist/utils/structuredEditor.js.map +1 -0
- package/packages/server/package.json +4 -1
- package/packages/server/resources/internals/INDEX.md +23 -0
- package/packages/server/resources/internals/harness-files.md +63 -0
- package/packages/server/resources/internals/image-storage.md +43 -0
- package/packages/server/resources/manual/01-getting-started.md +104 -0
- package/packages/server/resources/manual/02-chat.md +285 -0
- package/packages/server/resources/manual/03-sessions.md +48 -0
- package/packages/server/resources/manual/04-slash-commands-favorites.md +152 -0
- package/packages/server/resources/manual/05-projects.md +74 -0
- package/packages/server/resources/manual/06-file-explorer-editor.md +90 -0
- package/packages/server/resources/manual/07-git.md +94 -0
- package/packages/server/resources/manual/08-terminal.md +59 -0
- package/packages/server/resources/manual/09-queue-runner.md +262 -0
- package/packages/server/resources/manual/10-project-board.md +193 -0
- package/packages/server/resources/manual/11-bmad-method-integration.md +128 -0
- package/packages/server/resources/manual/12-harness-workbench.md +175 -0
- package/packages/server/resources/manual/13-settings.md +241 -0
- package/packages/server/resources/manual/14-keyboard-shortcuts.md +68 -0
- package/packages/server/resources/manual/15-environment-variables.md +28 -0
- package/packages/server/resources/manual/16-troubleshooting.md +110 -0
- package/packages/server/resources/manual/INDEX.md +60 -0
- package/packages/shared/dist/index.d.ts +3 -0
- package/packages/shared/dist/index.d.ts.map +1 -1
- package/packages/shared/dist/index.js +6 -0
- package/packages/shared/dist/index.js.map +1 -1
- package/packages/shared/dist/types/command.d.ts +3 -3
- package/packages/shared/dist/types/command.d.ts.map +1 -1
- package/packages/shared/dist/types/harness.d.ts +1211 -0
- package/packages/shared/dist/types/harness.d.ts.map +1 -0
- package/packages/shared/dist/types/harness.js +107 -0
- package/packages/shared/dist/types/harness.js.map +1 -0
- package/packages/shared/dist/types/harnessBundle.d.ts +170 -0
- package/packages/shared/dist/types/harnessBundle.d.ts.map +1 -0
- package/packages/shared/dist/types/harnessBundle.js +18 -0
- package/packages/shared/dist/types/harnessBundle.js.map +1 -0
- package/packages/shared/dist/types/preferences.d.ts +2 -0
- package/packages/shared/dist/types/preferences.d.ts.map +1 -1
- package/packages/shared/dist/types/preferences.js.map +1 -1
- package/packages/shared/dist/types/queue.d.ts +9 -0
- package/packages/shared/dist/types/queue.d.ts.map +1 -1
- package/packages/shared/dist/types/websocket.d.ts +10 -0
- package/packages/shared/dist/types/websocket.d.ts.map +1 -1
- package/packages/shared/dist/utils/markdownSections.d.ts +50 -0
- package/packages/shared/dist/utils/markdownSections.d.ts.map +1 -0
- package/packages/shared/dist/utils/markdownSections.js +111 -0
- package/packages/shared/dist/utils/markdownSections.js.map +1 -0
- package/packages/shared/dist/utils/queueParser.d.ts.map +1 -1
- package/packages/shared/dist/utils/queueParser.js +104 -0
- package/packages/shared/dist/utils/queueParser.js.map +1 -1
- package/scripts/build-manual-shards.mjs +100 -0
- package/packages/client/dist/assets/index-6jREnVYd.js +0 -2
- package/packages/client/dist/assets/index-BFF0iqyW.css +0 -32
- 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"}
|