hammoc 1.6.0 → 1.7.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 +6 -4
- package/package.json +2 -2
- package/packages/client/dist/assets/{agentExampleHighlight-ltj9ce0U.js → agentExampleHighlight-7Olvqdpf.js} +1 -1
- package/packages/client/dist/assets/{commandTokenHighlight-ji_ViMb4.js → commandTokenHighlight-DtKwh4lx.js} +1 -1
- package/packages/client/dist/assets/{index-BT4RIi0U.js → index-Bee52CKG.js} +556 -510
- package/packages/client/dist/assets/index-CVy582lU.css +32 -0
- package/packages/client/dist/assets/{index-B-DiRGuz.js → index-DLHBUw8c.js} +1 -1
- package/packages/client/dist/assets/{snippetTokenHighlight-CP3v4o2g.js → snippetTokenHighlight-C8O688f4.js} +1 -1
- package/packages/client/dist/index.html +2 -2
- package/packages/client/dist/sw.js +2 -2
- package/packages/server/dist/_dbg_agent_rows.d.ts +2 -0
- package/packages/server/dist/_dbg_agent_rows.d.ts.map +1 -0
- package/packages/server/dist/_dbg_agent_rows.js +60 -0
- package/packages/server/dist/_dbg_agent_rows.js.map +1 -0
- package/packages/server/dist/_dbg_flicker.d.ts +2 -0
- package/packages/server/dist/_dbg_flicker.d.ts.map +1 -0
- package/packages/server/dist/_dbg_flicker.js +38 -0
- package/packages/server/dist/_dbg_flicker.js.map +1 -0
- package/packages/server/dist/_harness_cli.d.ts +2 -0
- package/packages/server/dist/_harness_cli.d.ts.map +1 -0
- package/packages/server/dist/_harness_cli.js +81 -0
- package/packages/server/dist/_harness_cli.js.map +1 -0
- package/packages/server/dist/_harness_provcard.d.ts +2 -0
- package/packages/server/dist/_harness_provcard.d.ts.map +1 -0
- package/packages/server/dist/_harness_provcard.js +67 -0
- package/packages/server/dist/_harness_provcard.js.map +1 -0
- package/packages/server/dist/_replay_dump.d.ts +2 -0
- package/packages/server/dist/_replay_dump.d.ts.map +1 -0
- package/packages/server/dist/_replay_dump.js +62 -0
- package/packages/server/dist/_replay_dump.js.map +1 -0
- package/packages/server/dist/app.d.ts.map +1 -1
- package/packages/server/dist/app.js +6 -0
- package/packages/server/dist/app.js.map +1 -1
- package/packages/server/dist/config/index.d.ts.map +1 -1
- package/packages/server/dist/config/index.js.map +1 -1
- package/packages/server/dist/controllers/projectController.d.ts.map +1 -1
- package/packages/server/dist/controllers/projectController.js +7 -1
- package/packages/server/dist/controllers/projectController.js.map +1 -1
- package/packages/server/dist/controllers/sessionController.d.ts.map +1 -1
- package/packages/server/dist/controllers/sessionController.js +16 -4
- package/packages/server/dist/controllers/sessionController.js.map +1 -1
- package/packages/server/dist/controllers/systemBrowseController.d.ts +33 -0
- package/packages/server/dist/controllers/systemBrowseController.d.ts.map +1 -0
- package/packages/server/dist/controllers/systemBrowseController.js +105 -0
- package/packages/server/dist/controllers/systemBrowseController.js.map +1 -0
- package/packages/server/dist/handlers/streamCallbacks.d.ts +2 -0
- package/packages/server/dist/handlers/streamCallbacks.d.ts.map +1 -1
- package/packages/server/dist/handlers/streamCallbacks.js +17 -4
- package/packages/server/dist/handlers/streamCallbacks.js.map +1 -1
- package/packages/server/dist/handlers/websocket.d.ts +43 -3
- package/packages/server/dist/handlers/websocket.d.ts.map +1 -1
- package/packages/server/dist/handlers/websocket.js +362 -92
- package/packages/server/dist/handlers/websocket.js.map +1 -1
- package/packages/server/dist/index.js +2 -0
- package/packages/server/dist/index.js.map +1 -1
- package/packages/server/dist/locales/en/server.json +17 -0
- package/packages/server/dist/locales/es/server.json +17 -0
- package/packages/server/dist/locales/ja/server.json +17 -0
- package/packages/server/dist/locales/ko/server.json +17 -0
- package/packages/server/dist/locales/pt/server.json +17 -0
- package/packages/server/dist/locales/zh-CN/server.json +17 -0
- package/packages/server/dist/routes/preferences.d.ts.map +1 -1
- package/packages/server/dist/routes/preferences.js +12 -2
- package/packages/server/dist/routes/preferences.js.map +1 -1
- package/packages/server/dist/routes/systemBrowse.d.ts +17 -0
- package/packages/server/dist/routes/systemBrowse.d.ts.map +1 -0
- package/packages/server/dist/routes/systemBrowse.js +22 -0
- package/packages/server/dist/routes/systemBrowse.js.map +1 -0
- package/packages/server/dist/services/bmadStatusService.d.ts.map +1 -1
- package/packages/server/dist/services/bmadStatusService.js +18 -7
- package/packages/server/dist/services/bmadStatusService.js.map +1 -1
- package/packages/server/dist/services/chatEngine.d.ts +81 -0
- package/packages/server/dist/services/chatEngine.d.ts.map +1 -0
- package/packages/server/dist/services/chatEngine.js +2 -0
- package/packages/server/dist/services/chatEngine.js.map +1 -0
- package/packages/server/dist/services/chatEngineFactory.d.ts +15 -0
- package/packages/server/dist/services/chatEngineFactory.d.ts.map +1 -0
- package/packages/server/dist/services/chatEngineFactory.js +28 -0
- package/packages/server/dist/services/chatEngineFactory.js.map +1 -0
- package/packages/server/dist/services/chatService.d.ts +34 -26
- package/packages/server/dist/services/chatService.d.ts.map +1 -1
- package/packages/server/dist/services/chatService.js +43 -103
- package/packages/server/dist/services/chatService.js.map +1 -1
- package/packages/server/dist/services/cliChatEngine.d.ts +198 -0
- package/packages/server/dist/services/cliChatEngine.d.ts.map +1 -0
- package/packages/server/dist/services/cliChatEngine.js +2977 -0
- package/packages/server/dist/services/cliChatEngine.js.map +1 -0
- package/packages/server/dist/services/cliGridCards.d.ts +87 -0
- package/packages/server/dist/services/cliGridCards.d.ts.map +1 -0
- package/packages/server/dist/services/cliGridCards.js +298 -0
- package/packages/server/dist/services/cliGridCards.js.map +1 -0
- package/packages/server/dist/services/cliGridRegion.d.ts +44 -0
- package/packages/server/dist/services/cliGridRegion.d.ts.map +1 -0
- package/packages/server/dist/services/cliGridRegion.js +94 -0
- package/packages/server/dist/services/cliGridRegion.js.map +1 -0
- package/packages/server/dist/services/cliModalDetect.d.ts +299 -0
- package/packages/server/dist/services/cliModalDetect.d.ts.map +1 -0
- package/packages/server/dist/services/cliModalDetect.js +583 -0
- package/packages/server/dist/services/cliModalDetect.js.map +1 -0
- package/packages/server/dist/services/cliScreenCache.d.ts +33 -0
- package/packages/server/dist/services/cliScreenCache.d.ts.map +1 -0
- package/packages/server/dist/services/cliScreenCache.js +48 -0
- package/packages/server/dist/services/cliScreenCache.js.map +1 -0
- package/packages/server/dist/services/cliScreenModel.d.ts +141 -0
- package/packages/server/dist/services/cliScreenModel.d.ts.map +1 -0
- package/packages/server/dist/services/cliScreenModel.js +196 -0
- package/packages/server/dist/services/cliScreenModel.js.map +1 -0
- package/packages/server/dist/services/cliScreenStallWatchdog.d.ts +41 -0
- package/packages/server/dist/services/cliScreenStallWatchdog.d.ts.map +1 -0
- package/packages/server/dist/services/cliScreenStallWatchdog.js +45 -0
- package/packages/server/dist/services/cliScreenStallWatchdog.js.map +1 -0
- package/packages/server/dist/services/cliSessionPool.d.ts +97 -0
- package/packages/server/dist/services/cliSessionPool.d.ts.map +1 -0
- package/packages/server/dist/services/cliSessionPool.js +278 -0
- package/packages/server/dist/services/cliSessionPool.js.map +1 -0
- package/packages/server/dist/services/cliSpinnerProgress.d.ts +41 -0
- package/packages/server/dist/services/cliSpinnerProgress.d.ts.map +1 -0
- package/packages/server/dist/services/cliSpinnerProgress.js +131 -0
- package/packages/server/dist/services/cliSpinnerProgress.js.map +1 -0
- package/packages/server/dist/services/fileRewind.d.ts +46 -0
- package/packages/server/dist/services/fileRewind.d.ts.map +1 -0
- package/packages/server/dist/services/fileRewind.js +77 -0
- package/packages/server/dist/services/fileRewind.js.map +1 -0
- package/packages/server/dist/services/historyParser.d.ts +11 -0
- package/packages/server/dist/services/historyParser.d.ts.map +1 -1
- package/packages/server/dist/services/historyParser.js +10 -8
- package/packages/server/dist/services/historyParser.js.map +1 -1
- package/packages/server/dist/services/imageStorageService.d.ts +12 -0
- package/packages/server/dist/services/imageStorageService.d.ts.map +1 -1
- package/packages/server/dist/services/imageStorageService.js +22 -0
- package/packages/server/dist/services/imageStorageService.js.map +1 -1
- package/packages/server/dist/services/projectService.d.ts +13 -1
- package/packages/server/dist/services/projectService.d.ts.map +1 -1
- package/packages/server/dist/services/projectService.js +32 -0
- package/packages/server/dist/services/projectService.js.map +1 -1
- package/packages/server/dist/services/queueService.d.ts.map +1 -1
- package/packages/server/dist/services/queueService.js +9 -3
- package/packages/server/dist/services/queueService.js.map +1 -1
- package/packages/server/dist/services/rateLimitProbeService.d.ts +15 -0
- package/packages/server/dist/services/rateLimitProbeService.d.ts.map +1 -1
- package/packages/server/dist/services/rateLimitProbeService.js +48 -19
- package/packages/server/dist/services/rateLimitProbeService.js.map +1 -1
- package/packages/server/dist/services/streamHandler.d.ts +8 -1
- package/packages/server/dist/services/streamHandler.d.ts.map +1 -1
- package/packages/server/dist/services/streamHandler.js +12 -5
- package/packages/server/dist/services/streamHandler.js.map +1 -1
- package/packages/server/dist/services/systemBrowseService.d.ts +91 -0
- package/packages/server/dist/services/systemBrowseService.d.ts.map +1 -0
- package/packages/server/dist/services/systemBrowseService.js +372 -0
- package/packages/server/dist/services/systemBrowseService.js.map +1 -0
- package/packages/server/dist/services/workspaceContext.d.ts +24 -0
- package/packages/server/dist/services/workspaceContext.d.ts.map +1 -0
- package/packages/server/dist/services/workspaceContext.js +109 -0
- package/packages/server/dist/services/workspaceContext.js.map +1 -0
- package/packages/server/dist/snippets/qa-review +2 -0
- package/packages/server/dist/utils/backgroundBlock.d.ts +29 -0
- package/packages/server/dist/utils/backgroundBlock.d.ts.map +1 -0
- package/packages/server/dist/utils/backgroundBlock.js +33 -0
- package/packages/server/dist/utils/backgroundBlock.js.map +1 -0
- package/packages/server/dist/utils/backgroundTaskTracker.d.ts +20 -0
- package/packages/server/dist/utils/backgroundTaskTracker.d.ts.map +1 -0
- package/packages/server/dist/utils/backgroundTaskTracker.js +40 -0
- package/packages/server/dist/utils/backgroundTaskTracker.js.map +1 -0
- package/packages/server/dist/utils/bundledBinaryModelSupport.d.ts +7 -0
- package/packages/server/dist/utils/bundledBinaryModelSupport.d.ts.map +1 -1
- package/packages/server/dist/utils/bundledBinaryModelSupport.js +9 -4
- package/packages/server/dist/utils/bundledBinaryModelSupport.js.map +1 -1
- package/packages/server/dist/utils/cliDebugLog.d.ts +38 -0
- package/packages/server/dist/utils/cliDebugLog.d.ts.map +1 -0
- package/packages/server/dist/utils/cliDebugLog.js +65 -0
- package/packages/server/dist/utils/cliDebugLog.js.map +1 -0
- package/packages/server/dist/utils/cliEngineUtils.d.ts +35 -0
- package/packages/server/dist/utils/cliEngineUtils.d.ts.map +1 -0
- package/packages/server/dist/utils/cliEngineUtils.js +38 -0
- package/packages/server/dist/utils/cliEngineUtils.js.map +1 -0
- package/packages/server/dist/utils/effortUtils.d.ts.map +1 -1
- package/packages/server/dist/utils/effortUtils.js +5 -3
- package/packages/server/dist/utils/effortUtils.js.map +1 -1
- package/packages/server/dist/utils/trailingThrottle.d.ts +21 -0
- package/packages/server/dist/utils/trailingThrottle.d.ts.map +1 -0
- package/packages/server/dist/utils/trailingThrottle.js +36 -0
- package/packages/server/dist/utils/trailingThrottle.js.map +1 -0
- package/packages/server/package.json +61 -59
- package/packages/server/resources/internals/image-storage.md +9 -0
- package/packages/server/resources/manual/02-chat.md +22 -3
- package/packages/server/resources/manual/03-sessions.md +1 -0
- package/packages/server/resources/manual/05-projects.md +12 -1
- package/packages/server/resources/manual/11-bmad-method-integration.md +2 -0
- package/packages/server/resources/manual/13-settings.md +57 -5
- 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/bmadStatus.d.ts +16 -0
- package/packages/shared/dist/types/bmadStatus.d.ts.map +1 -1
- package/packages/shared/dist/types/bmadStatus.js.map +1 -1
- package/packages/shared/dist/types/preferences.d.ts +14 -2
- package/packages/shared/dist/types/preferences.d.ts.map +1 -1
- package/packages/shared/dist/types/preferences.js +1 -0
- package/packages/shared/dist/types/preferences.js.map +1 -1
- package/packages/shared/dist/types/project.d.ts +7 -1
- package/packages/shared/dist/types/project.d.ts.map +1 -1
- package/packages/shared/dist/types/project.js.map +1 -1
- package/packages/shared/dist/types/sdk.d.ts +123 -11
- package/packages/shared/dist/types/sdk.d.ts.map +1 -1
- package/packages/shared/dist/types/sdk.js +64 -19
- package/packages/shared/dist/types/sdk.js.map +1 -1
- package/packages/shared/dist/types/streaming.d.ts +2 -2
- package/packages/shared/dist/types/streaming.d.ts.map +1 -1
- package/packages/shared/dist/types/streaming.js.map +1 -1
- package/packages/shared/dist/types/systemBrowse.d.ts +120 -0
- package/packages/shared/dist/types/systemBrowse.d.ts.map +1 -0
- package/packages/shared/dist/types/systemBrowse.js +57 -0
- package/packages/shared/dist/types/systemBrowse.js.map +1 -0
- package/packages/shared/dist/types/websocket.d.ts +38 -0
- package/packages/shared/dist/types/websocket.d.ts.map +1 -1
- package/packages/shared/dist/utils/interruptFiller.d.ts +30 -0
- package/packages/shared/dist/utils/interruptFiller.d.ts.map +1 -0
- package/packages/shared/dist/utils/interruptFiller.js +38 -0
- package/packages/shared/dist/utils/interruptFiller.js.map +1 -0
- package/packages/shared/dist/utils/toolResultContent.d.ts +22 -0
- package/packages/shared/dist/utils/toolResultContent.d.ts.map +1 -0
- package/packages/shared/dist/utils/toolResultContent.js +29 -0
- package/packages/shared/dist/utils/toolResultContent.js.map +1 -0
- package/scripts/run-integration-test.mjs +362 -362
- package/packages/client/dist/assets/index-DyNJ5jEW.css +0 -32
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* System Browse Service
|
|
3
|
+
* Directory-only browsing across the whole host filesystem (Epic 34, Story 34.1).
|
|
4
|
+
*
|
|
5
|
+
* KEY DIFFERENCE from fileSystemService: there is NO project boundary. This API
|
|
6
|
+
* runs *before* a project is registered, so it accepts arbitrary ABSOLUTE paths
|
|
7
|
+
* (drive roots through any directory). It therefore does NOT use
|
|
8
|
+
* validateProjectPath / validateReadPath. Instead it carries its own guard
|
|
9
|
+
* (assertSafeAbsolutePath) that blocks null bytes / UNC-device paths and enforces
|
|
10
|
+
* absoluteness. The write surface is folder create/rename ONLY — there is no
|
|
11
|
+
* delete method (destructive actions removed from the surface entirely).
|
|
12
|
+
*
|
|
13
|
+
* The guard / realpath / EACCES-skip / errno-mapping idioms are borrowed 1:1
|
|
14
|
+
* from fileSystemService and pathGuard; only the boundary model differs.
|
|
15
|
+
* [Source: docs/prd/epic-34-directory-browser.md#Story 34.1;
|
|
16
|
+
* packages/server/src/services/fileSystemService.ts:162-176, 266-335, 765;
|
|
17
|
+
* packages/server/src/middleware/pathGuard.ts:34-58]
|
|
18
|
+
*/
|
|
19
|
+
import type { BrowseResponse, MkdirResponse, RenameResponse } from '@hammoc/shared';
|
|
20
|
+
declare class SystemBrowseService {
|
|
21
|
+
/**
|
|
22
|
+
* Build a domain error whose `code` the controller maps to an HTTP status +
|
|
23
|
+
* i18n message (same idiom as fileSystemService: `(err).code = 'CODE'`).
|
|
24
|
+
*/
|
|
25
|
+
private makeError;
|
|
26
|
+
/**
|
|
27
|
+
* Guard an incoming absolute path. Blocks null bytes and UNC/device paths,
|
|
28
|
+
* enforces absoluteness, and normalizes via path.resolve. Throws INVALID_PATH.
|
|
29
|
+
* Mirrors the null-byte / UNC checks from pathGuard, minus the project-root
|
|
30
|
+
* containment (this API has no project boundary).
|
|
31
|
+
*/
|
|
32
|
+
private assertSafeAbsolutePath;
|
|
33
|
+
/**
|
|
34
|
+
* Validate and normalize a single folder name (for mkdir/rename).
|
|
35
|
+
* Strips directory components via basename (traversal guard), then rejects
|
|
36
|
+
* empty / dot names, reserved characters, and Windows reserved device names.
|
|
37
|
+
* If basename(name) !== name the input contained a path separator → traversal
|
|
38
|
+
* attempt → INVALID_NAME. (fileSystemService:765 basename idiom.)
|
|
39
|
+
*/
|
|
40
|
+
private sanitizeEntryName;
|
|
41
|
+
/**
|
|
42
|
+
* Parent path for breadcrumb / up navigation. At a filesystem root (C:\, /)
|
|
43
|
+
* path.dirname returns the same path → return null so the client maps it to
|
|
44
|
+
* the "My PC" drive-roots view.
|
|
45
|
+
*/
|
|
46
|
+
private parentOf;
|
|
47
|
+
/**
|
|
48
|
+
* Whether a directory has at least one child directory (tree-chevron signal).
|
|
49
|
+
* depth-1 guarded readdir, short-circuits on the first directory found. There
|
|
50
|
+
* is NO recursion anywhere in this service, so symlink cycles can never blow
|
|
51
|
+
* the stack — a stronger guarantee than searchFiles' visited-set, achieved
|
|
52
|
+
* structurally. EACCES/EPERM (or any read failure) → false.
|
|
53
|
+
*/
|
|
54
|
+
private hasChildDirectory;
|
|
55
|
+
/**
|
|
56
|
+
* List the child DIRECTORIES of an absolute path (files excluded, AC1).
|
|
57
|
+
* - ENOENT → NOT_FOUND, non-directory → NOT_A_DIRECTORY
|
|
58
|
+
* - per-entry try/catch: EACCES/EPERM and broken symlinks are skipped so the
|
|
59
|
+
* listing returns a partial result instead of failing (AC4)
|
|
60
|
+
* - symlinks are resolved via realpath and evaluated as directory-or-not (AC3);
|
|
61
|
+
* no recursion means cycles cannot cause infinite descent
|
|
62
|
+
*/
|
|
63
|
+
listDirectory(absolutePath: string): Promise<BrowseResponse>;
|
|
64
|
+
/**
|
|
65
|
+
* List drive roots ("My PC" view, AC2).
|
|
66
|
+
* Windows: probe drive letters A–Z with fs.access — NO child_process (wmic is
|
|
67
|
+
* deprecated on newer Windows; PowerShell spawns are slow / shell-dependent /
|
|
68
|
+
* test-fragile). Drives that exist but are inaccessible (empty optical drive,
|
|
69
|
+
* locked BitLocker volume) make access() throw and are naturally omitted —
|
|
70
|
+
* this is the INTENDED meaning of AC2's "available drives", not a defect.
|
|
71
|
+
* POSIX: root "/" is always present; standard mount-point parents
|
|
72
|
+
* (/Volumes, /mnt, /media) are added best-effort only when they exist.
|
|
73
|
+
*/
|
|
74
|
+
listDriveRoots(): Promise<BrowseResponse>;
|
|
75
|
+
/**
|
|
76
|
+
* Create a new folder under an absolute parent directory (AC5).
|
|
77
|
+
* EEXIST → ALREADY_EXISTS (409); EACCES/EPERM/EROFS → PERMISSION_DENIED (403,
|
|
78
|
+
* AC9 — OS-protected areas are refused safely, never a crash/500).
|
|
79
|
+
*/
|
|
80
|
+
makeDirectory(parentAbsolute: string, name: string): Promise<MkdirResponse>;
|
|
81
|
+
/**
|
|
82
|
+
* Rename an entry within its SAME parent directory (AC6).
|
|
83
|
+
* Target-exists → ALREADY_EXISTS (409); EACCES/EPERM/EROFS → PERMISSION_DENIED
|
|
84
|
+
* (AC9). The new name is sanitized and joined to dirname(source), so a rename
|
|
85
|
+
* can never move an entry into another directory.
|
|
86
|
+
*/
|
|
87
|
+
rename(absolute: string, newName: string): Promise<RenameResponse>;
|
|
88
|
+
}
|
|
89
|
+
export declare const systemBrowseService: SystemBrowseService;
|
|
90
|
+
export {};
|
|
91
|
+
//# sourceMappingURL=systemBrowseService.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"systemBrowseService.d.ts","sourceRoot":"","sources":["../../src/services/systemBrowseService.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAKH,OAAO,KAAK,EAEV,cAAc,EACd,aAAa,EACb,cAAc,EACf,MAAM,gBAAgB,CAAC;AAmCxB,cAAM,mBAAmB;IACvB;;;OAGG;IACH,OAAO,CAAC,SAAS;IAMjB;;;;;OAKG;IACH,OAAO,CAAC,sBAAsB;IAyB9B;;;;;;OAMG;IACH,OAAO,CAAC,iBAAiB;IAuBzB;;;;OAIG;IACH,OAAO,CAAC,QAAQ;IAKhB;;;;;;OAMG;YACW,iBAAiB;IAyB/B;;;;;;;OAOG;IACG,aAAa,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAyDlE;;;;;;;;;OASG;IACG,cAAc,IAAI,OAAO,CAAC,cAAc,CAAC;IAuC/C;;;;OAIG;IACG,aAAa,CAAC,cAAc,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IA+BjF;;;;;OAKG;IACG,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;CA0CzE;AAED,eAAO,MAAM,mBAAmB,qBAA4B,CAAC"}
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* System Browse Service
|
|
3
|
+
* Directory-only browsing across the whole host filesystem (Epic 34, Story 34.1).
|
|
4
|
+
*
|
|
5
|
+
* KEY DIFFERENCE from fileSystemService: there is NO project boundary. This API
|
|
6
|
+
* runs *before* a project is registered, so it accepts arbitrary ABSOLUTE paths
|
|
7
|
+
* (drive roots through any directory). It therefore does NOT use
|
|
8
|
+
* validateProjectPath / validateReadPath. Instead it carries its own guard
|
|
9
|
+
* (assertSafeAbsolutePath) that blocks null bytes / UNC-device paths and enforces
|
|
10
|
+
* absoluteness. The write surface is folder create/rename ONLY — there is no
|
|
11
|
+
* delete method (destructive actions removed from the surface entirely).
|
|
12
|
+
*
|
|
13
|
+
* The guard / realpath / EACCES-skip / errno-mapping idioms are borrowed 1:1
|
|
14
|
+
* from fileSystemService and pathGuard; only the boundary model differs.
|
|
15
|
+
* [Source: docs/prd/epic-34-directory-browser.md#Story 34.1;
|
|
16
|
+
* packages/server/src/services/fileSystemService.ts:162-176, 266-335, 765;
|
|
17
|
+
* packages/server/src/middleware/pathGuard.ts:34-58]
|
|
18
|
+
*/
|
|
19
|
+
import fs from 'fs/promises';
|
|
20
|
+
import path from 'path';
|
|
21
|
+
import os from 'os';
|
|
22
|
+
/**
|
|
23
|
+
* Windows reserved device names (case-insensitive) — cannot be used as a folder
|
|
24
|
+
* name on Windows even with an extension (e.g. CON.txt). Rejected everywhere for
|
|
25
|
+
* cross-platform consistency.
|
|
26
|
+
*/
|
|
27
|
+
const WINDOWS_RESERVED_NAMES = new Set([
|
|
28
|
+
'CON', 'PRN', 'AUX', 'NUL',
|
|
29
|
+
'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9',
|
|
30
|
+
'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9',
|
|
31
|
+
]);
|
|
32
|
+
/** Printable characters not allowed in a folder name (Windows-strict, safe for POSIX). */
|
|
33
|
+
const RESERVED_PRINTABLE_CHARS = new Set(['<', '>', ':', '"', '/', '\\', '|', '?', '*']);
|
|
34
|
+
/**
|
|
35
|
+
* True if a name contains a reserved printable char OR any control character
|
|
36
|
+
* (0x00–0x1F). Char-code scan avoids a control-char regex literal entirely.
|
|
37
|
+
*/
|
|
38
|
+
function hasReservedChar(name) {
|
|
39
|
+
for (let i = 0; i < name.length; i++) {
|
|
40
|
+
const ch = name[i];
|
|
41
|
+
if (RESERVED_PRINTABLE_CHARS.has(ch))
|
|
42
|
+
return true;
|
|
43
|
+
if (name.charCodeAt(i) < 0x20)
|
|
44
|
+
return true; // control characters
|
|
45
|
+
}
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
/** OS errno codes that mean "write refused" → mapped to PERMISSION_DENIED (AC9). */
|
|
49
|
+
function isWriteDeniedErrno(error) {
|
|
50
|
+
const code = error.code;
|
|
51
|
+
return code === 'EACCES' || code === 'EPERM' || code === 'EROFS';
|
|
52
|
+
}
|
|
53
|
+
class SystemBrowseService {
|
|
54
|
+
/**
|
|
55
|
+
* Build a domain error whose `code` the controller maps to an HTTP status +
|
|
56
|
+
* i18n message (same idiom as fileSystemService: `(err).code = 'CODE'`).
|
|
57
|
+
*/
|
|
58
|
+
makeError(code, message) {
|
|
59
|
+
const err = new Error(message);
|
|
60
|
+
err.code = code;
|
|
61
|
+
return err;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Guard an incoming absolute path. Blocks null bytes and UNC/device paths,
|
|
65
|
+
* enforces absoluteness, and normalizes via path.resolve. Throws INVALID_PATH.
|
|
66
|
+
* Mirrors the null-byte / UNC checks from pathGuard, minus the project-root
|
|
67
|
+
* containment (this API has no project boundary).
|
|
68
|
+
*/
|
|
69
|
+
assertSafeAbsolutePath(input) {
|
|
70
|
+
if (typeof input !== 'string' || input.length === 0) {
|
|
71
|
+
throw this.makeError('INVALID_PATH', 'Path is required');
|
|
72
|
+
}
|
|
73
|
+
// Null byte
|
|
74
|
+
if (input.includes('\0')) {
|
|
75
|
+
throw this.makeError('INVALID_PATH', 'Invalid path: null byte detected');
|
|
76
|
+
}
|
|
77
|
+
// UNC / device paths (\\server\share, //server, \\?\...) — block network/device access
|
|
78
|
+
if (input.startsWith('\\\\') || input.startsWith('//')) {
|
|
79
|
+
throw this.makeError('INVALID_PATH', 'Invalid path: UNC paths are not allowed');
|
|
80
|
+
}
|
|
81
|
+
// Must be absolute (relative paths are meaningless without a project root)
|
|
82
|
+
if (!path.isAbsolute(input)) {
|
|
83
|
+
throw this.makeError('INVALID_PATH', 'Invalid path: must be absolute');
|
|
84
|
+
}
|
|
85
|
+
const resolved = path.resolve(input);
|
|
86
|
+
// Double-check the resolved form isn't UNC (e.g. mixed-separator input)
|
|
87
|
+
const normalizedForUNC = resolved.replace(/\//g, '\\');
|
|
88
|
+
if (normalizedForUNC.startsWith('\\\\')) {
|
|
89
|
+
throw this.makeError('INVALID_PATH', 'Invalid path: UNC paths are not allowed');
|
|
90
|
+
}
|
|
91
|
+
return resolved;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Validate and normalize a single folder name (for mkdir/rename).
|
|
95
|
+
* Strips directory components via basename (traversal guard), then rejects
|
|
96
|
+
* empty / dot names, reserved characters, and Windows reserved device names.
|
|
97
|
+
* If basename(name) !== name the input contained a path separator → traversal
|
|
98
|
+
* attempt → INVALID_NAME. (fileSystemService:765 basename idiom.)
|
|
99
|
+
*/
|
|
100
|
+
sanitizeEntryName(name) {
|
|
101
|
+
if (typeof name !== 'string') {
|
|
102
|
+
throw this.makeError('INVALID_NAME', 'Name is required');
|
|
103
|
+
}
|
|
104
|
+
const base = path.basename(name);
|
|
105
|
+
// Any directory component (a/b, ../x) makes basename differ from the input.
|
|
106
|
+
if (base !== name) {
|
|
107
|
+
throw this.makeError('INVALID_NAME', 'Name must not contain path separators');
|
|
108
|
+
}
|
|
109
|
+
if (base === '' || base === '.' || base === '..') {
|
|
110
|
+
throw this.makeError('INVALID_NAME', 'Invalid name');
|
|
111
|
+
}
|
|
112
|
+
if (hasReservedChar(base)) {
|
|
113
|
+
throw this.makeError('INVALID_NAME', 'Name contains reserved characters');
|
|
114
|
+
}
|
|
115
|
+
// Reserved device name check uses the stem before the first dot (CON, CON.txt).
|
|
116
|
+
const stem = base.split('.')[0].toUpperCase();
|
|
117
|
+
if (WINDOWS_RESERVED_NAMES.has(stem)) {
|
|
118
|
+
throw this.makeError('INVALID_NAME', 'Name is a reserved device name');
|
|
119
|
+
}
|
|
120
|
+
return base;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Parent path for breadcrumb / up navigation. At a filesystem root (C:\, /)
|
|
124
|
+
* path.dirname returns the same path → return null so the client maps it to
|
|
125
|
+
* the "My PC" drive-roots view.
|
|
126
|
+
*/
|
|
127
|
+
parentOf(abs) {
|
|
128
|
+
const parent = path.dirname(abs);
|
|
129
|
+
return parent === abs ? null : parent;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Whether a directory has at least one child directory (tree-chevron signal).
|
|
133
|
+
* depth-1 guarded readdir, short-circuits on the first directory found. There
|
|
134
|
+
* is NO recursion anywhere in this service, so symlink cycles can never blow
|
|
135
|
+
* the stack — a stronger guarantee than searchFiles' visited-set, achieved
|
|
136
|
+
* structurally. EACCES/EPERM (or any read failure) → false.
|
|
137
|
+
*/
|
|
138
|
+
async hasChildDirectory(absDir) {
|
|
139
|
+
let dirents;
|
|
140
|
+
try {
|
|
141
|
+
dirents = await fs.readdir(absDir, { withFileTypes: true });
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
// EACCES/EPERM/ENOENT/etc. — treat as "no expandable children"
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
for (const dirent of dirents) {
|
|
148
|
+
try {
|
|
149
|
+
if (dirent.isDirectory())
|
|
150
|
+
return true;
|
|
151
|
+
if (dirent.isSymbolicLink()) {
|
|
152
|
+
// readdir() reports the link itself; resolve to see if it targets a dir.
|
|
153
|
+
const target = await fs.realpath(path.join(absDir, dirent.name));
|
|
154
|
+
const st = await fs.stat(target);
|
|
155
|
+
if (st.isDirectory())
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
// broken symlink / unreadable entry — skip
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* List the child DIRECTORIES of an absolute path (files excluded, AC1).
|
|
168
|
+
* - ENOENT → NOT_FOUND, non-directory → NOT_A_DIRECTORY
|
|
169
|
+
* - per-entry try/catch: EACCES/EPERM and broken symlinks are skipped so the
|
|
170
|
+
* listing returns a partial result instead of failing (AC4)
|
|
171
|
+
* - symlinks are resolved via realpath and evaluated as directory-or-not (AC3);
|
|
172
|
+
* no recursion means cycles cannot cause infinite descent
|
|
173
|
+
*/
|
|
174
|
+
async listDirectory(absolutePath) {
|
|
175
|
+
const abs = this.assertSafeAbsolutePath(absolutePath);
|
|
176
|
+
let stat;
|
|
177
|
+
try {
|
|
178
|
+
stat = await fs.stat(abs);
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
const code = error.code;
|
|
182
|
+
if (code === 'ENOENT')
|
|
183
|
+
throw this.makeError('NOT_FOUND', 'Path not found');
|
|
184
|
+
if (isWriteDeniedErrno(error))
|
|
185
|
+
throw this.makeError('PERMISSION_DENIED', 'Permission denied');
|
|
186
|
+
throw this.makeError('BROWSE_ERROR', 'Browse error');
|
|
187
|
+
}
|
|
188
|
+
if (!stat.isDirectory()) {
|
|
189
|
+
throw this.makeError('NOT_A_DIRECTORY', 'Path is not a directory');
|
|
190
|
+
}
|
|
191
|
+
let dirents;
|
|
192
|
+
try {
|
|
193
|
+
dirents = await fs.readdir(abs, { withFileTypes: true });
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
// The whole listing failed (not a per-entry skip) → surface as a coherent code.
|
|
197
|
+
if (isWriteDeniedErrno(error))
|
|
198
|
+
throw this.makeError('PERMISSION_DENIED', 'Permission denied');
|
|
199
|
+
throw this.makeError('BROWSE_ERROR', 'Browse error');
|
|
200
|
+
}
|
|
201
|
+
const entries = [];
|
|
202
|
+
for (const dirent of dirents) {
|
|
203
|
+
try {
|
|
204
|
+
const entryPath = path.join(abs, dirent.name);
|
|
205
|
+
let isDir = dirent.isDirectory();
|
|
206
|
+
if (!isDir && dirent.isSymbolicLink()) {
|
|
207
|
+
// Broken symlink → realpath throws → caught below → skipped.
|
|
208
|
+
const target = await fs.realpath(entryPath);
|
|
209
|
+
const st = await fs.stat(target);
|
|
210
|
+
isDir = st.isDirectory();
|
|
211
|
+
}
|
|
212
|
+
if (!isDir)
|
|
213
|
+
continue; // files excluded (AC1)
|
|
214
|
+
const hasChildren = await this.hasChildDirectory(entryPath);
|
|
215
|
+
entries.push({ name: dirent.name, path: entryPath, hasChildren });
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
// EACCES/EPERM/broken symlink → skip (partial result, AC4/AC3)
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
entries.sort((a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: 'base' }));
|
|
223
|
+
return {
|
|
224
|
+
path: abs,
|
|
225
|
+
parent: this.parentOf(abs),
|
|
226
|
+
home: os.homedir(),
|
|
227
|
+
isDriveRoots: false,
|
|
228
|
+
entries,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* List drive roots ("My PC" view, AC2).
|
|
233
|
+
* Windows: probe drive letters A–Z with fs.access — NO child_process (wmic is
|
|
234
|
+
* deprecated on newer Windows; PowerShell spawns are slow / shell-dependent /
|
|
235
|
+
* test-fragile). Drives that exist but are inaccessible (empty optical drive,
|
|
236
|
+
* locked BitLocker volume) make access() throw and are naturally omitted —
|
|
237
|
+
* this is the INTENDED meaning of AC2's "available drives", not a defect.
|
|
238
|
+
* POSIX: root "/" is always present; standard mount-point parents
|
|
239
|
+
* (/Volumes, /mnt, /media) are added best-effort only when they exist.
|
|
240
|
+
*/
|
|
241
|
+
async listDriveRoots() {
|
|
242
|
+
const entries = [];
|
|
243
|
+
if (process.platform === 'win32') {
|
|
244
|
+
for (let i = 65; i <= 90; i++) { // 'A'..'Z'
|
|
245
|
+
const letter = String.fromCharCode(i);
|
|
246
|
+
const root = `${letter}:\\`;
|
|
247
|
+
try {
|
|
248
|
+
await fs.access(root);
|
|
249
|
+
// Drives are assumed expandable (avoids a probe of every drive's contents).
|
|
250
|
+
entries.push({ name: `${letter}:`, path: root, hasChildren: true });
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
// Non-existent OR inaccessible drive — intentionally omitted (see doc above).
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
entries.push({ name: '/', path: '/', hasChildren: true });
|
|
260
|
+
for (const mount of ['/Volumes', '/mnt', '/media']) {
|
|
261
|
+
try {
|
|
262
|
+
const st = await fs.stat(mount);
|
|
263
|
+
if (st.isDirectory()) {
|
|
264
|
+
entries.push({ name: mount, path: mount, hasChildren: true });
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
catch {
|
|
268
|
+
continue; // best-effort: absent mount parents are skipped
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return {
|
|
273
|
+
path: null,
|
|
274
|
+
parent: null,
|
|
275
|
+
home: os.homedir(),
|
|
276
|
+
isDriveRoots: true,
|
|
277
|
+
entries,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Create a new folder under an absolute parent directory (AC5).
|
|
282
|
+
* EEXIST → ALREADY_EXISTS (409); EACCES/EPERM/EROFS → PERMISSION_DENIED (403,
|
|
283
|
+
* AC9 — OS-protected areas are refused safely, never a crash/500).
|
|
284
|
+
*/
|
|
285
|
+
async makeDirectory(parentAbsolute, name) {
|
|
286
|
+
const parent = this.assertSafeAbsolutePath(parentAbsolute);
|
|
287
|
+
const safe = this.sanitizeEntryName(name);
|
|
288
|
+
let parentStat;
|
|
289
|
+
try {
|
|
290
|
+
parentStat = await fs.stat(parent);
|
|
291
|
+
}
|
|
292
|
+
catch (error) {
|
|
293
|
+
if (error.code === 'ENOENT') {
|
|
294
|
+
throw this.makeError('NOT_FOUND', 'Parent directory not found');
|
|
295
|
+
}
|
|
296
|
+
if (isWriteDeniedErrno(error))
|
|
297
|
+
throw this.makeError('PERMISSION_DENIED', 'Permission denied');
|
|
298
|
+
throw this.makeError('BROWSE_ERROR', 'Browse error');
|
|
299
|
+
}
|
|
300
|
+
if (!parentStat.isDirectory()) {
|
|
301
|
+
throw this.makeError('NOT_A_DIRECTORY', 'Parent path is not a directory');
|
|
302
|
+
}
|
|
303
|
+
const target = path.join(parent, safe);
|
|
304
|
+
try {
|
|
305
|
+
await fs.mkdir(target);
|
|
306
|
+
return { success: true, path: target };
|
|
307
|
+
}
|
|
308
|
+
catch (error) {
|
|
309
|
+
const code = error.code;
|
|
310
|
+
if (code === 'EEXIST')
|
|
311
|
+
throw this.makeError('ALREADY_EXISTS', 'Directory already exists');
|
|
312
|
+
if (code === 'ENOENT')
|
|
313
|
+
throw this.makeError('NOT_FOUND', 'Parent directory not found');
|
|
314
|
+
if (isWriteDeniedErrno(error))
|
|
315
|
+
throw this.makeError('PERMISSION_DENIED', 'Permission denied');
|
|
316
|
+
throw this.makeError('BROWSE_ERROR', 'Browse error');
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Rename an entry within its SAME parent directory (AC6).
|
|
321
|
+
* Target-exists → ALREADY_EXISTS (409); EACCES/EPERM/EROFS → PERMISSION_DENIED
|
|
322
|
+
* (AC9). The new name is sanitized and joined to dirname(source), so a rename
|
|
323
|
+
* can never move an entry into another directory.
|
|
324
|
+
*/
|
|
325
|
+
async rename(absolute, newName) {
|
|
326
|
+
const src = this.assertSafeAbsolutePath(absolute);
|
|
327
|
+
const safe = this.sanitizeEntryName(newName);
|
|
328
|
+
try {
|
|
329
|
+
await fs.stat(src);
|
|
330
|
+
}
|
|
331
|
+
catch (error) {
|
|
332
|
+
if (error.code === 'ENOENT') {
|
|
333
|
+
throw this.makeError('NOT_FOUND', 'Source not found');
|
|
334
|
+
}
|
|
335
|
+
if (isWriteDeniedErrno(error))
|
|
336
|
+
throw this.makeError('PERMISSION_DENIED', 'Permission denied');
|
|
337
|
+
throw this.makeError('BROWSE_ERROR', 'Browse error');
|
|
338
|
+
}
|
|
339
|
+
const target = path.join(path.dirname(src), safe);
|
|
340
|
+
// Target must not already exist (same-parent rename, AC6).
|
|
341
|
+
try {
|
|
342
|
+
await fs.stat(target);
|
|
343
|
+
throw this.makeError('ALREADY_EXISTS', 'Target already exists');
|
|
344
|
+
}
|
|
345
|
+
catch (error) {
|
|
346
|
+
const code = error.code;
|
|
347
|
+
if (code === 'ALREADY_EXISTS')
|
|
348
|
+
throw error;
|
|
349
|
+
if (code !== 'ENOENT') {
|
|
350
|
+
if (isWriteDeniedErrno(error))
|
|
351
|
+
throw this.makeError('PERMISSION_DENIED', 'Permission denied');
|
|
352
|
+
throw this.makeError('BROWSE_ERROR', 'Browse error');
|
|
353
|
+
}
|
|
354
|
+
// ENOENT — target is free, proceed.
|
|
355
|
+
}
|
|
356
|
+
try {
|
|
357
|
+
await fs.rename(src, target);
|
|
358
|
+
return { success: true, oldPath: src, newPath: target };
|
|
359
|
+
}
|
|
360
|
+
catch (error) {
|
|
361
|
+
const code = error.code;
|
|
362
|
+
if (code === 'EEXIST' || code === 'ENOTEMPTY') {
|
|
363
|
+
throw this.makeError('ALREADY_EXISTS', 'Target already exists');
|
|
364
|
+
}
|
|
365
|
+
if (isWriteDeniedErrno(error))
|
|
366
|
+
throw this.makeError('PERMISSION_DENIED', 'Permission denied');
|
|
367
|
+
throw this.makeError('BROWSE_ERROR', 'Browse error');
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
export const systemBrowseService = new SystemBrowseService();
|
|
372
|
+
//# sourceMappingURL=systemBrowseService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"systemBrowseService.js","sourceRoot":"","sources":["../../src/services/systemBrowseService.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default workspace context template with system variables.
|
|
3
|
+
* Variables like {gitBranch} are resolved at runtime via resolveTemplateVariables().
|
|
4
|
+
*/
|
|
5
|
+
export declare const DEFAULT_WORKSPACE_TEMPLATE: string;
|
|
6
|
+
/** Available template variables and their descriptions */
|
|
7
|
+
export declare const TEMPLATE_VARIABLES: readonly [{
|
|
8
|
+
readonly name: "gitBranch";
|
|
9
|
+
readonly description: "Current git branch name";
|
|
10
|
+
}, {
|
|
11
|
+
readonly name: "gitMainBranch";
|
|
12
|
+
readonly description: "Main branch name (main or master)";
|
|
13
|
+
}, {
|
|
14
|
+
readonly name: "gitStatus";
|
|
15
|
+
readonly description: "git status --short output (truncated to 30 lines)";
|
|
16
|
+
}, {
|
|
17
|
+
readonly name: "homeDir";
|
|
18
|
+
readonly description: "Absolute path to the user home directory";
|
|
19
|
+
}];
|
|
20
|
+
/**
|
|
21
|
+
* Resolve template variables like {gitBranch} using the project CWD.
|
|
22
|
+
*/
|
|
23
|
+
export declare function resolveTemplateVariables(template: string, cwd: string): string;
|
|
24
|
+
//# sourceMappingURL=workspaceContext.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspaceContext.d.ts","sourceRoot":"","sources":["../../src/services/workspaceContext.ts"],"names":[],"mappings":"AAgBA;;;GAGG;AACH,eAAO,MAAM,0BAA0B,QAqC3B,CAAC;AAEb,0DAA0D;AAC1D,eAAO,MAAM,kBAAkB;;;;;;;;;;;;EAKrB,CAAC;AAEX;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CA2C9E"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace Context — the Hammoc system-prompt augmentation shared by BOTH chat engines.
|
|
3
|
+
*
|
|
4
|
+
* SDK mode (`chatService`) appends `DEFAULT_WORKSPACE_TEMPLATE` to the `claude_code` system-prompt
|
|
5
|
+
* preset; CLI mode (`cliChatEngine`) reaches the SAME preset via the interactive `claude` binary and
|
|
6
|
+
* appends the identical resolved template through `--append-system-prompt`. This is the single source
|
|
7
|
+
* of truth for "what makes the agent Hammoc-aware" (identity, clickable-link convention, feature
|
|
8
|
+
* pointers, manual/internals locations).
|
|
9
|
+
*
|
|
10
|
+
* Extracted out of `chatService` on purpose: that module pulls in the Agent SDK, so anything that
|
|
11
|
+
* only needs the template (the CLI engine, the preferences/project routes that surface the default)
|
|
12
|
+
* would otherwise drag the SDK in transitively. This file is light — only `os` + `git` (execSync).
|
|
13
|
+
*/
|
|
14
|
+
import os from 'os';
|
|
15
|
+
import { execSync } from 'child_process';
|
|
16
|
+
/**
|
|
17
|
+
* Default workspace context template with system variables.
|
|
18
|
+
* Variables like {gitBranch} are resolved at runtime via resolveTemplateVariables().
|
|
19
|
+
*/
|
|
20
|
+
export const DEFAULT_WORKSPACE_TEMPLATE = [
|
|
21
|
+
'',
|
|
22
|
+
'# Hammoc Context',
|
|
23
|
+
'',
|
|
24
|
+
'You are running inside Hammoc, a web-based IDE for AI-driven development workflows. Hammoc is built on Claude Code with first-class BMAD-METHOD V4 support, fully responsive (the user may instruct from a phone with very short messages), and delegates implementation work to you while the user reviews via the UI.',
|
|
25
|
+
'',
|
|
26
|
+
'## Code References in Text',
|
|
27
|
+
'IMPORTANT: When referencing files or code locations, use markdown link syntax to make them clickable:',
|
|
28
|
+
'- For files: [filename.ts](src/filename.ts)',
|
|
29
|
+
'- For specific lines: [filename.ts:42](src/filename.ts#L42)',
|
|
30
|
+
'- For a range of lines: [filename.ts:42-51](src/filename.ts#L42-L51)',
|
|
31
|
+
'- For folders: [src/utils/](src/utils/)',
|
|
32
|
+
'Unless explicitly asked for by the user, DO NOT USE backticks ` or HTML tags like code for file references - always use markdown [text](link) format.',
|
|
33
|
+
"The URL links should be relative paths from the root of the user's workspace.",
|
|
34
|
+
'',
|
|
35
|
+
'## Hammoc-Specific Features the User May Invoke',
|
|
36
|
+
'When the user mentions any of these Hammoc concepts and you are unsure how the feature behaves, read the matching manual chapter (see Manual Reference below) before guessing:',
|
|
37
|
+
'- **Snippets** (`%name`) — reusable prompt templates with arguments. Files in <project-root>/.hammoc/snippets/ and {homeDir}/.hammoc/snippets/',
|
|
38
|
+
'- **Queue Runner** — batch script of prompts with `@`-prefixed commands (@new, @save, @load, @pause, @model, @delay, @pauseword, @loop/@end, @(/@), # comments)',
|
|
39
|
+
'- **Project Board** — Kanban with Bug/Improvement issues, severity Low/Medium/High/Critical, status workflow Open → Draft → Approved → In Progress → Blocked → Review → Done → Closed',
|
|
40
|
+
'- **BMAD-METHOD V4** — agile workflow with agents (SM, PM, Architect, Dev, QA, PO, etc.); .bmad-core directory holds the methodology files',
|
|
41
|
+
'- **Permission Modes** — Plan / Ask (default) / Auto / Bypass, per-project overridable',
|
|
42
|
+
'- **Sessions** — fork, rewind, summarize & continue, conversation branching',
|
|
43
|
+
'',
|
|
44
|
+
'## Manual Reference',
|
|
45
|
+
'The full Hammoc user manual is sharded by chapter and synced to:',
|
|
46
|
+
' {homeDir}/.hammoc/docs/manual/',
|
|
47
|
+
'Always start by reading the index — it maps each chapter to its trigger keywords and tags chapters as [agent] (worth reading) or [user-setup] (skip):',
|
|
48
|
+
' {homeDir}/.hammoc/docs/manual/INDEX.md',
|
|
49
|
+
'Read only the chapters you actually need. Do not load the full manual at once. The Read tool does not expand `~` so always use the absolute path above.',
|
|
50
|
+
'',
|
|
51
|
+
'## Internals Reference',
|
|
52
|
+
'Hammoc internal mechanisms that the user does not need to see but the agent may need to read or correlate (e.g. on-disk location of attached images) live separately at:',
|
|
53
|
+
' {homeDir}/.hammoc/docs/internals/',
|
|
54
|
+
'Index:',
|
|
55
|
+
' {homeDir}/.hammoc/docs/internals/INDEX.md',
|
|
56
|
+
'Read individual files only when the user request requires the underlying mechanism. Do not pre-load.',
|
|
57
|
+
].join('\n');
|
|
58
|
+
/** Available template variables and their descriptions */
|
|
59
|
+
export const TEMPLATE_VARIABLES = [
|
|
60
|
+
{ name: 'gitBranch', description: 'Current git branch name' },
|
|
61
|
+
{ name: 'gitMainBranch', description: 'Main branch name (main or master)' },
|
|
62
|
+
{ name: 'gitStatus', description: 'git status --short output (truncated to 30 lines)' },
|
|
63
|
+
{ name: 'homeDir', description: 'Absolute path to the user home directory' },
|
|
64
|
+
];
|
|
65
|
+
/**
|
|
66
|
+
* Resolve template variables like {gitBranch} using the project CWD.
|
|
67
|
+
*/
|
|
68
|
+
export function resolveTemplateVariables(template, cwd) {
|
|
69
|
+
const vars = {};
|
|
70
|
+
// Home directory is OS-level and always resolvable, independent of git state.
|
|
71
|
+
// The agent needs the absolute path to read ~/.hammoc/docs/manual/INDEX.md
|
|
72
|
+
// via Read/Edit tools, which do not perform shell-style ~ expansion.
|
|
73
|
+
vars.homeDir = os.homedir();
|
|
74
|
+
try {
|
|
75
|
+
const stdio = ['pipe', 'pipe', 'pipe'];
|
|
76
|
+
const execOpts = { cwd, encoding: 'utf-8', timeout: 3000, stdio };
|
|
77
|
+
vars.gitBranch = execSync('git rev-parse --abbrev-ref HEAD', execOpts).toString().trim();
|
|
78
|
+
let mainBranch = 'main';
|
|
79
|
+
try {
|
|
80
|
+
execSync('git rev-parse --verify refs/heads/main', execOpts);
|
|
81
|
+
mainBranch = 'main';
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
try {
|
|
85
|
+
execSync('git rev-parse --verify refs/heads/master', execOpts);
|
|
86
|
+
mainBranch = 'master';
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// fallback
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
vars.gitMainBranch = mainBranch;
|
|
93
|
+
const gitStatus = execSync('git status --short', { ...execOpts, timeout: 5000 }).toString().trim();
|
|
94
|
+
const statusLines = gitStatus ? gitStatus.split('\n') : [];
|
|
95
|
+
vars.gitStatus = statusLines.length > 30
|
|
96
|
+
? [...statusLines.slice(0, 30), `... and ${statusLines.length - 30} more files`].join('\n')
|
|
97
|
+
: (gitStatus || '(clean)');
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
// Not a git repo or git not available
|
|
101
|
+
vars.gitBranch = '(unknown)';
|
|
102
|
+
vars.gitMainBranch = 'main';
|
|
103
|
+
vars.gitStatus = '(not a git repo)';
|
|
104
|
+
}
|
|
105
|
+
return template.replace(/\{(\w+)\}/g, (match, varName) => {
|
|
106
|
+
return vars[varName] ?? match;
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=workspaceContext.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspaceContext.js","sourceRoot":"","sources":["../../src/services/workspaceContext.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
---
|
|
3
3
|
*review {arg1}
|
|
4
4
|
|
|
5
|
+
CRITICAL — the gate YAML must parse. Write every free-text field (status_reason, finding, fix_recommendation, notes, etc.) as a block scalar (e.g. `status_reason: |`) on its own indented lines, NOT as a quoted string. Free text here routinely contains backticks, colons, apostrophes and SQL/code snippets, and quoting them has repeatedly produced invalid YAML that silently breaks Hammoc's gate detection. Block scalars need no escaping. After writing the gate file, re-read it and confirm it loads as valid YAML (e.g. run a quick parse); if it fails, fix and rewrite before finishing.
|
|
6
|
+
|
|
5
7
|
When you write the quality gate, if the gate decision is CONCERNS or FAIL, also record it in the story so Hammoc tracks that a fix is pending. Append this exact line inside the story's QA Results section, replacing the placeholder with the gate's `updated:` value you just wrote:
|
|
6
8
|
<!-- hammoc:qa-fix gate="<paste the gate's updated value>" applied="false" -->
|
|
7
9
|
For a PASS or WAIVED gate, do not add this marker.
|