@praneeth_54/agentdoctor 1.1.1 → 2.0.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.
- package/CHANGELOG.md +164 -7
- package/README.md +263 -513
- package/dist/agents/aider/adapter.d.ts +1 -0
- package/dist/agents/aider/adapter.js +1 -0
- package/dist/agents/aider/detector.d.ts +14 -0
- package/dist/agents/aider/detector.js +120 -0
- package/dist/agents/copilot/adapter.d.ts +1 -0
- package/dist/agents/copilot/adapter.js +1 -0
- package/dist/agents/copilot/detector.d.ts +13 -0
- package/dist/agents/copilot/detector.js +113 -0
- package/dist/agents/gemini/adapter.d.ts +1 -0
- package/dist/agents/gemini/adapter.js +1 -0
- package/dist/agents/gemini/detector.d.ts +13 -0
- package/dist/agents/gemini/detector.js +151 -0
- package/dist/agents/registry.js +13 -1
- package/dist/agents/types.d.ts +1 -1
- package/dist/agents/windsurf/adapter.d.ts +1 -0
- package/dist/agents/windsurf/adapter.js +1 -0
- package/dist/agents/windsurf/detector.d.ts +14 -0
- package/dist/agents/windsurf/detector.js +119 -0
- package/dist/architecture/c4.d.ts +22 -0
- package/dist/architecture/c4.js +62 -0
- package/dist/architecture/contract.d.ts +64 -0
- package/dist/architecture/contract.js +373 -0
- package/dist/assurance/change.d.ts +211 -0
- package/dist/assurance/change.js +624 -0
- package/dist/assurance/proof.d.ts +136 -0
- package/dist/assurance/proof.js +338 -0
- package/dist/auth/index.d.ts +81 -0
- package/dist/auth/index.js +130 -0
- package/dist/auth/rbac.d.ts +9 -0
- package/dist/auth/rbac.js +26 -0
- package/dist/cli/commands/architecture.d.ts +6 -0
- package/dist/cli/commands/architecture.js +109 -0
- package/dist/cli/commands/assurance.d.ts +67 -0
- package/dist/cli/commands/assurance.js +210 -0
- package/dist/cli/commands/brain.d.ts +12 -0
- package/dist/cli/commands/brain.js +162 -0
- package/dist/cli/commands/complete.d.ts +23 -0
- package/dist/cli/commands/complete.js +232 -0
- package/dist/cli/commands/explain.js +1 -1
- package/dist/cli/commands/fix.js +22 -2
- package/dist/cli/commands/mcp.d.ts +8 -0
- package/dist/cli/commands/mcp.js +14 -0
- package/dist/cli/commands/platform.d.ts +17 -0
- package/dist/cli/commands/platform.js +195 -0
- package/dist/cli/commands/policy-graph-run.d.ts +40 -0
- package/dist/cli/commands/policy-graph-run.js +200 -0
- package/dist/cli/commands/v2.d.ts +55 -0
- package/dist/cli/commands/v2.js +278 -0
- package/dist/cli/commands/workspace.d.ts +9 -0
- package/dist/cli/commands/workspace.js +82 -0
- package/dist/cli/program.js +1328 -3
- package/dist/constants.d.ts +3 -2
- package/dist/constants.js +5 -1
- package/dist/contracts/adapters.d.ts +7 -0
- package/dist/contracts/adapters.js +60 -0
- package/dist/contracts/index.d.ts +162 -0
- package/dist/contracts/index.js +18 -0
- package/dist/core/baseline/store.d.ts +48 -0
- package/dist/core/baseline/store.js +163 -0
- package/dist/core/brain-cli/service.d.ts +26 -0
- package/dist/core/brain-cli/service.js +175 -0
- package/dist/core/brain-product/init.d.ts +45 -0
- package/dist/core/brain-product/init.js +235 -0
- package/dist/core/changes/analyze.d.ts +41 -0
- package/dist/core/changes/analyze.js +223 -0
- package/dist/core/context-health/analyze.d.ts +15 -0
- package/dist/core/context-health/analyze.js +234 -0
- package/dist/core/fix/apply.d.ts +21 -4
- package/dist/core/fix/apply.js +118 -18
- package/dist/core/fix/backup.d.ts +30 -0
- package/dist/core/fix/backup.js +193 -0
- package/dist/core/fix/plan.d.ts +6 -3
- package/dist/core/fix/plan.js +113 -9
- package/dist/core/fix/render.d.ts +2 -0
- package/dist/core/fix/render.js +36 -21
- package/dist/core/fix/run.js +5 -1
- package/dist/core/fix/safe-target.d.ts +14 -0
- package/dist/core/fix/safe-target.js +81 -0
- package/dist/core/fix/types.d.ts +12 -1
- package/dist/core/fix/types.js +2 -0
- package/dist/core/fix/writers/claude-settings.js +3 -1
- package/dist/core/fix/writers/codex-config.d.ts +3 -0
- package/dist/core/fix/writers/codex-config.js +24 -3
- package/dist/core/fix/writers/cursorignore.js +3 -1
- package/dist/core/fix/writers/simple-ignore.d.ts +16 -0
- package/dist/core/fix/writers/simple-ignore.js +68 -0
- package/dist/core/monorepo/detect.d.ts +13 -0
- package/dist/core/monorepo/detect.js +121 -0
- package/dist/core/rules/build-context.js +22 -3
- package/dist/core/rules/context/generated-directory.js +11 -1
- package/dist/core/rules/context/large-instruction-file.js +6 -0
- package/dist/core/rules/context/large-log-file.js +16 -1
- package/dist/core/rules/ignore.d.ts +11 -1
- package/dist/core/rules/ignore.js +22 -1
- package/dist/core/rules/instructions/empty-instructions.js +7 -0
- package/dist/core/rules/security/env-file-exposure.js +23 -2
- package/dist/core/rules/security/private-key-file.js +1 -1
- package/dist/core/scanner/scan.js +1 -1
- package/dist/core/schemas/validate.d.ts +6 -0
- package/dist/core/schemas/validate.js +62 -0
- package/dist/core/scoring/compute-scores.d.ts +1 -1
- package/dist/core/scoring/compute-scores.js +5 -1
- package/dist/core/scoring/placeholder.d.ts +1 -1
- package/dist/core/scoring/placeholder.js +5 -1
- package/dist/core/secrets/scan.d.ts +29 -0
- package/dist/core/secrets/scan.js +172 -0
- package/dist/coverage/cobertura.d.ts +6 -0
- package/dist/coverage/cobertura.js +52 -0
- package/dist/coverage/istanbul.d.ts +7 -0
- package/dist/coverage/istanbul.js +134 -0
- package/dist/coverage/lcov.d.ts +7 -0
- package/dist/coverage/lcov.js +59 -0
- package/dist/coverage/load.d.ts +9 -0
- package/dist/coverage/load.js +39 -0
- package/dist/coverage/types.d.ts +23 -0
- package/dist/coverage/types.js +5 -0
- package/dist/dashboard/server.d.ts +24 -0
- package/dist/dashboard/server.js +402 -0
- package/dist/enforcement/runner.d.ts +67 -0
- package/dist/enforcement/runner.js +460 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.js +25 -0
- package/dist/integrations/github/pr-analyze.d.ts +28 -0
- package/dist/integrations/github/pr-analyze.js +98 -0
- package/dist/integrations/local-ai/provider.d.ts +45 -0
- package/dist/integrations/local-ai/provider.js +104 -0
- package/dist/intelligence/git/analyze.d.ts +34 -0
- package/dist/intelligence/git/analyze.js +136 -0
- package/dist/intelligence/graph/build.d.ts +15 -0
- package/dist/intelligence/graph/build.js +259 -0
- package/dist/intelligence/graph/incremental.d.ts +68 -0
- package/dist/intelligence/graph/incremental.js +234 -0
- package/dist/intelligence/resolve/imports.d.ts +36 -0
- package/dist/intelligence/resolve/imports.js +245 -0
- package/dist/knowledge/store.d.ts +25 -0
- package/dist/knowledge/store.js +91 -0
- package/dist/languages/go.d.ts +9 -0
- package/dist/languages/go.js +45 -0
- package/dist/languages/index.d.ts +11 -0
- package/dist/languages/index.js +76 -0
- package/dist/languages/php.d.ts +7 -0
- package/dist/languages/php.js +193 -0
- package/dist/languages/python.d.ts +7 -0
- package/dist/languages/python.js +150 -0
- package/dist/languages/types.d.ts +42 -0
- package/dist/languages/types.js +26 -0
- package/dist/languages/typescript.d.ts +3 -0
- package/dist/languages/typescript.js +95 -0
- package/dist/mcp/agentdoctor/server.d.ts +16 -0
- package/dist/mcp/agentdoctor/server.js +67 -0
- package/dist/mcp/brain/session.js +7 -7
- package/dist/mcp/intelligence/handlers.d.ts +16 -0
- package/dist/mcp/intelligence/handlers.js +364 -0
- package/dist/mcp/intelligence/path-safety.d.ts +9 -0
- package/dist/mcp/intelligence/path-safety.js +63 -0
- package/dist/mcp/intelligence/registry.d.ts +8 -0
- package/dist/mcp/intelligence/registry.js +228 -0
- package/dist/ops/health.d.ts +18 -0
- package/dist/ops/health.js +51 -0
- package/dist/platform/ai-quality/analyze.d.ts +9 -0
- package/dist/platform/ai-quality/analyze.js +65 -0
- package/dist/platform/architecture/drift.d.ts +17 -0
- package/dist/platform/architecture/drift.js +74 -0
- package/dist/platform/auth/local.d.ts +15 -0
- package/dist/platform/auth/local.js +58 -0
- package/dist/platform/context-security/analyze.d.ts +6 -0
- package/dist/platform/context-security/analyze.js +97 -0
- package/dist/platform/firewall/evaluate.d.ts +57 -0
- package/dist/platform/firewall/evaluate.js +359 -0
- package/dist/platform/graph/build.d.ts +6 -0
- package/dist/platform/graph/build.js +189 -0
- package/dist/platform/health/analyze.d.ts +7 -0
- package/dist/platform/health/analyze.js +192 -0
- package/dist/platform/index.d.ts +27 -0
- package/dist/platform/index.js +89 -0
- package/dist/platform/knowledge/analyze.d.ts +20 -0
- package/dist/platform/knowledge/analyze.js +96 -0
- package/dist/platform/provenance/build.d.ts +44 -0
- package/dist/platform/provenance/build.js +65 -0
- package/dist/platform/readiness/scorecard.d.ts +5 -0
- package/dist/platform/readiness/scorecard.js +98 -0
- package/dist/platform/refactor/impact.d.ts +20 -0
- package/dist/platform/refactor/impact.js +60 -0
- package/dist/platform/reports/export.d.ts +12 -0
- package/dist/platform/reports/export.js +92 -0
- package/dist/platform/security/redact.d.ts +15 -0
- package/dist/platform/security/redact.js +87 -0
- package/dist/platform/sessions/store.d.ts +32 -0
- package/dist/platform/sessions/store.js +97 -0
- package/dist/platform/store.d.ts +6 -0
- package/dist/platform/store.js +57 -0
- package/dist/platform/test-impact/analyze.d.ts +50 -0
- package/dist/platform/test-impact/analyze.js +350 -0
- package/dist/platform/time-machine/compare.d.ts +17 -0
- package/dist/platform/time-machine/compare.js +62 -0
- package/dist/platform/tokens/plan.d.ts +28 -0
- package/dist/platform/tokens/plan.js +108 -0
- package/dist/platform/types.d.ts +120 -0
- package/dist/platform/types.js +5 -0
- package/dist/plugins/runtime.d.ts +36 -0
- package/dist/plugins/runtime.js +101 -0
- package/dist/plugins/sdk.d.ts +29 -0
- package/dist/plugins/sdk.js +114 -0
- package/dist/policy/compose.d.ts +34 -0
- package/dist/policy/compose.js +118 -0
- package/dist/policy/packs.d.ts +9 -0
- package/dist/policy/packs.js +91 -0
- package/dist/reporters/terminal/report.js +38 -5
- package/dist/security/paths.d.ts +21 -0
- package/dist/security/paths.js +105 -0
- package/dist/storage/postgres.d.ts +26 -0
- package/dist/storage/postgres.js +90 -0
- package/dist/storage/provider.d.ts +30 -0
- package/dist/storage/provider.js +109 -0
- package/dist/storage/sqlite.d.ts +33 -0
- package/dist/storage/sqlite.js +77 -0
- package/dist/team/auth.d.ts +35 -0
- package/dist/team/auth.js +70 -0
- package/dist/types/index.d.ts +5 -1
- package/dist/workspace/index.d.ts +69 -0
- package/dist/workspace/index.js +220 -0
- package/package.json +15 -5
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { resolveRepoRoot, isPathInsideRoot, toPosixRelative } from "../utils/path.js";
|
|
4
|
+
export class PathEscapeError extends Error {
|
|
5
|
+
code = "PATH_ESCAPE";
|
|
6
|
+
constructor(message = "path escapes repository root") {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = "PathEscapeError";
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
function tryDecodeUri(value) {
|
|
12
|
+
try {
|
|
13
|
+
return decodeURIComponent(value);
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function hasTraversalSegment(value) {
|
|
20
|
+
const normalized = value.replace(/\\/g, "/");
|
|
21
|
+
if (normalized.includes("\0"))
|
|
22
|
+
return true;
|
|
23
|
+
// Reject any `..` occurrence (including encoded forms after decode and `....` tricks).
|
|
24
|
+
if (normalized.includes(".."))
|
|
25
|
+
return true;
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
function isWindowsDriveAbsolute(value) {
|
|
29
|
+
return /^[A-Za-z]:[\\/]/.test(value);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Lexical hostility checks shared by resolve/assert helpers.
|
|
33
|
+
* Does not leak the candidate path in thrown messages.
|
|
34
|
+
*/
|
|
35
|
+
export function rejectHostilePathInput(candidate) {
|
|
36
|
+
if (typeof candidate !== "string" || candidate.length === 0) {
|
|
37
|
+
throw new PathEscapeError("invalid path");
|
|
38
|
+
}
|
|
39
|
+
if (candidate.includes("\0")) {
|
|
40
|
+
throw new PathEscapeError("invalid path");
|
|
41
|
+
}
|
|
42
|
+
const decoded = tryDecodeUri(candidate);
|
|
43
|
+
if (decoded === null) {
|
|
44
|
+
throw new PathEscapeError("invalid path");
|
|
45
|
+
}
|
|
46
|
+
for (const form of decoded === candidate ? [candidate] : [candidate, decoded]) {
|
|
47
|
+
if (form.includes("\0") || hasTraversalSegment(form) || isWindowsDriveAbsolute(form)) {
|
|
48
|
+
throw new PathEscapeError("path escapes repository root");
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function realpathOrSelf(target) {
|
|
53
|
+
try {
|
|
54
|
+
if (fs.existsSync(target)) {
|
|
55
|
+
return fs.realpathSync(target);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// fall through
|
|
60
|
+
}
|
|
61
|
+
return target;
|
|
62
|
+
}
|
|
63
|
+
function realpathRoot(root) {
|
|
64
|
+
try {
|
|
65
|
+
return fs.realpathSync(root);
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return path.resolve(root);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Resolve a user-supplied path and require it to remain inside the repository root.
|
|
73
|
+
* Rejects `..`, URI-encoded traversal, null bytes, Windows-drive absolutes, and
|
|
74
|
+
* symlink realpath escapes. Error messages never echo the hostile candidate.
|
|
75
|
+
*/
|
|
76
|
+
export function resolveSafeRepoPath(rootInput, candidate) {
|
|
77
|
+
const root = resolveRepoRoot(rootInput);
|
|
78
|
+
rejectHostilePathInput(candidate);
|
|
79
|
+
const decoded = tryDecodeUri(candidate) ?? candidate;
|
|
80
|
+
const primary = decoded;
|
|
81
|
+
const absolute = path.isAbsolute(primary) ? path.normalize(primary) : path.resolve(root, primary);
|
|
82
|
+
// Lexical containment against the resolved (non-realpath) root first.
|
|
83
|
+
// On macOS, /var vs /private/var must not false-reject before realpath.
|
|
84
|
+
if (!isPathInsideRoot(root, absolute) && path.resolve(absolute) !== path.resolve(root)) {
|
|
85
|
+
throw new PathEscapeError("path escapes repository root");
|
|
86
|
+
}
|
|
87
|
+
const realRoot = realpathRoot(root);
|
|
88
|
+
const realTarget = realpathOrSelf(absolute);
|
|
89
|
+
// Symlink / realpath escape check (both sides realpath'd when possible).
|
|
90
|
+
if (!isPathInsideRoot(realRoot, realTarget) && realTarget !== realRoot) {
|
|
91
|
+
throw new PathEscapeError("path escapes repository root");
|
|
92
|
+
}
|
|
93
|
+
return realTarget;
|
|
94
|
+
}
|
|
95
|
+
/** Assert `candidate` resolves inside `root`; throws PathEscapeError otherwise. */
|
|
96
|
+
export function assertInsideRepo(rootInput, candidate) {
|
|
97
|
+
resolveSafeRepoPath(rootInput, candidate);
|
|
98
|
+
}
|
|
99
|
+
/** Resolve safely and return a POSIX path relative to the repo root. */
|
|
100
|
+
export function safeRelPath(rootInput, absoluteOrRel) {
|
|
101
|
+
const abs = resolveSafeRepoPath(rootInput, absoluteOrRel);
|
|
102
|
+
const root = realpathRoot(resolveRepoRoot(rootInput));
|
|
103
|
+
return toPosixRelative(root, abs);
|
|
104
|
+
}
|
|
105
|
+
export { tryDecodeUri as tryDecodeUriComponent };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Pool } from "pg";
|
|
2
|
+
import type { StorageProvider } from "../contracts/index.js";
|
|
3
|
+
/**
|
|
4
|
+
* Postgres-backed StorageProvider via the `pg` package.
|
|
5
|
+
* Only usable when a real connection string works — not claimed as always-on enterprise storage.
|
|
6
|
+
*/
|
|
7
|
+
export declare class PostgresStorageProvider implements StorageProvider {
|
|
8
|
+
readonly kind: "postgres";
|
|
9
|
+
private readonly pool;
|
|
10
|
+
private ready;
|
|
11
|
+
constructor(pool: Pool);
|
|
12
|
+
private ensureSchema;
|
|
13
|
+
private withClient;
|
|
14
|
+
get(key: string): Promise<string | null>;
|
|
15
|
+
set(key: string, value: string): Promise<void>;
|
|
16
|
+
delete(key: string): Promise<void>;
|
|
17
|
+
list(prefix: string): Promise<string[]>;
|
|
18
|
+
close(): Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Create Postgres storage when a connection string is provided and the server accepts it.
|
|
22
|
+
* Returns `{ error }` on failure — do not claim Postgres is supported without a working URL.
|
|
23
|
+
*/
|
|
24
|
+
export declare function tryCreatePostgresStorage(connectionString: string): Promise<PostgresStorageProvider | {
|
|
25
|
+
error: string;
|
|
26
|
+
}>;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import pg from "pg";
|
|
2
|
+
const { Pool: PgPool } = pg;
|
|
3
|
+
/**
|
|
4
|
+
* Postgres-backed StorageProvider via the `pg` package.
|
|
5
|
+
* Only usable when a real connection string works — not claimed as always-on enterprise storage.
|
|
6
|
+
*/
|
|
7
|
+
export class PostgresStorageProvider {
|
|
8
|
+
kind = "postgres";
|
|
9
|
+
pool;
|
|
10
|
+
ready;
|
|
11
|
+
constructor(pool) {
|
|
12
|
+
this.pool = pool;
|
|
13
|
+
this.ready = this.ensureSchema();
|
|
14
|
+
}
|
|
15
|
+
async ensureSchema() {
|
|
16
|
+
await this.pool.query(`
|
|
17
|
+
CREATE TABLE IF NOT EXISTS agentdoctor_kv (
|
|
18
|
+
key TEXT PRIMARY KEY,
|
|
19
|
+
value TEXT NOT NULL
|
|
20
|
+
);
|
|
21
|
+
`);
|
|
22
|
+
}
|
|
23
|
+
async withClient(fn) {
|
|
24
|
+
await this.ready;
|
|
25
|
+
const client = await this.pool.connect();
|
|
26
|
+
try {
|
|
27
|
+
return await fn(client);
|
|
28
|
+
}
|
|
29
|
+
finally {
|
|
30
|
+
client.release();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async get(key) {
|
|
34
|
+
return this.withClient(async (client) => {
|
|
35
|
+
const result = await client.query("SELECT value FROM agentdoctor_kv WHERE key = $1", [key]);
|
|
36
|
+
return result.rows[0]?.value ?? null;
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
async set(key, value) {
|
|
40
|
+
await this.withClient(async (client) => {
|
|
41
|
+
await client.query(`INSERT INTO agentdoctor_kv (key, value) VALUES ($1, $2)
|
|
42
|
+
ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value`, [key, value]);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
async delete(key) {
|
|
46
|
+
await this.withClient(async (client) => {
|
|
47
|
+
await client.query("DELETE FROM agentdoctor_kv WHERE key = $1", [key]);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
async list(prefix) {
|
|
51
|
+
return this.withClient(async (client) => {
|
|
52
|
+
const result = await client.query("SELECT key FROM agentdoctor_kv WHERE key LIKE $1 ORDER BY key", [`${prefix}%`]);
|
|
53
|
+
return result.rows.map((r) => r.key);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
async close() {
|
|
57
|
+
await this.pool.end();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Create Postgres storage when a connection string is provided and the server accepts it.
|
|
62
|
+
* Returns `{ error }` on failure — do not claim Postgres is supported without a working URL.
|
|
63
|
+
*/
|
|
64
|
+
export async function tryCreatePostgresStorage(connectionString) {
|
|
65
|
+
if (!connectionString || typeof connectionString !== "string" || !connectionString.trim()) {
|
|
66
|
+
return { error: "Postgres connection string is empty" };
|
|
67
|
+
}
|
|
68
|
+
const pool = new PgPool({
|
|
69
|
+
connectionString: connectionString.trim(),
|
|
70
|
+
max: 2,
|
|
71
|
+
idleTimeoutMillis: 5_000,
|
|
72
|
+
connectionTimeoutMillis: 5_000,
|
|
73
|
+
});
|
|
74
|
+
try {
|
|
75
|
+
const client = await pool.connect();
|
|
76
|
+
try {
|
|
77
|
+
await client.query("SELECT 1");
|
|
78
|
+
}
|
|
79
|
+
finally {
|
|
80
|
+
client.release();
|
|
81
|
+
}
|
|
82
|
+
return new PostgresStorageProvider(pool);
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
await pool.end().catch(() => undefined);
|
|
86
|
+
return {
|
|
87
|
+
error: `Failed to connect to Postgres: ${error instanceof Error ? error.message : String(error)}`,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { StorageProvider } from "../contracts/index.js";
|
|
2
|
+
export declare class FilesystemStorageProvider implements StorageProvider {
|
|
3
|
+
readonly kind: "filesystem";
|
|
4
|
+
private readonly root;
|
|
5
|
+
constructor(rootInput: string, subdir?: string);
|
|
6
|
+
private resolveKey;
|
|
7
|
+
get(key: string): Promise<string | null>;
|
|
8
|
+
set(key: string, value: string): Promise<void>;
|
|
9
|
+
delete(key: string): Promise<void>;
|
|
10
|
+
list(prefix: string): Promise<string[]>;
|
|
11
|
+
}
|
|
12
|
+
/** In-memory provider for tests and optional ephemeral mode. */
|
|
13
|
+
export declare class MemoryStorageProvider implements StorageProvider {
|
|
14
|
+
readonly kind: "memory";
|
|
15
|
+
private readonly map;
|
|
16
|
+
get(key: string): Promise<string | null>;
|
|
17
|
+
set(key: string, value: string): Promise<void>;
|
|
18
|
+
delete(key: string): Promise<void>;
|
|
19
|
+
list(prefix: string): Promise<string[]>;
|
|
20
|
+
}
|
|
21
|
+
export { tryCreateSqliteStorage, SqliteStorageProvider } from "./sqlite.js";
|
|
22
|
+
export { tryCreatePostgresStorage, PostgresStorageProvider } from "./postgres.js";
|
|
23
|
+
export interface WorkspaceRecord {
|
|
24
|
+
id: string;
|
|
25
|
+
name: string;
|
|
26
|
+
repositoryRoots: string[];
|
|
27
|
+
createdAt: string;
|
|
28
|
+
}
|
|
29
|
+
export declare function saveWorkspace(storage: StorageProvider, workspace: WorkspaceRecord): Promise<void>;
|
|
30
|
+
export declare function loadWorkspace(storage: StorageProvider, id: string): Promise<WorkspaceRecord | null>;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { isPathInsideRoot, resolveRepoRoot } from "../utils/path.js";
|
|
4
|
+
import { atomicWriteTextFile } from "../utils/fs.js";
|
|
5
|
+
import { rejectHostilePathInput, PathEscapeError } from "../security/paths.js";
|
|
6
|
+
export class FilesystemStorageProvider {
|
|
7
|
+
kind = "filesystem";
|
|
8
|
+
root;
|
|
9
|
+
constructor(rootInput, subdir = ".agentdoctor/storage") {
|
|
10
|
+
this.root = path.join(resolveRepoRoot(rootInput), subdir);
|
|
11
|
+
}
|
|
12
|
+
resolveKey(key) {
|
|
13
|
+
if (path.isAbsolute(key)) {
|
|
14
|
+
throw new Error("unsafe storage key");
|
|
15
|
+
}
|
|
16
|
+
try {
|
|
17
|
+
// Do not use resolveSafeRepoPath here — it calls resolveRepoRoot() which
|
|
18
|
+
// walks up from the storage subdir and can false-reject valid keys.
|
|
19
|
+
rejectHostilePathInput(key);
|
|
20
|
+
const target = path.resolve(this.root, key);
|
|
21
|
+
if (!isPathInsideRoot(this.root, target)) {
|
|
22
|
+
throw new PathEscapeError("storage path escape");
|
|
23
|
+
}
|
|
24
|
+
return target;
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
if (error instanceof PathEscapeError) {
|
|
28
|
+
throw new Error("storage path escape");
|
|
29
|
+
}
|
|
30
|
+
throw new Error("unsafe storage key");
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async get(key) {
|
|
34
|
+
try {
|
|
35
|
+
return await fs.readFile(this.resolveKey(key), "utf8");
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async set(key, value) {
|
|
42
|
+
const target = this.resolveKey(key);
|
|
43
|
+
await fs.mkdir(path.dirname(target), { recursive: true });
|
|
44
|
+
await atomicWriteTextFile(target, value);
|
|
45
|
+
}
|
|
46
|
+
async delete(key) {
|
|
47
|
+
try {
|
|
48
|
+
await fs.unlink(this.resolveKey(key));
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// ignore
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async list(prefix) {
|
|
55
|
+
const base = this.resolveKey(prefix || ".");
|
|
56
|
+
const out = [];
|
|
57
|
+
async function walk(dir, rel) {
|
|
58
|
+
let entries;
|
|
59
|
+
try {
|
|
60
|
+
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
for (const e of entries) {
|
|
66
|
+
const nextRel = rel ? `${rel}/${e.name}` : e.name;
|
|
67
|
+
if (e.isDirectory())
|
|
68
|
+
await walk(path.join(dir, e.name), nextRel);
|
|
69
|
+
else
|
|
70
|
+
out.push(nextRel);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const st = await fs.stat(base).catch(() => null);
|
|
74
|
+
if (!st)
|
|
75
|
+
return [];
|
|
76
|
+
if (st.isFile())
|
|
77
|
+
return [prefix];
|
|
78
|
+
await walk(base, prefix === "." ? "" : prefix);
|
|
79
|
+
return out.sort();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/** In-memory provider for tests and optional ephemeral mode. */
|
|
83
|
+
export class MemoryStorageProvider {
|
|
84
|
+
kind = "memory";
|
|
85
|
+
map = new Map();
|
|
86
|
+
async get(key) {
|
|
87
|
+
return this.map.get(key) ?? null;
|
|
88
|
+
}
|
|
89
|
+
async set(key, value) {
|
|
90
|
+
this.map.set(key, value);
|
|
91
|
+
}
|
|
92
|
+
async delete(key) {
|
|
93
|
+
this.map.delete(key);
|
|
94
|
+
}
|
|
95
|
+
async list(prefix) {
|
|
96
|
+
return [...this.map.keys()].filter((k) => k.startsWith(prefix)).sort();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
export { tryCreateSqliteStorage, SqliteStorageProvider } from "./sqlite.js";
|
|
100
|
+
export { tryCreatePostgresStorage, PostgresStorageProvider } from "./postgres.js";
|
|
101
|
+
export async function saveWorkspace(storage, workspace) {
|
|
102
|
+
await storage.set(`workspaces/${workspace.id}.json`, `${JSON.stringify(workspace, null, 2)}\n`);
|
|
103
|
+
}
|
|
104
|
+
export async function loadWorkspace(storage, id) {
|
|
105
|
+
const raw = await storage.get(`workspaces/${id}.json`);
|
|
106
|
+
if (!raw)
|
|
107
|
+
return null;
|
|
108
|
+
return JSON.parse(raw);
|
|
109
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { StorageProvider } from "../contracts/index.js";
|
|
2
|
+
type DatabaseSyncCtor = new (path: string) => {
|
|
3
|
+
exec(sql: string): void;
|
|
4
|
+
prepare(sql: string): {
|
|
5
|
+
get(...params: unknown[]): unknown;
|
|
6
|
+
run(...params: unknown[]): unknown;
|
|
7
|
+
all(...params: unknown[]): unknown[];
|
|
8
|
+
};
|
|
9
|
+
close(): void;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* SQLite-backed StorageProvider using Node's built-in `node:sqlite` (DatabaseSync).
|
|
13
|
+
* Available on Node 22+; feature-detected at runtime.
|
|
14
|
+
*/
|
|
15
|
+
export declare class SqliteStorageProvider implements StorageProvider {
|
|
16
|
+
readonly kind: "sqlite";
|
|
17
|
+
private readonly db;
|
|
18
|
+
private readonly dbPath;
|
|
19
|
+
constructor(db: InstanceType<DatabaseSyncCtor>, dbPath: string);
|
|
20
|
+
get path(): string;
|
|
21
|
+
get(key: string): Promise<string | null>;
|
|
22
|
+
set(key: string, value: string): Promise<void>;
|
|
23
|
+
delete(key: string): Promise<void>;
|
|
24
|
+
list(prefix: string): Promise<string[]>;
|
|
25
|
+
close(): void;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Create SQLite storage at `.agentdoctor/storage.sqlite` when `node:sqlite` is available.
|
|
29
|
+
*/
|
|
30
|
+
export declare function tryCreateSqliteStorage(rootInput: string): Promise<SqliteStorageProvider | {
|
|
31
|
+
error: string;
|
|
32
|
+
}>;
|
|
33
|
+
export {};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { resolveRepoRoot } from "../utils/path.js";
|
|
4
|
+
/**
|
|
5
|
+
* SQLite-backed StorageProvider using Node's built-in `node:sqlite` (DatabaseSync).
|
|
6
|
+
* Available on Node 22+; feature-detected at runtime.
|
|
7
|
+
*/
|
|
8
|
+
export class SqliteStorageProvider {
|
|
9
|
+
kind = "sqlite";
|
|
10
|
+
db;
|
|
11
|
+
dbPath;
|
|
12
|
+
constructor(db, dbPath) {
|
|
13
|
+
this.db = db;
|
|
14
|
+
this.dbPath = dbPath;
|
|
15
|
+
this.db.exec(`
|
|
16
|
+
CREATE TABLE IF NOT EXISTS kv (
|
|
17
|
+
key TEXT PRIMARY KEY,
|
|
18
|
+
value TEXT NOT NULL
|
|
19
|
+
);
|
|
20
|
+
`);
|
|
21
|
+
}
|
|
22
|
+
get path() {
|
|
23
|
+
return this.dbPath;
|
|
24
|
+
}
|
|
25
|
+
async get(key) {
|
|
26
|
+
const row = this.db.prepare("SELECT value FROM kv WHERE key = ?").get(key);
|
|
27
|
+
return row?.value ?? null;
|
|
28
|
+
}
|
|
29
|
+
async set(key, value) {
|
|
30
|
+
this.db
|
|
31
|
+
.prepare("INSERT INTO kv (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value")
|
|
32
|
+
.run(key, value);
|
|
33
|
+
}
|
|
34
|
+
async delete(key) {
|
|
35
|
+
this.db.prepare("DELETE FROM kv WHERE key = ?").run(key);
|
|
36
|
+
}
|
|
37
|
+
async list(prefix) {
|
|
38
|
+
const rows = this.db
|
|
39
|
+
.prepare("SELECT key FROM kv WHERE key LIKE ? ORDER BY key")
|
|
40
|
+
.all(`${prefix}%`);
|
|
41
|
+
return rows.map((r) => r.key);
|
|
42
|
+
}
|
|
43
|
+
close() {
|
|
44
|
+
this.db.close();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Create SQLite storage at `.agentdoctor/storage.sqlite` when `node:sqlite` is available.
|
|
49
|
+
*/
|
|
50
|
+
export async function tryCreateSqliteStorage(rootInput) {
|
|
51
|
+
let DatabaseSync;
|
|
52
|
+
try {
|
|
53
|
+
const mod = await import("node:sqlite");
|
|
54
|
+
if (!mod || typeof mod.DatabaseSync !== "function") {
|
|
55
|
+
return { error: "node:sqlite DatabaseSync is not available in this Node runtime" };
|
|
56
|
+
}
|
|
57
|
+
DatabaseSync = mod.DatabaseSync;
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
return {
|
|
61
|
+
error: `SQLite storage requires Node.js with node:sqlite (typically Node 22+): ${error instanceof Error ? error.message : String(error)}`,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
const root = resolveRepoRoot(rootInput);
|
|
66
|
+
const dir = path.join(root, ".agentdoctor");
|
|
67
|
+
await fs.mkdir(dir, { recursive: true });
|
|
68
|
+
const dbPath = path.join(dir, "storage.sqlite");
|
|
69
|
+
const db = new DatabaseSync(dbPath);
|
|
70
|
+
return new SqliteStorageProvider(db, dbPath);
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
return {
|
|
74
|
+
error: `Failed to open SQLite storage: ${error instanceof Error ? error.message : String(error)}`,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { StorageProvider } from "../contracts/index.js";
|
|
2
|
+
export type TeamRole = "owner" | "admin" | "member" | "viewer";
|
|
3
|
+
export interface TeamUser {
|
|
4
|
+
id: string;
|
|
5
|
+
username: string;
|
|
6
|
+
passwordHash: string;
|
|
7
|
+
salt: string;
|
|
8
|
+
role: TeamRole;
|
|
9
|
+
createdAt: string;
|
|
10
|
+
/** Development-only local auth — not enterprise SSO. */
|
|
11
|
+
authMode: "local-dev";
|
|
12
|
+
}
|
|
13
|
+
export interface SessionToken {
|
|
14
|
+
token: string;
|
|
15
|
+
userId: string;
|
|
16
|
+
expiresAt: string;
|
|
17
|
+
}
|
|
18
|
+
export declare function registerLocalDevUser(options: {
|
|
19
|
+
root: string;
|
|
20
|
+
username: string;
|
|
21
|
+
password: string;
|
|
22
|
+
role?: TeamRole;
|
|
23
|
+
storage?: StorageProvider;
|
|
24
|
+
}): Promise<TeamUser>;
|
|
25
|
+
export declare function authenticateLocalDev(options: {
|
|
26
|
+
root: string;
|
|
27
|
+
username: string;
|
|
28
|
+
password: string;
|
|
29
|
+
storage?: StorageProvider;
|
|
30
|
+
}): Promise<SessionToken>;
|
|
31
|
+
export declare function authorize(root: string, token: string, minRole: TeamRole, storage?: StorageProvider): Promise<{
|
|
32
|
+
ok: boolean;
|
|
33
|
+
user?: TeamUser;
|
|
34
|
+
reason: string;
|
|
35
|
+
}>;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { createHash, randomBytes, scryptSync, timingSafeEqual } from "node:crypto";
|
|
2
|
+
import { FilesystemStorageProvider } from "../storage/provider.js";
|
|
3
|
+
function hashPassword(password, salt) {
|
|
4
|
+
return scryptSync(password, salt, 32).toString("hex");
|
|
5
|
+
}
|
|
6
|
+
export async function registerLocalDevUser(options) {
|
|
7
|
+
if (options.password.length < 8)
|
|
8
|
+
throw new Error("password must be at least 8 characters");
|
|
9
|
+
const storage = options.storage ?? new FilesystemStorageProvider(options.root, ".agentdoctor/team");
|
|
10
|
+
const salt = randomBytes(16).toString("hex");
|
|
11
|
+
const user = {
|
|
12
|
+
id: `user_${createHash("sha256").update(options.username).digest("hex").slice(0, 10)}`,
|
|
13
|
+
username: options.username,
|
|
14
|
+
passwordHash: hashPassword(options.password, salt),
|
|
15
|
+
salt,
|
|
16
|
+
role: options.role ?? "member",
|
|
17
|
+
createdAt: new Date().toISOString(),
|
|
18
|
+
authMode: "local-dev",
|
|
19
|
+
};
|
|
20
|
+
await storage.set(`users/${user.id}.json`, `${JSON.stringify(user, null, 2)}\n`);
|
|
21
|
+
return {
|
|
22
|
+
...user,
|
|
23
|
+
passwordHash: "[REDACTED]",
|
|
24
|
+
salt: "[REDACTED]",
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export async function authenticateLocalDev(options) {
|
|
28
|
+
const storage = options.storage ?? new FilesystemStorageProvider(options.root, ".agentdoctor/team");
|
|
29
|
+
const keys = await storage.list("users");
|
|
30
|
+
for (const key of keys) {
|
|
31
|
+
const raw = await storage.get(key.startsWith("users/") ? key : `users/${key}`);
|
|
32
|
+
if (!raw)
|
|
33
|
+
continue;
|
|
34
|
+
const user = JSON.parse(raw);
|
|
35
|
+
if (user.username !== options.username)
|
|
36
|
+
continue;
|
|
37
|
+
const next = hashPassword(options.password, user.salt);
|
|
38
|
+
const a = Buffer.from(next, "hex");
|
|
39
|
+
const b = Buffer.from(user.passwordHash, "hex");
|
|
40
|
+
if (a.length !== b.length || !timingSafeEqual(a, b))
|
|
41
|
+
break;
|
|
42
|
+
const token = randomBytes(24).toString("hex");
|
|
43
|
+
const session = {
|
|
44
|
+
token,
|
|
45
|
+
userId: user.id,
|
|
46
|
+
expiresAt: new Date(Date.now() + 8 * 3600_000).toISOString(),
|
|
47
|
+
};
|
|
48
|
+
await storage.set(`sessions/${token}.json`, `${JSON.stringify(session, null, 2)}\n`);
|
|
49
|
+
return session;
|
|
50
|
+
}
|
|
51
|
+
throw new Error("invalid credentials");
|
|
52
|
+
}
|
|
53
|
+
export async function authorize(root, token, minRole, storage) {
|
|
54
|
+
const store = storage ?? new FilesystemStorageProvider(root, ".agentdoctor/team");
|
|
55
|
+
const raw = await store.get(`sessions/${token}.json`);
|
|
56
|
+
if (!raw)
|
|
57
|
+
return { ok: false, reason: "missing session" };
|
|
58
|
+
const session = JSON.parse(raw);
|
|
59
|
+
if (Date.parse(session.expiresAt) < Date.now())
|
|
60
|
+
return { ok: false, reason: "expired session" };
|
|
61
|
+
const userRaw = await store.get(`users/${session.userId}.json`);
|
|
62
|
+
if (!userRaw)
|
|
63
|
+
return { ok: false, reason: "missing user" };
|
|
64
|
+
const user = JSON.parse(userRaw);
|
|
65
|
+
const rank = { viewer: 1, member: 2, admin: 3, owner: 4 };
|
|
66
|
+
if ((rank[user.role] ?? 0) < (rank[minRole] ?? 99)) {
|
|
67
|
+
return { ok: false, reason: "insufficient role", user };
|
|
68
|
+
}
|
|
69
|
+
return { ok: true, user, reason: "authorized" };
|
|
70
|
+
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ export type LanguageId = "typescript" | "javascript" | "python" | "dart" | "php"
|
|
|
9
9
|
export type FrameworkId = "nextjs" | "react" | "vue" | "nuxt" | "svelte" | "nodejs" | "express" | "nestjs" | "flutter" | "laravel" | "django" | "fastapi" | "unknown";
|
|
10
10
|
export type PackageManagerId = "npm" | "pnpm" | "yarn" | "bun" | "composer" | "pub" | "pip" | "poetry" | "cargo" | "gradle" | "unknown";
|
|
11
11
|
export type MonorepoToolId = "npm-workspaces" | "pnpm-workspaces" | "turborepo" | "nx" | "multi-project" | "none";
|
|
12
|
-
export type AgentId = "cursor" | "claude-code" | "codex";
|
|
12
|
+
export type AgentId = "cursor" | "claude-code" | "codex" | "copilot" | "windsurf" | "gemini-cli" | "aider";
|
|
13
13
|
export interface DiscoveredFile {
|
|
14
14
|
/** Absolute path */
|
|
15
15
|
absolutePath: string;
|
|
@@ -76,6 +76,10 @@ export interface AgentScores {
|
|
|
76
76
|
cursor: number;
|
|
77
77
|
"claude-code": number;
|
|
78
78
|
codex: number;
|
|
79
|
+
copilot: number;
|
|
80
|
+
windsurf: number;
|
|
81
|
+
"gemini-cli": number;
|
|
82
|
+
aider: number;
|
|
79
83
|
}
|
|
80
84
|
export interface Scores {
|
|
81
85
|
overall: number;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
export declare const WORKSPACE_SCHEMA_VERSION: "1.0.0";
|
|
2
|
+
export interface WorkspaceModel {
|
|
3
|
+
schemaVersion: typeof WORKSPACE_SCHEMA_VERSION;
|
|
4
|
+
id: string;
|
|
5
|
+
name: string;
|
|
6
|
+
/** Absolute, resolved repository roots that belong to this workspace. */
|
|
7
|
+
repositoryRoots: string[];
|
|
8
|
+
/**
|
|
9
|
+
* When false (default), operations scoped to repo A must not read paths under repo B
|
|
10
|
+
* even if both are members of the same workspace.
|
|
11
|
+
*/
|
|
12
|
+
allowCrossRead: boolean;
|
|
13
|
+
createdAt: string;
|
|
14
|
+
updatedAt: string;
|
|
15
|
+
}
|
|
16
|
+
export interface WorkspacePathAccess {
|
|
17
|
+
allowed: boolean;
|
|
18
|
+
reason: string;
|
|
19
|
+
workspaceId?: string;
|
|
20
|
+
requestedRoot?: string;
|
|
21
|
+
targetRoot?: string;
|
|
22
|
+
}
|
|
23
|
+
export declare function initWorkspace(options: {
|
|
24
|
+
controlRoot: string;
|
|
25
|
+
name: string;
|
|
26
|
+
repositoryRoot?: string;
|
|
27
|
+
id?: string;
|
|
28
|
+
allowCrossRead?: boolean;
|
|
29
|
+
}): Promise<WorkspaceModel>;
|
|
30
|
+
export declare function loadWorkspace(controlRoot: string, id: string): Promise<WorkspaceModel | null>;
|
|
31
|
+
export declare function listWorkspaces(controlRoot: string): Promise<WorkspaceModel[]>;
|
|
32
|
+
export declare function addRepositoryToWorkspace(options: {
|
|
33
|
+
controlRoot: string;
|
|
34
|
+
workspaceId: string;
|
|
35
|
+
repositoryRoot: string;
|
|
36
|
+
}): Promise<WorkspaceModel>;
|
|
37
|
+
export declare function removeWorkspace(controlRoot: string, id: string): Promise<boolean>;
|
|
38
|
+
export declare function workspaceStatus(controlRoot: string, id: string): Promise<{
|
|
39
|
+
ok: boolean;
|
|
40
|
+
workspace?: WorkspaceModel;
|
|
41
|
+
missingRoots: string[];
|
|
42
|
+
error?: string;
|
|
43
|
+
}>;
|
|
44
|
+
/**
|
|
45
|
+
* Isolation gate: a path may be read only when it is inside the operation's own
|
|
46
|
+
* repositoryRoot, OR (allowCrossRead && both roots are members of the same workspace).
|
|
47
|
+
*/
|
|
48
|
+
export declare function assertWorkspacePathAccess(options: {
|
|
49
|
+
workspace: WorkspaceModel | null;
|
|
50
|
+
operationRoot: string;
|
|
51
|
+
targetPath: string;
|
|
52
|
+
}): WorkspacePathAccess;
|
|
53
|
+
/**
|
|
54
|
+
* Read a file only when workspace isolation allows it.
|
|
55
|
+
*/
|
|
56
|
+
export declare function readFileWithinWorkspace(options: {
|
|
57
|
+
workspace: WorkspaceModel | null;
|
|
58
|
+
operationRoot: string;
|
|
59
|
+
targetPath: string;
|
|
60
|
+
maxBytes?: number;
|
|
61
|
+
}): Promise<{
|
|
62
|
+
ok: true;
|
|
63
|
+
content: string;
|
|
64
|
+
access: WorkspacePathAccess;
|
|
65
|
+
} | {
|
|
66
|
+
ok: false;
|
|
67
|
+
access: WorkspacePathAccess;
|
|
68
|
+
error: string;
|
|
69
|
+
}>;
|