@praneeth_54/agentdoctor 3.0.0 → 3.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 +30 -1
- package/README.md +9 -8
- package/dist/agent/context/retrieve.d.ts +2 -1
- package/dist/agent/context/retrieve.js +14 -1
- package/dist/agent/tools/execute.js +39 -43
- package/dist/agent/tools/write.d.ts +1 -1
- package/dist/agent/tools/write.js +18 -4
- package/dist/ai/providers/adversarial-local.d.ts +7 -0
- package/dist/ai/providers/adversarial-local.js +146 -0
- package/dist/cli/commands/agent.js +13 -3
- package/dist/cli/commands/architecture.js +7 -2
- package/dist/cli/commands/brain.js +7 -2
- package/dist/cli/commands/chat.js +13 -3
- package/dist/cli/commands/complete.js +13 -3
- package/dist/cli/commands/fix.js +6 -0
- package/dist/cli/commands/learn.js +7 -2
- package/dist/cli/commands/mcp.js +7 -3
- package/dist/cli/commands/platform.js +7 -2
- package/dist/cli/commands/policy-graph-run.js +7 -0
- package/dist/cli/commands/product.js +13 -3
- package/dist/cli/commands/scan.js +6 -0
- package/dist/cli/commands/start.js +7 -1
- package/dist/cli/commands/v2.js +61 -11
- package/dist/cli/commands/verify.js +6 -0
- package/dist/cli/safe-root.d.ts +17 -0
- package/dist/cli/safe-root.js +32 -0
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -1
- package/dist/core/brain-cli/service.js +5 -2
- package/dist/core/monorepo/detect.js +10 -0
- package/dist/core/secrets/scan.js +9 -0
- package/dist/core/understanding/brain/storage/store.d.ts +4 -0
- package/dist/core/understanding/brain/storage/store.js +22 -2
- package/dist/dashboard/page.d.ts +2 -0
- package/dist/dashboard/page.js +53 -0
- package/dist/dashboard/server.js +13 -238
- package/dist/dashboard/ui/client.d.ts +2 -0
- package/dist/dashboard/ui/client.js +2 -0
- package/dist/dashboard/ui/styles.d.ts +2 -0
- package/dist/dashboard/ui/styles.js +138 -0
- package/dist/discovery/files.js +13 -0
- package/dist/intelligence/graph/build.js +10 -30
- package/dist/intelligence/graph/incremental.d.ts +2 -0
- package/dist/intelligence/graph/incremental.js +9 -0
- package/dist/mcp/intelligence/path-safety.d.ts +6 -1
- package/dist/mcp/intelligence/path-safety.js +55 -2
- package/dist/platform/graph/build.js +9 -0
- package/dist/product/decisions/ledger.d.ts +13 -1
- package/dist/product/decisions/ledger.js +41 -10
- package/dist/product/discovery/roots.d.ts +10 -0
- package/dist/product/discovery/roots.js +23 -7
- package/dist/product/graph/enrich-languages.js +7 -32
- package/dist/product/index.d.ts +4 -1
- package/dist/product/index.js +2 -1
- package/dist/product/map/software-map.js +2 -1
- package/dist/product/memory/institutional.js +5 -0
- package/dist/product/search/software-search.js +5 -0
- package/dist/product/twin/store.js +9 -1
- package/dist/product/whatif/engine.js +22 -1
- package/dist/project/ownership.d.ts +65 -0
- package/dist/project/ownership.js +169 -0
- package/package.json +1 -1
|
@@ -2,9 +2,14 @@ import { MockModelProvider, createModelProvider, loadAiConfig } from "../../ai/i
|
|
|
2
2
|
import { StudentService } from "../../agent/student.js";
|
|
3
3
|
import { parseAgentMode, defaultStudentMode } from "../../agent/modes.js";
|
|
4
4
|
import { EXIT_CODES } from "../../types/index.js";
|
|
5
|
-
import {
|
|
5
|
+
import { resolveCliProjectRoot } from "../safe-root.js";
|
|
6
6
|
export async function runLearnCommand(options) {
|
|
7
|
-
const
|
|
7
|
+
const gated = await resolveCliProjectRoot(options.root ?? process.cwd());
|
|
8
|
+
if (!gated.ok) {
|
|
9
|
+
console.error(`Error: ${gated.message}`);
|
|
10
|
+
return gated.code;
|
|
11
|
+
}
|
|
12
|
+
const root = gated.root;
|
|
8
13
|
const mode = parseAgentMode(options.mode) ?? defaultStudentMode();
|
|
9
14
|
const provider = options.useMock ? new MockModelProvider() : createModelProvider(loadAiConfig());
|
|
10
15
|
const resolvedMode = options.build
|
package/dist/cli/commands/mcp.js
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import { EXIT_CODES } from "../../types/index.js";
|
|
2
|
-
import {
|
|
2
|
+
import { resolveCliProjectRoot } from "../safe-root.js";
|
|
3
3
|
import { runAgentDoctorMcpStdio } from "../../mcp/agentdoctor/server.js";
|
|
4
4
|
/**
|
|
5
5
|
* Start combined AgentDoctor MCP (Brain tools + intelligence tools) over STDIO.
|
|
6
6
|
*/
|
|
7
7
|
export async function runMcpCommand(options) {
|
|
8
|
-
const
|
|
8
|
+
const gated = await resolveCliProjectRoot(options.root);
|
|
9
|
+
if (!gated.ok) {
|
|
10
|
+
console.error(`Error: ${gated.message}`);
|
|
11
|
+
return gated.code;
|
|
12
|
+
}
|
|
9
13
|
await runAgentDoctorMcpStdio({
|
|
10
|
-
root,
|
|
14
|
+
root: gated.root,
|
|
11
15
|
buildIfMissing: options.buildIfMissing !== false,
|
|
12
16
|
});
|
|
13
17
|
return EXIT_CODES.SUCCESS;
|
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { EXIT_CODES } from "../../types/index.js";
|
|
3
|
-
import {
|
|
3
|
+
import { resolveCliProjectRoot } from "../safe-root.js";
|
|
4
4
|
import { analyzeRenameImpact, analyzeTestImpact, appendSessionEvent, buildProvenance, compareCommits, createSession, endSession, evaluateAgentAction, exportSessionMarkdown, formatTestImpactHuman, listSessions, loadSession, persistTestImpactReport, planContext, runPlatformScan, saveProvenance, EVALUATE_ONLY, } from "../../platform/index.js";
|
|
5
5
|
import { buildRepositoryGraph } from "../../platform/graph/build.js";
|
|
6
6
|
function printJson(value) {
|
|
7
7
|
process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
|
|
8
8
|
}
|
|
9
9
|
export async function runPlatformCommand(options) {
|
|
10
|
-
const
|
|
10
|
+
const gated = await resolveCliProjectRoot(options.root ?? process.cwd());
|
|
11
|
+
if (!gated.ok) {
|
|
12
|
+
console.error(`Error: ${gated.message}`);
|
|
13
|
+
return gated.code;
|
|
14
|
+
}
|
|
15
|
+
const root = gated.root;
|
|
11
16
|
try {
|
|
12
17
|
switch (options.action) {
|
|
13
18
|
case "scan": {
|
|
@@ -3,6 +3,7 @@ import { runControlledCommand } from "../../enforcement/runner.js";
|
|
|
3
3
|
import { graphBuild, graphRebuild, graphStatus, graphUpdate, } from "../../intelligence/graph/incremental.js";
|
|
4
4
|
import { buildIntelligenceGraph } from "../../intelligence/graph/build.js";
|
|
5
5
|
import { EXIT_CODES } from "../../types/index.js";
|
|
6
|
+
import { resolveCliProjectRoot } from "../safe-root.js";
|
|
6
7
|
function emit(json, value, human) {
|
|
7
8
|
if (json) {
|
|
8
9
|
process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
|
|
@@ -13,6 +14,12 @@ function emit(json, value, human) {
|
|
|
13
14
|
}
|
|
14
15
|
export async function runGraphSurfaceCommand(options) {
|
|
15
16
|
try {
|
|
17
|
+
const gated = await resolveCliProjectRoot(options.root);
|
|
18
|
+
if (!gated.ok) {
|
|
19
|
+
console.error(`Error: ${gated.message}`);
|
|
20
|
+
return gated.code;
|
|
21
|
+
}
|
|
22
|
+
options = { ...options, root: gated.root };
|
|
16
23
|
const mode = options.mode ?? "auto";
|
|
17
24
|
if (options.action === "status") {
|
|
18
25
|
const status = await graphStatus({ root: options.root });
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { EXIT_CODES } from "../../types/index.js";
|
|
2
|
-
import {
|
|
2
|
+
import { resolveCliProjectRoot } from "../safe-root.js";
|
|
3
3
|
import { resolveTargetArgument } from "./scan.js";
|
|
4
4
|
import { traceRequirements, analyzeApiSurface, analyzeDatabaseSchema, analyzeEvents, analyzeDependencies, analyzeCodeHealth, buildSoftwareMap, analyzeWhatIf, loadDecisionLedger, runForensicAnalysis, buildSoftwareDigitalTwin, runEvalLab, diagnoseAgentDoctorSelf, analyzeInfra, buildIncidentHypotheses, readChangeLedger, analyzeSecuritySurface, analyzePrivacySurface, searchSymbolsAndConcepts, buildTechnicalDebtRoadmap, analyzeFeatureIntelligence, buildSoftwareEvolutionTimeline, queryMemory, loadOrganizationModel, analyzeTestBrain, } from "../../product/index.js";
|
|
5
5
|
import { runRoleAgent } from "../../agent/roles.js";
|
|
@@ -7,7 +7,12 @@ function printJson(value) {
|
|
|
7
7
|
process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
|
|
8
8
|
}
|
|
9
9
|
export async function runProductCommand(options) {
|
|
10
|
-
const
|
|
10
|
+
const gated = await resolveCliProjectRoot(options.root ?? resolveTargetArgument(undefined));
|
|
11
|
+
if (!gated.ok) {
|
|
12
|
+
console.error(`Error: ${gated.message}`);
|
|
13
|
+
return gated.code;
|
|
14
|
+
}
|
|
15
|
+
const root = gated.root;
|
|
11
16
|
try {
|
|
12
17
|
let payload;
|
|
13
18
|
switch (options.action) {
|
|
@@ -137,7 +142,12 @@ export async function runProductCommand(options) {
|
|
|
137
142
|
}
|
|
138
143
|
}
|
|
139
144
|
export async function runChangeLedgerListCommand(options) {
|
|
140
|
-
const
|
|
145
|
+
const gated = await resolveCliProjectRoot(options.root);
|
|
146
|
+
if (!gated.ok) {
|
|
147
|
+
console.error(`Error: ${gated.message}`);
|
|
148
|
+
return gated.code;
|
|
149
|
+
}
|
|
150
|
+
const root = gated.root;
|
|
141
151
|
const payload = await readChangeLedger(root);
|
|
142
152
|
if (options.json)
|
|
143
153
|
printJson(payload);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { evaluateScanPolicy } from "../../core/policy/evaluate.js";
|
|
3
3
|
import { scan } from "../../core/scanner/scan.js";
|
|
4
|
+
import { classifyBroadUserScanRoot } from "../../product/discovery/roots.js";
|
|
4
5
|
import { emitGithubReports } from "../../reporters/github/emit.js";
|
|
5
6
|
import { renderJsonReport } from "../../reporters/json/report.js";
|
|
6
7
|
import { renderTerminalReport } from "../../reporters/terminal/report.js";
|
|
@@ -13,6 +14,11 @@ export async function runScanCommand(options) {
|
|
|
13
14
|
console.error(`Error: not a directory: ${target}`);
|
|
14
15
|
return EXIT_CODES.USAGE_ERROR;
|
|
15
16
|
}
|
|
17
|
+
const broad = classifyBroadUserScanRoot(target);
|
|
18
|
+
if (broad.blocked) {
|
|
19
|
+
console.error(`Error: ${broad.reason}`);
|
|
20
|
+
return EXIT_CODES.USAGE_ERROR;
|
|
21
|
+
}
|
|
16
22
|
try {
|
|
17
23
|
const result = await scan({
|
|
18
24
|
cwd: target,
|
|
@@ -3,6 +3,7 @@ import { initBrainStore, rebuildBrain, getBrainStatus } from "../../core/brain-c
|
|
|
3
3
|
import { discoverProjectRoots } from "../../product/discovery/roots.js";
|
|
4
4
|
import { buildProjectDna, persistProjectDna } from "../../product/dna/build.js";
|
|
5
5
|
import { resolveRepoRoot } from "../../utils/path.js";
|
|
6
|
+
import { resolveCliProjectRoot } from "../safe-root.js";
|
|
6
7
|
function printJson(value) {
|
|
7
8
|
process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
|
|
8
9
|
}
|
|
@@ -64,7 +65,12 @@ export async function runStartCommand(options) {
|
|
|
64
65
|
return EXIT_CODES.SUCCESS;
|
|
65
66
|
}
|
|
66
67
|
export async function runDnaCommand(options) {
|
|
67
|
-
const
|
|
68
|
+
const gated = await resolveCliProjectRoot(options.root);
|
|
69
|
+
if (!gated.ok) {
|
|
70
|
+
console.error(`Error: ${gated.message}`);
|
|
71
|
+
return gated.code;
|
|
72
|
+
}
|
|
73
|
+
const root = gated.root;
|
|
68
74
|
const dna = options.persist ? await persistProjectDna(root) : await buildProjectDna(root);
|
|
69
75
|
if (options.json) {
|
|
70
76
|
printJson(dna);
|
package/dist/cli/commands/v2.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { EXIT_CODES } from "../../types/index.js";
|
|
2
|
-
import {
|
|
2
|
+
import { resolveCliProjectRoot } from "../safe-root.js";
|
|
3
3
|
import { analyzeChanges } from "../../core/changes/analyze.js";
|
|
4
4
|
import { analyzeContextHealth } from "../../core/context-health/analyze.js";
|
|
5
5
|
import { scanSecrets } from "../../core/secrets/scan.js";
|
|
@@ -16,7 +16,12 @@ function printJson(value) {
|
|
|
16
16
|
process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
|
|
17
17
|
}
|
|
18
18
|
export async function runChangesCommand(options) {
|
|
19
|
-
const
|
|
19
|
+
const gated = await resolveCliProjectRoot(options.root ?? process.cwd());
|
|
20
|
+
if (!gated.ok) {
|
|
21
|
+
console.error(`Error: ${gated.message}`);
|
|
22
|
+
return gated.code;
|
|
23
|
+
}
|
|
24
|
+
const root = gated.root;
|
|
20
25
|
const report = await analyzeChanges({
|
|
21
26
|
root,
|
|
22
27
|
...(options.since ? { since: options.since } : {}),
|
|
@@ -38,7 +43,12 @@ export async function runChangesCommand(options) {
|
|
|
38
43
|
return EXIT_CODES.SUCCESS;
|
|
39
44
|
}
|
|
40
45
|
export async function runContextHealthCommand(options) {
|
|
41
|
-
const
|
|
46
|
+
const gated = await resolveCliProjectRoot(options.root ?? process.cwd());
|
|
47
|
+
if (!gated.ok) {
|
|
48
|
+
console.error(`Error: ${gated.message}`);
|
|
49
|
+
return gated.code;
|
|
50
|
+
}
|
|
51
|
+
const root = gated.root;
|
|
42
52
|
const report = await analyzeContextHealth(root);
|
|
43
53
|
if (options.json)
|
|
44
54
|
printJson(report);
|
|
@@ -51,7 +61,12 @@ export async function runContextHealthCommand(options) {
|
|
|
51
61
|
return EXIT_CODES.SUCCESS;
|
|
52
62
|
}
|
|
53
63
|
export async function runSecretsCommand(options) {
|
|
54
|
-
const
|
|
64
|
+
const gated = await resolveCliProjectRoot(options.root ?? process.cwd());
|
|
65
|
+
if (!gated.ok) {
|
|
66
|
+
console.error(`Error: ${gated.message}`);
|
|
67
|
+
return gated.code;
|
|
68
|
+
}
|
|
69
|
+
const root = gated.root;
|
|
55
70
|
const report = await scanSecrets({ root, enabled: true });
|
|
56
71
|
if (options.json)
|
|
57
72
|
printJson(report);
|
|
@@ -67,8 +82,13 @@ export async function runSecretsCommand(options) {
|
|
|
67
82
|
}
|
|
68
83
|
export async function runFixUndoCommand(options) {
|
|
69
84
|
try {
|
|
85
|
+
const gated = await resolveCliProjectRoot(options.root);
|
|
86
|
+
if (!gated.ok) {
|
|
87
|
+
console.error(`Error: ${gated.message}`);
|
|
88
|
+
return gated.code;
|
|
89
|
+
}
|
|
70
90
|
const record = await undoFix({
|
|
71
|
-
root:
|
|
91
|
+
root: gated.root,
|
|
72
92
|
auditId: options.auditId,
|
|
73
93
|
});
|
|
74
94
|
process.stdout.write(`Undid Safe Fix audit ${options.auditId} → undo audit ${record.id}\n`);
|
|
@@ -80,7 +100,12 @@ export async function runFixUndoCommand(options) {
|
|
|
80
100
|
}
|
|
81
101
|
}
|
|
82
102
|
export async function runFixHistoryCommand(options) {
|
|
83
|
-
const
|
|
103
|
+
const gated = await resolveCliProjectRoot(options.root);
|
|
104
|
+
if (!gated.ok) {
|
|
105
|
+
console.error(`Error: ${gated.message}`);
|
|
106
|
+
return gated.code;
|
|
107
|
+
}
|
|
108
|
+
const audits = await listFixAudits(gated.root);
|
|
84
109
|
if (options.json)
|
|
85
110
|
printJson({ audits });
|
|
86
111
|
else if (audits.length === 0)
|
|
@@ -93,7 +118,12 @@ export async function runFixHistoryCommand(options) {
|
|
|
93
118
|
return EXIT_CODES.SUCCESS;
|
|
94
119
|
}
|
|
95
120
|
export async function runBaselineCommand(options) {
|
|
96
|
-
const
|
|
121
|
+
const gated = await resolveCliProjectRoot(options.root ?? process.cwd());
|
|
122
|
+
if (!gated.ok) {
|
|
123
|
+
console.error(`Error: ${gated.message}`);
|
|
124
|
+
return gated.code;
|
|
125
|
+
}
|
|
126
|
+
const root = gated.root;
|
|
97
127
|
try {
|
|
98
128
|
if (options.action === "list") {
|
|
99
129
|
const names = await listNamedBaselines(root);
|
|
@@ -167,7 +197,12 @@ export async function runBaselineCommand(options) {
|
|
|
167
197
|
}
|
|
168
198
|
}
|
|
169
199
|
export async function runPackagesCommand(options) {
|
|
170
|
-
const
|
|
200
|
+
const gated = await resolveCliProjectRoot(options.root ?? process.cwd());
|
|
201
|
+
if (!gated.ok) {
|
|
202
|
+
console.error(`Error: ${gated.message}`);
|
|
203
|
+
return gated.code;
|
|
204
|
+
}
|
|
205
|
+
const root = gated.root;
|
|
171
206
|
const mono = await detectMonorepo(root);
|
|
172
207
|
if (!options.scan) {
|
|
173
208
|
if (options.json)
|
|
@@ -200,8 +235,13 @@ export async function runPackagesCommand(options) {
|
|
|
200
235
|
return EXIT_CODES.SUCCESS;
|
|
201
236
|
}
|
|
202
237
|
export async function runPrReviewCommand(options) {
|
|
238
|
+
const gated = await resolveCliProjectRoot(options.root);
|
|
239
|
+
if (!gated.ok) {
|
|
240
|
+
console.error(`Error: ${gated.message}`);
|
|
241
|
+
return gated.code;
|
|
242
|
+
}
|
|
203
243
|
const report = await analyzePullRequest({
|
|
204
|
-
root:
|
|
244
|
+
root: gated.root,
|
|
205
245
|
dryRun: true,
|
|
206
246
|
...(options.base ? { baseRef: options.base } : {}),
|
|
207
247
|
});
|
|
@@ -215,8 +255,13 @@ export async function runPrReviewCommand(options) {
|
|
|
215
255
|
}
|
|
216
256
|
export async function runDashboardCommand(options) {
|
|
217
257
|
try {
|
|
258
|
+
const gated = await resolveCliProjectRoot(options.root);
|
|
259
|
+
if (!gated.ok) {
|
|
260
|
+
console.error(`Error: ${gated.message}`);
|
|
261
|
+
return gated.code;
|
|
262
|
+
}
|
|
218
263
|
const server = await startDashboardServer({
|
|
219
|
-
root:
|
|
264
|
+
root: gated.root,
|
|
220
265
|
host: options.host ?? "127.0.0.1",
|
|
221
266
|
port: options.port ?? 8787,
|
|
222
267
|
readOnly: true,
|
|
@@ -237,7 +282,12 @@ export async function runDashboardCommand(options) {
|
|
|
237
282
|
}
|
|
238
283
|
}
|
|
239
284
|
export async function runPluginsCommand(options) {
|
|
240
|
-
const
|
|
285
|
+
const gated = await resolveCliProjectRoot(options.root);
|
|
286
|
+
if (!gated.ok) {
|
|
287
|
+
console.error(`Error: ${gated.message}`);
|
|
288
|
+
return gated.code;
|
|
289
|
+
}
|
|
290
|
+
const root = gated.root;
|
|
241
291
|
const plugins = await discoverPlugins(root);
|
|
242
292
|
const analyzerResults = options.run ? await runPluginAnalyzers(root) : undefined;
|
|
243
293
|
if (options.json) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { evaluateVerifyPolicy } from "../../core/policy/evaluate.js";
|
|
2
2
|
import { verify } from "../../core/verify/verify.js";
|
|
3
|
+
import { classifyBroadUserScanRoot } from "../../product/discovery/roots.js";
|
|
3
4
|
import { emitGithubReports } from "../../reporters/github/emit.js";
|
|
4
5
|
import { renderVerifyJsonReport } from "../../reporters/verify/json.js";
|
|
5
6
|
import { renderVerifyTerminalReport } from "../../reporters/verify/terminal.js";
|
|
@@ -15,6 +16,11 @@ export async function runVerifyCommand(options) {
|
|
|
15
16
|
console.error(`Error: not a directory: ${target}`);
|
|
16
17
|
return EXIT_CODES.USAGE_ERROR;
|
|
17
18
|
}
|
|
19
|
+
const broad = classifyBroadUserScanRoot(target);
|
|
20
|
+
if (broad.blocked) {
|
|
21
|
+
console.error(`Error: ${broad.reason}`);
|
|
22
|
+
return EXIT_CODES.USAGE_ERROR;
|
|
23
|
+
}
|
|
18
24
|
try {
|
|
19
25
|
const result = await verify({
|
|
20
26
|
cwd: target,
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type ExitCode } from "../types/index.js";
|
|
2
|
+
export type CliRootOk = {
|
|
3
|
+
ok: true;
|
|
4
|
+
root: string;
|
|
5
|
+
};
|
|
6
|
+
export type CliRootErr = {
|
|
7
|
+
ok: false;
|
|
8
|
+
message: string;
|
|
9
|
+
code: ExitCode;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Resolve a CLI project root and refuse home / Desktop / Downloads / Documents
|
|
13
|
+
* (and similarly broad user folders) before any recursive intelligence work.
|
|
14
|
+
*/
|
|
15
|
+
export declare function resolveCliProjectRoot(pathArg?: string): Promise<CliRootOk | CliRootErr>;
|
|
16
|
+
/** Sync variant when the path is already known to exist (tests / internal). */
|
|
17
|
+
export declare function assertCliProjectRootSync(pathArg?: string): CliRootOk | CliRootErr;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { classifyBroadUserScanRoot } from "../product/discovery/roots.js";
|
|
2
|
+
import { EXIT_CODES } from "../types/index.js";
|
|
3
|
+
import { isDirectory } from "../utils/fs.js";
|
|
4
|
+
import { resolveRepoRoot } from "../utils/path.js";
|
|
5
|
+
/**
|
|
6
|
+
* Resolve a CLI project root and refuse home / Desktop / Downloads / Documents
|
|
7
|
+
* (and similarly broad user folders) before any recursive intelligence work.
|
|
8
|
+
*/
|
|
9
|
+
export async function resolveCliProjectRoot(pathArg) {
|
|
10
|
+
const root = resolveRepoRoot(pathArg ?? process.cwd());
|
|
11
|
+
if (!(await isDirectory(root))) {
|
|
12
|
+
return {
|
|
13
|
+
ok: false,
|
|
14
|
+
message: `not a directory: ${root}`,
|
|
15
|
+
code: EXIT_CODES.USAGE_ERROR,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
const broad = classifyBroadUserScanRoot(root);
|
|
19
|
+
if (broad.blocked) {
|
|
20
|
+
return { ok: false, message: broad.reason, code: EXIT_CODES.USAGE_ERROR };
|
|
21
|
+
}
|
|
22
|
+
return { ok: true, root };
|
|
23
|
+
}
|
|
24
|
+
/** Sync variant when the path is already known to exist (tests / internal). */
|
|
25
|
+
export function assertCliProjectRootSync(pathArg) {
|
|
26
|
+
const root = resolveRepoRoot(pathArg ?? process.cwd());
|
|
27
|
+
const broad = classifyBroadUserScanRoot(root);
|
|
28
|
+
if (broad.blocked) {
|
|
29
|
+
return { ok: false, message: broad.reason, code: EXIT_CODES.USAGE_ERROR };
|
|
30
|
+
}
|
|
31
|
+
return { ok: true, root };
|
|
32
|
+
}
|
package/dist/constants.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AgentId } from "./types/index.js";
|
|
2
|
-
export declare const PACKAGE_VERSION = "3.0.
|
|
2
|
+
export declare const PACKAGE_VERSION = "3.0.1";
|
|
3
3
|
export declare const DEFAULT_MAX_FILE_SIZE_BYTES: number;
|
|
4
4
|
/** Directories skipped during normal discovery (unless a rule needs them later). */
|
|
5
5
|
export declare const DEFAULT_IGNORE_DIRECTORIES: Set<string>;
|
package/dist/constants.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const PACKAGE_VERSION = "3.0.
|
|
1
|
+
export const PACKAGE_VERSION = "3.0.1";
|
|
2
2
|
export const DEFAULT_MAX_FILE_SIZE_BYTES = 2 * 1024 * 1024; // 2 MiB
|
|
3
3
|
/** Directories skipped during normal discovery (unless a rule needs them later). */
|
|
4
4
|
export const DEFAULT_IGNORE_DIRECTORIES = new Set([
|
|
@@ -2,6 +2,7 @@ import fs from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { LocalBrainStore, createBrainQueryEngine, redactBrainForStorage, } from "../understanding/brain/index.js";
|
|
4
4
|
import { compileProjectBrain } from "../../mcp/brain/compile.js";
|
|
5
|
+
import { OWNERSHIP_BOUNDARY_VERSION } from "../../project/ownership.js";
|
|
5
6
|
import { resolveRepoRoot } from "../../utils/path.js";
|
|
6
7
|
import { isDirectory } from "../../utils/fs.js";
|
|
7
8
|
function storeFor(root) {
|
|
@@ -16,11 +17,13 @@ export async function getBrainStatus(root) {
|
|
|
16
17
|
const storeRoot = path.join(resolved, ".agentdoctor", "project-brain");
|
|
17
18
|
try {
|
|
18
19
|
const meta = await store.readMeta();
|
|
20
|
+
const boundaryOk = meta.ownershipBoundaryVersion === OWNERSHIP_BOUNDARY_VERSION;
|
|
21
|
+
const hasUsableLatest = boundaryOk && meta.latestSnapshotId !== null;
|
|
19
22
|
return {
|
|
20
23
|
root: resolved,
|
|
21
24
|
storeRoot,
|
|
22
|
-
hasSnapshot:
|
|
23
|
-
latestSnapshotId: meta.latestSnapshotId,
|
|
25
|
+
hasSnapshot: hasUsableLatest,
|
|
26
|
+
latestSnapshotId: hasUsableLatest ? meta.latestSnapshotId : null,
|
|
24
27
|
snapshotCount: meta.snapshots.length,
|
|
25
28
|
projectName: meta.projectName || null,
|
|
26
29
|
schemaVersion: meta.schemaVersion || null,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { classifyRelativePathOwnership, isUnderNestedRepository } from "../../project/ownership.js";
|
|
3
4
|
import { resolveRepoRoot, toPosixRelative } from "../../utils/path.js";
|
|
4
5
|
async function readJson(file) {
|
|
5
6
|
try {
|
|
@@ -99,6 +100,15 @@ export async function detectMonorepo(rootInput) {
|
|
|
99
100
|
continue;
|
|
100
101
|
if (seen.has(pkg.absolutePath))
|
|
101
102
|
continue;
|
|
103
|
+
const ownership = classifyRelativePathOwnership(pkg.relativePath);
|
|
104
|
+
if (ownership !== "project_owned") {
|
|
105
|
+
limitations.push(`Skipped non-owned workspace package: ${pkg.relativePath} (${ownership})`);
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
if (await isUnderNestedRepository(root, pkg.absolutePath)) {
|
|
109
|
+
limitations.push(`Skipped nested-repository workspace package: ${pkg.relativePath}`);
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
102
112
|
seen.add(pkg.absolutePath);
|
|
103
113
|
packages.push(pkg);
|
|
104
114
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { decideDirectoryTraversal } from "../../project/ownership.js";
|
|
3
4
|
import { readTextFile } from "../../utils/fs.js";
|
|
4
5
|
import { resolveRepoRoot, toPosixRelative } from "../../utils/path.js";
|
|
5
6
|
const SECRET_PATTERNS = [
|
|
@@ -94,6 +95,14 @@ async function walkFiles(root, dir, out, limit) {
|
|
|
94
95
|
if (entry.isSymbolicLink())
|
|
95
96
|
continue;
|
|
96
97
|
if (entry.isDirectory()) {
|
|
98
|
+
const relativeDir = toPosixRelative(root, absolute);
|
|
99
|
+
const traversal = await decideDirectoryTraversal({
|
|
100
|
+
projectRoot: root,
|
|
101
|
+
absoluteDir: absolute,
|
|
102
|
+
relativeDir,
|
|
103
|
+
});
|
|
104
|
+
if (!traversal.traverse)
|
|
105
|
+
continue;
|
|
97
106
|
await walkFiles(root, absolute, out, limit);
|
|
98
107
|
continue;
|
|
99
108
|
}
|
|
@@ -9,6 +9,8 @@ export interface SnapshotMeta {
|
|
|
9
9
|
projectName: string;
|
|
10
10
|
brainId: string;
|
|
11
11
|
checksum: string;
|
|
12
|
+
/** Snapshots without this stamp (or with a mismatch) must not be served as current truth. */
|
|
13
|
+
ownershipBoundaryVersion?: number;
|
|
12
14
|
}
|
|
13
15
|
export interface BrainStoreMeta {
|
|
14
16
|
storageFormatVersion: BrainStorageFormatVersion;
|
|
@@ -16,6 +18,8 @@ export interface BrainStoreMeta {
|
|
|
16
18
|
projectName: string;
|
|
17
19
|
latestSnapshotId: string | null;
|
|
18
20
|
snapshots: SnapshotMeta[];
|
|
21
|
+
/** When missing or mismatched, latest brain is treated as stale vs current ownership rules. */
|
|
22
|
+
ownershipBoundaryVersion?: number;
|
|
19
23
|
}
|
|
20
24
|
export interface SnapshotComparison {
|
|
21
25
|
leftId: string;
|
|
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { atomicWriteTextFile } from "../../../../utils/fs.js";
|
|
5
|
+
import { OWNERSHIP_BOUNDARY_VERSION } from "../../../../project/ownership.js";
|
|
5
6
|
import { parseBrainDelta, serializeBrainDelta } from "../delta.js";
|
|
6
7
|
import { checkBrainCompatibility, migrateBrain } from "../migrate.js";
|
|
7
8
|
import { assertBrainContract } from "../contract.js";
|
|
@@ -198,6 +199,7 @@ export class LocalBrainStore {
|
|
|
198
199
|
projectName: brain.metadata.projectName,
|
|
199
200
|
brainId: brain.metadata.brainId,
|
|
200
201
|
checksum: nextChecksum,
|
|
202
|
+
ownershipBoundaryVersion: OWNERSHIP_BOUNDARY_VERSION,
|
|
201
203
|
};
|
|
202
204
|
const meta = await this.readMeta();
|
|
203
205
|
const snapshots = meta.snapshots.filter((s) => s.id !== snapshotId);
|
|
@@ -209,10 +211,15 @@ export class LocalBrainStore {
|
|
|
209
211
|
projectName: brain.metadata.projectName,
|
|
210
212
|
latestSnapshotId: snapshotId,
|
|
211
213
|
snapshots,
|
|
214
|
+
ownershipBoundaryVersion: OWNERSHIP_BOUNDARY_VERSION,
|
|
212
215
|
});
|
|
213
216
|
return snapMeta;
|
|
214
217
|
}
|
|
215
218
|
async loadSnapshot(snapshotId) {
|
|
219
|
+
const meta = await this.readMeta();
|
|
220
|
+
if (meta.ownershipBoundaryVersion !== OWNERSHIP_BOUNDARY_VERSION) {
|
|
221
|
+
throw new BrainStorageError(`brain store ownership boundary is stale (have ${String(meta.ownershipBoundaryVersion ?? "missing")}, need ${OWNERSHIP_BOUNDARY_VERSION}); rebuild required`);
|
|
222
|
+
}
|
|
216
223
|
const target = await assertInsideRootResolved(this.root, this.brainPath(snapshotId));
|
|
217
224
|
let raw;
|
|
218
225
|
try {
|
|
@@ -224,9 +231,17 @@ export class LocalBrainStore {
|
|
|
224
231
|
}
|
|
225
232
|
throw error;
|
|
226
233
|
}
|
|
227
|
-
const meta = await this.readMeta();
|
|
228
234
|
const snap = meta.snapshots.find((s) => s.id === snapshotId);
|
|
229
235
|
if (snap) {
|
|
236
|
+
if (snap.ownershipBoundaryVersion !== undefined &&
|
|
237
|
+
snap.ownershipBoundaryVersion !== OWNERSHIP_BOUNDARY_VERSION) {
|
|
238
|
+
throw new BrainStorageError(`snapshot ${snapshotId} ownership boundary is stale (have ${snap.ownershipBoundaryVersion}, need ${OWNERSHIP_BOUNDARY_VERSION}); rebuild required`);
|
|
239
|
+
}
|
|
240
|
+
// Pre-stamp snapshots created before ownershipBoundaryVersion was recorded are
|
|
241
|
+
// unsafe to serve once the store claims a current boundary — require rebuild.
|
|
242
|
+
if (snap.ownershipBoundaryVersion === undefined) {
|
|
243
|
+
throw new BrainStorageError(`snapshot ${snapshotId} missing ownershipBoundaryVersion; rebuild required`);
|
|
244
|
+
}
|
|
230
245
|
const actual = checksumPayload(raw);
|
|
231
246
|
if (actual !== snap.checksum) {
|
|
232
247
|
throw new BrainStorageError(`checksum mismatch for snapshot ${snapshotId}`);
|
|
@@ -261,7 +276,12 @@ export class LocalBrainStore {
|
|
|
261
276
|
return meta.latestSnapshotId;
|
|
262
277
|
}
|
|
263
278
|
async loadLatest() {
|
|
264
|
-
const
|
|
279
|
+
const meta = await this.readMeta();
|
|
280
|
+
if (meta.ownershipBoundaryVersion !== OWNERSHIP_BOUNDARY_VERSION) {
|
|
281
|
+
// Pre-boundary or outdated snapshots must not be served as current project truth.
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
const id = meta.latestSnapshotId;
|
|
265
285
|
if (!id) {
|
|
266
286
|
return null;
|
|
267
287
|
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { PACKAGE_VERSION } from "../constants.js";
|
|
2
|
+
import { DASHBOARD_CSS } from "./ui/styles.js";
|
|
3
|
+
import { DASHBOARD_CLIENT_JS } from "./ui/client.js";
|
|
4
|
+
/** Embedded dashboard HTML. Version injected only from PACKAGE_VERSION. */
|
|
5
|
+
export function htmlPage() {
|
|
6
|
+
const version = PACKAGE_VERSION;
|
|
7
|
+
const client = DASHBOARD_CLIENT_JS.replaceAll("%%PACKAGE_VERSION%%", version).replaceAll("</script>", "<\\/script>");
|
|
8
|
+
return `<!doctype html>
|
|
9
|
+
<html lang="en">
|
|
10
|
+
<head>
|
|
11
|
+
<meta charset="utf-8" />
|
|
12
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
13
|
+
<title>AgentDoctor ${version}</title>
|
|
14
|
+
<style>${DASHBOARD_CSS}</style>
|
|
15
|
+
</head>
|
|
16
|
+
<body>
|
|
17
|
+
<div class="app" id="app">
|
|
18
|
+
<header class="topbar" role="banner">
|
|
19
|
+
<button type="button" class="btn btn-ghost mobile-nav" id="btnMenu" aria-label="Open navigation">☰</button>
|
|
20
|
+
<div class="brand">
|
|
21
|
+
<span class="brand-name">AgentDoctor</span>
|
|
22
|
+
<span class="brand-ver" id="headerVersion">v${version}</span>
|
|
23
|
+
</div>
|
|
24
|
+
<div class="project-chip" title="Active project">
|
|
25
|
+
<strong id="projectName">Loading…</strong>
|
|
26
|
+
<span id="projectMeta">Detecting stack…</span>
|
|
27
|
+
</div>
|
|
28
|
+
<div class="topbar-spacer"></div>
|
|
29
|
+
<div class="topbar-actions">
|
|
30
|
+
<span class="topbar-status" id="topbarStatus" title="Dashboard mode"><span class="status-dot" aria-hidden="true"></span>Local · Read-only</span>
|
|
31
|
+
<button type="button" class="btn" id="btnCommand" aria-label="Open command palette">Search <span class="kbd">⌘K</span></button>
|
|
32
|
+
<a class="btn" href="#system">System</a>
|
|
33
|
+
</div>
|
|
34
|
+
</header>
|
|
35
|
+
<aside class="sidebar" id="sidebar" aria-label="Primary">
|
|
36
|
+
<button type="button" class="btn btn-ghost side-toggle" id="btnCollapse" aria-label="Collapse sidebar">⇄</button>
|
|
37
|
+
<nav id="sidebarNav"></nav>
|
|
38
|
+
</aside>
|
|
39
|
+
<main class="main" role="main">
|
|
40
|
+
<div class="content" id="pageRoot"></div>
|
|
41
|
+
</main>
|
|
42
|
+
</div>
|
|
43
|
+
<div class="drawer-backdrop" id="drawerBackdrop"></div>
|
|
44
|
+
<div class="palette" id="palette" role="dialog" aria-modal="true" aria-label="Command palette">
|
|
45
|
+
<div class="palette-box">
|
|
46
|
+
<input id="paletteInput" placeholder="Go to page or search project…" aria-label="Command search" />
|
|
47
|
+
<div class="palette-list" id="paletteList"></div>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
<script>${client}</script>
|
|
51
|
+
</body>
|
|
52
|
+
</html>`;
|
|
53
|
+
}
|