@agentlip/workspace 0.1.0 → 0.1.1-rc.1

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.
@@ -0,0 +1,62 @@
1
+ /**
2
+ * @agentlip/workspace - Workspace discovery + initialization
3
+ *
4
+ * Provides upward workspace discovery with security boundaries:
5
+ * - Starts at cwd (or provided path)
6
+ * - Walks upward until .agentlip/db.sqlite3 exists
7
+ * - Stops at filesystem boundary OR user home directory
8
+ * - Initializes workspace at starting directory if not found
9
+ */
10
+ /**
11
+ * Result of workspace discovery
12
+ */
13
+ export interface WorkspaceDiscoveryResult {
14
+ /** Absolute path to workspace root directory */
15
+ root: string;
16
+ /** Absolute path to db.sqlite3 file */
17
+ dbPath: string;
18
+ /** Whether workspace was discovered (true) or needs initialization (false) */
19
+ discovered: boolean;
20
+ }
21
+ /**
22
+ * Result of workspace initialization
23
+ */
24
+ export interface WorkspaceInitResult {
25
+ /** Absolute path to workspace root directory */
26
+ root: string;
27
+ /** Absolute path to db.sqlite3 file */
28
+ dbPath: string;
29
+ /** Whether workspace was newly created (true) or already existed (false) */
30
+ created: boolean;
31
+ }
32
+ /**
33
+ * Discover workspace root by walking upward from startPath.
34
+ *
35
+ * Stops at:
36
+ * - Filesystem boundary (device ID change)
37
+ * - User home directory (never traverse above home)
38
+ *
39
+ * @param startPath - Directory to start search from (defaults to cwd)
40
+ * @returns Discovery result or null if no workspace found within boundary
41
+ */
42
+ export declare function discoverWorkspaceRoot(startPath?: string): Promise<WorkspaceDiscoveryResult | null>;
43
+ /**
44
+ * Ensure workspace is initialized at workspaceRoot.
45
+ * Creates .agentlip/ directory and empty db.sqlite3 file if they don't exist.
46
+ *
47
+ * @param workspaceRoot - Directory to initialize workspace in
48
+ * @returns Init result indicating whether workspace was newly created
49
+ */
50
+ export declare function ensureWorkspaceInitialized(workspaceRoot: string): Promise<WorkspaceInitResult>;
51
+ /**
52
+ * Discover workspace or initialize if not found.
53
+ *
54
+ * Combines discovery + initialization:
55
+ * - First tries to discover workspace by walking upward
56
+ * - If not found, initializes workspace at startPath
57
+ *
58
+ * @param startPath - Directory to start search from (defaults to cwd)
59
+ * @returns Discovery result (never null)
60
+ */
61
+ export declare function discoverOrInitWorkspace(startPath?: string): Promise<WorkspaceDiscoveryResult>;
62
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AASH;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,gDAAgD;IAChD,IAAI,EAAE,MAAM,CAAC;IACb,uCAAuC;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,8EAA8E;IAC9E,UAAU,EAAE,OAAO,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,gDAAgD;IAChD,IAAI,EAAE,MAAM,CAAC;IACb,uCAAuC;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,4EAA4E;IAC5E,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;;;;;;;;GASG;AACH,wBAAsB,qBAAqB,CACzC,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,wBAAwB,GAAG,IAAI,CAAC,CAsD1C;AAED;;;;;;GAMG;AACH,wBAAsB,0BAA0B,CAC9C,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,mBAAmB,CAAC,CAyC9B;AAED;;;;;;;;;GASG;AACH,wBAAsB,uBAAuB,CAC3C,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,wBAAwB,CAAC,CAiBnC"}
package/dist/index.js ADDED
@@ -0,0 +1,146 @@
1
+ /**
2
+ * @agentlip/workspace - Workspace discovery + initialization
3
+ *
4
+ * Provides upward workspace discovery with security boundaries:
5
+ * - Starts at cwd (or provided path)
6
+ * - Walks upward until .agentlip/db.sqlite3 exists
7
+ * - Stops at filesystem boundary OR user home directory
8
+ * - Initializes workspace at starting directory if not found
9
+ */
10
+ import { promises as fs } from 'node:fs';
11
+ import { join, dirname, resolve } from 'node:path';
12
+ import { homedir } from 'node:os';
13
+ const WORKSPACE_MARKER = '.agentlip';
14
+ const DB_FILENAME = 'db.sqlite3';
15
+ /**
16
+ * Discover workspace root by walking upward from startPath.
17
+ *
18
+ * Stops at:
19
+ * - Filesystem boundary (device ID change)
20
+ * - User home directory (never traverse above home)
21
+ *
22
+ * @param startPath - Directory to start search from (defaults to cwd)
23
+ * @returns Discovery result or null if no workspace found within boundary
24
+ */
25
+ export async function discoverWorkspaceRoot(startPath) {
26
+ const start = resolve(startPath ?? process.cwd());
27
+ const home = resolve(homedir());
28
+ // Get initial filesystem device ID
29
+ const startStat = await fs.lstat(start);
30
+ const startDevice = startStat.dev;
31
+ let current = start;
32
+ while (true) {
33
+ // Check if .agentlip/db.sqlite3 exists at current level
34
+ const workspaceDir = join(current, WORKSPACE_MARKER);
35
+ const dbPath = join(workspaceDir, DB_FILENAME);
36
+ try {
37
+ await fs.access(dbPath);
38
+ // Found it!
39
+ return {
40
+ root: current,
41
+ dbPath,
42
+ discovered: true
43
+ };
44
+ }
45
+ catch {
46
+ // Not found, continue upward
47
+ }
48
+ // Check boundary conditions before going up
49
+ const parent = dirname(current);
50
+ // Reached filesystem root (parent === current)
51
+ if (parent === current) {
52
+ return null;
53
+ }
54
+ // Stop traversal at user home directory (security boundary)
55
+ if (current === home) {
56
+ return null;
57
+ }
58
+ // Check filesystem boundary (device ID change)
59
+ try {
60
+ const parentStat = await fs.lstat(parent);
61
+ if (parentStat.dev !== startDevice) {
62
+ // Crossed filesystem boundary
63
+ return null;
64
+ }
65
+ }
66
+ catch {
67
+ // Can't stat parent - stop here
68
+ return null;
69
+ }
70
+ current = parent;
71
+ }
72
+ }
73
+ /**
74
+ * Ensure workspace is initialized at workspaceRoot.
75
+ * Creates .agentlip/ directory and empty db.sqlite3 file if they don't exist.
76
+ *
77
+ * @param workspaceRoot - Directory to initialize workspace in
78
+ * @returns Init result indicating whether workspace was newly created
79
+ */
80
+ export async function ensureWorkspaceInitialized(workspaceRoot) {
81
+ const root = resolve(workspaceRoot);
82
+ const workspaceDir = join(root, WORKSPACE_MARKER);
83
+ const dbPath = join(workspaceDir, DB_FILENAME);
84
+ let created = false;
85
+ // Check if db already exists
86
+ try {
87
+ await fs.access(dbPath);
88
+ // Already initialized
89
+ return { root, dbPath, created: false };
90
+ }
91
+ catch {
92
+ // Need to initialize
93
+ }
94
+ // Create .agentlip directory with mode 0700 (owner rwx only)
95
+ try {
96
+ await fs.mkdir(workspaceDir, { mode: 0o700, recursive: true });
97
+ }
98
+ catch (err) {
99
+ // If directory already exists, that's fine
100
+ if (err.code !== 'EEXIST') {
101
+ throw err;
102
+ }
103
+ }
104
+ // Create empty db.sqlite3 with mode 0600 (owner rw only)
105
+ try {
106
+ const handle = await fs.open(dbPath, 'wx', 0o600); // x = fail if exists
107
+ await handle.close();
108
+ created = true;
109
+ }
110
+ catch (err) {
111
+ if (err.code === 'EEXIST') {
112
+ // File was created between our check and now - that's fine
113
+ created = false;
114
+ }
115
+ else {
116
+ throw err;
117
+ }
118
+ }
119
+ return { root, dbPath, created };
120
+ }
121
+ /**
122
+ * Discover workspace or initialize if not found.
123
+ *
124
+ * Combines discovery + initialization:
125
+ * - First tries to discover workspace by walking upward
126
+ * - If not found, initializes workspace at startPath
127
+ *
128
+ * @param startPath - Directory to start search from (defaults to cwd)
129
+ * @returns Discovery result (never null)
130
+ */
131
+ export async function discoverOrInitWorkspace(startPath) {
132
+ const start = resolve(startPath ?? process.cwd());
133
+ // Try discovery first
134
+ const discovered = await discoverWorkspaceRoot(start);
135
+ if (discovered) {
136
+ return discovered;
137
+ }
138
+ // No workspace found - initialize at start path
139
+ const initialized = await ensureWorkspaceInitialized(start);
140
+ return {
141
+ root: initialized.root,
142
+ dbPath: initialized.dbPath,
143
+ discovered: false
144
+ };
145
+ }
146
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentlip/workspace",
3
- "version": "0.1.0",
3
+ "version": "0.1.1-rc.1",
4
4
  "description": "Workspace discovery and initialization for Agentlip",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -17,13 +17,19 @@
17
17
  "access": "public"
18
18
  },
19
19
  "files": [
20
+ "dist/**/*",
20
21
  "src/**/*.ts",
21
22
  "!src/**/*.test.ts"
22
23
  ],
23
24
  "exports": {
24
- ".": "./src/index.ts"
25
+ ".": {
26
+ "bun": "./src/index.ts",
27
+ "types": "./dist/index.d.ts",
28
+ "import": "./dist/index.js"
29
+ }
25
30
  },
26
31
  "scripts": {
32
+ "build": "tsc -p tsconfig.build.json",
27
33
  "test": "bun test",
28
34
  "verify": "bun verify.ts"
29
35
  }