@vibe-agent-toolkit/utils 0.1.36 → 0.1.38
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/dist/asset-reference.d.ts.map +1 -1
- package/dist/asset-reference.js +61 -3
- package/dist/asset-reference.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/project-utils.d.ts +50 -11
- package/dist/project-utils.d.ts.map +1 -1
- package/dist/project-utils.js +160 -32
- package/dist/project-utils.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"asset-reference.d.ts","sourceRoot":"","sources":["../src/asset-reference.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"asset-reference.d.ts","sourceRoot":"","sources":["../src/asset-reference.ts"],"names":[],"mappings":"AAYA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAmBhF"}
|
package/dist/asset-reference.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
1
2
|
import { createRequire } from 'node:module';
|
|
2
3
|
import { pathToFileURL } from 'node:url';
|
|
3
|
-
import { safePath } from './path-utils.js';
|
|
4
|
+
import { isAbsolutePath, safePath } from './path-utils.js';
|
|
4
5
|
// First segment must be a valid npm package name (scoped or unscoped),
|
|
5
6
|
// followed by `/` and at least one subpath segment. Paths starting with
|
|
6
7
|
// `.`, `/`, or a Windows drive letter are filesystem paths, never bare
|
|
@@ -55,8 +56,65 @@ export function resolveAssetReference(specifier, baseDir) {
|
|
|
55
56
|
if (!specifier.startsWith('@') && isModuleNotFound(cause)) {
|
|
56
57
|
return safePath.resolve(baseDir, specifier);
|
|
57
58
|
}
|
|
58
|
-
throw new Error(
|
|
59
|
-
|
|
59
|
+
throw new Error(formatActionableError(specifier, baseDir, cause), { cause: cause });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Build a message that distinguishes the three common failure modes so callers
|
|
64
|
+
* know where to look. Node's raw error often points at the resolved on-disk
|
|
65
|
+
* path, which adopters easily misread as a VAT path-handling bug.
|
|
66
|
+
*/
|
|
67
|
+
function formatActionableError(specifier, baseDir, cause) {
|
|
68
|
+
const headline = formatResolutionError(cause);
|
|
69
|
+
const code = cause?.code;
|
|
70
|
+
const missingPath = extractMissingModulePath(cause);
|
|
71
|
+
// Mode 1: Node walked the package's `exports` map, computed an absolute
|
|
72
|
+
// target path, and that file is not on disk. This is the most confusing
|
|
73
|
+
// case for adopters: the package IS installed, the exports map IS correct,
|
|
74
|
+
// but a build step in the target package didn't run (or produced different
|
|
75
|
+
// output). Name the missing file explicitly and point at the publisher.
|
|
76
|
+
if (code === 'MODULE_NOT_FOUND' && missingPath && missingPath !== specifier && isAbsolutePath(missingPath)) {
|
|
77
|
+
const fileExists = safeExistsSync(missingPath);
|
|
78
|
+
if (!fileExists) {
|
|
79
|
+
return (`Failed to resolve asset reference '${specifier}': ` +
|
|
80
|
+
`the package's "exports" map points to '${missingPath}', but that file does not exist on disk.\n` +
|
|
81
|
+
`Hint: the target package was found, but a build step did not produce this file. ` +
|
|
82
|
+
`Rebuild the publishing package (e.g., \`pnpm --filter <package> build\`) to generate the missing artifact, ` +
|
|
83
|
+
`or verify the package's "exports" subpath pattern matches what its build emits.\n` +
|
|
84
|
+
`Node error: ${headline}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// Mode 2: Exports map didn't expose the requested subpath at all.
|
|
88
|
+
if (code === 'ERR_PACKAGE_PATH_NOT_EXPORTED') {
|
|
89
|
+
return (`Failed to resolve asset reference '${specifier}': ` +
|
|
90
|
+
`the target package does not expose this subpath in its "exports" map.\n` +
|
|
91
|
+
`Hint: check the package's package.json "exports" field — only paths declared there are reachable via bare specifier.\n` +
|
|
92
|
+
`Node error: ${headline}`);
|
|
93
|
+
}
|
|
94
|
+
// Mode 3: Package itself not installed / not reachable from baseDir.
|
|
95
|
+
return (`Failed to resolve asset reference '${specifier}': ${headline}\n` +
|
|
96
|
+
`Check the package's "exports" field, or run install in ${baseDir}.`);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Node's MODULE_NOT_FOUND message has the form: `Cannot find module 'X'`.
|
|
100
|
+
* Pull `X` out so we can decide whether the error refers to the original
|
|
101
|
+
* specifier (package not installed) or to a resolved on-disk path (file
|
|
102
|
+
* missing at exports target).
|
|
103
|
+
*/
|
|
104
|
+
const CANNOT_FIND_MODULE_RE = /Cannot find module '([^']+)'/;
|
|
105
|
+
function extractMissingModulePath(cause) {
|
|
106
|
+
if (!(cause instanceof Error))
|
|
107
|
+
return undefined;
|
|
108
|
+
const match = CANNOT_FIND_MODULE_RE.exec(cause.message);
|
|
109
|
+
return match?.[1];
|
|
110
|
+
}
|
|
111
|
+
function safeExistsSync(filePath) {
|
|
112
|
+
try {
|
|
113
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename -- filePath is a Node-resolved exports target, used only to refine the error message
|
|
114
|
+
return existsSync(filePath);
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return false;
|
|
60
118
|
}
|
|
61
119
|
}
|
|
62
120
|
function isBareSpecifier(value) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"asset-reference.js","sourceRoot":"","sources":["../src/asset-reference.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"asset-reference.js","sourceRoot":"","sources":["../src/asset-reference.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,cAAc,gBAAgB,CAAC;AAG/B,cAAc,iBAAiB,CAAC;AAGhC,cAAc,sBAAsB,CAAC;AAGrC,cAAc,eAAe,CAAC;AAG9B,cAAc,mBAAmB,CAAC;AAGlC,cAAc,wBAAwB,CAAC;AAGvC,cAAc,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,cAAc,gBAAgB,CAAC;AAG/B,cAAc,iBAAiB,CAAC;AAGhC,cAAc,sBAAsB,CAAC;AAGrC,cAAc,eAAe,CAAC;AAG9B,cAAc,mBAAmB,CAAC;AAGlC,cAAc,wBAAwB,CAAC;AAGvC,cAAc,gBAAgB,CAAC;AAI/B,cAAc,oBAAoB,CAAC;AAGnC,cAAc,kBAAkB,CAAC;AAGjC,cAAc,mBAAmB,CAAC;AAGlC,cAAc,wBAAwB,CAAC;AAGvC,cAAc,eAAe,CAAC;AAG9B,cAAc,oBAAoB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -18,7 +18,8 @@ export * from './file-crawler.js';
|
|
|
18
18
|
export * from './gitignore-checker.js';
|
|
19
19
|
// Git utilities (using git commands directly)
|
|
20
20
|
export * from './git-utils.js';
|
|
21
|
-
// Project root discovery (
|
|
21
|
+
// Project root discovery (canonical: config → git → null).
|
|
22
|
+
// CLI-boundary use only — see docs/concepts/roots-and-config.md.
|
|
22
23
|
export * from './project-utils.js';
|
|
23
24
|
// Git tracking cache (for efficient git-ignore checking)
|
|
24
25
|
export * from './git-tracker.js';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,8DAA8D;AAC9D,cAAc,gBAAgB,CAAC;AAE/B,gCAAgC;AAChC,cAAc,iBAAiB,CAAC;AAEhC,2DAA2D;AAC3D,cAAc,sBAAsB,CAAC;AAErC,uBAAuB;AACvB,cAAc,eAAe,CAAC;AAE9B,wCAAwC;AACxC,cAAc,mBAAmB,CAAC;AAElC,sBAAsB;AACtB,cAAc,wBAAwB,CAAC;AAEvC,8CAA8C;AAC9C,cAAc,gBAAgB,CAAC;AAE/B,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,8DAA8D;AAC9D,cAAc,gBAAgB,CAAC;AAE/B,gCAAgC;AAChC,cAAc,iBAAiB,CAAC;AAEhC,2DAA2D;AAC3D,cAAc,sBAAsB,CAAC;AAErC,uBAAuB;AACvB,cAAc,eAAe,CAAC;AAE9B,wCAAwC;AACxC,cAAc,mBAAmB,CAAC;AAElC,sBAAsB;AACtB,cAAc,wBAAwB,CAAC;AAEvC,8CAA8C;AAC9C,cAAc,gBAAgB,CAAC;AAE/B,2DAA2D;AAC3D,iEAAiE;AACjE,cAAc,oBAAoB,CAAC;AAEnC,yDAAyD;AACzD,cAAc,kBAAkB,CAAC;AAEjC,oDAAoD;AACpD,cAAc,mBAAmB,CAAC;AAElC,4CAA4C;AAC5C,cAAc,wBAAwB,CAAC;AAEvC,2DAA2D;AAC3D,cAAc,eAAe,CAAC;AAE9B,oEAAoE;AACpE,cAAc,oBAAoB,CAAC"}
|
package/dist/project-utils.d.ts
CHANGED
|
@@ -1,19 +1,58 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Canonical root-discovery primitives for VAT.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Per spec docs/superpowers/specs/2026-05-17-root-model-and-leading-slash-design.md §6,
|
|
5
|
+
* these are CLI-boundary functions: inner libraries take roots as parameters, not these.
|
|
6
|
+
* All return `string | null` with no internal fallbacks.
|
|
6
7
|
*/
|
|
7
8
|
/**
|
|
8
|
-
* Find the
|
|
9
|
+
* Find the nearest vibe-agent-toolkit.config.yaml by walking up from startDir.
|
|
9
10
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* 2. Fall back to git repository root
|
|
13
|
-
* 3. Fall back to startDir itself (tests / standalone)
|
|
11
|
+
* Returns the path to the config file itself (not its directory). Returns null
|
|
12
|
+
* if no config exists in any ancestor.
|
|
14
13
|
*
|
|
15
|
-
* @param startDir - Directory to start
|
|
16
|
-
* @returns
|
|
14
|
+
* @param startDir - Directory to start the walk from
|
|
15
|
+
* @returns Path to the config file, or null if not found
|
|
17
16
|
*/
|
|
18
|
-
export declare function
|
|
17
|
+
export declare function findConfigFile(startDir: string): string | null;
|
|
18
|
+
/**
|
|
19
|
+
* Find the nearest package.json with a "workspaces" key by walking up.
|
|
20
|
+
*
|
|
21
|
+
* Used for Node-monorepo binary discovery and Node-specific tooling only.
|
|
22
|
+
* NOT a substitute for findProjectRoot — VAT projects are not required to be
|
|
23
|
+
* npm workspaces. Returns null when no workspaces-bearing package.json is found.
|
|
24
|
+
*
|
|
25
|
+
* @param startDir - Directory to start the walk from
|
|
26
|
+
* @returns Path to the workspace root directory, or null if not found
|
|
27
|
+
*/
|
|
28
|
+
export declare function findNodeWorkspaceRoot(startDir: string): string | null;
|
|
29
|
+
/**
|
|
30
|
+
* Reset {@link findProjectRoot}'s module-level cache.
|
|
31
|
+
*
|
|
32
|
+
* Call at the start of each independent CLI invocation so in-process callers
|
|
33
|
+
* (and integration tests sharing a vitest worker) don't observe stale results.
|
|
34
|
+
*/
|
|
35
|
+
export declare function resetProjectRootCaches(): void;
|
|
36
|
+
/**
|
|
37
|
+
* Find the VAT project root.
|
|
38
|
+
*
|
|
39
|
+
* Discovery ladder (checks startDir itself first, then each ancestor):
|
|
40
|
+
* 1. Directory containing vibe-agent-toolkit.config.yaml → that directory
|
|
41
|
+
* 2. Directory containing .git/ → that directory
|
|
42
|
+
* 3. null
|
|
43
|
+
*
|
|
44
|
+
* The config-anchored ladder runs to completion first; only if no config is
|
|
45
|
+
* found anywhere up the tree do we walk a second time looking for .git/.
|
|
46
|
+
* This implements the "config wins over git, regardless of relative depth"
|
|
47
|
+
* semantic from spec §4 (config-file placement is a stronger declaration of
|
|
48
|
+
* intent than the git boundary).
|
|
49
|
+
*
|
|
50
|
+
* Cached at the module level via {@link walkUpCache}. The cache is keyed by
|
|
51
|
+
* each walked directory; entries are written for every dir touched on the
|
|
52
|
+
* walk so subsequent calls from siblings/descendants become Layer-1 hits.
|
|
53
|
+
*
|
|
54
|
+
* @param startDir - Directory to start the walk from
|
|
55
|
+
* @returns Project root directory, or null if neither config nor git root found
|
|
56
|
+
*/
|
|
57
|
+
export declare function findProjectRoot(startDir: string): string | null;
|
|
19
58
|
//# sourceMappingURL=project-utils.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project-utils.d.ts","sourceRoot":"","sources":["../src/project-utils.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"project-utils.d.ts","sourceRoot":"","sources":["../src/project-utils.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAUH;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAY9D;AAED;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAmBrE;AAkBD;;;;;GAKG;AACH,wBAAgB,sBAAsB,IAAI,IAAI,CAE7C;AAoED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAK/D"}
|
package/dist/project-utils.js
CHANGED
|
@@ -1,53 +1,181 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Canonical root-discovery primitives for VAT.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Per spec docs/superpowers/specs/2026-05-17-root-model-and-leading-slash-design.md §6,
|
|
5
|
+
* these are CLI-boundary functions: inner libraries take roots as parameters, not these.
|
|
6
|
+
* All return `string | null` with no internal fallbacks.
|
|
6
7
|
*/
|
|
7
8
|
import { existsSync, readFileSync } from 'node:fs';
|
|
8
|
-
import { dirname } from 'node:path';
|
|
9
|
-
import { gitFindRoot } from './git-utils.js';
|
|
9
|
+
import { dirname, parse } from 'node:path';
|
|
10
10
|
import { safePath } from './path-utils.js';
|
|
11
|
+
const CONFIG_FILENAME = 'vibe-agent-toolkit.config.yaml';
|
|
11
12
|
const PACKAGE_JSON_FILENAME = 'package.json';
|
|
12
13
|
/**
|
|
13
|
-
* Find the
|
|
14
|
+
* Find the nearest vibe-agent-toolkit.config.yaml by walking up from startDir.
|
|
14
15
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* 2. Fall back to git repository root
|
|
18
|
-
* 3. Fall back to startDir itself (tests / standalone)
|
|
16
|
+
* Returns the path to the config file itself (not its directory). Returns null
|
|
17
|
+
* if no config exists in any ancestor.
|
|
19
18
|
*
|
|
20
|
-
* @param startDir - Directory to start
|
|
21
|
-
* @returns
|
|
19
|
+
* @param startDir - Directory to start the walk from
|
|
20
|
+
* @returns Path to the config file, or null if not found
|
|
22
21
|
*/
|
|
23
|
-
export function
|
|
24
|
-
let
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
22
|
+
export function findConfigFile(startDir) {
|
|
23
|
+
let current = safePath.resolve(startDir);
|
|
24
|
+
const root = parse(current).root;
|
|
25
|
+
while (true) {
|
|
26
|
+
const candidate = safePath.join(current, CONFIG_FILENAME);
|
|
27
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename -- walk-up is intentional
|
|
28
|
+
if (existsSync(candidate))
|
|
29
|
+
return candidate;
|
|
30
|
+
if (current === root)
|
|
31
|
+
return null;
|
|
32
|
+
const parent = dirname(current);
|
|
33
|
+
if (parent === current)
|
|
34
|
+
return null;
|
|
35
|
+
current = parent;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Find the nearest package.json with a "workspaces" key by walking up.
|
|
40
|
+
*
|
|
41
|
+
* Used for Node-monorepo binary discovery and Node-specific tooling only.
|
|
42
|
+
* NOT a substitute for findProjectRoot — VAT projects are not required to be
|
|
43
|
+
* npm workspaces. Returns null when no workspaces-bearing package.json is found.
|
|
44
|
+
*
|
|
45
|
+
* @param startDir - Directory to start the walk from
|
|
46
|
+
* @returns Path to the workspace root directory, or null if not found
|
|
47
|
+
*/
|
|
48
|
+
export function findNodeWorkspaceRoot(startDir) {
|
|
49
|
+
let current = safePath.resolve(startDir);
|
|
50
|
+
while (current !== dirname(current)) {
|
|
51
|
+
const pkgPath = safePath.join(current, PACKAGE_JSON_FILENAME);
|
|
52
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename -- walk-up is intentional
|
|
53
|
+
if (existsSync(pkgPath)) {
|
|
31
54
|
try {
|
|
32
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename --
|
|
33
|
-
const
|
|
34
|
-
const parsed = JSON.parse(content);
|
|
55
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename -- walk-up is intentional
|
|
56
|
+
const parsed = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
35
57
|
if (typeof parsed === 'object' && parsed !== null && 'workspaces' in parsed) {
|
|
36
|
-
return
|
|
58
|
+
return current;
|
|
37
59
|
}
|
|
38
60
|
}
|
|
39
61
|
catch {
|
|
40
|
-
// Invalid JSON
|
|
62
|
+
// Invalid JSON — skip and continue walking up.
|
|
41
63
|
}
|
|
42
64
|
}
|
|
43
|
-
|
|
65
|
+
current = dirname(current);
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Layer 1 cache for {@link findProjectRoot}.
|
|
71
|
+
*
|
|
72
|
+
* Each entry answers a property *of the keyed directory*: "what `projectRoot`
|
|
73
|
+
* governs files at or below this dir?" Because the answer is independent of
|
|
74
|
+
* where a walk-up started, entries can be safely shared across starting
|
|
75
|
+
* points.
|
|
76
|
+
*
|
|
77
|
+
* Tests that mutate fixtures between runs (or in-process callers that
|
|
78
|
+
* re-enter `vat audit` in the same process) must call
|
|
79
|
+
* {@link resetProjectRootCaches} to invalidate this cache.
|
|
80
|
+
*
|
|
81
|
+
* See spec docs/superpowers/specs/2026-05-17-root-model-and-leading-slash-design.md §8.
|
|
82
|
+
*/
|
|
83
|
+
const walkUpCache = new Map();
|
|
84
|
+
/**
|
|
85
|
+
* Reset {@link findProjectRoot}'s module-level cache.
|
|
86
|
+
*
|
|
87
|
+
* Call at the start of each independent CLI invocation so in-process callers
|
|
88
|
+
* (and integration tests sharing a vitest worker) don't observe stale results.
|
|
89
|
+
*/
|
|
90
|
+
export function resetProjectRootCaches() {
|
|
91
|
+
walkUpCache.clear();
|
|
92
|
+
}
|
|
93
|
+
/** Write `entry` into walkUpCache for every dir in `visited`. */
|
|
94
|
+
function propagateCache(visited, entry) {
|
|
95
|
+
for (const dir of visited)
|
|
96
|
+
walkUpCache.set(dir, entry);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Config-anchored walk-up phase. Walks ancestors of `startDir`, populating
|
|
100
|
+
* `visited` and consulting/writing the walk-up cache.
|
|
101
|
+
*
|
|
102
|
+
* Returns one of three results:
|
|
103
|
+
* - `{ kind: 'found', configRoot }` — a config or cache hit produced an
|
|
104
|
+
* answer; cache has already been propagated to all visited dirs.
|
|
105
|
+
* - `{ kind: 'exhausted' }` — reached filesystem root without finding a
|
|
106
|
+
* config; the caller should run the git-anchored phase.
|
|
107
|
+
*
|
|
108
|
+
* @returns walk result
|
|
109
|
+
*/
|
|
110
|
+
function configWalkPhase(startDir, visited) {
|
|
111
|
+
let current = safePath.resolve(startDir);
|
|
112
|
+
while (true) {
|
|
113
|
+
const cached = walkUpCache.get(current);
|
|
114
|
+
if (cached !== undefined) {
|
|
115
|
+
propagateCache(visited, cached);
|
|
116
|
+
return { kind: 'found', configRoot: cached.configRoot };
|
|
117
|
+
}
|
|
118
|
+
visited.push(current);
|
|
119
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename -- walk-up is intentional
|
|
120
|
+
if (existsSync(safePath.join(current, CONFIG_FILENAME))) {
|
|
121
|
+
const entry = { configRoot: current };
|
|
122
|
+
propagateCache(visited, entry);
|
|
123
|
+
return { kind: 'found', configRoot: current };
|
|
124
|
+
}
|
|
125
|
+
const parent = dirname(current);
|
|
126
|
+
if (parent === current)
|
|
127
|
+
return { kind: 'exhausted' };
|
|
128
|
+
current = parent;
|
|
44
129
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Git-anchored walk-up phase. Only runs after `configWalkPhase` reports
|
|
133
|
+
* `exhausted`. Walks the same chain looking for `.git/` and writes the final
|
|
134
|
+
* answer (the .git dir or null) into walkUpCache for every visited dir.
|
|
135
|
+
*/
|
|
136
|
+
function gitWalkPhase(startDir, visited) {
|
|
137
|
+
let current = safePath.resolve(startDir);
|
|
138
|
+
while (true) {
|
|
139
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename -- walk-up is intentional
|
|
140
|
+
if (existsSync(safePath.join(current, '.git'))) {
|
|
141
|
+
const entry = { configRoot: current };
|
|
142
|
+
propagateCache(visited, entry);
|
|
143
|
+
return current;
|
|
144
|
+
}
|
|
145
|
+
const parent = dirname(current);
|
|
146
|
+
if (parent === current) {
|
|
147
|
+
propagateCache(visited, { configRoot: null });
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
current = parent;
|
|
49
151
|
}
|
|
50
|
-
|
|
51
|
-
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Find the VAT project root.
|
|
155
|
+
*
|
|
156
|
+
* Discovery ladder (checks startDir itself first, then each ancestor):
|
|
157
|
+
* 1. Directory containing vibe-agent-toolkit.config.yaml → that directory
|
|
158
|
+
* 2. Directory containing .git/ → that directory
|
|
159
|
+
* 3. null
|
|
160
|
+
*
|
|
161
|
+
* The config-anchored ladder runs to completion first; only if no config is
|
|
162
|
+
* found anywhere up the tree do we walk a second time looking for .git/.
|
|
163
|
+
* This implements the "config wins over git, regardless of relative depth"
|
|
164
|
+
* semantic from spec §4 (config-file placement is a stronger declaration of
|
|
165
|
+
* intent than the git boundary).
|
|
166
|
+
*
|
|
167
|
+
* Cached at the module level via {@link walkUpCache}. The cache is keyed by
|
|
168
|
+
* each walked directory; entries are written for every dir touched on the
|
|
169
|
+
* walk so subsequent calls from siblings/descendants become Layer-1 hits.
|
|
170
|
+
*
|
|
171
|
+
* @param startDir - Directory to start the walk from
|
|
172
|
+
* @returns Project root directory, or null if neither config nor git root found
|
|
173
|
+
*/
|
|
174
|
+
export function findProjectRoot(startDir) {
|
|
175
|
+
const visited = [];
|
|
176
|
+
const configPhase = configWalkPhase(startDir, visited);
|
|
177
|
+
if (configPhase.kind === 'found')
|
|
178
|
+
return configPhase.configRoot;
|
|
179
|
+
return gitWalkPhase(startDir, visited);
|
|
52
180
|
}
|
|
53
181
|
//# sourceMappingURL=project-utils.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project-utils.js","sourceRoot":"","sources":["../src/project-utils.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"project-utils.js","sourceRoot":"","sources":["../src/project-utils.ts"],"names":[],"mappings":"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"}
|