@praneeth_54/agentdoctor 2.0.0 → 2.1.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/CHANGELOG.md +141 -4
- package/README.md +329 -153
- package/dist/agent/approvals.d.ts +24 -0
- package/dist/agent/approvals.js +64 -0
- package/dist/agent/chat/index.d.ts +7 -0
- package/dist/agent/chat/index.js +5 -0
- package/dist/agent/chat/memory.d.ts +44 -0
- package/dist/agent/chat/memory.js +103 -0
- package/dist/agent/chat/project-summary.d.ts +16 -0
- package/dist/agent/chat/project-summary.js +70 -0
- package/dist/agent/chat/prompts.d.ts +6 -0
- package/dist/agent/chat/prompts.js +39 -0
- package/dist/agent/chat/response.d.ts +19 -0
- package/dist/agent/chat/response.js +109 -0
- package/dist/agent/chat/service.d.ts +42 -0
- package/dist/agent/chat/service.js +252 -0
- package/dist/agent/chat/types.d.ts +48 -0
- package/dist/agent/chat/types.js +1 -0
- package/dist/agent/context/retrieve.d.ts +21 -0
- package/dist/agent/context/retrieve.js +117 -0
- package/dist/agent/context/truth.d.ts +6 -0
- package/dist/agent/context/truth.js +19 -0
- package/dist/agent/context/types.d.ts +23 -0
- package/dist/agent/context/types.js +4 -0
- package/dist/agent/index.d.ts +26 -0
- package/dist/agent/index.js +14 -0
- package/dist/agent/loop.d.ts +51 -0
- package/dist/agent/loop.js +222 -0
- package/dist/agent/modes.d.ts +18 -0
- package/dist/agent/modes.js +102 -0
- package/dist/agent/plan.d.ts +30 -0
- package/dist/agent/plan.js +121 -0
- package/dist/agent/runtime.d.ts +67 -0
- package/dist/agent/runtime.js +180 -0
- package/dist/agent/state.d.ts +30 -0
- package/dist/agent/state.js +95 -0
- package/dist/agent/student.d.ts +56 -0
- package/dist/agent/student.js +230 -0
- package/dist/agent/tools/execute.d.ts +18 -0
- package/dist/agent/tools/execute.js +355 -0
- package/dist/agent/tools/index.d.ts +6 -0
- package/dist/agent/tools/index.js +5 -0
- package/dist/agent/tools/registry.d.ts +7 -0
- package/dist/agent/tools/registry.js +212 -0
- package/dist/agent/tools/run.d.ts +24 -0
- package/dist/agent/tools/run.js +44 -0
- package/dist/agent/tools/types.d.ts +32 -0
- package/dist/agent/tools/types.js +10 -0
- package/dist/agent/tools/write.d.ts +24 -0
- package/dist/agent/tools/write.js +121 -0
- package/dist/agent/verify.d.ts +29 -0
- package/dist/agent/verify.js +210 -0
- package/dist/ai/config.d.ts +22 -0
- package/dist/ai/config.js +68 -0
- package/dist/ai/index.d.ts +17 -0
- package/dist/ai/index.js +52 -0
- package/dist/ai/providers/mock.d.ts +20 -0
- package/dist/ai/providers/mock.js +84 -0
- package/dist/ai/providers/none.d.ts +6 -0
- package/dist/ai/providers/none.js +23 -0
- package/dist/ai/providers/openai-compatible.d.ts +21 -0
- package/dist/ai/providers/openai-compatible.js +151 -0
- package/dist/ai/redact.d.ts +8 -0
- package/dist/ai/redact.js +21 -0
- package/dist/ai/types.d.ts +71 -0
- package/dist/ai/types.js +6 -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/agent.d.ts +27 -0
- package/dist/cli/commands/agent.js +153 -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/chat.d.ts +14 -0
- package/dist/cli/commands/chat.js +155 -0
- package/dist/cli/commands/complete.js +2 -2
- package/dist/cli/commands/learn.d.ts +12 -0
- package/dist/cli/commands/learn.js +107 -0
- package/dist/cli/commands/platform.d.ts +2 -0
- package/dist/cli/commands/platform.js +5 -1
- 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/workspace.d.ts +9 -0
- package/dist/cli/commands/workspace.js +82 -0
- package/dist/cli/program.js +627 -17
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -1
- package/dist/contracts/index.d.ts +1 -1
- package/dist/contracts/index.js +2 -2
- package/dist/core/brain-product/init.js +5 -2
- package/dist/core/secrets/scan.d.ts +7 -1
- package/dist/core/secrets/scan.js +9 -9
- 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 +7 -1
- package/dist/dashboard/server.js +110 -6
- package/dist/enforcement/runner.d.ts +52 -3
- package/dist/enforcement/runner.js +467 -16
- package/dist/index.d.ts +36 -2
- package/dist/index.js +23 -2
- package/dist/intelligence/graph/build.d.ts +2 -0
- package/dist/intelligence/graph/build.js +49 -5
- 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/languages/go.d.ts +9 -0
- package/dist/languages/go.js +50 -0
- package/dist/languages/index.d.ts +11 -0
- package/dist/languages/index.js +76 -0
- package/dist/languages/php.d.ts +8 -0
- package/dist/languages/php.js +202 -0
- package/dist/languages/python.d.ts +11 -0
- package/dist/languages/python.js +180 -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/agent/registry.d.ts +13 -0
- package/dist/mcp/agent/registry.js +234 -0
- package/dist/mcp/agentdoctor/server.js +8 -1
- package/dist/mcp/intelligence/handlers.d.ts +5 -0
- package/dist/mcp/intelligence/handlers.js +229 -40
- package/dist/mcp/intelligence/path-safety.d.ts +2 -4
- package/dist/mcp/intelligence/path-safety.js +25 -44
- package/dist/mcp/intelligence/registry.d.ts +1 -1
- package/dist/mcp/intelligence/registry.js +69 -1
- package/dist/platform/firewall/evaluate.d.ts +6 -3
- package/dist/platform/firewall/evaluate.js +66 -15
- package/dist/platform/graph/build.js +19 -23
- package/dist/platform/health/analyze.js +23 -17
- package/dist/platform/index.js +2 -0
- package/dist/platform/test-impact/analyze.d.ts +21 -3
- package/dist/platform/test-impact/analyze.js +214 -28
- package/dist/platform/tokens/plan.js +39 -20
- package/dist/platform/types.d.ts +9 -0
- package/dist/policy/compose.d.ts +34 -0
- package/dist/policy/compose.js +118 -0
- package/dist/policy/packs.js +33 -1
- package/dist/security/paths.d.ts +21 -0
- package/dist/security/paths.js +133 -0
- package/dist/storage/postgres.d.ts +26 -0
- package/dist/storage/postgres.js +90 -0
- package/dist/storage/provider.d.ts +2 -7
- package/dist/storage/provider.js +22 -16
- package/dist/storage/sqlite.d.ts +33 -0
- package/dist/storage/sqlite.js +77 -0
- package/dist/team/auth.js +1 -1
- package/dist/workspace/index.d.ts +69 -0
- package/dist/workspace/index.js +220 -0
- package/package.json +9 -3
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
const GO_PROBE_TIMEOUT_MS = 5_000;
|
|
3
|
+
/**
|
|
4
|
+
* Go toolchain bridge placeholder.
|
|
5
|
+
* Prefer real `go/ast` via `go run` when the Go toolchain is present; until an
|
|
6
|
+
* extractor is bundled, capability remains unsupported even if `go` exists.
|
|
7
|
+
* Today: always unsupported (no bundled extractor; avoids fake AST claims).
|
|
8
|
+
*/
|
|
9
|
+
export function goAvailable() {
|
|
10
|
+
const r = spawnSync("go", ["version"], {
|
|
11
|
+
encoding: "utf8",
|
|
12
|
+
timeout: GO_PROBE_TIMEOUT_MS,
|
|
13
|
+
windowsHide: true,
|
|
14
|
+
});
|
|
15
|
+
return r.status === 0 && !r.error;
|
|
16
|
+
}
|
|
17
|
+
export const goAdapter = {
|
|
18
|
+
id: "go",
|
|
19
|
+
extensions: [".go"],
|
|
20
|
+
capabilities: () => ({
|
|
21
|
+
parse: "unsupported",
|
|
22
|
+
symbols: "unsupported",
|
|
23
|
+
definitions: "unsupported",
|
|
24
|
+
references: "unsupported",
|
|
25
|
+
imports: "unsupported",
|
|
26
|
+
calls: "unsupported",
|
|
27
|
+
diagnostics: "unsupported",
|
|
28
|
+
}),
|
|
29
|
+
async parse(filePath, _source) {
|
|
30
|
+
const hasGo = goAvailable();
|
|
31
|
+
return {
|
|
32
|
+
language: "go",
|
|
33
|
+
file: filePath,
|
|
34
|
+
ok: false,
|
|
35
|
+
capabilities: goAdapter.capabilities(),
|
|
36
|
+
symbols: [],
|
|
37
|
+
imports: [],
|
|
38
|
+
calls: [],
|
|
39
|
+
diagnostics: [
|
|
40
|
+
hasGo
|
|
41
|
+
? "Go toolchain present but AgentDoctor go/ast extractor is not bundled yet"
|
|
42
|
+
: "go not available on PATH",
|
|
43
|
+
],
|
|
44
|
+
limitations: [
|
|
45
|
+
"Go AST requires a go/ast bridge or tree-sitter-go; not shipped in this package",
|
|
46
|
+
"Capability remains unsupported — no regex fallback under an AST claim",
|
|
47
|
+
],
|
|
48
|
+
};
|
|
49
|
+
},
|
|
50
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { LanguageAdapter, ParseResult } from "./types.js";
|
|
2
|
+
/** Languages with real AST backends today (or honest unsupported stubs). */
|
|
3
|
+
export declare const languageAdapters: LanguageAdapter[];
|
|
4
|
+
export declare function getAdapterForFile(filePath: string): LanguageAdapter | null;
|
|
5
|
+
export declare function parseSourceFile(filePath: string, source: string): Promise<ParseResult>;
|
|
6
|
+
export { detectLanguage } from "./types.js";
|
|
7
|
+
export { typescriptAdapter, javascriptAdapter } from "./typescript.js";
|
|
8
|
+
export { pythonAdapter, pythonAvailable } from "./python.js";
|
|
9
|
+
export { phpAdapter, phpAvailable } from "./php.js";
|
|
10
|
+
export { goAdapter, goAvailable } from "./go.js";
|
|
11
|
+
export type { LanguageAdapter, ParseResult, LanguageId } from "./types.js";
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { detectLanguage } from "./types.js";
|
|
2
|
+
import { javascriptAdapter, typescriptAdapter } from "./typescript.js";
|
|
3
|
+
import { pythonAdapter } from "./python.js";
|
|
4
|
+
import { phpAdapter } from "./php.js";
|
|
5
|
+
import { goAdapter } from "./go.js";
|
|
6
|
+
function unsupportedAdapter(id, extensions, reason) {
|
|
7
|
+
return {
|
|
8
|
+
id,
|
|
9
|
+
extensions,
|
|
10
|
+
capabilities: () => ({
|
|
11
|
+
parse: "unsupported",
|
|
12
|
+
symbols: "unsupported",
|
|
13
|
+
definitions: "unsupported",
|
|
14
|
+
references: "unsupported",
|
|
15
|
+
imports: "unsupported",
|
|
16
|
+
calls: "unsupported",
|
|
17
|
+
diagnostics: "unsupported",
|
|
18
|
+
}),
|
|
19
|
+
async parse(filePath, _source) {
|
|
20
|
+
return {
|
|
21
|
+
language: id,
|
|
22
|
+
file: filePath,
|
|
23
|
+
ok: false,
|
|
24
|
+
capabilities: this.capabilities(),
|
|
25
|
+
symbols: [],
|
|
26
|
+
imports: [],
|
|
27
|
+
calls: [],
|
|
28
|
+
diagnostics: [reason],
|
|
29
|
+
limitations: [
|
|
30
|
+
reason,
|
|
31
|
+
"No regex fallback is offered under an AST claim — capability remains unsupported until a mature parser is integrated",
|
|
32
|
+
],
|
|
33
|
+
};
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/** Languages with real AST backends today (or honest unsupported stubs). */
|
|
38
|
+
export const languageAdapters = [
|
|
39
|
+
typescriptAdapter,
|
|
40
|
+
javascriptAdapter,
|
|
41
|
+
pythonAdapter,
|
|
42
|
+
phpAdapter,
|
|
43
|
+
goAdapter,
|
|
44
|
+
unsupportedAdapter("java", [".java"], "Java AST requires an external parser (e.g. tree-sitter-java or javaparser); not bundled"),
|
|
45
|
+
unsupportedAdapter("kotlin", [".kt", ".kts"], "Kotlin AST requires an external parser; not bundled"),
|
|
46
|
+
unsupportedAdapter("rust", [".rs"], "Rust AST requires syn/rustc or tree-sitter-rust; not bundled"),
|
|
47
|
+
unsupportedAdapter("dart", [".dart"], "Dart AST requires Dart analyzer APIs; not bundled"),
|
|
48
|
+
];
|
|
49
|
+
export function getAdapterForFile(filePath) {
|
|
50
|
+
const lang = detectLanguage(filePath);
|
|
51
|
+
if (lang === "unknown")
|
|
52
|
+
return null;
|
|
53
|
+
return languageAdapters.find((a) => a.id === lang) ?? null;
|
|
54
|
+
}
|
|
55
|
+
export async function parseSourceFile(filePath, source) {
|
|
56
|
+
const adapter = getAdapterForFile(filePath);
|
|
57
|
+
if (!adapter) {
|
|
58
|
+
return {
|
|
59
|
+
language: "unknown",
|
|
60
|
+
file: filePath,
|
|
61
|
+
ok: false,
|
|
62
|
+
capabilities: {},
|
|
63
|
+
symbols: [],
|
|
64
|
+
imports: [],
|
|
65
|
+
calls: [],
|
|
66
|
+
diagnostics: ["unknown language"],
|
|
67
|
+
limitations: ["No language adapter for this file extension"],
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
return adapter.parse(filePath, source);
|
|
71
|
+
}
|
|
72
|
+
export { detectLanguage } from "./types.js";
|
|
73
|
+
export { typescriptAdapter, javascriptAdapter } from "./typescript.js";
|
|
74
|
+
export { pythonAdapter, pythonAvailable } from "./python.js";
|
|
75
|
+
export { phpAdapter, phpAvailable } from "./php.js";
|
|
76
|
+
export { goAdapter, goAvailable } from "./go.js";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { LanguageAdapter } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Real PHP token extractor via `php -r` + token_get_all (requires `php` on PATH).
|
|
4
|
+
* Not regex. If php is missing, capabilities are unsupported.
|
|
5
|
+
* Probe timeout skips hanging Windows Store / stub `php` binaries.
|
|
6
|
+
*/
|
|
7
|
+
export declare function phpAvailable(): boolean;
|
|
8
|
+
export declare const phpAdapter: LanguageAdapter;
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
function sid(kind, key) {
|
|
4
|
+
return `${kind}:${createHash("sha256").update(key).digest("hex").slice(0, 12)}`;
|
|
5
|
+
}
|
|
6
|
+
const PHP_PROBE_TIMEOUT_MS = 5_000;
|
|
7
|
+
const PHP_EXTRACT_TIMEOUT_MS = 15_000;
|
|
8
|
+
/**
|
|
9
|
+
* Real PHP token extractor via `php -r` + token_get_all (requires `php` on PATH).
|
|
10
|
+
* Not regex. If php is missing, capabilities are unsupported.
|
|
11
|
+
* Probe timeout skips hanging Windows Store / stub `php` binaries.
|
|
12
|
+
*/
|
|
13
|
+
export function phpAvailable() {
|
|
14
|
+
const r = spawnSync("php", ["-r", "echo PHP_VERSION;"], {
|
|
15
|
+
encoding: "utf8",
|
|
16
|
+
timeout: PHP_PROBE_TIMEOUT_MS,
|
|
17
|
+
windowsHide: true,
|
|
18
|
+
});
|
|
19
|
+
return r.status === 0 && !r.error && Boolean(r.stdout?.trim());
|
|
20
|
+
}
|
|
21
|
+
const PHP_EXTRACTOR = `
|
|
22
|
+
$source = stream_get_contents(STDIN);
|
|
23
|
+
$path = $argv[1] ?? "stdin.php";
|
|
24
|
+
if ($source === false || $source === "") {
|
|
25
|
+
echo json_encode(["ok" => false, "error" => "empty source", "symbols" => [], "imports" => [], "calls" => []]);
|
|
26
|
+
exit(0);
|
|
27
|
+
}
|
|
28
|
+
if (strpos($source, "<?") === false) {
|
|
29
|
+
$source = "<?php\\n" . $source;
|
|
30
|
+
}
|
|
31
|
+
$tokens = @token_get_all($source);
|
|
32
|
+
if (!is_array($tokens)) {
|
|
33
|
+
echo json_encode(["ok" => false, "error" => "token_get_all failed", "symbols" => [], "imports" => [], "calls" => []]);
|
|
34
|
+
exit(0);
|
|
35
|
+
}
|
|
36
|
+
$symbols = [];
|
|
37
|
+
$imports = [];
|
|
38
|
+
$calls = [];
|
|
39
|
+
$count = count($tokens);
|
|
40
|
+
for ($i = 0; $i < $count; $i++) {
|
|
41
|
+
$t = $tokens[$i];
|
|
42
|
+
if (!is_array($t)) continue;
|
|
43
|
+
[$id, $text, $line] = $t;
|
|
44
|
+
if ($id === T_FUNCTION) {
|
|
45
|
+
for ($j = $i + 1; $j < $count; $j++) {
|
|
46
|
+
$n = $tokens[$j];
|
|
47
|
+
if (!is_array($n)) continue;
|
|
48
|
+
if ($n[0] === T_STRING) {
|
|
49
|
+
$symbols[] = ["name" => $n[1], "kind" => "function", "line" => $n[2]];
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
if ($n[0] === T_WHITESPACE || $n[0] === T_COMMENT || $n[0] === T_DOC_COMMENT) continue;
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
} elseif ($id === T_CLASS || $id === T_INTERFACE || $id === T_TRAIT) {
|
|
56
|
+
$kind = $id === T_CLASS ? "class" : ($id === T_INTERFACE ? "interface" : "trait");
|
|
57
|
+
for ($j = $i + 1; $j < $count; $j++) {
|
|
58
|
+
$n = $tokens[$j];
|
|
59
|
+
if (!is_array($n)) continue;
|
|
60
|
+
if ($n[0] === T_STRING) {
|
|
61
|
+
$symbols[] = ["name" => $n[1], "kind" => $kind, "line" => $n[2]];
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
if ($n[0] === T_WHITESPACE || $n[0] === T_COMMENT || $n[0] === T_DOC_COMMENT) continue;
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
} elseif ($id === T_USE) {
|
|
68
|
+
$spec = "";
|
|
69
|
+
for ($j = $i + 1; $j < $count; $j++) {
|
|
70
|
+
$n = $tokens[$j];
|
|
71
|
+
if (is_array($n)) {
|
|
72
|
+
if ($n[0] === T_STRING || $n[0] === T_NS_SEPARATOR || $n[0] === T_NAME_QUALIFIED || $n[0] === T_NAME_FULLY_QUALIFIED) {
|
|
73
|
+
$spec .= $n[1];
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if ($n[0] === T_WHITESPACE) continue;
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
if ($n === ";" || $n === "{") break;
|
|
80
|
+
}
|
|
81
|
+
if ($spec !== "") $imports[] = ["specifier" => $spec];
|
|
82
|
+
} elseif ($id === T_STRING) {
|
|
83
|
+
$next = $tokens[$i + 1] ?? null;
|
|
84
|
+
$prev = $tokens[$i - 1] ?? null;
|
|
85
|
+
$prevIsObject = is_array($prev) && ($prev[0] === T_OBJECT_OPERATOR || $prev[0] === T_NULLSAFE_OBJECT_OPERATOR || $prev[0] === T_DOUBLE_COLON);
|
|
86
|
+
if ($next === "(" && !$prevIsObject) {
|
|
87
|
+
$calls[] = ["callee" => $text, "line" => $line];
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
echo json_encode(["ok" => true, "symbols" => $symbols, "imports" => $imports, "calls" => $calls], JSON_UNESCAPED_SLASHES);
|
|
92
|
+
`;
|
|
93
|
+
export const phpAdapter = {
|
|
94
|
+
id: "php",
|
|
95
|
+
extensions: [".php"],
|
|
96
|
+
capabilities: () => {
|
|
97
|
+
const ok = phpAvailable();
|
|
98
|
+
const v = ok ? "supported" : "unsupported";
|
|
99
|
+
return {
|
|
100
|
+
parse: v,
|
|
101
|
+
symbols: v,
|
|
102
|
+
definitions: v,
|
|
103
|
+
imports: v,
|
|
104
|
+
calls: v,
|
|
105
|
+
references: "unsupported",
|
|
106
|
+
diagnostics: ok ? "supported" : "unsupported",
|
|
107
|
+
};
|
|
108
|
+
},
|
|
109
|
+
async parse(filePath, source) {
|
|
110
|
+
if (!phpAvailable()) {
|
|
111
|
+
return {
|
|
112
|
+
language: "php",
|
|
113
|
+
file: filePath,
|
|
114
|
+
ok: false,
|
|
115
|
+
capabilities: phpAdapter.capabilities(),
|
|
116
|
+
symbols: [],
|
|
117
|
+
imports: [],
|
|
118
|
+
calls: [],
|
|
119
|
+
diagnostics: ["php not available on PATH"],
|
|
120
|
+
limitations: ["PHP adapter requires php CLI with token_get_all"],
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
const r = spawnSync("php", ["-r", PHP_EXTRACTOR, "--", filePath], {
|
|
124
|
+
input: source,
|
|
125
|
+
encoding: "utf8",
|
|
126
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
127
|
+
timeout: PHP_EXTRACT_TIMEOUT_MS,
|
|
128
|
+
windowsHide: true,
|
|
129
|
+
});
|
|
130
|
+
if (r.status !== 0 || !r.stdout) {
|
|
131
|
+
return {
|
|
132
|
+
language: "php",
|
|
133
|
+
file: filePath,
|
|
134
|
+
ok: false,
|
|
135
|
+
capabilities: phpAdapter.capabilities(),
|
|
136
|
+
symbols: [],
|
|
137
|
+
imports: [],
|
|
138
|
+
calls: [],
|
|
139
|
+
diagnostics: [r.stderr || "php extractor failed"],
|
|
140
|
+
limitations: [],
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
let parsed;
|
|
144
|
+
try {
|
|
145
|
+
parsed = JSON.parse(r.stdout);
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
return {
|
|
149
|
+
language: "php",
|
|
150
|
+
file: filePath,
|
|
151
|
+
ok: false,
|
|
152
|
+
capabilities: phpAdapter.capabilities(),
|
|
153
|
+
symbols: [],
|
|
154
|
+
imports: [],
|
|
155
|
+
calls: [],
|
|
156
|
+
diagnostics: ["invalid JSON from php extractor"],
|
|
157
|
+
limitations: [],
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
if (!parsed.ok) {
|
|
161
|
+
return {
|
|
162
|
+
language: "php",
|
|
163
|
+
file: filePath,
|
|
164
|
+
ok: false,
|
|
165
|
+
capabilities: phpAdapter.capabilities(),
|
|
166
|
+
symbols: [],
|
|
167
|
+
imports: [],
|
|
168
|
+
calls: [],
|
|
169
|
+
diagnostics: [parsed.error ?? "parse error"],
|
|
170
|
+
limitations: [],
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
language: "php",
|
|
175
|
+
file: filePath,
|
|
176
|
+
ok: true,
|
|
177
|
+
capabilities: phpAdapter.capabilities(),
|
|
178
|
+
symbols: parsed.symbols.map((s) => ({
|
|
179
|
+
id: sid(s.kind, `${filePath}:${s.name}:${s.line}`),
|
|
180
|
+
name: s.name,
|
|
181
|
+
kind: s.kind,
|
|
182
|
+
file: filePath,
|
|
183
|
+
line: s.line,
|
|
184
|
+
evidence: "ast",
|
|
185
|
+
})),
|
|
186
|
+
imports: parsed.imports.map((i) => ({
|
|
187
|
+
fromFile: filePath,
|
|
188
|
+
specifier: i.specifier,
|
|
189
|
+
evidence: "ast",
|
|
190
|
+
})),
|
|
191
|
+
calls: parsed.calls.map((c) => ({
|
|
192
|
+
fromFile: filePath,
|
|
193
|
+
callee: c.callee,
|
|
194
|
+
evidence: "ast",
|
|
195
|
+
})),
|
|
196
|
+
diagnostics: [],
|
|
197
|
+
limitations: [
|
|
198
|
+
"PHP token_get_all single-file extract — not a full php-parser AST; cross-file refs unsupported",
|
|
199
|
+
],
|
|
200
|
+
};
|
|
201
|
+
},
|
|
202
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { LanguageAdapter } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Resolve a working CPython binary, or null if none responds in time.
|
|
4
|
+
*/
|
|
5
|
+
export declare function resolvePythonBin(): string | null;
|
|
6
|
+
/**
|
|
7
|
+
* Real Python AST via the CPython `ast` module (requires python on PATH).
|
|
8
|
+
* Not regex. If no interpreter is available, capabilities are unsupported.
|
|
9
|
+
*/
|
|
10
|
+
export declare function pythonAvailable(): boolean;
|
|
11
|
+
export declare const pythonAdapter: LanguageAdapter;
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
function sid(kind, key) {
|
|
4
|
+
return `${kind}:${createHash("sha256").update(key).digest("hex").slice(0, 12)}`;
|
|
5
|
+
}
|
|
6
|
+
const PYTHON_PROBE_TIMEOUT_MS = 5_000;
|
|
7
|
+
/**
|
|
8
|
+
* Prefer real interpreters; skip hanging Windows Store `python3` stubs via timeout.
|
|
9
|
+
*/
|
|
10
|
+
function pythonCandidates() {
|
|
11
|
+
return process.platform === "win32" ? ["python", "py", "python3"] : ["python3", "python"];
|
|
12
|
+
}
|
|
13
|
+
let cachedPythonBin;
|
|
14
|
+
/**
|
|
15
|
+
* Resolve a working CPython binary, or null if none responds in time.
|
|
16
|
+
*/
|
|
17
|
+
export function resolvePythonBin() {
|
|
18
|
+
if (cachedPythonBin !== undefined)
|
|
19
|
+
return cachedPythonBin;
|
|
20
|
+
for (const bin of pythonCandidates()) {
|
|
21
|
+
const r = spawnSync(bin, ["-c", "import ast, json"], {
|
|
22
|
+
encoding: "utf8",
|
|
23
|
+
timeout: PYTHON_PROBE_TIMEOUT_MS,
|
|
24
|
+
windowsHide: true,
|
|
25
|
+
});
|
|
26
|
+
if (r.status === 0 && !r.error) {
|
|
27
|
+
cachedPythonBin = bin;
|
|
28
|
+
return bin;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
cachedPythonBin = null;
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Real Python AST via the CPython `ast` module (requires python on PATH).
|
|
36
|
+
* Not regex. If no interpreter is available, capabilities are unsupported.
|
|
37
|
+
*/
|
|
38
|
+
export function pythonAvailable() {
|
|
39
|
+
return resolvePythonBin() !== null;
|
|
40
|
+
}
|
|
41
|
+
const PY_EXTRACTOR = `
|
|
42
|
+
import ast, json, sys
|
|
43
|
+
source = sys.stdin.read()
|
|
44
|
+
path = sys.argv[1]
|
|
45
|
+
try:
|
|
46
|
+
tree = ast.parse(source, filename=path)
|
|
47
|
+
except SyntaxError as e:
|
|
48
|
+
print(json.dumps({"ok": False, "error": str(e), "symbols": [], "imports": [], "calls": []}))
|
|
49
|
+
sys.exit(0)
|
|
50
|
+
symbols = []
|
|
51
|
+
imports = []
|
|
52
|
+
calls = []
|
|
53
|
+
for node in ast.walk(tree):
|
|
54
|
+
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
55
|
+
symbols.append({"name": node.name, "kind": "function", "line": node.lineno})
|
|
56
|
+
elif isinstance(node, ast.ClassDef):
|
|
57
|
+
symbols.append({"name": node.name, "kind": "class", "line": node.lineno})
|
|
58
|
+
elif isinstance(node, ast.Import):
|
|
59
|
+
for a in node.names:
|
|
60
|
+
imports.append({"specifier": a.name})
|
|
61
|
+
elif isinstance(node, ast.ImportFrom):
|
|
62
|
+
mod = node.module or ""
|
|
63
|
+
imports.append({"specifier": mod if mod else "."})
|
|
64
|
+
elif isinstance(node, ast.Call):
|
|
65
|
+
try:
|
|
66
|
+
callee = ast.unparse(node.func)
|
|
67
|
+
except Exception:
|
|
68
|
+
callee = type(node.func).__name__
|
|
69
|
+
calls.append({"callee": callee})
|
|
70
|
+
print(json.dumps({"ok": True, "symbols": symbols, "imports": imports, "calls": calls}))
|
|
71
|
+
`;
|
|
72
|
+
export const pythonAdapter = {
|
|
73
|
+
id: "python",
|
|
74
|
+
extensions: [".py"],
|
|
75
|
+
capabilities: () => {
|
|
76
|
+
const ok = pythonAvailable();
|
|
77
|
+
const v = ok ? "supported" : "unsupported";
|
|
78
|
+
return {
|
|
79
|
+
parse: v,
|
|
80
|
+
symbols: v,
|
|
81
|
+
definitions: v,
|
|
82
|
+
imports: v,
|
|
83
|
+
calls: v,
|
|
84
|
+
references: "unsupported",
|
|
85
|
+
diagnostics: ok ? "supported" : "unsupported",
|
|
86
|
+
};
|
|
87
|
+
},
|
|
88
|
+
async parse(filePath, source) {
|
|
89
|
+
const pythonBin = resolvePythonBin();
|
|
90
|
+
if (!pythonBin) {
|
|
91
|
+
return {
|
|
92
|
+
language: "python",
|
|
93
|
+
file: filePath,
|
|
94
|
+
ok: false,
|
|
95
|
+
capabilities: pythonAdapter.capabilities(),
|
|
96
|
+
symbols: [],
|
|
97
|
+
imports: [],
|
|
98
|
+
calls: [],
|
|
99
|
+
diagnostics: ["python not available on PATH"],
|
|
100
|
+
limitations: ["Python AST adapter requires python3/python with stdlib ast module"],
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
const r = spawnSync(pythonBin, ["-c", PY_EXTRACTOR, filePath], {
|
|
104
|
+
input: source,
|
|
105
|
+
encoding: "utf8",
|
|
106
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
107
|
+
timeout: 15_000,
|
|
108
|
+
windowsHide: true,
|
|
109
|
+
});
|
|
110
|
+
if (r.status !== 0 || !r.stdout) {
|
|
111
|
+
return {
|
|
112
|
+
language: "python",
|
|
113
|
+
file: filePath,
|
|
114
|
+
ok: false,
|
|
115
|
+
capabilities: pythonAdapter.capabilities(),
|
|
116
|
+
symbols: [],
|
|
117
|
+
imports: [],
|
|
118
|
+
calls: [],
|
|
119
|
+
diagnostics: [r.stderr || r.error?.message || "python extractor failed"],
|
|
120
|
+
limitations: [],
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
let parsed;
|
|
124
|
+
try {
|
|
125
|
+
parsed = JSON.parse(r.stdout);
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
return {
|
|
129
|
+
language: "python",
|
|
130
|
+
file: filePath,
|
|
131
|
+
ok: false,
|
|
132
|
+
capabilities: pythonAdapter.capabilities(),
|
|
133
|
+
symbols: [],
|
|
134
|
+
imports: [],
|
|
135
|
+
calls: [],
|
|
136
|
+
diagnostics: ["invalid JSON from python extractor"],
|
|
137
|
+
limitations: [],
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
if (!parsed.ok) {
|
|
141
|
+
return {
|
|
142
|
+
language: "python",
|
|
143
|
+
file: filePath,
|
|
144
|
+
ok: false,
|
|
145
|
+
capabilities: pythonAdapter.capabilities(),
|
|
146
|
+
symbols: [],
|
|
147
|
+
imports: [],
|
|
148
|
+
calls: [],
|
|
149
|
+
diagnostics: [parsed.error ?? "parse error"],
|
|
150
|
+
limitations: [],
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
return {
|
|
154
|
+
language: "python",
|
|
155
|
+
file: filePath,
|
|
156
|
+
ok: true,
|
|
157
|
+
capabilities: pythonAdapter.capabilities(),
|
|
158
|
+
symbols: parsed.symbols.map((s) => ({
|
|
159
|
+
id: sid(s.kind, `${filePath}:${s.name}:${s.line}`),
|
|
160
|
+
name: s.name,
|
|
161
|
+
kind: s.kind,
|
|
162
|
+
file: filePath,
|
|
163
|
+
line: s.line,
|
|
164
|
+
evidence: "ast",
|
|
165
|
+
})),
|
|
166
|
+
imports: parsed.imports.map((i) => ({
|
|
167
|
+
fromFile: filePath,
|
|
168
|
+
specifier: i.specifier,
|
|
169
|
+
evidence: "ast",
|
|
170
|
+
})),
|
|
171
|
+
calls: parsed.calls.map((c) => ({
|
|
172
|
+
fromFile: filePath,
|
|
173
|
+
callee: c.callee,
|
|
174
|
+
evidence: "ast",
|
|
175
|
+
})),
|
|
176
|
+
diagnostics: [],
|
|
177
|
+
limitations: ["Cross-file references not resolved; CPython ast single-file only"],
|
|
178
|
+
};
|
|
179
|
+
},
|
|
180
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Language adapter contract — real parsers only.
|
|
3
|
+
* Unsupported capabilities must be reported as unsupported, never as inferred AST.
|
|
4
|
+
*/
|
|
5
|
+
export type LanguageId = "typescript" | "javascript" | "python" | "java" | "kotlin" | "go" | "rust" | "php" | "dart" | "unknown";
|
|
6
|
+
export type AdapterCapability = "parse" | "symbols" | "definitions" | "references" | "imports" | "calls" | "diagnostics";
|
|
7
|
+
export interface LanguageSymbol {
|
|
8
|
+
id: string;
|
|
9
|
+
name: string;
|
|
10
|
+
kind: string;
|
|
11
|
+
file: string;
|
|
12
|
+
line?: number;
|
|
13
|
+
evidence: "ast" | "unsupported";
|
|
14
|
+
}
|
|
15
|
+
export interface LanguageImport {
|
|
16
|
+
fromFile: string;
|
|
17
|
+
specifier: string;
|
|
18
|
+
evidence: "ast" | "unsupported";
|
|
19
|
+
}
|
|
20
|
+
export interface LanguageCall {
|
|
21
|
+
fromFile: string;
|
|
22
|
+
callee: string;
|
|
23
|
+
evidence: "ast" | "unsupported";
|
|
24
|
+
}
|
|
25
|
+
export interface ParseResult {
|
|
26
|
+
language: LanguageId;
|
|
27
|
+
file: string;
|
|
28
|
+
ok: boolean;
|
|
29
|
+
capabilities: Partial<Record<AdapterCapability, "supported" | "unsupported">>;
|
|
30
|
+
symbols: LanguageSymbol[];
|
|
31
|
+
imports: LanguageImport[];
|
|
32
|
+
calls: LanguageCall[];
|
|
33
|
+
diagnostics: string[];
|
|
34
|
+
limitations: string[];
|
|
35
|
+
}
|
|
36
|
+
export interface LanguageAdapter {
|
|
37
|
+
id: LanguageId;
|
|
38
|
+
extensions: string[];
|
|
39
|
+
capabilities(): Partial<Record<AdapterCapability, "supported" | "unsupported">>;
|
|
40
|
+
parse(filePath: string, source: string): Promise<ParseResult>;
|
|
41
|
+
}
|
|
42
|
+
export declare function detectLanguage(filePath: string): LanguageId;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Language adapter contract — real parsers only.
|
|
3
|
+
* Unsupported capabilities must be reported as unsupported, never as inferred AST.
|
|
4
|
+
*/
|
|
5
|
+
export function detectLanguage(filePath) {
|
|
6
|
+
const lower = filePath.toLowerCase();
|
|
7
|
+
if (/\.tsx?$/.test(lower) || /\.mts$/.test(lower) || /\.cts$/.test(lower))
|
|
8
|
+
return "typescript";
|
|
9
|
+
if (/\.jsx?$/.test(lower) || /\.mjs$/.test(lower) || /\.cjs$/.test(lower))
|
|
10
|
+
return "javascript";
|
|
11
|
+
if (/\.py$/.test(lower))
|
|
12
|
+
return "python";
|
|
13
|
+
if (/\.java$/.test(lower))
|
|
14
|
+
return "java";
|
|
15
|
+
if (/\.kt$/.test(lower) || /\.kts$/.test(lower))
|
|
16
|
+
return "kotlin";
|
|
17
|
+
if (/\.go$/.test(lower))
|
|
18
|
+
return "go";
|
|
19
|
+
if (/\.rs$/.test(lower))
|
|
20
|
+
return "rust";
|
|
21
|
+
if (/\.php$/.test(lower))
|
|
22
|
+
return "php";
|
|
23
|
+
if (/\.dart$/.test(lower))
|
|
24
|
+
return "dart";
|
|
25
|
+
return "unknown";
|
|
26
|
+
}
|