@praneeth_54/agentdoctor 2.1.0 → 3.0.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 +57 -3
- package/README.md +489 -290
- package/dist/agent/chat/deterministic.d.ts +6 -0
- package/dist/agent/chat/deterministic.js +180 -0
- package/dist/agent/chat/service.js +8 -14
- package/dist/agent/index.d.ts +2 -0
- package/dist/agent/index.js +1 -0
- package/dist/agent/loop.d.ts +2 -0
- package/dist/agent/loop.js +31 -0
- package/dist/agent/roles.d.ts +12 -0
- package/dist/agent/roles.js +138 -0
- package/dist/agent/runtime.d.ts +21 -5
- package/dist/agent/runtime.js +141 -28
- package/dist/agent/tools/execute.d.ts +2 -0
- package/dist/agent/tools/execute.js +10 -0
- package/dist/ai/config.js +1 -0
- package/dist/ai/redact.d.ts +1 -0
- package/dist/ai/redact.js +47 -7
- package/dist/ai/types.d.ts +1 -1
- package/dist/cli/commands/agent.d.ts +2 -0
- package/dist/cli/commands/agent.js +19 -8
- package/dist/cli/commands/chat.js +2 -4
- package/dist/cli/commands/product.d.ts +19 -0
- package/dist/cli/commands/product.js +147 -0
- package/dist/cli/commands/start.d.ts +15 -0
- package/dist/cli/commands/start.js +80 -0
- package/dist/cli/program.js +141 -0
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -1
- package/dist/dashboard/server.js +255 -53
- package/dist/index.d.ts +4 -0
- package/dist/index.js +2 -0
- package/dist/intelligence/graph/build.js +36 -8
- package/dist/intelligence/resolve/imports.js +1 -1
- package/dist/languages/dart.d.ts +10 -0
- package/dist/languages/dart.js +99 -0
- package/dist/languages/go.d.ts +13 -7
- package/dist/languages/go.js +84 -24
- package/dist/languages/index.d.ts +5 -1
- package/dist/languages/index.js +13 -36
- package/dist/languages/java.d.ts +10 -0
- package/dist/languages/java.js +80 -0
- package/dist/languages/kotlin.d.ts +10 -0
- package/dist/languages/kotlin.js +85 -0
- package/dist/languages/rust.d.ts +10 -0
- package/dist/languages/rust.js +93 -0
- package/dist/languages/types.d.ts +4 -3
- package/dist/mcp/agent/registry.d.ts +1 -1
- package/dist/mcp/agent/registry.js +109 -23
- package/dist/mcp/intelligence/handlers.d.ts +3 -0
- package/dist/mcp/intelligence/handlers.js +28 -0
- package/dist/mcp/intelligence/registry.d.ts +1 -1
- package/dist/mcp/intelligence/registry.js +30 -1
- package/dist/product/api/doctor.d.ts +14 -0
- package/dist/product/api/doctor.js +185 -0
- package/dist/product/api/openapi.d.ts +7 -0
- package/dist/product/api/openapi.js +122 -0
- package/dist/product/approval/model.d.ts +30 -0
- package/dist/product/approval/model.js +64 -0
- package/dist/product/approval/session.d.ts +49 -0
- package/dist/product/approval/session.js +134 -0
- package/dist/product/database/doctor.d.ts +30 -0
- package/dist/product/database/doctor.js +184 -0
- package/dist/product/decisions/ledger.d.ts +24 -0
- package/dist/product/decisions/ledger.js +110 -0
- package/dist/product/deps/analyze.d.ts +46 -0
- package/dist/product/deps/analyze.js +137 -0
- package/dist/product/deps/lockfiles.d.ts +25 -0
- package/dist/product/deps/lockfiles.js +200 -0
- package/dist/product/discovery/roots.d.ts +42 -0
- package/dist/product/discovery/roots.js +215 -0
- package/dist/product/dna/build.d.ts +50 -0
- package/dist/product/dna/build.js +255 -0
- package/dist/product/eval/lab.d.ts +17 -0
- package/dist/product/eval/lab.js +218 -0
- package/dist/product/events/doctor.d.ts +21 -0
- package/dist/product/events/doctor.js +147 -0
- package/dist/product/evidence-scan.d.ts +12 -0
- package/dist/product/evidence-scan.js +46 -0
- package/dist/product/evolution/timeline.d.ts +29 -0
- package/dist/product/evolution/timeline.js +123 -0
- package/dist/product/features/intelligence.d.ts +23 -0
- package/dist/product/features/intelligence.js +158 -0
- package/dist/product/forensic/mode.d.ts +25 -0
- package/dist/product/forensic/mode.js +70 -0
- package/dist/product/graph/enrich-languages.d.ts +20 -0
- package/dist/product/graph/enrich-languages.js +193 -0
- package/dist/product/health/code-health.d.ts +20 -0
- package/dist/product/health/code-health.js +149 -0
- package/dist/product/index.d.ts +69 -0
- package/dist/product/index.js +35 -0
- package/dist/product/ledger/change-ledger.d.ts +23 -0
- package/dist/product/ledger/change-ledger.js +64 -0
- package/dist/product/map/software-map.d.ts +17 -0
- package/dist/product/map/software-map.js +95 -0
- package/dist/product/memory/institutional.d.ts +16 -0
- package/dist/product/memory/institutional.js +87 -0
- package/dist/product/ops/incident.d.ts +20 -0
- package/dist/product/ops/incident.js +61 -0
- package/dist/product/ops/infra.d.ts +15 -0
- package/dist/product/ops/infra.js +112 -0
- package/dist/product/org/model.d.ts +34 -0
- package/dist/product/org/model.js +195 -0
- package/dist/product/privacy/doctor.d.ts +13 -0
- package/dist/product/privacy/doctor.js +90 -0
- package/dist/product/requirements/trace.d.ts +21 -0
- package/dist/product/requirements/trace.js +166 -0
- package/dist/product/search/index.d.ts +29 -0
- package/dist/product/search/index.js +116 -0
- package/dist/product/search/software-search.d.ts +19 -0
- package/dist/product/search/software-search.js +100 -0
- package/dist/product/security/doctor.d.ts +23 -0
- package/dist/product/security/doctor.js +124 -0
- package/dist/product/self/diagnose.d.ts +15 -0
- package/dist/product/self/diagnose.js +82 -0
- package/dist/product/techdebt/roadmap.d.ts +20 -0
- package/dist/product/techdebt/roadmap.js +118 -0
- package/dist/product/testbrain/analyze.d.ts +29 -0
- package/dist/product/testbrain/analyze.js +129 -0
- package/dist/product/truth.d.ts +12 -0
- package/dist/product/truth.js +30 -0
- package/dist/product/twin/digital-twin.d.ts +23 -0
- package/dist/product/twin/digital-twin.js +52 -0
- package/dist/product/twin/store.d.ts +21 -0
- package/dist/product/twin/store.js +70 -0
- package/dist/product/whatif/engine.d.ts +28 -0
- package/dist/product/whatif/engine.js +70 -0
- package/dist/utils/fs.js +10 -2
- package/package.json +1 -1
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const TRUTH_ORDER = [
|
|
2
|
+
"VERIFIED",
|
|
3
|
+
"INFERRED",
|
|
4
|
+
"PARTIAL",
|
|
5
|
+
"EXPERIMENTAL",
|
|
6
|
+
"UNKNOWN",
|
|
7
|
+
"EXTERNAL",
|
|
8
|
+
];
|
|
9
|
+
/** Never promote weaker evidence to stronger by accident. */
|
|
10
|
+
export function minProductTruth(a, b) {
|
|
11
|
+
const ia = TRUTH_ORDER.indexOf(a);
|
|
12
|
+
const ib = TRUTH_ORDER.indexOf(b);
|
|
13
|
+
return TRUTH_ORDER[Math.max(ia, ib)];
|
|
14
|
+
}
|
|
15
|
+
export function truthLabelDescription(label) {
|
|
16
|
+
switch (label) {
|
|
17
|
+
case "VERIFIED":
|
|
18
|
+
return "Directly supported by repository file evidence.";
|
|
19
|
+
case "INFERRED":
|
|
20
|
+
return "Heuristic or pattern match; not fully verified.";
|
|
21
|
+
case "PARTIAL":
|
|
22
|
+
return "Some evidence exists; coverage is incomplete.";
|
|
23
|
+
case "EXPERIMENTAL":
|
|
24
|
+
return "Early capability; behavior may change.";
|
|
25
|
+
case "UNKNOWN":
|
|
26
|
+
return "Cannot be determined from current evidence.";
|
|
27
|
+
case "EXTERNAL":
|
|
28
|
+
return "Requires information outside the repository.";
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { getBrainStatus } from "../../core/brain-cli/service.js";
|
|
2
|
+
import { analyzeCodeHealth } from "../health/code-health.js";
|
|
3
|
+
import { type ProjectDna } from "../dna/build.js";
|
|
4
|
+
import type { TruthLabel } from "../truth.js";
|
|
5
|
+
export interface SoftwareDigitalTwin {
|
|
6
|
+
root: string;
|
|
7
|
+
generatedAt: string;
|
|
8
|
+
dna: ProjectDna;
|
|
9
|
+
brain: Awaited<ReturnType<typeof getBrainStatus>>;
|
|
10
|
+
graphSummary: {
|
|
11
|
+
nodeCount: number;
|
|
12
|
+
edgeCount: number;
|
|
13
|
+
truth: TruthLabel;
|
|
14
|
+
};
|
|
15
|
+
gitSummary: {
|
|
16
|
+
gitAvailable: boolean;
|
|
17
|
+
hotspotCount: number;
|
|
18
|
+
truth: TruthLabel;
|
|
19
|
+
};
|
|
20
|
+
health: Awaited<ReturnType<typeof analyzeCodeHealth>>;
|
|
21
|
+
limitations: string[];
|
|
22
|
+
}
|
|
23
|
+
export declare function buildSoftwareDigitalTwinSnapshot(rootInput: string): Promise<SoftwareDigitalTwin>;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { getBrainStatus } from "../../core/brain-cli/service.js";
|
|
2
|
+
import { buildIntelligenceGraph } from "../../intelligence/graph/build.js";
|
|
3
|
+
import { analyzeGitIntelligence } from "../../intelligence/git/analyze.js";
|
|
4
|
+
import { analyzeCodeHealth } from "../health/code-health.js";
|
|
5
|
+
import { buildProjectDna } from "../dna/build.js";
|
|
6
|
+
import { resolveRepoRoot } from "../../utils/path.js";
|
|
7
|
+
export async function buildSoftwareDigitalTwinSnapshot(rootInput) {
|
|
8
|
+
const root = resolveRepoRoot(rootInput);
|
|
9
|
+
const limitations = [
|
|
10
|
+
"Digital twin is a deterministic snapshot of local analyzers, not live runtime state.",
|
|
11
|
+
"Graph summary may be partial when graph build skips files.",
|
|
12
|
+
];
|
|
13
|
+
const [dna, brain, git] = await Promise.all([
|
|
14
|
+
buildProjectDna(root),
|
|
15
|
+
getBrainStatus(root),
|
|
16
|
+
analyzeGitIntelligence(root),
|
|
17
|
+
]);
|
|
18
|
+
let nodeCount = 0;
|
|
19
|
+
let edgeCount = 0;
|
|
20
|
+
let graphTruth = "UNKNOWN";
|
|
21
|
+
try {
|
|
22
|
+
const graph = await buildIntelligenceGraph({ root, mode: "auto" });
|
|
23
|
+
nodeCount = graph.nodes.length;
|
|
24
|
+
edgeCount = graph.edges.length;
|
|
25
|
+
graphTruth = "INFERRED";
|
|
26
|
+
if (graph.limitations?.length) {
|
|
27
|
+
limitations.push(...graph.limitations.slice(0, 3));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
limitations.push("Intelligence graph build failed for twin snapshot.");
|
|
32
|
+
}
|
|
33
|
+
const healthWithGit = await analyzeCodeHealth(root, { gitReport: git });
|
|
34
|
+
return {
|
|
35
|
+
root,
|
|
36
|
+
generatedAt: new Date().toISOString(),
|
|
37
|
+
dna,
|
|
38
|
+
brain,
|
|
39
|
+
graphSummary: {
|
|
40
|
+
nodeCount,
|
|
41
|
+
edgeCount,
|
|
42
|
+
truth: graphTruth,
|
|
43
|
+
},
|
|
44
|
+
gitSummary: {
|
|
45
|
+
gitAvailable: git.gitAvailable,
|
|
46
|
+
hotspotCount: git.hotspots.length,
|
|
47
|
+
truth: git.gitAvailable ? "INFERRED" : "UNKNOWN",
|
|
48
|
+
},
|
|
49
|
+
health: healthWithGit,
|
|
50
|
+
limitations,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { SoftwareDigitalTwin } from "./digital-twin.js";
|
|
2
|
+
export interface TwinSnapshotMeta {
|
|
3
|
+
root: string;
|
|
4
|
+
generatedAt: string;
|
|
5
|
+
invalidationHash: string;
|
|
6
|
+
changedFiles?: string[];
|
|
7
|
+
}
|
|
8
|
+
export interface StoredTwinSnapshot {
|
|
9
|
+
meta: TwinSnapshotMeta;
|
|
10
|
+
twin: SoftwareDigitalTwin;
|
|
11
|
+
}
|
|
12
|
+
export declare function loadTwinSnapshot(rootInput: string): Promise<StoredTwinSnapshot | null>;
|
|
13
|
+
export declare function saveTwinSnapshot(rootInput: string, twin: SoftwareDigitalTwin, meta: Omit<TwinSnapshotMeta, "generatedAt"> & {
|
|
14
|
+
generatedAt?: string;
|
|
15
|
+
}): Promise<string>;
|
|
16
|
+
export declare function invalidateTwinSnapshot(rootInput: string): Promise<void>;
|
|
17
|
+
export declare function updateTwin(rootInput: string, options?: {
|
|
18
|
+
changedFiles?: string[];
|
|
19
|
+
force?: boolean;
|
|
20
|
+
}): Promise<SoftwareDigitalTwin>;
|
|
21
|
+
export declare function buildSoftwareDigitalTwin(rootInput: string): Promise<SoftwareDigitalTwin>;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import { DEFAULT_MAX_FILE_SIZE_BYTES } from "../../constants.js";
|
|
5
|
+
import { atomicWriteTextFile, pathExists, readJsonFile } from "../../utils/fs.js";
|
|
6
|
+
import { resolveRepoRoot } from "../../utils/path.js";
|
|
7
|
+
import { buildSoftwareDigitalTwinSnapshot } from "./digital-twin.js";
|
|
8
|
+
function twinDir(root) {
|
|
9
|
+
return path.join(resolveRepoRoot(root), ".agentdoctor", "twin");
|
|
10
|
+
}
|
|
11
|
+
function snapshotPath(root) {
|
|
12
|
+
return path.join(twinDir(root), "snapshot.json");
|
|
13
|
+
}
|
|
14
|
+
function hashInputs(changedFiles) {
|
|
15
|
+
const payload = (changedFiles ?? []).slice().sort().join("\n");
|
|
16
|
+
return createHash("sha256").update(payload).digest("hex").slice(0, 16);
|
|
17
|
+
}
|
|
18
|
+
export async function loadTwinSnapshot(rootInput) {
|
|
19
|
+
const root = resolveRepoRoot(rootInput);
|
|
20
|
+
const parsed = await readJsonFile(snapshotPath(root), DEFAULT_MAX_FILE_SIZE_BYTES);
|
|
21
|
+
if (!parsed.ok)
|
|
22
|
+
return null;
|
|
23
|
+
if (parsed.data.meta.root !== root)
|
|
24
|
+
return null;
|
|
25
|
+
return parsed.data;
|
|
26
|
+
}
|
|
27
|
+
export async function saveTwinSnapshot(rootInput, twin, meta) {
|
|
28
|
+
const root = resolveRepoRoot(rootInput);
|
|
29
|
+
const file = snapshotPath(root);
|
|
30
|
+
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
31
|
+
const stored = {
|
|
32
|
+
meta: {
|
|
33
|
+
root,
|
|
34
|
+
generatedAt: meta.generatedAt ?? new Date().toISOString(),
|
|
35
|
+
invalidationHash: meta.invalidationHash,
|
|
36
|
+
...(meta.changedFiles?.length ? { changedFiles: meta.changedFiles } : {}),
|
|
37
|
+
},
|
|
38
|
+
twin,
|
|
39
|
+
};
|
|
40
|
+
await atomicWriteTextFile(file, `${JSON.stringify(stored, null, 2)}\n`);
|
|
41
|
+
return file;
|
|
42
|
+
}
|
|
43
|
+
export async function invalidateTwinSnapshot(rootInput) {
|
|
44
|
+
const root = resolveRepoRoot(rootInput);
|
|
45
|
+
const file = snapshotPath(root);
|
|
46
|
+
if (await pathExists(file)) {
|
|
47
|
+
await fs.rm(file, { force: true });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
export async function updateTwin(rootInput, options) {
|
|
51
|
+
const root = resolveRepoRoot(rootInput);
|
|
52
|
+
const invalidationHash = hashInputs(options?.changedFiles);
|
|
53
|
+
const existing = options?.force ? null : await loadTwinSnapshot(root);
|
|
54
|
+
if (existing &&
|
|
55
|
+
existing.meta.invalidationHash === invalidationHash &&
|
|
56
|
+
existing.twin.root === root) {
|
|
57
|
+
return existing.twin;
|
|
58
|
+
}
|
|
59
|
+
await invalidateTwinSnapshot(root);
|
|
60
|
+
const twin = await buildSoftwareDigitalTwinSnapshot(root);
|
|
61
|
+
await saveTwinSnapshot(root, twin, {
|
|
62
|
+
root,
|
|
63
|
+
invalidationHash,
|
|
64
|
+
...(options?.changedFiles?.length ? { changedFiles: options.changedFiles } : {}),
|
|
65
|
+
});
|
|
66
|
+
return twin;
|
|
67
|
+
}
|
|
68
|
+
export async function buildSoftwareDigitalTwin(rootInput) {
|
|
69
|
+
return updateTwin(rootInput);
|
|
70
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { TruthLabel } from "../truth.js";
|
|
2
|
+
export interface WhatIfAffectedItem {
|
|
3
|
+
pathOrSymbol: string;
|
|
4
|
+
role: "changed" | "caller" | "callee" | "reverse-dependency";
|
|
5
|
+
truth: TruthLabel;
|
|
6
|
+
}
|
|
7
|
+
export interface WhatIfReport {
|
|
8
|
+
root: string;
|
|
9
|
+
target: string;
|
|
10
|
+
affected: WhatIfAffectedItem[];
|
|
11
|
+
limitations: string[];
|
|
12
|
+
}
|
|
13
|
+
export declare function analyzeWhatIf(rootInput: string, target: string, options?: {
|
|
14
|
+
graph?: {
|
|
15
|
+
nodes: Array<{
|
|
16
|
+
id: string;
|
|
17
|
+
kind: string;
|
|
18
|
+
label: string;
|
|
19
|
+
path?: string;
|
|
20
|
+
}>;
|
|
21
|
+
edges: Array<{
|
|
22
|
+
from: string;
|
|
23
|
+
to: string;
|
|
24
|
+
kind: string;
|
|
25
|
+
}>;
|
|
26
|
+
};
|
|
27
|
+
buildGraph?: boolean;
|
|
28
|
+
}): Promise<WhatIfReport>;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { deriveGraphChangeImpact } from "../../assurance/change.js";
|
|
2
|
+
import { buildIntelligenceGraph } from "../../intelligence/graph/build.js";
|
|
3
|
+
import { resolveRepoRoot } from "../../utils/path.js";
|
|
4
|
+
import { minProductTruth } from "../truth.js";
|
|
5
|
+
function normalizeTarget(target) {
|
|
6
|
+
return target.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
7
|
+
}
|
|
8
|
+
export async function analyzeWhatIf(rootInput, target, options) {
|
|
9
|
+
const root = resolveRepoRoot(rootInput);
|
|
10
|
+
const normalized = normalizeTarget(target);
|
|
11
|
+
const limitations = [
|
|
12
|
+
"Impact analysis uses intelligence graph import/call edges when available.",
|
|
13
|
+
"Symbol-level precision depends on graph node coverage.",
|
|
14
|
+
];
|
|
15
|
+
let graph = options?.graph;
|
|
16
|
+
if (!graph && options?.buildGraph !== false) {
|
|
17
|
+
try {
|
|
18
|
+
graph = await buildIntelligenceGraph({ root, mode: "auto" });
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
limitations.push("Graph build failed; impact may be empty.");
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
if (!graph) {
|
|
25
|
+
limitations.push("No graph provided or built; only the target file is listed.");
|
|
26
|
+
return {
|
|
27
|
+
root,
|
|
28
|
+
target: normalized,
|
|
29
|
+
affected: [
|
|
30
|
+
{
|
|
31
|
+
pathOrSymbol: normalized,
|
|
32
|
+
role: "changed",
|
|
33
|
+
truth: "VERIFIED",
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
limitations,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
const changedFiles = [{ path: normalized, kind: "modified" }];
|
|
40
|
+
const impact = deriveGraphChangeImpact(graph, changedFiles, []);
|
|
41
|
+
const affected = [
|
|
42
|
+
{
|
|
43
|
+
pathOrSymbol: normalized,
|
|
44
|
+
role: "changed",
|
|
45
|
+
truth: "VERIFIED",
|
|
46
|
+
},
|
|
47
|
+
];
|
|
48
|
+
for (const c of impact.callers) {
|
|
49
|
+
affected.push({
|
|
50
|
+
pathOrSymbol: c,
|
|
51
|
+
role: "caller",
|
|
52
|
+
truth: minProductTruth("INFERRED", "PARTIAL"),
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
for (const c of impact.callees) {
|
|
56
|
+
affected.push({
|
|
57
|
+
pathOrSymbol: c,
|
|
58
|
+
role: "callee",
|
|
59
|
+
truth: minProductTruth("INFERRED", "PARTIAL"),
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
for (const c of impact.reverseDependencies) {
|
|
63
|
+
affected.push({
|
|
64
|
+
pathOrSymbol: c,
|
|
65
|
+
role: "reverse-dependency",
|
|
66
|
+
truth: minProductTruth("INFERRED", "PARTIAL"),
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
return { root, target: normalized, affected, limitations };
|
|
70
|
+
}
|
package/dist/utils/fs.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
1
2
|
import fs from "node:fs/promises";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
export async function pathExists(target) {
|
|
@@ -68,8 +69,15 @@ export async function listDirectorySafe(dirPath) {
|
|
|
68
69
|
export async function atomicWriteTextFile(filePath, content) {
|
|
69
70
|
const dir = path.dirname(filePath);
|
|
70
71
|
await fs.mkdir(dir, { recursive: true });
|
|
71
|
-
const tmp = path.join(dir, `.${path.basename(filePath)}
|
|
72
|
-
|
|
72
|
+
const tmp = path.join(dir, `.${path.basename(filePath)}.${randomBytes(16).toString("hex")}.tmp`);
|
|
73
|
+
// Exclusive create + owner-only mode — closes CodeQL js/insecure-temporary-file.
|
|
74
|
+
const handle = await fs.open(tmp, "wx", 0o600);
|
|
75
|
+
try {
|
|
76
|
+
await handle.writeFile(content, "utf8");
|
|
77
|
+
}
|
|
78
|
+
finally {
|
|
79
|
+
await handle.close();
|
|
80
|
+
}
|
|
73
81
|
try {
|
|
74
82
|
await replaceFile(tmp, filePath);
|
|
75
83
|
}
|
package/package.json
CHANGED