@neurynae/toolcairn-mcp 0.9.1 → 0.10.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/dist/index.js +2695 -803
- package/dist/index.js.map +1 -1
- package/package.json +8 -1
package/dist/index.js
CHANGED
|
@@ -34,11 +34,11 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
34
34
|
mod
|
|
35
35
|
));
|
|
36
36
|
|
|
37
|
-
// ../../node_modules/.pnpm/tsup@8.5.1_postcss@8.5.8_tsx@4.21.0_typescript@5.9.3/node_modules/tsup/assets/esm_shims.js
|
|
37
|
+
// ../../node_modules/.pnpm/tsup@8.5.1_postcss@8.5.8_tsx@4.21.0_typescript@5.9.3_yaml@2.8.3/node_modules/tsup/assets/esm_shims.js
|
|
38
38
|
import path from "path";
|
|
39
39
|
import { fileURLToPath } from "url";
|
|
40
40
|
var init_esm_shims = __esm({
|
|
41
|
-
"../../node_modules/.pnpm/tsup@8.5.1_postcss@8.5.8_tsx@4.21.0_typescript@5.9.3/node_modules/tsup/assets/esm_shims.js"() {
|
|
41
|
+
"../../node_modules/.pnpm/tsup@8.5.1_postcss@8.5.8_tsx@4.21.0_typescript@5.9.3_yaml@2.8.3/node_modules/tsup/assets/esm_shims.js"() {
|
|
42
42
|
"use strict";
|
|
43
43
|
}
|
|
44
44
|
});
|
|
@@ -336,8 +336,8 @@ var require_logger = __commonJS({
|
|
|
336
336
|
return mod && mod.__esModule ? mod : { "default": mod };
|
|
337
337
|
};
|
|
338
338
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
339
|
-
exports.createMcpLogger =
|
|
340
|
-
exports.createLogger =
|
|
339
|
+
exports.createMcpLogger = createMcpLogger14;
|
|
340
|
+
exports.createLogger = createMcpLogger14;
|
|
341
341
|
var node_os_1 = __require("os");
|
|
342
342
|
var node_path_1 = __require("path");
|
|
343
343
|
var pino_1 = __importDefault(__require("pino"));
|
|
@@ -361,7 +361,7 @@ var require_logger = __commonJS({
|
|
|
361
361
|
"*.apiKey",
|
|
362
362
|
"*.api_key"
|
|
363
363
|
];
|
|
364
|
-
function
|
|
364
|
+
function createMcpLogger14(opts) {
|
|
365
365
|
const level = opts.level ?? process.env.LOG_LEVEL ?? (process.env.NODE_ENV !== "production" ? "debug" : "info");
|
|
366
366
|
const pinoOpts = {
|
|
367
367
|
name: opts.name,
|
|
@@ -407,14 +407,14 @@ var require_mcp_error_wrapper = __commonJS({
|
|
|
407
407
|
exports.withErrorHandling = withErrorHandling2;
|
|
408
408
|
var error_codes_js_1 = require_error_codes();
|
|
409
409
|
var errors_js_1 = require_errors();
|
|
410
|
-
function withErrorHandling2(toolName,
|
|
410
|
+
function withErrorHandling2(toolName, logger14, handler) {
|
|
411
411
|
return async (args) => {
|
|
412
412
|
try {
|
|
413
413
|
return await handler(args);
|
|
414
414
|
} catch (err) {
|
|
415
415
|
if (err instanceof errors_js_1.AppError) {
|
|
416
416
|
const logLevel = err.severity === "critical" || err.severity === "high" ? "error" : "warn";
|
|
417
|
-
|
|
417
|
+
logger14[logLevel]({ err, tool: toolName }, `Tool ${toolName} failed: ${err.message}`);
|
|
418
418
|
return {
|
|
419
419
|
content: [
|
|
420
420
|
{
|
|
@@ -429,7 +429,7 @@ var require_mcp_error_wrapper = __commonJS({
|
|
|
429
429
|
isError: true
|
|
430
430
|
};
|
|
431
431
|
}
|
|
432
|
-
|
|
432
|
+
logger14.error({ err, tool: toolName }, `Unexpected error in tool ${toolName}`);
|
|
433
433
|
return {
|
|
434
434
|
content: [
|
|
435
435
|
{
|
|
@@ -512,7 +512,7 @@ var require_dist2 = __commonJS({
|
|
|
512
512
|
// src/index.prod.ts
|
|
513
513
|
init_esm_shims();
|
|
514
514
|
var import_config4 = __toESM(require_dist(), 1);
|
|
515
|
-
var
|
|
515
|
+
var import_errors15 = __toESM(require_dist2(), 1);
|
|
516
516
|
import { McpServer as McpServer2 } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
517
517
|
|
|
518
518
|
// ../../packages/remote/dist/index.js
|
|
@@ -560,6 +560,95 @@ var ToolCairnClient = class {
|
|
|
560
560
|
async checkIssue(args) {
|
|
561
561
|
return this.post("/v1/intelligence/issue", args);
|
|
562
562
|
}
|
|
563
|
+
// ── Tool resolution ──────────────────────────────────────────────────────
|
|
564
|
+
/**
|
|
565
|
+
* Classify a batch of (ecosystem, name) tuples against the ToolCairn graph.
|
|
566
|
+
*
|
|
567
|
+
* Used by the discovery pipeline inside `toolcairn_init` — NOT exposed as an
|
|
568
|
+
* MCP tool to the agent. Returns typed data, not CallToolResult.
|
|
569
|
+
*
|
|
570
|
+
* Graceful degradation:
|
|
571
|
+
* - HTTP 404 (endpoint not deployed yet): returns all inputs as unmatched,
|
|
572
|
+
* with a warning.
|
|
573
|
+
* - Network error / timeout: same.
|
|
574
|
+
* - HTTP 200 but malformed body: logs a warning, returns unmatched.
|
|
575
|
+
*
|
|
576
|
+
* Caller (scan-project) uses the unmatched results to classify tools as
|
|
577
|
+
* `source: "non_oss"` and still returns a valid scan.
|
|
578
|
+
*/
|
|
579
|
+
async batchResolve(items) {
|
|
580
|
+
const warnings = [];
|
|
581
|
+
const methods = /* @__PURE__ */ new Map();
|
|
582
|
+
const githubUrls = /* @__PURE__ */ new Map();
|
|
583
|
+
if (items.length === 0) {
|
|
584
|
+
return { results: [], warnings, methods, githubUrls };
|
|
585
|
+
}
|
|
586
|
+
try {
|
|
587
|
+
const res = await this.rawPost("/v1/tools/batch-resolve", {
|
|
588
|
+
api_version: "1",
|
|
589
|
+
tools: items
|
|
590
|
+
});
|
|
591
|
+
if (res.status === 404) {
|
|
592
|
+
warnings.push({
|
|
593
|
+
scope: "batch-resolve",
|
|
594
|
+
message: "/v1/tools/batch-resolve not deployed on this engine \u2014 falling back to offline classification (source: non_oss)."
|
|
595
|
+
});
|
|
596
|
+
return {
|
|
597
|
+
results: items.map((input) => ({ input, matched: false, match_method: "none" })),
|
|
598
|
+
warnings,
|
|
599
|
+
methods,
|
|
600
|
+
githubUrls
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
if (!res.ok) {
|
|
604
|
+
warnings.push({
|
|
605
|
+
scope: "batch-resolve",
|
|
606
|
+
message: `batch-resolve returned HTTP ${res.status} \u2014 all tools marked as non_oss.`
|
|
607
|
+
});
|
|
608
|
+
return {
|
|
609
|
+
results: items.map((input) => ({ input, matched: false, match_method: "none" })),
|
|
610
|
+
warnings,
|
|
611
|
+
methods,
|
|
612
|
+
githubUrls
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
const body = await res.json();
|
|
616
|
+
if (!Array.isArray(body.resolved)) {
|
|
617
|
+
warnings.push({
|
|
618
|
+
scope: "batch-resolve",
|
|
619
|
+
message: "batch-resolve returned unexpected body shape \u2014 falling back."
|
|
620
|
+
});
|
|
621
|
+
return {
|
|
622
|
+
results: items.map((input) => ({ input, matched: false, match_method: "none" })),
|
|
623
|
+
warnings,
|
|
624
|
+
methods,
|
|
625
|
+
githubUrls
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
const results = [];
|
|
629
|
+
for (const entry of body.resolved) {
|
|
630
|
+
const method = entry.match_method ?? (entry.matched ? "tool_name_exact" : "none");
|
|
631
|
+
const matched = entry.matched ?? method !== "none";
|
|
632
|
+
const key = `${entry.input.ecosystem}:${entry.input.name}`;
|
|
633
|
+
methods.set(key, method);
|
|
634
|
+
if (entry.tool?.github_url)
|
|
635
|
+
githubUrls.set(key, entry.tool.github_url);
|
|
636
|
+
results.push({ input: entry.input, matched, match_method: method, tool: entry.tool });
|
|
637
|
+
}
|
|
638
|
+
return { results, warnings, methods, githubUrls };
|
|
639
|
+
} catch (err) {
|
|
640
|
+
warnings.push({
|
|
641
|
+
scope: "batch-resolve",
|
|
642
|
+
message: `batch-resolve network failure: ${err instanceof Error ? err.message : String(err)}. Tools classified as non_oss.`
|
|
643
|
+
});
|
|
644
|
+
return {
|
|
645
|
+
results: items.map((input) => ({ input, matched: false, match_method: "none" })),
|
|
646
|
+
warnings,
|
|
647
|
+
methods,
|
|
648
|
+
githubUrls
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
}
|
|
563
652
|
// ── Feedback ─────────────────────────────────────────────────────────────
|
|
564
653
|
async reportOutcome(args) {
|
|
565
654
|
return this.post("/v1/feedback/outcome", args);
|
|
@@ -834,7 +923,7 @@ async function pollForToken(apiUrl, deviceCode, intervalSec) {
|
|
|
834
923
|
}
|
|
835
924
|
}
|
|
836
925
|
function sleep(ms) {
|
|
837
|
-
return new Promise((
|
|
926
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
838
927
|
}
|
|
839
928
|
|
|
840
929
|
// src/index.prod.ts
|
|
@@ -1254,19 +1343,6 @@ if (!EVENTS_PATH || EVENTS_PATH === 'null') {
|
|
|
1254
1343
|
|
|
1255
1344
|
// src/project-setup.ts
|
|
1256
1345
|
var logger = (0, import_errors3.createMcpLogger)({ name: "@toolcairn/mcp-server:project-setup" });
|
|
1257
|
-
var INITIAL_CONFIG = {
|
|
1258
|
-
version: "1.0",
|
|
1259
|
-
project: {
|
|
1260
|
-
name: "",
|
|
1261
|
-
language: "",
|
|
1262
|
-
framework: ""
|
|
1263
|
-
},
|
|
1264
|
-
tools: {
|
|
1265
|
-
confirmed: [],
|
|
1266
|
-
pending_evaluation: []
|
|
1267
|
-
},
|
|
1268
|
-
audit_log: []
|
|
1269
|
-
};
|
|
1270
1346
|
function detectOs() {
|
|
1271
1347
|
const p = platform();
|
|
1272
1348
|
const labels = {
|
|
@@ -1290,20 +1366,17 @@ async function ensureProjectSetup(projectRoot = process.cwd()) {
|
|
|
1290
1366
|
"Detected OS \u2014 starting project setup"
|
|
1291
1367
|
);
|
|
1292
1368
|
const dir = join2(projectRoot, ".toolcairn");
|
|
1293
|
-
const configPath = join2(dir, "config.json");
|
|
1294
1369
|
const trackerPath = join2(dir, "tracker.html");
|
|
1295
|
-
const
|
|
1296
|
-
const eventsPathForUrl = toFileUrl(
|
|
1370
|
+
const eventsPathAbs = join2(dir, "events.jsonl");
|
|
1371
|
+
const eventsPathForUrl = toFileUrl(eventsPathAbs);
|
|
1297
1372
|
try {
|
|
1298
1373
|
await mkdir2(dir, { recursive: true });
|
|
1299
|
-
await createIfAbsent(configPath, JSON.stringify(INITIAL_CONFIG, null, 2), "config.json");
|
|
1300
1374
|
await createIfAbsent(trackerPath, generateTrackerHtml(eventsPathForUrl), "tracker.html");
|
|
1301
|
-
|
|
1302
|
-
logger.info({ dir, os: os.label }, ".toolcairn setup ready");
|
|
1375
|
+
logger.info({ dir, os: os.label }, ".toolcairn tracker ready");
|
|
1303
1376
|
} catch (e) {
|
|
1304
1377
|
logger.warn(
|
|
1305
1378
|
{ err: e, dir, os: os.label },
|
|
1306
|
-
"
|
|
1379
|
+
"tracker.html setup failed \u2014 continuing (config.json still bootstrapped by handlers)"
|
|
1307
1380
|
);
|
|
1308
1381
|
}
|
|
1309
1382
|
}
|
|
@@ -1320,7 +1393,7 @@ async function createIfAbsent(filePath, content, label) {
|
|
|
1320
1393
|
// src/server.prod.ts
|
|
1321
1394
|
init_esm_shims();
|
|
1322
1395
|
var import_config2 = __toESM(require_dist(), 1);
|
|
1323
|
-
var
|
|
1396
|
+
var import_errors14 = __toESM(require_dist2(), 1);
|
|
1324
1397
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
1325
1398
|
|
|
1326
1399
|
// ../../packages/tools-local/dist/index.js
|
|
@@ -1412,27 +1485,18 @@ var compareToolsSchema = {
|
|
|
1412
1485
|
use_case: z.string().optional(),
|
|
1413
1486
|
project_config: z.string().max(1e5).optional()
|
|
1414
1487
|
};
|
|
1415
|
-
var
|
|
1488
|
+
var toolcairnInitSchema = {
|
|
1416
1489
|
agent: z.enum(["claude", "cursor", "windsurf", "copilot", "copilot-cli", "opencode", "generic"]),
|
|
1417
1490
|
project_root: z.string().min(1),
|
|
1418
|
-
server_path: z.string().optional()
|
|
1419
|
-
detected_files: z.array(z.string()).optional()
|
|
1420
|
-
};
|
|
1421
|
-
var initProjectConfigSchema = {
|
|
1422
|
-
project_name: z.string().min(1).max(200),
|
|
1423
|
-
language: z.string().min(1).max(50),
|
|
1424
|
-
framework: z.string().optional(),
|
|
1425
|
-
detected_tools: z.array(z.object({
|
|
1426
|
-
name: z.string(),
|
|
1427
|
-
source: z.enum(["toolpilot", "manual", "non_oss"]),
|
|
1428
|
-
version: z.string().optional()
|
|
1429
|
-
})).optional()
|
|
1491
|
+
server_path: z.string().optional()
|
|
1430
1492
|
};
|
|
1431
1493
|
var readProjectConfigSchema = {
|
|
1432
|
-
|
|
1494
|
+
project_root: z.string().min(1),
|
|
1495
|
+
/** When true, the response includes per-tool `locations[]`. Default false (smaller payload). */
|
|
1496
|
+
include_locations: z.boolean().optional()
|
|
1433
1497
|
};
|
|
1434
1498
|
var updateProjectConfigSchema = {
|
|
1435
|
-
|
|
1499
|
+
project_root: z.string().min(1),
|
|
1436
1500
|
action: z.enum(["add_tool", "remove_tool", "update_tool", "add_evaluation"]),
|
|
1437
1501
|
tool_name: z.string().min(1),
|
|
1438
1502
|
data: z.record(z.string(), z.unknown()).optional()
|
|
@@ -1538,711 +1602,2514 @@ Respond with ONLY 0 or 1.`;
|
|
|
1538
1602
|
|
|
1539
1603
|
// ../../packages/tools-local/dist/handlers/toolcairn-init.js
|
|
1540
1604
|
init_esm_shims();
|
|
1541
|
-
var
|
|
1605
|
+
var import_errors10 = __toESM(require_dist2(), 1);
|
|
1542
1606
|
|
|
1543
|
-
// ../../packages/tools-local/dist/
|
|
1607
|
+
// ../../packages/tools-local/dist/config-store/index.js
|
|
1544
1608
|
init_esm_shims();
|
|
1545
|
-
var TOOLCAIRN_MCP_ENTRY = {
|
|
1546
|
-
toolcairn: {
|
|
1547
|
-
command: "npx",
|
|
1548
|
-
args: ["-y", "@neurynae/toolcairn-mcp"]
|
|
1549
|
-
}
|
|
1550
|
-
};
|
|
1551
|
-
var CORE_RULES = `
|
|
1552
|
-
## ToolCairn \u2014 Tool Intelligence MCP
|
|
1553
1609
|
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1610
|
+
// ../../packages/tools-local/dist/config-store/paths.js
|
|
1611
|
+
init_esm_shims();
|
|
1612
|
+
import { join as join3 } from "path";
|
|
1613
|
+
var CONFIG_DIR = ".toolcairn";
|
|
1614
|
+
var CONFIG_FILE = "config.json";
|
|
1615
|
+
var AUDIT_LOG_FILE = "audit-log.jsonl";
|
|
1616
|
+
var AUDIT_ARCHIVE_FILE = "audit-log.archive.jsonl";
|
|
1617
|
+
function joinConfigDir(projectRoot) {
|
|
1618
|
+
return join3(projectRoot, CONFIG_DIR);
|
|
1619
|
+
}
|
|
1620
|
+
function joinConfigPath(projectRoot) {
|
|
1621
|
+
return join3(projectRoot, CONFIG_DIR, CONFIG_FILE);
|
|
1622
|
+
}
|
|
1623
|
+
function joinAuditPath(projectRoot) {
|
|
1624
|
+
return join3(projectRoot, CONFIG_DIR, AUDIT_LOG_FILE);
|
|
1625
|
+
}
|
|
1626
|
+
function joinAuditArchivePath(projectRoot) {
|
|
1627
|
+
return join3(projectRoot, CONFIG_DIR, AUDIT_ARCHIVE_FILE);
|
|
1628
|
+
}
|
|
1568
1629
|
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
| \`search_tools_respond\` | Answer clarification questions from search_tools |
|
|
1575
|
-
| \`get_stack\` | Get recommended tool stack for a use case |
|
|
1576
|
-
| \`check_issue\` | Check if an error is a known tool bug before debugging |
|
|
1577
|
-
| \`check_compatibility\` | Check if two tools work well together |
|
|
1578
|
-
| \`compare_tools\` | Compare two tools with health and graph data |
|
|
1579
|
-
| \`report_outcome\` | Report whether a recommended tool worked (improves future results) |
|
|
1580
|
-
| \`toolcairn_init\` | Set up ToolCairn for a new project |
|
|
1581
|
-
| \`init_project_config\` | Initialize .toolcairn/config.json |
|
|
1582
|
-
| \`read_project_config\` | Parse .toolcairn/config.json to get confirmed tools |
|
|
1583
|
-
| \`update_project_config\` | Add/remove/update tools in .toolcairn/config.json |
|
|
1584
|
-
| \`suggest_graph_update\` | Suggest a new tool or relationship for the ToolCairn graph |
|
|
1630
|
+
// ../../packages/tools-local/dist/config-store/read.js
|
|
1631
|
+
init_esm_shims();
|
|
1632
|
+
var import_errors5 = __toESM(require_dist2(), 1);
|
|
1633
|
+
import { readFile as readFile2, rename } from "fs/promises";
|
|
1634
|
+
import { join as join4 } from "path";
|
|
1585
1635
|
|
|
1586
|
-
|
|
1636
|
+
// ../../packages/tools-local/dist/discovery/util/fs.js
|
|
1637
|
+
init_esm_shims();
|
|
1638
|
+
import { access as access2, readdir, stat } from "fs/promises";
|
|
1639
|
+
async function fileExists(path2) {
|
|
1640
|
+
try {
|
|
1641
|
+
await access2(path2);
|
|
1642
|
+
return true;
|
|
1643
|
+
} catch {
|
|
1644
|
+
return false;
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
async function isDir(path2) {
|
|
1648
|
+
try {
|
|
1649
|
+
return (await stat(path2)).isDirectory();
|
|
1650
|
+
} catch {
|
|
1651
|
+
return false;
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
1655
|
+
"node_modules",
|
|
1656
|
+
".git",
|
|
1657
|
+
".hg",
|
|
1658
|
+
".svn",
|
|
1659
|
+
"dist",
|
|
1660
|
+
"build",
|
|
1661
|
+
"out",
|
|
1662
|
+
".next",
|
|
1663
|
+
".turbo",
|
|
1664
|
+
".nuxt",
|
|
1665
|
+
"target",
|
|
1666
|
+
// rust, java
|
|
1667
|
+
"vendor",
|
|
1668
|
+
// go, ruby, composer
|
|
1669
|
+
"__pycache__",
|
|
1670
|
+
".venv",
|
|
1671
|
+
"venv",
|
|
1672
|
+
".tox",
|
|
1673
|
+
".pytest_cache",
|
|
1674
|
+
".mypy_cache",
|
|
1675
|
+
"bin",
|
|
1676
|
+
"obj",
|
|
1677
|
+
// dotnet
|
|
1678
|
+
".gradle",
|
|
1679
|
+
".idea",
|
|
1680
|
+
".vscode",
|
|
1681
|
+
".DS_Store",
|
|
1682
|
+
"coverage",
|
|
1683
|
+
".cache",
|
|
1684
|
+
".pnpm-store"
|
|
1685
|
+
]);
|
|
1686
|
+
|
|
1687
|
+
// ../../packages/tools-local/dist/config-store/read.js
|
|
1688
|
+
var logger3 = (0, import_errors5.createMcpLogger)({ name: "@toolcairn/tools:config-store" });
|
|
1689
|
+
async function readConfig(projectRoot) {
|
|
1690
|
+
const configPath = joinConfigPath(projectRoot);
|
|
1691
|
+
if (!await fileExists(configPath)) {
|
|
1692
|
+
return { config: null, path: configPath, corrupt_backup_path: null };
|
|
1693
|
+
}
|
|
1694
|
+
let raw;
|
|
1695
|
+
try {
|
|
1696
|
+
raw = await readFile2(configPath, "utf-8");
|
|
1697
|
+
} catch (err) {
|
|
1698
|
+
logger3.error({ err, configPath }, "Failed to read config.json");
|
|
1699
|
+
throw err;
|
|
1700
|
+
}
|
|
1701
|
+
try {
|
|
1702
|
+
const parsed = JSON.parse(raw);
|
|
1703
|
+
return { config: parsed, path: configPath, corrupt_backup_path: null };
|
|
1704
|
+
} catch (err) {
|
|
1705
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
1706
|
+
const backup = join4(projectRoot, CONFIG_DIR, `config.json.corrupt.${stamp}`);
|
|
1707
|
+
try {
|
|
1708
|
+
await rename(configPath, backup);
|
|
1709
|
+
logger3.warn({ configPath, backup, err }, "config.json was unparseable \u2014 moved to backup");
|
|
1710
|
+
} catch (renameErr) {
|
|
1711
|
+
logger3.error({ err: renameErr, configPath, backup }, "Failed to rename corrupt config.json");
|
|
1712
|
+
}
|
|
1713
|
+
return { config: null, path: configPath, corrupt_backup_path: backup };
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1587
1716
|
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1717
|
+
// ../../packages/tools-local/dist/config-store/write.js
|
|
1718
|
+
init_esm_shims();
|
|
1719
|
+
var import_errors6 = __toESM(require_dist2(), 1);
|
|
1720
|
+
import { mkdir as mkdir3 } from "fs/promises";
|
|
1721
|
+
import writeFileAtomic from "write-file-atomic";
|
|
1722
|
+
var logger4 = (0, import_errors6.createMcpLogger)({ name: "@toolcairn/tools:config-store" });
|
|
1723
|
+
async function writeConfig(projectRoot, config5) {
|
|
1724
|
+
await mkdir3(joinConfigDir(projectRoot), { recursive: true });
|
|
1725
|
+
const configPath = joinConfigPath(projectRoot);
|
|
1726
|
+
const serialised = `${JSON.stringify(config5, null, 2)}
|
|
1593
1727
|
`;
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
file_path: "CLAUDE.md",
|
|
1597
|
-
mode: "append",
|
|
1598
|
-
content: CORE_RULES
|
|
1599
|
-
};
|
|
1728
|
+
await writeFileAtomic(configPath, serialised);
|
|
1729
|
+
logger4.debug({ configPath, bytes: serialised.length }, "config.json written atomically");
|
|
1600
1730
|
}
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1731
|
+
|
|
1732
|
+
// ../../packages/tools-local/dist/config-store/audit.js
|
|
1733
|
+
init_esm_shims();
|
|
1734
|
+
var import_errors7 = __toESM(require_dist2(), 1);
|
|
1735
|
+
import { appendFile, mkdir as mkdir4, readFile as readFile3, rm, writeFile as writeFile3 } from "fs/promises";
|
|
1736
|
+
import writeFileAtomic2 from "write-file-atomic";
|
|
1737
|
+
var logger5 = (0, import_errors7.createMcpLogger)({ name: "@toolcairn/tools:audit-log" });
|
|
1738
|
+
var MAX_LIVE_ENTRIES = 1e3;
|
|
1739
|
+
var ARCHIVE_BATCH = 500;
|
|
1740
|
+
async function appendAudit(projectRoot, entry) {
|
|
1741
|
+
await mkdir4(joinConfigDir(projectRoot), { recursive: true });
|
|
1742
|
+
const auditPath = joinAuditPath(projectRoot);
|
|
1743
|
+
const line = `${JSON.stringify(entry)}
|
|
1744
|
+
`;
|
|
1745
|
+
await appendFile(auditPath, line, "utf-8");
|
|
1746
|
+
await rotateIfNeeded(projectRoot, auditPath);
|
|
1607
1747
|
}
|
|
1608
|
-
function
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
}
|
|
1748
|
+
async function bulkAppendAudit(projectRoot, entries) {
|
|
1749
|
+
if (entries.length === 0)
|
|
1750
|
+
return;
|
|
1751
|
+
await mkdir4(joinConfigDir(projectRoot), { recursive: true });
|
|
1752
|
+
const auditPath = joinAuditPath(projectRoot);
|
|
1753
|
+
const payload = entries.map((e) => `${JSON.stringify(e)}
|
|
1754
|
+
`).join("");
|
|
1755
|
+
await appendFile(auditPath, payload, "utf-8");
|
|
1756
|
+
await rotateIfNeeded(projectRoot, auditPath);
|
|
1614
1757
|
}
|
|
1615
|
-
function
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1758
|
+
async function rotateIfNeeded(projectRoot, auditPath) {
|
|
1759
|
+
const raw = await readFile3(auditPath, "utf-8");
|
|
1760
|
+
const lines = raw.split("\n").filter((l) => l.trim().length > 0);
|
|
1761
|
+
if (lines.length <= MAX_LIVE_ENTRIES)
|
|
1762
|
+
return;
|
|
1763
|
+
const archiveBatch = lines.slice(0, ARCHIVE_BATCH);
|
|
1764
|
+
const keep = lines.slice(ARCHIVE_BATCH);
|
|
1765
|
+
const archivePath = joinAuditArchivePath(projectRoot);
|
|
1766
|
+
try {
|
|
1767
|
+
await appendFile(archivePath, `${archiveBatch.join("\n")}
|
|
1768
|
+
`, "utf-8");
|
|
1769
|
+
const newContent = `${keep.join("\n")}
|
|
1770
|
+
`;
|
|
1771
|
+
await writeFileAtomic2(auditPath, newContent);
|
|
1772
|
+
logger5.info({ archived: archiveBatch.length, retained: keep.length }, "audit-log.jsonl rotated");
|
|
1773
|
+
} catch (err) {
|
|
1774
|
+
logger5.warn({ err, auditPath, archivePath }, "Audit-log rotation failed \u2014 live file intact");
|
|
1775
|
+
}
|
|
1622
1776
|
}
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1777
|
+
|
|
1778
|
+
// ../../packages/tools-local/dist/config-store/migrate.js
|
|
1779
|
+
init_esm_shims();
|
|
1780
|
+
async function migrateToV1_1(config5, projectRoot) {
|
|
1781
|
+
if (config5.version === "1.1") {
|
|
1782
|
+
for (const tool of config5.tools.confirmed) {
|
|
1783
|
+
if (!tool.locations)
|
|
1784
|
+
tool.locations = [];
|
|
1785
|
+
}
|
|
1786
|
+
return { migrated: false, was_v1_0: false, legacy_audit_entries: [] };
|
|
1787
|
+
}
|
|
1788
|
+
if (!config5.project.languages) {
|
|
1789
|
+
config5.project.languages = config5.project.language ? [{ name: config5.project.language, file_count: 0, workspaces: ["."] }] : [];
|
|
1790
|
+
}
|
|
1791
|
+
if (!config5.project.frameworks) {
|
|
1792
|
+
config5.project.frameworks = config5.project.framework ? [
|
|
1793
|
+
{
|
|
1794
|
+
name: config5.project.framework,
|
|
1795
|
+
ecosystem: "npm",
|
|
1796
|
+
workspace: ".",
|
|
1797
|
+
source: "local"
|
|
1798
|
+
}
|
|
1799
|
+
] : [];
|
|
1800
|
+
}
|
|
1801
|
+
if (!config5.project.subprojects)
|
|
1802
|
+
config5.project.subprojects = [];
|
|
1803
|
+
for (const tool of config5.tools.confirmed) {
|
|
1804
|
+
if (!tool.locations)
|
|
1805
|
+
tool.locations = [];
|
|
1806
|
+
}
|
|
1807
|
+
const legacy = config5.audit_log ?? [];
|
|
1808
|
+
delete config5.audit_log;
|
|
1809
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1810
|
+
const migrationEntry = {
|
|
1811
|
+
action: "migrate",
|
|
1812
|
+
tool: "__schema__",
|
|
1813
|
+
timestamp: now,
|
|
1814
|
+
reason: "Schema 1.0 \u2192 1.1: audit_log relocated to audit-log.jsonl; languages/frameworks expanded to arrays"
|
|
1628
1815
|
};
|
|
1816
|
+
config5.last_audit_entry = migrationEntry;
|
|
1817
|
+
config5.version = "1.1";
|
|
1818
|
+
await bulkAppendAudit(projectRoot, [...legacy, migrationEntry]);
|
|
1819
|
+
return { migrated: true, was_v1_0: true, legacy_audit_entries: legacy };
|
|
1629
1820
|
}
|
|
1630
|
-
|
|
1821
|
+
|
|
1822
|
+
// ../../packages/tools-local/dist/config-store/mutate.js
|
|
1823
|
+
init_esm_shims();
|
|
1824
|
+
var import_errors8 = __toESM(require_dist2(), 1);
|
|
1825
|
+
import { mkdir as mkdir5, writeFile as writeFile4 } from "fs/promises";
|
|
1826
|
+
import lockfile from "proper-lockfile";
|
|
1827
|
+
|
|
1828
|
+
// ../../packages/tools-local/dist/config-store/skeleton.js
|
|
1829
|
+
init_esm_shims();
|
|
1830
|
+
function emptySkeleton(name = "") {
|
|
1631
1831
|
return {
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1832
|
+
version: "1.1",
|
|
1833
|
+
project: {
|
|
1834
|
+
name,
|
|
1835
|
+
languages: [],
|
|
1836
|
+
frameworks: [],
|
|
1837
|
+
subprojects: []
|
|
1838
|
+
},
|
|
1839
|
+
tools: {
|
|
1840
|
+
confirmed: [],
|
|
1841
|
+
pending_evaluation: []
|
|
1842
|
+
},
|
|
1843
|
+
last_audit_entry: null
|
|
1635
1844
|
};
|
|
1636
1845
|
}
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1846
|
+
|
|
1847
|
+
// ../../packages/tools-local/dist/config-store/mutate.js
|
|
1848
|
+
var logger6 = (0, import_errors8.createMcpLogger)({ name: "@toolcairn/tools:config-store" });
|
|
1849
|
+
async function mutateConfig(projectRoot, mutator, audit) {
|
|
1850
|
+
const configPath = joinConfigPath(projectRoot);
|
|
1851
|
+
const preExisted = await fileExists(configPath);
|
|
1852
|
+
await ensureLockableDir(projectRoot);
|
|
1853
|
+
const release = await lockfile.lock(configPath, {
|
|
1854
|
+
stale: 1e4,
|
|
1855
|
+
retries: { retries: 5, minTimeout: 50, factor: 2, maxTimeout: 500 },
|
|
1856
|
+
realpath: false
|
|
1857
|
+
});
|
|
1858
|
+
try {
|
|
1859
|
+
const { config: existing } = await readConfig(projectRoot);
|
|
1860
|
+
let config5;
|
|
1861
|
+
const bootstrapped = !preExisted;
|
|
1862
|
+
let migrated = false;
|
|
1863
|
+
if (!existing) {
|
|
1864
|
+
config5 = emptySkeleton();
|
|
1865
|
+
logger6.info({ projectRoot }, "Bootstrapping fresh .toolcairn/config.json");
|
|
1866
|
+
} else {
|
|
1867
|
+
config5 = existing;
|
|
1868
|
+
}
|
|
1869
|
+
if (config5.version === "1.0") {
|
|
1870
|
+
const result = await migrateToV1_1(config5, projectRoot);
|
|
1871
|
+
migrated = result.migrated;
|
|
1872
|
+
} else {
|
|
1873
|
+
for (const tool of config5.tools.confirmed) {
|
|
1874
|
+
if (!tool.locations)
|
|
1875
|
+
tool.locations = [];
|
|
1876
|
+
}
|
|
1877
|
+
if (!config5.project.languages)
|
|
1878
|
+
config5.project.languages = [];
|
|
1879
|
+
if (!config5.project.frameworks)
|
|
1880
|
+
config5.project.frameworks = [];
|
|
1881
|
+
if (!config5.project.subprojects)
|
|
1882
|
+
config5.project.subprojects = [];
|
|
1883
|
+
}
|
|
1884
|
+
await mutator(config5);
|
|
1885
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1886
|
+
const entry = { ...audit, timestamp: now };
|
|
1887
|
+
config5.last_audit_entry = entry;
|
|
1888
|
+
config5.version = "1.1";
|
|
1889
|
+
await writeConfig(projectRoot, config5);
|
|
1890
|
+
await appendAudit(projectRoot, entry);
|
|
1891
|
+
return { config: config5, audit_entry: entry, bootstrapped, migrated };
|
|
1892
|
+
} finally {
|
|
1893
|
+
try {
|
|
1894
|
+
await release();
|
|
1895
|
+
} catch (err) {
|
|
1896
|
+
logger6.warn({ err, configPath }, "Failed to release config lock \u2014 may be stale");
|
|
1897
|
+
}
|
|
1898
|
+
}
|
|
1644
1899
|
}
|
|
1645
|
-
function
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
case "copilot-cli":
|
|
1656
|
-
return getCopilotCliInstructions();
|
|
1657
|
-
case "opencode":
|
|
1658
|
-
return getOpenCodeInstructions();
|
|
1659
|
-
case "generic":
|
|
1660
|
-
return getGenericInstructions();
|
|
1900
|
+
async function ensureLockableDir(projectRoot) {
|
|
1901
|
+
await mkdir5(joinConfigDir(projectRoot), { recursive: true });
|
|
1902
|
+
const configPath = joinConfigPath(projectRoot);
|
|
1903
|
+
if (!await fileExists(configPath)) {
|
|
1904
|
+
try {
|
|
1905
|
+
await writeFile4(configPath, `${JSON.stringify(emptySkeleton(), null, 2)}
|
|
1906
|
+
`, "utf-8");
|
|
1907
|
+
} catch (err) {
|
|
1908
|
+
logger6.debug({ err, configPath }, "Bootstrap seed skipped (likely race)");
|
|
1909
|
+
}
|
|
1661
1910
|
}
|
|
1662
1911
|
}
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1912
|
+
|
|
1913
|
+
// ../../packages/tools-local/dist/discovery/index.js
|
|
1914
|
+
init_esm_shims();
|
|
1915
|
+
|
|
1916
|
+
// ../../packages/tools-local/dist/discovery/scan-project.js
|
|
1917
|
+
init_esm_shims();
|
|
1918
|
+
var import_errors9 = __toESM(require_dist2(), 1);
|
|
1919
|
+
import { readFile as readFile17 } from "fs/promises";
|
|
1920
|
+
import { basename, resolve } from "path";
|
|
1921
|
+
|
|
1922
|
+
// ../../packages/tools-local/dist/discovery/ecosystem-detect.js
|
|
1923
|
+
init_esm_shims();
|
|
1924
|
+
import { readdir as readdir2 } from "fs/promises";
|
|
1925
|
+
import { join as join5 } from "path";
|
|
1926
|
+
var ECOSYSTEM_MANIFESTS = {
|
|
1927
|
+
npm: ["package.json"],
|
|
1928
|
+
pypi: ["pyproject.toml", "requirements.txt", "requirements-dev.txt", "setup.py", "Pipfile"],
|
|
1929
|
+
cargo: ["Cargo.toml"],
|
|
1930
|
+
go: ["go.mod"],
|
|
1931
|
+
rubygems: ["Gemfile"],
|
|
1932
|
+
maven: ["pom.xml"],
|
|
1933
|
+
gradle: ["build.gradle", "build.gradle.kts", "gradle.lockfile"],
|
|
1934
|
+
composer: ["composer.json"],
|
|
1935
|
+
hex: ["mix.exs"],
|
|
1936
|
+
pub: ["pubspec.yaml"],
|
|
1937
|
+
nuget: ["packages.config"],
|
|
1938
|
+
"swift-pm": ["Package.swift"]
|
|
1939
|
+
};
|
|
1940
|
+
var ECOSYSTEM_EXTENSIONS = {
|
|
1941
|
+
".csproj": "nuget",
|
|
1942
|
+
".fsproj": "nuget"
|
|
1943
|
+
};
|
|
1944
|
+
async function detectEcosystems(workspaceDir) {
|
|
1945
|
+
const found = /* @__PURE__ */ new Set();
|
|
1946
|
+
for (const [ecosystem, files] of Object.entries(ECOSYSTEM_MANIFESTS)) {
|
|
1947
|
+
for (const file of files) {
|
|
1948
|
+
if (await fileExists(join5(workspaceDir, file))) {
|
|
1949
|
+
found.add(ecosystem);
|
|
1950
|
+
break;
|
|
1669
1951
|
}
|
|
1670
|
-
}
|
|
1952
|
+
}
|
|
1671
1953
|
}
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1954
|
+
try {
|
|
1955
|
+
const entries = await readdir2(workspaceDir);
|
|
1956
|
+
for (const entry of entries) {
|
|
1957
|
+
for (const [ext, ecosystem] of Object.entries(ECOSYSTEM_EXTENSIONS)) {
|
|
1958
|
+
if (entry.endsWith(ext)) {
|
|
1959
|
+
found.add(ecosystem);
|
|
1960
|
+
break;
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1681
1963
|
}
|
|
1682
|
-
}
|
|
1964
|
+
} catch {
|
|
1965
|
+
}
|
|
1966
|
+
return Array.from(found);
|
|
1683
1967
|
}
|
|
1684
1968
|
|
|
1685
|
-
// ../../packages/tools-local/dist/
|
|
1969
|
+
// ../../packages/tools-local/dist/discovery/frameworks/detect.js
|
|
1686
1970
|
init_esm_shims();
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1971
|
+
var FALLBACK = {
|
|
1972
|
+
npm: {
|
|
1973
|
+
next: "Next.js",
|
|
1974
|
+
react: "React",
|
|
1975
|
+
vue: "Vue",
|
|
1976
|
+
nuxt: "Nuxt",
|
|
1977
|
+
svelte: "Svelte",
|
|
1978
|
+
"@sveltejs/kit": "SvelteKit",
|
|
1979
|
+
astro: "Astro",
|
|
1980
|
+
"solid-js": "SolidJS",
|
|
1981
|
+
express: "Express",
|
|
1982
|
+
fastify: "Fastify",
|
|
1983
|
+
koa: "Koa",
|
|
1984
|
+
hono: "Hono",
|
|
1985
|
+
"@nestjs/core": "NestJS",
|
|
1986
|
+
remix: "Remix",
|
|
1987
|
+
"@remix-run/react": "Remix",
|
|
1988
|
+
gatsby: "Gatsby",
|
|
1989
|
+
electron: "Electron",
|
|
1990
|
+
"react-native": "React Native",
|
|
1991
|
+
expo: "Expo",
|
|
1992
|
+
angular: "Angular",
|
|
1993
|
+
"@angular/core": "Angular",
|
|
1994
|
+
turbo: "Turborepo",
|
|
1995
|
+
nx: "Nx",
|
|
1996
|
+
vite: "Vite",
|
|
1997
|
+
webpack: "Webpack"
|
|
1998
|
+
},
|
|
1999
|
+
pypi: {
|
|
2000
|
+
django: "Django",
|
|
2001
|
+
flask: "Flask",
|
|
2002
|
+
fastapi: "FastAPI",
|
|
2003
|
+
starlette: "Starlette",
|
|
2004
|
+
pyramid: "Pyramid",
|
|
2005
|
+
tornado: "Tornado",
|
|
2006
|
+
aiohttp: "aiohttp",
|
|
2007
|
+
litestar: "Litestar",
|
|
2008
|
+
sanic: "Sanic",
|
|
2009
|
+
bottle: "Bottle",
|
|
2010
|
+
quart: "Quart",
|
|
2011
|
+
celery: "Celery",
|
|
2012
|
+
streamlit: "Streamlit",
|
|
2013
|
+
gradio: "Gradio",
|
|
2014
|
+
torch: "PyTorch",
|
|
2015
|
+
tensorflow: "TensorFlow",
|
|
2016
|
+
transformers: "Transformers",
|
|
2017
|
+
langchain: "LangChain",
|
|
2018
|
+
"llama-index": "LlamaIndex"
|
|
2019
|
+
},
|
|
2020
|
+
cargo: {
|
|
2021
|
+
"actix-web": "Actix Web",
|
|
2022
|
+
axum: "Axum",
|
|
2023
|
+
rocket: "Rocket",
|
|
2024
|
+
warp: "Warp",
|
|
2025
|
+
tide: "Tide",
|
|
2026
|
+
poem: "Poem",
|
|
2027
|
+
salvo: "Salvo",
|
|
2028
|
+
leptos: "Leptos",
|
|
2029
|
+
dioxus: "Dioxus",
|
|
2030
|
+
yew: "Yew",
|
|
2031
|
+
tauri: "Tauri",
|
|
2032
|
+
bevy: "Bevy",
|
|
2033
|
+
tokio: "Tokio"
|
|
2034
|
+
},
|
|
2035
|
+
go: {
|
|
2036
|
+
"github.com/gin-gonic/gin": "Gin",
|
|
2037
|
+
"github.com/labstack/echo": "Echo",
|
|
2038
|
+
"github.com/labstack/echo/v4": "Echo",
|
|
2039
|
+
"github.com/gofiber/fiber": "Fiber",
|
|
2040
|
+
"github.com/gofiber/fiber/v2": "Fiber",
|
|
2041
|
+
"github.com/beego/beego": "Beego",
|
|
2042
|
+
"github.com/go-chi/chi": "Chi",
|
|
2043
|
+
"github.com/gorilla/mux": "Gorilla",
|
|
2044
|
+
"github.com/revel/revel": "Revel"
|
|
2045
|
+
},
|
|
2046
|
+
rubygems: {
|
|
2047
|
+
rails: "Ruby on Rails",
|
|
2048
|
+
sinatra: "Sinatra",
|
|
2049
|
+
hanami: "Hanami",
|
|
2050
|
+
roda: "Roda",
|
|
2051
|
+
rack: "Rack"
|
|
2052
|
+
},
|
|
2053
|
+
maven: {
|
|
2054
|
+
"org.springframework.boot:spring-boot-starter": "Spring Boot",
|
|
2055
|
+
"org.springframework.boot:spring-boot-starter-web": "Spring Boot",
|
|
2056
|
+
"io.quarkus:quarkus-core": "Quarkus",
|
|
2057
|
+
"io.micronaut:micronaut-core": "Micronaut",
|
|
2058
|
+
"io.vertx:vertx-core": "Vert.x",
|
|
2059
|
+
"com.google.inject:guice": "Guice"
|
|
2060
|
+
},
|
|
2061
|
+
gradle: {
|
|
2062
|
+
"org.springframework.boot:spring-boot-starter": "Spring Boot",
|
|
2063
|
+
"io.quarkus:quarkus-core": "Quarkus",
|
|
2064
|
+
"io.micronaut:micronaut-core": "Micronaut",
|
|
2065
|
+
"io.ktor:ktor-server-core": "Ktor"
|
|
2066
|
+
},
|
|
2067
|
+
composer: {
|
|
2068
|
+
"laravel/framework": "Laravel",
|
|
2069
|
+
"symfony/framework-bundle": "Symfony",
|
|
2070
|
+
"cakephp/cakephp": "CakePHP",
|
|
2071
|
+
"yiisoft/yii2": "Yii",
|
|
2072
|
+
"slim/slim": "Slim"
|
|
2073
|
+
},
|
|
2074
|
+
hex: {
|
|
2075
|
+
phoenix: "Phoenix",
|
|
2076
|
+
ecto: "Ecto",
|
|
2077
|
+
nerves: "Nerves",
|
|
2078
|
+
ash: "Ash"
|
|
2079
|
+
},
|
|
2080
|
+
pub: {
|
|
2081
|
+
flutter: "Flutter",
|
|
2082
|
+
flutter_bloc: "Flutter BLoC"
|
|
2083
|
+
},
|
|
2084
|
+
nuget: {
|
|
2085
|
+
"Microsoft.AspNetCore.App": "ASP.NET Core",
|
|
2086
|
+
"Microsoft.AspNetCore": "ASP.NET Core",
|
|
2087
|
+
"Microsoft.EntityFrameworkCore": "Entity Framework Core",
|
|
2088
|
+
"Microsoft.NET.Sdk.Web": "ASP.NET Core",
|
|
2089
|
+
Avalonia: "Avalonia",
|
|
2090
|
+
MAUI: ".NET MAUI"
|
|
2091
|
+
},
|
|
2092
|
+
"swift-pm": {
|
|
2093
|
+
vapor: "Vapor",
|
|
2094
|
+
kitura: "Kitura",
|
|
2095
|
+
perfect: "Perfect"
|
|
1708
2096
|
}
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
.kv .v.yellow { color: var(--yellow); }
|
|
2097
|
+
};
|
|
2098
|
+
var FRAMEWORK_CATEGORIES = /* @__PURE__ */ new Set([
|
|
2099
|
+
"framework",
|
|
2100
|
+
"web-framework",
|
|
2101
|
+
"ui-framework",
|
|
2102
|
+
"meta-framework",
|
|
2103
|
+
"backend-framework",
|
|
2104
|
+
"frontend-framework",
|
|
2105
|
+
"mobile-framework"
|
|
2106
|
+
]);
|
|
2107
|
+
function detectFrameworks(tools, resolved) {
|
|
2108
|
+
const out = [];
|
|
2109
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2110
|
+
for (const tool of tools) {
|
|
2111
|
+
if (tool.section === "dev")
|
|
2112
|
+
continue;
|
|
2113
|
+
const workspace = tool.workspace_path || ".";
|
|
2114
|
+
const resolvedKey = `${tool.ecosystem}:${tool.name}`;
|
|
2115
|
+
const graphMatch = resolved.get(resolvedKey);
|
|
2116
|
+
let frameworkName = null;
|
|
2117
|
+
let source = "local";
|
|
2118
|
+
if (graphMatch?.matched && graphMatch.tool) {
|
|
2119
|
+
const categories = graphMatch.tool.categories ?? [];
|
|
2120
|
+
if (categories.some((c) => FRAMEWORK_CATEGORIES.has(c.toLowerCase()))) {
|
|
2121
|
+
frameworkName = graphMatch.tool.canonical_name;
|
|
2122
|
+
source = "graph";
|
|
2123
|
+
}
|
|
2124
|
+
}
|
|
2125
|
+
if (!frameworkName) {
|
|
2126
|
+
const localName = FALLBACK[tool.ecosystem]?.[tool.name];
|
|
2127
|
+
if (localName) {
|
|
2128
|
+
frameworkName = localName;
|
|
2129
|
+
source = "local";
|
|
2130
|
+
}
|
|
2131
|
+
}
|
|
2132
|
+
if (!frameworkName)
|
|
2133
|
+
continue;
|
|
2134
|
+
const dedupeKey = `${frameworkName}:${workspace}`;
|
|
2135
|
+
if (seen.has(dedupeKey))
|
|
2136
|
+
continue;
|
|
2137
|
+
seen.add(dedupeKey);
|
|
2138
|
+
out.push({
|
|
2139
|
+
name: frameworkName,
|
|
2140
|
+
ecosystem: tool.ecosystem,
|
|
2141
|
+
workspace,
|
|
2142
|
+
source
|
|
2143
|
+
});
|
|
2144
|
+
}
|
|
2145
|
+
return out;
|
|
2146
|
+
}
|
|
1760
2147
|
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
.
|
|
2148
|
+
// ../../packages/tools-local/dist/discovery/language-detect.js
|
|
2149
|
+
init_esm_shims();
|
|
2150
|
+
import { readdir as readdir3 } from "fs/promises";
|
|
2151
|
+
import { join as join6, relative, sep } from "path";
|
|
2152
|
+
var EXT_TO_LANGUAGE = {
|
|
2153
|
+
".ts": "TypeScript",
|
|
2154
|
+
".tsx": "TypeScript",
|
|
2155
|
+
".js": "JavaScript",
|
|
2156
|
+
".jsx": "JavaScript",
|
|
2157
|
+
".mjs": "JavaScript",
|
|
2158
|
+
".cjs": "JavaScript",
|
|
2159
|
+
".py": "Python",
|
|
2160
|
+
".pyi": "Python",
|
|
2161
|
+
".rs": "Rust",
|
|
2162
|
+
".go": "Go",
|
|
2163
|
+
".rb": "Ruby",
|
|
2164
|
+
".java": "Java",
|
|
2165
|
+
".kt": "Kotlin",
|
|
2166
|
+
".kts": "Kotlin",
|
|
2167
|
+
".scala": "Scala",
|
|
2168
|
+
".php": "PHP",
|
|
2169
|
+
".ex": "Elixir",
|
|
2170
|
+
".exs": "Elixir",
|
|
2171
|
+
".erl": "Erlang",
|
|
2172
|
+
".dart": "Dart",
|
|
2173
|
+
".cs": "C#",
|
|
2174
|
+
".fs": "F#",
|
|
2175
|
+
".vb": "Visual Basic",
|
|
2176
|
+
".swift": "Swift",
|
|
2177
|
+
".c": "C",
|
|
2178
|
+
".h": "C",
|
|
2179
|
+
".cpp": "C++",
|
|
2180
|
+
".cxx": "C++",
|
|
2181
|
+
".cc": "C++",
|
|
2182
|
+
".hpp": "C++",
|
|
2183
|
+
".m": "Objective-C",
|
|
2184
|
+
".mm": "Objective-C",
|
|
2185
|
+
".lua": "Lua",
|
|
2186
|
+
".r": "R",
|
|
2187
|
+
".jl": "Julia",
|
|
2188
|
+
".nim": "Nim",
|
|
2189
|
+
".zig": "Zig",
|
|
2190
|
+
".clj": "Clojure",
|
|
2191
|
+
".cljs": "Clojure",
|
|
2192
|
+
".hs": "Haskell",
|
|
2193
|
+
".elm": "Elm",
|
|
2194
|
+
".ml": "OCaml",
|
|
2195
|
+
".mli": "OCaml",
|
|
2196
|
+
".vue": "Vue",
|
|
2197
|
+
".svelte": "Svelte",
|
|
2198
|
+
".astro": "Astro"
|
|
2199
|
+
};
|
|
2200
|
+
async function detectLanguages(projectRoot, workspaceRels) {
|
|
2201
|
+
const globalCounts = /* @__PURE__ */ new Map();
|
|
2202
|
+
const perWorkspace = /* @__PURE__ */ new Map();
|
|
2203
|
+
const sortedRels = [...workspaceRels, ""].filter((v, i, arr) => arr.indexOf(v) === i).sort((a, b) => b.length - a.length);
|
|
2204
|
+
for (const rel of sortedRels)
|
|
2205
|
+
perWorkspace.set(rel, /* @__PURE__ */ new Map());
|
|
2206
|
+
await walk(projectRoot, projectRoot, globalCounts, perWorkspace, sortedRels);
|
|
2207
|
+
return [...globalCounts.entries()].map(([name, file_count]) => {
|
|
2208
|
+
const workspaces = sortedRels.filter((rel) => (perWorkspace.get(rel)?.get(name) ?? 0) > 0).map((rel) => rel || ".");
|
|
2209
|
+
return { name, file_count, workspaces };
|
|
2210
|
+
}).filter((l) => l.file_count > 0).sort((a, b) => b.file_count - a.file_count);
|
|
2211
|
+
}
|
|
2212
|
+
async function walk(root, dir, global, perWorkspace, workspaceRels) {
|
|
2213
|
+
let entries;
|
|
2214
|
+
try {
|
|
2215
|
+
entries = await readdir3(dir, { withFileTypes: true });
|
|
2216
|
+
} catch {
|
|
2217
|
+
return;
|
|
2218
|
+
}
|
|
2219
|
+
for (const entry of entries) {
|
|
2220
|
+
if (entry.name.startsWith(".") && entry.name !== ".github") {
|
|
2221
|
+
if (![".toolcairn", ".claude"].includes(entry.name))
|
|
2222
|
+
continue;
|
|
2223
|
+
}
|
|
2224
|
+
if (IGNORED_DIRS.has(entry.name))
|
|
2225
|
+
continue;
|
|
2226
|
+
const full = join6(dir, entry.name);
|
|
2227
|
+
if (entry.isDirectory()) {
|
|
2228
|
+
await walk(root, full, global, perWorkspace, workspaceRels);
|
|
2229
|
+
} else if (entry.isFile()) {
|
|
2230
|
+
const ext = pickExtension(entry.name);
|
|
2231
|
+
if (!ext)
|
|
2232
|
+
continue;
|
|
2233
|
+
const lang = EXT_TO_LANGUAGE[ext];
|
|
2234
|
+
if (!lang)
|
|
2235
|
+
continue;
|
|
2236
|
+
global.set(lang, (global.get(lang) ?? 0) + 1);
|
|
2237
|
+
const relFile = relative(root, full).split(sep).join("/");
|
|
2238
|
+
for (const wsRel of workspaceRels) {
|
|
2239
|
+
if (wsRel === "" || relFile === wsRel || relFile.startsWith(`${wsRel}/`)) {
|
|
2240
|
+
const m = perWorkspace.get(wsRel);
|
|
2241
|
+
if (m)
|
|
2242
|
+
m.set(lang, (m.get(lang) ?? 0) + 1);
|
|
2243
|
+
break;
|
|
2244
|
+
}
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2249
|
+
function pickExtension(filename) {
|
|
2250
|
+
const idx = filename.lastIndexOf(".");
|
|
2251
|
+
if (idx < 0 || idx === 0)
|
|
2252
|
+
return null;
|
|
2253
|
+
return filename.slice(idx).toLowerCase();
|
|
2254
|
+
}
|
|
1767
2255
|
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
.empty p { font-size: 13px; }
|
|
1771
|
-
.empty code { font-family: var(--mono); font-size: 11px; background: var(--surface2); padding: 3px 8px; border-radius: 4px; color: var(--accent); }
|
|
2256
|
+
// ../../packages/tools-local/dist/discovery/parsers/index.js
|
|
2257
|
+
init_esm_shims();
|
|
1772
2258
|
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
2259
|
+
// ../../packages/tools-local/dist/discovery/parsers/cargo.js
|
|
2260
|
+
init_esm_shims();
|
|
2261
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
2262
|
+
import { join as join7 } from "path";
|
|
2263
|
+
import { parse as parseToml } from "smol-toml";
|
|
2264
|
+
var SECTION_MAP = [
|
|
2265
|
+
["dependencies", "dep"],
|
|
2266
|
+
["dev-dependencies", "dev"],
|
|
2267
|
+
["build-dependencies", "build"]
|
|
2268
|
+
];
|
|
2269
|
+
function extractDeps(obj, section, resolved, out, manifestFile, workspaceRel) {
|
|
2270
|
+
if (!obj)
|
|
2271
|
+
return;
|
|
2272
|
+
for (const [name, value] of Object.entries(obj)) {
|
|
2273
|
+
const constraint = typeof value === "string" ? value : value.version;
|
|
2274
|
+
out.push({
|
|
2275
|
+
name,
|
|
2276
|
+
ecosystem: "cargo",
|
|
2277
|
+
version_constraint: constraint,
|
|
2278
|
+
resolved_version: resolved.get(name),
|
|
2279
|
+
section,
|
|
2280
|
+
manifest_file: manifestFile,
|
|
2281
|
+
workspace_path: workspaceRel
|
|
2282
|
+
});
|
|
2283
|
+
}
|
|
2284
|
+
}
|
|
2285
|
+
var parseCargo = async ({ workspace_dir, workspace_rel }) => {
|
|
2286
|
+
const warnings = [];
|
|
2287
|
+
const tools = [];
|
|
2288
|
+
const manifestPath = join7(workspace_dir, "Cargo.toml");
|
|
2289
|
+
if (!await fileExists(manifestPath))
|
|
2290
|
+
return { ecosystem: "cargo", tools, warnings };
|
|
2291
|
+
let manifest;
|
|
2292
|
+
try {
|
|
2293
|
+
manifest = parseToml(await readFile4(manifestPath, "utf-8"));
|
|
2294
|
+
} catch (err) {
|
|
2295
|
+
warnings.push({
|
|
2296
|
+
scope: "parser:cargo",
|
|
2297
|
+
path: manifestPath,
|
|
2298
|
+
message: `Failed to parse Cargo.toml: ${err instanceof Error ? err.message : String(err)}`
|
|
2299
|
+
});
|
|
2300
|
+
return { ecosystem: "cargo", tools, warnings };
|
|
2301
|
+
}
|
|
2302
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
2303
|
+
const lockPath = join7(workspace_dir, "Cargo.lock");
|
|
2304
|
+
if (await fileExists(lockPath)) {
|
|
2305
|
+
try {
|
|
2306
|
+
const lock = parseToml(await readFile4(lockPath, "utf-8"));
|
|
2307
|
+
for (const pkg of lock.package ?? []) {
|
|
2308
|
+
if (pkg.name && pkg.version)
|
|
2309
|
+
resolved.set(pkg.name, pkg.version);
|
|
2310
|
+
}
|
|
2311
|
+
} catch (err) {
|
|
2312
|
+
warnings.push({
|
|
2313
|
+
scope: "parser:cargo",
|
|
2314
|
+
path: lockPath,
|
|
2315
|
+
message: `Failed to parse Cargo.lock: ${err instanceof Error ? err.message : String(err)}`
|
|
2316
|
+
});
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
const manifestFile = workspace_rel ? `${workspace_rel}/Cargo.toml` : "Cargo.toml";
|
|
2320
|
+
for (const [field, section] of SECTION_MAP) {
|
|
2321
|
+
extractDeps(manifest[field], section, resolved, tools, manifestFile, workspace_rel);
|
|
2322
|
+
}
|
|
2323
|
+
extractDeps(manifest.workspace?.dependencies, "dep", resolved, tools, manifestFile, workspace_rel);
|
|
2324
|
+
return { ecosystem: "cargo", tools, warnings };
|
|
2325
|
+
};
|
|
1777
2326
|
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
2327
|
+
// ../../packages/tools-local/dist/discovery/parsers/composer.js
|
|
2328
|
+
init_esm_shims();
|
|
2329
|
+
import { readFile as readFile5 } from "fs/promises";
|
|
2330
|
+
import { join as join8 } from "path";
|
|
2331
|
+
function isPhpPlatform(name) {
|
|
2332
|
+
return name === "php" || name.startsWith("ext-") || name.startsWith("lib-");
|
|
2333
|
+
}
|
|
2334
|
+
var parseComposer = async ({ workspace_dir, workspace_rel }) => {
|
|
2335
|
+
const warnings = [];
|
|
2336
|
+
const tools = [];
|
|
2337
|
+
const lockPath = join8(workspace_dir, "composer.lock");
|
|
2338
|
+
const manifestPath = join8(workspace_dir, "composer.json");
|
|
2339
|
+
if (await fileExists(lockPath)) {
|
|
2340
|
+
try {
|
|
2341
|
+
const lock = JSON.parse(await readFile5(lockPath, "utf-8"));
|
|
2342
|
+
const manifestFile = workspace_rel ? `${workspace_rel}/composer.lock` : "composer.lock";
|
|
2343
|
+
for (const [pkgs, section] of [
|
|
2344
|
+
[lock.packages ?? [], "dep"],
|
|
2345
|
+
[lock["packages-dev"] ?? [], "dev"]
|
|
2346
|
+
]) {
|
|
2347
|
+
for (const pkg of pkgs) {
|
|
2348
|
+
if (!pkg.name || isPhpPlatform(pkg.name))
|
|
2349
|
+
continue;
|
|
2350
|
+
tools.push({
|
|
2351
|
+
name: pkg.name,
|
|
2352
|
+
ecosystem: "composer",
|
|
2353
|
+
version_constraint: void 0,
|
|
2354
|
+
resolved_version: pkg.version,
|
|
2355
|
+
section,
|
|
2356
|
+
manifest_file: manifestFile,
|
|
2357
|
+
workspace_path: workspace_rel
|
|
2358
|
+
});
|
|
2359
|
+
}
|
|
2360
|
+
}
|
|
2361
|
+
if (tools.length > 0)
|
|
2362
|
+
return { ecosystem: "composer", tools, warnings };
|
|
2363
|
+
} catch (err) {
|
|
2364
|
+
warnings.push({
|
|
2365
|
+
scope: "parser:composer",
|
|
2366
|
+
path: lockPath,
|
|
2367
|
+
message: `Failed to parse composer.lock: ${err instanceof Error ? err.message : String(err)}`
|
|
2368
|
+
});
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
2371
|
+
if (await fileExists(manifestPath)) {
|
|
2372
|
+
try {
|
|
2373
|
+
const manifest = JSON.parse(await readFile5(manifestPath, "utf-8"));
|
|
2374
|
+
const manifestFile = workspace_rel ? `${workspace_rel}/composer.json` : "composer.json";
|
|
2375
|
+
for (const [obj, section] of [
|
|
2376
|
+
[manifest.require, "dep"],
|
|
2377
|
+
[manifest["require-dev"], "dev"]
|
|
2378
|
+
]) {
|
|
2379
|
+
for (const [name, constraint] of Object.entries(obj ?? {})) {
|
|
2380
|
+
if (isPhpPlatform(name))
|
|
2381
|
+
continue;
|
|
2382
|
+
tools.push({
|
|
2383
|
+
name,
|
|
2384
|
+
ecosystem: "composer",
|
|
2385
|
+
version_constraint: constraint,
|
|
2386
|
+
section,
|
|
2387
|
+
manifest_file: manifestFile,
|
|
2388
|
+
workspace_path: workspace_rel
|
|
2389
|
+
});
|
|
2390
|
+
}
|
|
2391
|
+
}
|
|
2392
|
+
} catch (err) {
|
|
2393
|
+
warnings.push({
|
|
2394
|
+
scope: "parser:composer",
|
|
2395
|
+
path: manifestPath,
|
|
2396
|
+
message: `Failed to parse composer.json: ${err instanceof Error ? err.message : String(err)}`
|
|
2397
|
+
});
|
|
2398
|
+
}
|
|
2399
|
+
}
|
|
2400
|
+
return { ecosystem: "composer", tools, warnings };
|
|
2401
|
+
};
|
|
1784
2402
|
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
2403
|
+
// ../../packages/tools-local/dist/discovery/parsers/dart.js
|
|
2404
|
+
init_esm_shims();
|
|
2405
|
+
import { readFile as readFile6 } from "fs/promises";
|
|
2406
|
+
import { join as join9 } from "path";
|
|
2407
|
+
import { parse as parseYaml } from "yaml";
|
|
2408
|
+
function isSkippableDep(value) {
|
|
2409
|
+
if (typeof value === "object" && value !== null) {
|
|
2410
|
+
const v = value;
|
|
2411
|
+
if (v.sdk || v.path)
|
|
2412
|
+
return true;
|
|
2413
|
+
}
|
|
2414
|
+
return false;
|
|
2415
|
+
}
|
|
2416
|
+
function extractDeps2(obj, section, resolved, out, manifestFile, workspaceRel) {
|
|
2417
|
+
if (!obj)
|
|
2418
|
+
return;
|
|
2419
|
+
for (const [name, value] of Object.entries(obj)) {
|
|
2420
|
+
if (isSkippableDep(value))
|
|
2421
|
+
continue;
|
|
2422
|
+
const constraint = typeof value === "string" ? value : value.version;
|
|
2423
|
+
out.push({
|
|
2424
|
+
name,
|
|
2425
|
+
ecosystem: "pub",
|
|
2426
|
+
version_constraint: constraint,
|
|
2427
|
+
resolved_version: resolved.get(name),
|
|
2428
|
+
section,
|
|
2429
|
+
manifest_file: manifestFile,
|
|
2430
|
+
workspace_path: workspaceRel
|
|
2431
|
+
});
|
|
2432
|
+
}
|
|
2433
|
+
}
|
|
2434
|
+
var parseDart = async ({ workspace_dir, workspace_rel }) => {
|
|
2435
|
+
const warnings = [];
|
|
2436
|
+
const tools = [];
|
|
2437
|
+
const pubspecPath = join9(workspace_dir, "pubspec.yaml");
|
|
2438
|
+
if (!await fileExists(pubspecPath))
|
|
2439
|
+
return { ecosystem: "pub", tools, warnings };
|
|
2440
|
+
let pubspec;
|
|
2441
|
+
try {
|
|
2442
|
+
pubspec = parseYaml(await readFile6(pubspecPath, "utf-8"));
|
|
2443
|
+
} catch (err) {
|
|
2444
|
+
warnings.push({
|
|
2445
|
+
scope: "parser:dart",
|
|
2446
|
+
path: pubspecPath,
|
|
2447
|
+
message: `Failed to parse pubspec.yaml: ${err instanceof Error ? err.message : String(err)}`
|
|
2448
|
+
});
|
|
2449
|
+
return { ecosystem: "pub", tools, warnings };
|
|
2450
|
+
}
|
|
2451
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
2452
|
+
const lockPath = join9(workspace_dir, "pubspec.lock");
|
|
2453
|
+
if (await fileExists(lockPath)) {
|
|
2454
|
+
try {
|
|
2455
|
+
const lock = parseYaml(await readFile6(lockPath, "utf-8"));
|
|
2456
|
+
for (const [name, pkg] of Object.entries(lock.packages ?? {})) {
|
|
2457
|
+
if (pkg.version)
|
|
2458
|
+
resolved.set(name, pkg.version);
|
|
2459
|
+
}
|
|
2460
|
+
} catch (err) {
|
|
2461
|
+
warnings.push({
|
|
2462
|
+
scope: "parser:dart",
|
|
2463
|
+
path: lockPath,
|
|
2464
|
+
message: `Failed to parse pubspec.lock: ${err instanceof Error ? err.message : String(err)}`
|
|
2465
|
+
});
|
|
2466
|
+
}
|
|
2467
|
+
}
|
|
2468
|
+
const manifestFile = workspace_rel ? `${workspace_rel}/pubspec.yaml` : "pubspec.yaml";
|
|
2469
|
+
extractDeps2(pubspec.dependencies, "dep", resolved, tools, manifestFile, workspace_rel);
|
|
2470
|
+
extractDeps2(pubspec.dev_dependencies, "dev", resolved, tools, manifestFile, workspace_rel);
|
|
2471
|
+
return { ecosystem: "pub", tools, warnings };
|
|
2472
|
+
};
|
|
1794
2473
|
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
2474
|
+
// ../../packages/tools-local/dist/discovery/parsers/dotnet.js
|
|
2475
|
+
init_esm_shims();
|
|
2476
|
+
import { readFile as readFile7, readdir as readdir4 } from "fs/promises";
|
|
2477
|
+
import { join as join10, relative as relative2 } from "path";
|
|
2478
|
+
import { XMLParser } from "fast-xml-parser";
|
|
2479
|
+
function toArray(v) {
|
|
2480
|
+
if (v === void 0)
|
|
2481
|
+
return [];
|
|
2482
|
+
return Array.isArray(v) ? v : [v];
|
|
2483
|
+
}
|
|
2484
|
+
var parseDotnet = async ({ workspace_dir, workspace_rel }) => {
|
|
2485
|
+
const warnings = [];
|
|
2486
|
+
const tools = [];
|
|
2487
|
+
let entries = [];
|
|
2488
|
+
try {
|
|
2489
|
+
entries = await readdir4(workspace_dir);
|
|
2490
|
+
} catch {
|
|
2491
|
+
return { ecosystem: "nuget", tools, warnings };
|
|
2492
|
+
}
|
|
2493
|
+
const csprojFiles = entries.filter((f) => f.endsWith(".csproj") || f.endsWith(".fsproj"));
|
|
2494
|
+
const xmlParser = new XMLParser({ ignoreAttributes: false });
|
|
2495
|
+
for (const proj of csprojFiles) {
|
|
2496
|
+
const path2 = join10(workspace_dir, proj);
|
|
2497
|
+
try {
|
|
2498
|
+
const raw = await readFile7(path2, "utf-8");
|
|
2499
|
+
const doc = xmlParser.parse(raw);
|
|
2500
|
+
const itemGroups = Array.isArray(doc.Project?.ItemGroup) ? doc.Project?.ItemGroup ?? [] : doc.Project?.ItemGroup ? [doc.Project.ItemGroup] : [];
|
|
2501
|
+
const manifestFile = workspace_rel ? `${workspace_rel}/${relative2(workspace_dir, path2)}` : relative2(workspace_dir, path2);
|
|
2502
|
+
for (const group of itemGroups) {
|
|
2503
|
+
for (const ref of toArray(group.PackageReference)) {
|
|
2504
|
+
const name = ref["@_Include"];
|
|
2505
|
+
if (!name)
|
|
2506
|
+
continue;
|
|
2507
|
+
const version = ref["@_Version"];
|
|
2508
|
+
tools.push({
|
|
2509
|
+
name,
|
|
2510
|
+
ecosystem: "nuget",
|
|
2511
|
+
version_constraint: version,
|
|
2512
|
+
resolved_version: version,
|
|
2513
|
+
section: ref["@_PrivateAssets"] ? "build" : "dep",
|
|
2514
|
+
manifest_file: manifestFile,
|
|
2515
|
+
workspace_path: workspace_rel
|
|
2516
|
+
});
|
|
2517
|
+
}
|
|
2518
|
+
}
|
|
2519
|
+
} catch (err) {
|
|
2520
|
+
warnings.push({
|
|
2521
|
+
scope: "parser:dotnet",
|
|
2522
|
+
path: path2,
|
|
2523
|
+
message: `Failed to parse ${proj}: ${err instanceof Error ? err.message : String(err)}`
|
|
2524
|
+
});
|
|
2525
|
+
}
|
|
2526
|
+
}
|
|
2527
|
+
const pkgConfigPath = join10(workspace_dir, "packages.config");
|
|
2528
|
+
if (await fileExists(pkgConfigPath)) {
|
|
2529
|
+
try {
|
|
2530
|
+
const raw = await readFile7(pkgConfigPath, "utf-8");
|
|
2531
|
+
const doc = xmlParser.parse(raw);
|
|
2532
|
+
const manifestFile = workspace_rel ? `${workspace_rel}/packages.config` : "packages.config";
|
|
2533
|
+
for (const pkg of toArray(doc.packages?.package)) {
|
|
2534
|
+
const name = pkg["@_Include"];
|
|
2535
|
+
if (!name)
|
|
2536
|
+
continue;
|
|
2537
|
+
tools.push({
|
|
2538
|
+
name,
|
|
2539
|
+
ecosystem: "nuget",
|
|
2540
|
+
version_constraint: pkg["@_Version"],
|
|
2541
|
+
resolved_version: pkg["@_Version"],
|
|
2542
|
+
section: "dep",
|
|
2543
|
+
manifest_file: manifestFile,
|
|
2544
|
+
workspace_path: workspace_rel
|
|
2545
|
+
});
|
|
2546
|
+
}
|
|
2547
|
+
} catch (err) {
|
|
2548
|
+
warnings.push({
|
|
2549
|
+
scope: "parser:dotnet",
|
|
2550
|
+
path: pkgConfigPath,
|
|
2551
|
+
message: `Failed to parse packages.config: ${err instanceof Error ? err.message : String(err)}`
|
|
2552
|
+
});
|
|
2553
|
+
}
|
|
2554
|
+
}
|
|
2555
|
+
return { ecosystem: "nuget", tools, warnings };
|
|
2556
|
+
};
|
|
1803
2557
|
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
2558
|
+
// ../../packages/tools-local/dist/discovery/parsers/go.js
|
|
2559
|
+
init_esm_shims();
|
|
2560
|
+
import { readFile as readFile8 } from "fs/promises";
|
|
2561
|
+
import { join as join11 } from "path";
|
|
2562
|
+
function parseGoMod(raw) {
|
|
2563
|
+
const out = [];
|
|
2564
|
+
const lines = raw.split("\n");
|
|
2565
|
+
let inRequireBlock = false;
|
|
2566
|
+
for (const rawLine of lines) {
|
|
2567
|
+
const line = rawLine.replace(/\/\/.*$/, "").trim();
|
|
2568
|
+
const commentTail = rawLine.match(/\/\/\s*(.*)$/)?.[1] ?? "";
|
|
2569
|
+
const indirect = /\bindirect\b/.test(commentTail);
|
|
2570
|
+
if (!line)
|
|
2571
|
+
continue;
|
|
2572
|
+
if (line === "require (") {
|
|
2573
|
+
inRequireBlock = true;
|
|
2574
|
+
continue;
|
|
2575
|
+
}
|
|
2576
|
+
if (line === ")") {
|
|
2577
|
+
inRequireBlock = false;
|
|
2578
|
+
continue;
|
|
2579
|
+
}
|
|
2580
|
+
if (line.startsWith("require ")) {
|
|
2581
|
+
const parts = line.slice(8).trim().split(/\s+/);
|
|
2582
|
+
if (parts[0] && parts[1])
|
|
2583
|
+
out.push({ name: parts[0], version: parts[1], indirect });
|
|
2584
|
+
continue;
|
|
2585
|
+
}
|
|
2586
|
+
if (inRequireBlock) {
|
|
2587
|
+
const parts = line.split(/\s+/);
|
|
2588
|
+
if (parts[0] && parts[1])
|
|
2589
|
+
out.push({ name: parts[0], version: parts[1], indirect });
|
|
2590
|
+
}
|
|
2591
|
+
}
|
|
2592
|
+
return out;
|
|
2593
|
+
}
|
|
2594
|
+
var parseGo = async ({ workspace_dir, workspace_rel }) => {
|
|
2595
|
+
const warnings = [];
|
|
2596
|
+
const tools = [];
|
|
2597
|
+
const modPath = join11(workspace_dir, "go.mod");
|
|
2598
|
+
if (!await fileExists(modPath))
|
|
2599
|
+
return { ecosystem: "go", tools, warnings };
|
|
2600
|
+
try {
|
|
2601
|
+
const raw = await readFile8(modPath, "utf-8");
|
|
2602
|
+
const manifestFile = workspace_rel ? `${workspace_rel}/go.mod` : "go.mod";
|
|
2603
|
+
for (const dep of parseGoMod(raw)) {
|
|
2604
|
+
tools.push({
|
|
2605
|
+
name: dep.name,
|
|
2606
|
+
ecosystem: "go",
|
|
2607
|
+
version_constraint: dep.version,
|
|
2608
|
+
resolved_version: dep.version,
|
|
2609
|
+
// go.mod pins exact version
|
|
2610
|
+
section: dep.indirect ? "optional" : "dep",
|
|
2611
|
+
manifest_file: manifestFile,
|
|
2612
|
+
workspace_path: workspace_rel
|
|
2613
|
+
});
|
|
2614
|
+
}
|
|
2615
|
+
} catch (err) {
|
|
2616
|
+
warnings.push({
|
|
2617
|
+
scope: "parser:go",
|
|
2618
|
+
path: modPath,
|
|
2619
|
+
message: `Failed to parse go.mod: ${err instanceof Error ? err.message : String(err)}`
|
|
2620
|
+
});
|
|
2621
|
+
}
|
|
2622
|
+
return { ecosystem: "go", tools, warnings };
|
|
2623
|
+
};
|
|
1813
2624
|
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
2625
|
+
// ../../packages/tools-local/dist/discovery/parsers/gradle.js
|
|
2626
|
+
init_esm_shims();
|
|
2627
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
2628
|
+
import { join as join12 } from "path";
|
|
2629
|
+
function parseGradleLockfile(raw) {
|
|
2630
|
+
const out = [];
|
|
2631
|
+
for (const line of raw.split("\n")) {
|
|
2632
|
+
const trimmed = line.trim();
|
|
2633
|
+
if (!trimmed || trimmed.startsWith("#"))
|
|
2634
|
+
continue;
|
|
2635
|
+
const match = trimmed.match(/^([^:]+):([^:]+):([^=]+)=(.*)$/);
|
|
2636
|
+
if (match?.[1] && match[2] && match[3]) {
|
|
2637
|
+
out.push({
|
|
2638
|
+
group: match[1],
|
|
2639
|
+
name: match[2],
|
|
2640
|
+
version: match[3],
|
|
2641
|
+
configurations: (match[4] ?? "").split(",").map((s) => s.trim())
|
|
2642
|
+
});
|
|
2643
|
+
}
|
|
2644
|
+
}
|
|
2645
|
+
return out;
|
|
2646
|
+
}
|
|
2647
|
+
function gradleConfigToSection(configs) {
|
|
2648
|
+
const joined = configs.join(" ").toLowerCase();
|
|
2649
|
+
if (joined.includes("test"))
|
|
2650
|
+
return "dev";
|
|
2651
|
+
if (joined.includes("annotationprocessor") || joined.includes("kapt"))
|
|
2652
|
+
return "build";
|
|
2653
|
+
return "dep";
|
|
2654
|
+
}
|
|
2655
|
+
function parseBuildGradle(raw) {
|
|
2656
|
+
const out = [];
|
|
2657
|
+
const patterns = [
|
|
2658
|
+
/(implementation|api|compileOnly|runtimeOnly|testImplementation|testRuntimeOnly|annotationProcessor|kapt|ksp)\s*\(?\s*(['"])([A-Za-z0-9_.\-]+:[A-Za-z0-9_.\-]+:[^'"]+)\2/g
|
|
2659
|
+
];
|
|
2660
|
+
for (const pattern of patterns) {
|
|
2661
|
+
let match;
|
|
2662
|
+
while ((match = pattern.exec(raw)) !== null) {
|
|
2663
|
+
if (match[1] && match[3])
|
|
2664
|
+
out.push({ spec: match[3], config: match[1] });
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
return out;
|
|
2668
|
+
}
|
|
2669
|
+
function configKeywordToSection(config5) {
|
|
2670
|
+
const c = config5.toLowerCase();
|
|
2671
|
+
if (c.startsWith("test"))
|
|
2672
|
+
return "dev";
|
|
2673
|
+
if (c.includes("annotationprocessor") || c === "kapt" || c === "ksp")
|
|
2674
|
+
return "build";
|
|
2675
|
+
return "dep";
|
|
2676
|
+
}
|
|
2677
|
+
var parseGradle = async ({ workspace_dir, workspace_rel }) => {
|
|
2678
|
+
const warnings = [];
|
|
2679
|
+
const tools = [];
|
|
2680
|
+
const lockPath = join12(workspace_dir, "gradle.lockfile");
|
|
2681
|
+
if (await fileExists(lockPath)) {
|
|
2682
|
+
try {
|
|
2683
|
+
const raw = await readFile9(lockPath, "utf-8");
|
|
2684
|
+
const manifestFile = workspace_rel ? `${workspace_rel}/gradle.lockfile` : "gradle.lockfile";
|
|
2685
|
+
for (const dep of parseGradleLockfile(raw)) {
|
|
2686
|
+
tools.push({
|
|
2687
|
+
name: `${dep.group}:${dep.name}`,
|
|
2688
|
+
ecosystem: "gradle",
|
|
2689
|
+
version_constraint: dep.version,
|
|
2690
|
+
resolved_version: dep.version,
|
|
2691
|
+
section: gradleConfigToSection(dep.configurations),
|
|
2692
|
+
manifest_file: manifestFile,
|
|
2693
|
+
workspace_path: workspace_rel
|
|
2694
|
+
});
|
|
2695
|
+
}
|
|
2696
|
+
if (tools.length > 0)
|
|
2697
|
+
return { ecosystem: "gradle", tools, warnings };
|
|
2698
|
+
} catch (err) {
|
|
2699
|
+
warnings.push({
|
|
2700
|
+
scope: "parser:gradle",
|
|
2701
|
+
path: lockPath,
|
|
2702
|
+
message: `Failed to parse gradle.lockfile: ${err instanceof Error ? err.message : String(err)}`
|
|
2703
|
+
});
|
|
2704
|
+
}
|
|
2705
|
+
}
|
|
2706
|
+
for (const filename of ["build.gradle.kts", "build.gradle"]) {
|
|
2707
|
+
const path2 = join12(workspace_dir, filename);
|
|
2708
|
+
if (!await fileExists(path2))
|
|
2709
|
+
continue;
|
|
2710
|
+
try {
|
|
2711
|
+
const raw = await readFile9(path2, "utf-8");
|
|
2712
|
+
const manifestFile = workspace_rel ? `${workspace_rel}/${filename}` : filename;
|
|
2713
|
+
let hasVariable = false;
|
|
2714
|
+
for (const dep of parseBuildGradle(raw)) {
|
|
2715
|
+
const parts = dep.spec.split(":");
|
|
2716
|
+
if (parts.length < 3 || !parts[0] || !parts[1])
|
|
2717
|
+
continue;
|
|
2718
|
+
const version = parts.slice(2).join(":");
|
|
2719
|
+
if (version.startsWith("$") || version.includes("${")) {
|
|
2720
|
+
hasVariable = true;
|
|
2721
|
+
continue;
|
|
2722
|
+
}
|
|
2723
|
+
tools.push({
|
|
2724
|
+
name: `${parts[0]}:${parts[1]}`,
|
|
2725
|
+
ecosystem: "gradle",
|
|
2726
|
+
version_constraint: version,
|
|
2727
|
+
section: configKeywordToSection(dep.config),
|
|
2728
|
+
manifest_file: manifestFile,
|
|
2729
|
+
workspace_path: workspace_rel
|
|
2730
|
+
});
|
|
2731
|
+
}
|
|
2732
|
+
warnings.push({
|
|
2733
|
+
scope: "parser:gradle",
|
|
2734
|
+
path: manifestFile,
|
|
2735
|
+
message: "Shallow parse of build.gradle \u2014 results may be incomplete. Add `dependencyLocking` to the project for deterministic discovery."
|
|
2736
|
+
});
|
|
2737
|
+
if (hasVariable) {
|
|
2738
|
+
warnings.push({
|
|
2739
|
+
scope: "parser:gradle",
|
|
2740
|
+
path: manifestFile,
|
|
2741
|
+
message: "Some deps use variable interpolation ($ver / ${var}) \u2014 skipped."
|
|
2742
|
+
});
|
|
2743
|
+
}
|
|
2744
|
+
break;
|
|
2745
|
+
} catch (err) {
|
|
2746
|
+
warnings.push({
|
|
2747
|
+
scope: "parser:gradle",
|
|
2748
|
+
path: path2,
|
|
2749
|
+
message: `Failed to read ${filename}: ${err instanceof Error ? err.message : String(err)}`
|
|
2750
|
+
});
|
|
2751
|
+
}
|
|
2752
|
+
}
|
|
2753
|
+
return { ecosystem: "gradle", tools, warnings };
|
|
2754
|
+
};
|
|
1837
2755
|
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
2756
|
+
// ../../packages/tools-local/dist/discovery/parsers/maven.js
|
|
2757
|
+
init_esm_shims();
|
|
2758
|
+
import { readFile as readFile10 } from "fs/promises";
|
|
2759
|
+
import { join as join13 } from "path";
|
|
2760
|
+
import { XMLParser as XMLParser2 } from "fast-xml-parser";
|
|
2761
|
+
function scopeToSection(scope, optional) {
|
|
2762
|
+
if (optional)
|
|
2763
|
+
return "optional";
|
|
2764
|
+
switch (scope) {
|
|
2765
|
+
case "test":
|
|
2766
|
+
case "provided":
|
|
2767
|
+
return "dev";
|
|
2768
|
+
default:
|
|
2769
|
+
return "dep";
|
|
2770
|
+
}
|
|
2771
|
+
}
|
|
2772
|
+
function toArray2(v) {
|
|
2773
|
+
if (v === void 0)
|
|
2774
|
+
return [];
|
|
2775
|
+
return Array.isArray(v) ? v : [v];
|
|
2776
|
+
}
|
|
2777
|
+
var parseMaven = async ({ workspace_dir, workspace_rel }) => {
|
|
2778
|
+
const warnings = [];
|
|
2779
|
+
const tools = [];
|
|
2780
|
+
const pomPath = join13(workspace_dir, "pom.xml");
|
|
2781
|
+
if (!await fileExists(pomPath))
|
|
2782
|
+
return { ecosystem: "maven", tools, warnings };
|
|
2783
|
+
let doc;
|
|
2784
|
+
try {
|
|
2785
|
+
const raw = await readFile10(pomPath, "utf-8");
|
|
2786
|
+
const parser = new XMLParser2({ ignoreAttributes: true, parseTagValue: true });
|
|
2787
|
+
doc = parser.parse(raw);
|
|
2788
|
+
} catch (err) {
|
|
2789
|
+
warnings.push({
|
|
2790
|
+
scope: "parser:maven",
|
|
2791
|
+
path: pomPath,
|
|
2792
|
+
message: `Failed to parse pom.xml: ${err instanceof Error ? err.message : String(err)}`
|
|
2793
|
+
});
|
|
2794
|
+
return { ecosystem: "maven", tools, warnings };
|
|
2795
|
+
}
|
|
2796
|
+
const manifestFile = workspace_rel ? `${workspace_rel}/pom.xml` : "pom.xml";
|
|
2797
|
+
const deps = toArray2(doc.project?.dependencies?.dependency);
|
|
2798
|
+
const managedDeps = toArray2(doc.project?.dependencyManagement?.dependencies?.dependency);
|
|
2799
|
+
let hasUnresolvedVariable = false;
|
|
2800
|
+
for (const dep of [...deps, ...managedDeps]) {
|
|
2801
|
+
if (!dep.groupId || !dep.artifactId)
|
|
2802
|
+
continue;
|
|
2803
|
+
const name = `${dep.groupId}:${dep.artifactId}`;
|
|
2804
|
+
const version = typeof dep.version === "string" ? dep.version : void 0;
|
|
2805
|
+
if (version && version.includes("${"))
|
|
2806
|
+
hasUnresolvedVariable = true;
|
|
2807
|
+
const optional = dep.optional === true || dep.optional === "true";
|
|
2808
|
+
tools.push({
|
|
2809
|
+
name,
|
|
2810
|
+
ecosystem: "maven",
|
|
2811
|
+
version_constraint: version,
|
|
2812
|
+
resolved_version: version && !version.includes("${") ? version : void 0,
|
|
2813
|
+
section: scopeToSection(dep.scope, optional),
|
|
2814
|
+
manifest_file: manifestFile,
|
|
2815
|
+
workspace_path: workspace_rel
|
|
2816
|
+
});
|
|
2817
|
+
}
|
|
2818
|
+
if (hasUnresolvedVariable) {
|
|
2819
|
+
warnings.push({
|
|
2820
|
+
scope: "parser:maven",
|
|
2821
|
+
path: pomPath,
|
|
2822
|
+
message: "Some dependencies use ${...} variable interpolation which is not resolved \u2014 version info may be incomplete."
|
|
2823
|
+
});
|
|
2824
|
+
}
|
|
2825
|
+
return { ecosystem: "maven", tools, warnings };
|
|
2826
|
+
};
|
|
1841
2827
|
|
|
1842
|
-
//
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
2828
|
+
// ../../packages/tools-local/dist/discovery/parsers/mix.js
|
|
2829
|
+
init_esm_shims();
|
|
2830
|
+
import { readFile as readFile11 } from "fs/promises";
|
|
2831
|
+
import { join as join14 } from "path";
|
|
2832
|
+
function parseMixLock(raw) {
|
|
2833
|
+
const out = [];
|
|
2834
|
+
const pattern = /"([^"]+)":\s*\{\s*:hex\s*,\s*:[A-Za-z_][A-Za-z0-9_]*\s*,\s*"([^"]+)"/g;
|
|
2835
|
+
let match;
|
|
2836
|
+
while ((match = pattern.exec(raw)) !== null) {
|
|
2837
|
+
if (match[1] && match[2])
|
|
2838
|
+
out.push({ name: match[1], version: match[2] });
|
|
2839
|
+
}
|
|
2840
|
+
return out;
|
|
2841
|
+
}
|
|
2842
|
+
function parseMixExs(raw) {
|
|
2843
|
+
const out = [];
|
|
2844
|
+
const pattern = /\{:([a-z_][a-z0-9_]*)\s*,\s*"([^"]+)"(?:[^}]*only:\s*(?:\[?:?([a-z_]+))?[^}]*)?\}/g;
|
|
2845
|
+
let match;
|
|
2846
|
+
while ((match = pattern.exec(raw)) !== null) {
|
|
2847
|
+
if (match[1]) {
|
|
2848
|
+
const onlyScope = match[3];
|
|
2849
|
+
out.push({
|
|
2850
|
+
name: match[1],
|
|
2851
|
+
constraint: match[2],
|
|
2852
|
+
dev: onlyScope === "dev" || onlyScope === "test"
|
|
2853
|
+
});
|
|
2854
|
+
}
|
|
2855
|
+
}
|
|
2856
|
+
return out;
|
|
2857
|
+
}
|
|
2858
|
+
var parseMix = async ({ workspace_dir, workspace_rel }) => {
|
|
2859
|
+
const warnings = [];
|
|
2860
|
+
const tools = [];
|
|
2861
|
+
const lockPath = join14(workspace_dir, "mix.lock");
|
|
2862
|
+
if (await fileExists(lockPath)) {
|
|
2863
|
+
try {
|
|
2864
|
+
const raw = await readFile11(lockPath, "utf-8");
|
|
2865
|
+
const manifestFile = workspace_rel ? `${workspace_rel}/mix.lock` : "mix.lock";
|
|
2866
|
+
for (const dep of parseMixLock(raw)) {
|
|
2867
|
+
tools.push({
|
|
2868
|
+
name: dep.name,
|
|
2869
|
+
ecosystem: "hex",
|
|
2870
|
+
version_constraint: void 0,
|
|
2871
|
+
resolved_version: dep.version,
|
|
2872
|
+
section: "dep",
|
|
2873
|
+
manifest_file: manifestFile,
|
|
2874
|
+
workspace_path: workspace_rel
|
|
2875
|
+
});
|
|
2876
|
+
}
|
|
2877
|
+
if (tools.length > 0)
|
|
2878
|
+
return { ecosystem: "hex", tools, warnings };
|
|
2879
|
+
} catch (err) {
|
|
2880
|
+
warnings.push({
|
|
2881
|
+
scope: "parser:mix",
|
|
2882
|
+
path: lockPath,
|
|
2883
|
+
message: `Failed to parse mix.lock: ${err instanceof Error ? err.message : String(err)}`
|
|
2884
|
+
});
|
|
2885
|
+
}
|
|
2886
|
+
}
|
|
2887
|
+
const exsPath = join14(workspace_dir, "mix.exs");
|
|
2888
|
+
if (await fileExists(exsPath)) {
|
|
2889
|
+
try {
|
|
2890
|
+
const raw = await readFile11(exsPath, "utf-8");
|
|
2891
|
+
const manifestFile = workspace_rel ? `${workspace_rel}/mix.exs` : "mix.exs";
|
|
2892
|
+
for (const dep of parseMixExs(raw)) {
|
|
2893
|
+
tools.push({
|
|
2894
|
+
name: dep.name,
|
|
2895
|
+
ecosystem: "hex",
|
|
2896
|
+
version_constraint: dep.constraint,
|
|
2897
|
+
section: dep.dev ? "dev" : "dep",
|
|
2898
|
+
manifest_file: manifestFile,
|
|
2899
|
+
workspace_path: workspace_rel
|
|
2900
|
+
});
|
|
2901
|
+
}
|
|
2902
|
+
} catch (err) {
|
|
2903
|
+
warnings.push({
|
|
2904
|
+
scope: "parser:mix",
|
|
2905
|
+
path: exsPath,
|
|
2906
|
+
message: `Failed to parse mix.exs: ${err instanceof Error ? err.message : String(err)}`
|
|
2907
|
+
});
|
|
2908
|
+
}
|
|
2909
|
+
}
|
|
2910
|
+
return { ecosystem: "hex", tools, warnings };
|
|
2911
|
+
};
|
|
1849
2912
|
|
|
1850
|
-
//
|
|
1851
|
-
|
|
1852
|
-
|
|
2913
|
+
// ../../packages/tools-local/dist/discovery/parsers/npm.js
|
|
2914
|
+
init_esm_shims();
|
|
2915
|
+
import { readFile as readFile12 } from "fs/promises";
|
|
2916
|
+
import { join as join15 } from "path";
|
|
2917
|
+
import { parse as parseYaml2 } from "yaml";
|
|
2918
|
+
var SECTION_MAP2 = [
|
|
2919
|
+
["dependencies", "dep"],
|
|
2920
|
+
["devDependencies", "dev"],
|
|
2921
|
+
["peerDependencies", "peer"],
|
|
2922
|
+
["optionalDependencies", "optional"]
|
|
2923
|
+
];
|
|
2924
|
+
function stripPnpmRange(raw) {
|
|
2925
|
+
const atIdx = raw.lastIndexOf("@");
|
|
2926
|
+
if (atIdx <= 0)
|
|
2927
|
+
return void 0;
|
|
2928
|
+
const tail = raw.slice(atIdx + 1);
|
|
2929
|
+
const parenIdx = tail.indexOf("(");
|
|
2930
|
+
return parenIdx >= 0 ? tail.slice(0, parenIdx) : tail || void 0;
|
|
2931
|
+
}
|
|
2932
|
+
function resolvePnpmVersion(lock, importerKey, section, depName) {
|
|
2933
|
+
const importer = lock.importers?.[importerKey];
|
|
2934
|
+
const entry = importer?.[section]?.[depName];
|
|
2935
|
+
if (!entry)
|
|
2936
|
+
return void 0;
|
|
2937
|
+
if (typeof entry === "string")
|
|
2938
|
+
return entry;
|
|
2939
|
+
if (entry.version) {
|
|
2940
|
+
const parenIdx = entry.version.indexOf("(");
|
|
2941
|
+
return parenIdx >= 0 ? entry.version.slice(0, parenIdx) : entry.version;
|
|
2942
|
+
}
|
|
2943
|
+
return void 0;
|
|
2944
|
+
}
|
|
2945
|
+
function resolveNpmLockVersion(lock, depName) {
|
|
2946
|
+
const key = `node_modules/${depName}`;
|
|
2947
|
+
return lock.packages?.[key]?.version ?? lock.dependencies?.[depName]?.version;
|
|
2948
|
+
}
|
|
2949
|
+
var parseNpm = async ({ workspace_dir, workspace_rel }) => {
|
|
2950
|
+
const warnings = [];
|
|
2951
|
+
const tools = [];
|
|
2952
|
+
const manifestPath = join15(workspace_dir, "package.json");
|
|
2953
|
+
if (!await fileExists(manifestPath)) {
|
|
2954
|
+
return { ecosystem: "npm", tools, warnings };
|
|
2955
|
+
}
|
|
2956
|
+
let manifest;
|
|
1853
2957
|
try {
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
2958
|
+
manifest = JSON.parse(await readFile12(manifestPath, "utf-8"));
|
|
2959
|
+
} catch (err) {
|
|
2960
|
+
warnings.push({
|
|
2961
|
+
scope: "parser:npm",
|
|
2962
|
+
path: manifestPath,
|
|
2963
|
+
message: `Failed to parse package.json: ${err instanceof Error ? err.message : String(err)}`
|
|
2964
|
+
});
|
|
2965
|
+
return { ecosystem: "npm", tools, warnings };
|
|
2966
|
+
}
|
|
2967
|
+
let pnpmLock;
|
|
2968
|
+
let npmLock;
|
|
2969
|
+
const pnpmLockPath = join15(workspace_dir, "pnpm-lock.yaml");
|
|
2970
|
+
const npmLockPath = join15(workspace_dir, "package-lock.json");
|
|
2971
|
+
if (await fileExists(pnpmLockPath)) {
|
|
2972
|
+
try {
|
|
2973
|
+
pnpmLock = parseYaml2(await readFile12(pnpmLockPath, "utf-8"));
|
|
2974
|
+
} catch (err) {
|
|
2975
|
+
warnings.push({
|
|
2976
|
+
scope: "parser:npm",
|
|
2977
|
+
path: pnpmLockPath,
|
|
2978
|
+
message: `Failed to parse pnpm-lock.yaml, falling back to manifest: ${err instanceof Error ? err.message : String(err)}`
|
|
2979
|
+
});
|
|
2980
|
+
}
|
|
2981
|
+
} else if (await fileExists(npmLockPath)) {
|
|
2982
|
+
try {
|
|
2983
|
+
npmLock = JSON.parse(await readFile12(npmLockPath, "utf-8"));
|
|
2984
|
+
} catch (err) {
|
|
2985
|
+
warnings.push({
|
|
2986
|
+
scope: "parser:npm",
|
|
2987
|
+
path: npmLockPath,
|
|
2988
|
+
message: `Failed to parse package-lock.json, falling back to manifest: ${err instanceof Error ? err.message : String(err)}`
|
|
2989
|
+
});
|
|
2990
|
+
}
|
|
2991
|
+
} else if (!await fileExists(join15(workspace_dir, "yarn.lock"))) {
|
|
2992
|
+
warnings.push({
|
|
2993
|
+
scope: "parser:npm",
|
|
2994
|
+
path: manifestPath,
|
|
2995
|
+
message: "No lockfile present \u2014 resolved_version will be absent."
|
|
2996
|
+
});
|
|
2997
|
+
}
|
|
2998
|
+
const manifestFile = workspace_rel ? `${workspace_rel}/package.json` : "package.json";
|
|
2999
|
+
const pnpmImporterKey = workspace_rel || ".";
|
|
3000
|
+
for (const [field, section] of SECTION_MAP2) {
|
|
3001
|
+
const deps = manifest[field];
|
|
3002
|
+
if (!deps)
|
|
3003
|
+
continue;
|
|
3004
|
+
for (const [name, constraint] of Object.entries(deps)) {
|
|
3005
|
+
let resolved;
|
|
3006
|
+
if (pnpmLock) {
|
|
3007
|
+
resolved = resolvePnpmVersion(pnpmLock, pnpmImporterKey, field, name);
|
|
3008
|
+
} else if (npmLock) {
|
|
3009
|
+
resolved = resolveNpmLockVersion(npmLock, name);
|
|
3010
|
+
}
|
|
3011
|
+
tools.push({
|
|
3012
|
+
name,
|
|
3013
|
+
ecosystem: "npm",
|
|
3014
|
+
version_constraint: constraint,
|
|
3015
|
+
resolved_version: resolved,
|
|
3016
|
+
section,
|
|
3017
|
+
manifest_file: manifestFile,
|
|
3018
|
+
workspace_path: workspace_rel
|
|
3019
|
+
});
|
|
3020
|
+
}
|
|
3021
|
+
}
|
|
3022
|
+
void stripPnpmRange;
|
|
3023
|
+
return { ecosystem: "npm", tools, warnings };
|
|
3024
|
+
};
|
|
1858
3025
|
|
|
1859
|
-
|
|
1860
|
-
|
|
3026
|
+
// ../../packages/tools-local/dist/discovery/parsers/pypi.js
|
|
3027
|
+
init_esm_shims();
|
|
3028
|
+
import { readFile as readFile13 } from "fs/promises";
|
|
3029
|
+
import { join as join16 } from "path";
|
|
3030
|
+
import { parse as parseToml2 } from "smol-toml";
|
|
3031
|
+
function parseRequirementString(raw) {
|
|
3032
|
+
const trimmed = raw.trim();
|
|
3033
|
+
if (!trimmed || trimmed.startsWith("#"))
|
|
3034
|
+
return null;
|
|
3035
|
+
const match = trimmed.match(/^([A-Za-z0-9_.\-]+)(\[[^\]]*\])?(.*)$/);
|
|
3036
|
+
if (!match || !match[1])
|
|
3037
|
+
return null;
|
|
3038
|
+
const constraint = (match[3] ?? "").trim();
|
|
3039
|
+
return { name: match[1], constraint: constraint || void 0 };
|
|
3040
|
+
}
|
|
3041
|
+
function addPoetryDeps(obj, section, out, manifestFile, workspaceRel, resolvedVersions) {
|
|
3042
|
+
if (!obj)
|
|
3043
|
+
return;
|
|
3044
|
+
for (const [name, value] of Object.entries(obj)) {
|
|
3045
|
+
if (name === "python")
|
|
3046
|
+
continue;
|
|
3047
|
+
const constraint = typeof value === "string" ? value : value.version;
|
|
3048
|
+
out.push({
|
|
3049
|
+
name,
|
|
3050
|
+
ecosystem: "pypi",
|
|
3051
|
+
version_constraint: constraint,
|
|
3052
|
+
resolved_version: resolvedVersions.get(name.toLowerCase()),
|
|
3053
|
+
section,
|
|
3054
|
+
manifest_file: manifestFile,
|
|
3055
|
+
workspace_path: workspaceRel
|
|
3056
|
+
});
|
|
3057
|
+
}
|
|
3058
|
+
}
|
|
3059
|
+
var parsePypi = async ({ workspace_dir, workspace_rel }) => {
|
|
3060
|
+
const warnings = [];
|
|
3061
|
+
const tools = [];
|
|
3062
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
3063
|
+
const uvLockPath = join16(workspace_dir, "uv.lock");
|
|
3064
|
+
if (await fileExists(uvLockPath)) {
|
|
3065
|
+
try {
|
|
3066
|
+
const lock = parseToml2(await readFile13(uvLockPath, "utf-8"));
|
|
3067
|
+
for (const pkg of lock.package ?? []) {
|
|
3068
|
+
if (pkg.name && pkg.version)
|
|
3069
|
+
resolved.set(pkg.name.toLowerCase(), pkg.version);
|
|
3070
|
+
}
|
|
3071
|
+
} catch (err) {
|
|
3072
|
+
warnings.push({
|
|
3073
|
+
scope: "parser:pypi",
|
|
3074
|
+
path: uvLockPath,
|
|
3075
|
+
message: `Failed to parse uv.lock: ${err instanceof Error ? err.message : String(err)}`
|
|
3076
|
+
});
|
|
3077
|
+
}
|
|
3078
|
+
}
|
|
3079
|
+
const pyprojectPath = join16(workspace_dir, "pyproject.toml");
|
|
3080
|
+
if (await fileExists(pyprojectPath)) {
|
|
3081
|
+
try {
|
|
3082
|
+
const doc = parseToml2(await readFile13(pyprojectPath, "utf-8"));
|
|
3083
|
+
const manifestFile = workspace_rel ? `${workspace_rel}/pyproject.toml` : "pyproject.toml";
|
|
3084
|
+
for (const dep of doc.project?.dependencies ?? []) {
|
|
3085
|
+
const parsed = parseRequirementString(dep);
|
|
3086
|
+
if (!parsed)
|
|
3087
|
+
continue;
|
|
3088
|
+
tools.push({
|
|
3089
|
+
name: parsed.name,
|
|
3090
|
+
ecosystem: "pypi",
|
|
3091
|
+
version_constraint: parsed.constraint,
|
|
3092
|
+
resolved_version: resolved.get(parsed.name.toLowerCase()),
|
|
3093
|
+
section: "dep",
|
|
3094
|
+
manifest_file: manifestFile,
|
|
3095
|
+
workspace_path: workspace_rel
|
|
3096
|
+
});
|
|
3097
|
+
}
|
|
3098
|
+
for (const [groupName, deps] of Object.entries(doc.project?.["optional-dependencies"] ?? {})) {
|
|
3099
|
+
for (const dep of deps) {
|
|
3100
|
+
const parsed = parseRequirementString(dep);
|
|
3101
|
+
if (!parsed)
|
|
3102
|
+
continue;
|
|
3103
|
+
tools.push({
|
|
3104
|
+
name: parsed.name,
|
|
3105
|
+
ecosystem: "pypi",
|
|
3106
|
+
version_constraint: parsed.constraint,
|
|
3107
|
+
resolved_version: resolved.get(parsed.name.toLowerCase()),
|
|
3108
|
+
section: groupName === "dev" ? "dev" : "optional",
|
|
3109
|
+
manifest_file: manifestFile,
|
|
3110
|
+
workspace_path: workspace_rel
|
|
3111
|
+
});
|
|
3112
|
+
}
|
|
3113
|
+
}
|
|
3114
|
+
for (const [groupName, deps] of Object.entries(doc["dependency-groups"] ?? {})) {
|
|
3115
|
+
for (const dep of deps) {
|
|
3116
|
+
const parsed = parseRequirementString(dep);
|
|
3117
|
+
if (!parsed)
|
|
3118
|
+
continue;
|
|
3119
|
+
tools.push({
|
|
3120
|
+
name: parsed.name,
|
|
3121
|
+
ecosystem: "pypi",
|
|
3122
|
+
version_constraint: parsed.constraint,
|
|
3123
|
+
resolved_version: resolved.get(parsed.name.toLowerCase()),
|
|
3124
|
+
section: groupName === "dev" ? "dev" : "optional",
|
|
3125
|
+
manifest_file: manifestFile,
|
|
3126
|
+
workspace_path: workspace_rel
|
|
3127
|
+
});
|
|
3128
|
+
}
|
|
3129
|
+
}
|
|
3130
|
+
addPoetryDeps(doc.tool?.poetry?.dependencies, "dep", tools, manifestFile, workspace_rel, resolved);
|
|
3131
|
+
addPoetryDeps(doc.tool?.poetry?.["dev-dependencies"], "dev", tools, manifestFile, workspace_rel, resolved);
|
|
3132
|
+
for (const group of Object.values(doc.tool?.poetry?.group ?? {})) {
|
|
3133
|
+
addPoetryDeps(group.dependencies, "dev", tools, manifestFile, workspace_rel, resolved);
|
|
3134
|
+
}
|
|
3135
|
+
if (tools.length > 0)
|
|
3136
|
+
return { ecosystem: "pypi", tools, warnings };
|
|
3137
|
+
} catch (err) {
|
|
3138
|
+
warnings.push({
|
|
3139
|
+
scope: "parser:pypi",
|
|
3140
|
+
path: pyprojectPath,
|
|
3141
|
+
message: `Failed to parse pyproject.toml: ${err instanceof Error ? err.message : String(err)}`
|
|
3142
|
+
});
|
|
3143
|
+
}
|
|
3144
|
+
}
|
|
3145
|
+
for (const [file, section] of [
|
|
3146
|
+
["requirements.txt", "dep"],
|
|
3147
|
+
["requirements-dev.txt", "dev"],
|
|
3148
|
+
["dev-requirements.txt", "dev"]
|
|
3149
|
+
]) {
|
|
3150
|
+
const path2 = join16(workspace_dir, file);
|
|
3151
|
+
if (!await fileExists(path2))
|
|
3152
|
+
continue;
|
|
3153
|
+
try {
|
|
3154
|
+
const raw = await readFile13(path2, "utf-8");
|
|
3155
|
+
const manifestFile = workspace_rel ? `${workspace_rel}/${file}` : file;
|
|
3156
|
+
for (const line of raw.split("\n")) {
|
|
3157
|
+
const parsed = parseRequirementString(line);
|
|
3158
|
+
if (!parsed)
|
|
3159
|
+
continue;
|
|
3160
|
+
tools.push({
|
|
3161
|
+
name: parsed.name,
|
|
3162
|
+
ecosystem: "pypi",
|
|
3163
|
+
version_constraint: parsed.constraint,
|
|
3164
|
+
resolved_version: resolved.get(parsed.name.toLowerCase()),
|
|
3165
|
+
section,
|
|
3166
|
+
manifest_file: manifestFile,
|
|
3167
|
+
workspace_path: workspace_rel
|
|
3168
|
+
});
|
|
3169
|
+
}
|
|
3170
|
+
} catch (err) {
|
|
3171
|
+
warnings.push({
|
|
3172
|
+
scope: "parser:pypi",
|
|
3173
|
+
path: path2,
|
|
3174
|
+
message: `Failed to read ${file}: ${err instanceof Error ? err.message : String(err)}`
|
|
3175
|
+
});
|
|
3176
|
+
}
|
|
3177
|
+
}
|
|
3178
|
+
return { ecosystem: "pypi", tools, warnings };
|
|
3179
|
+
};
|
|
1861
3180
|
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
3181
|
+
// ../../packages/tools-local/dist/discovery/parsers/ruby.js
|
|
3182
|
+
init_esm_shims();
|
|
3183
|
+
import { readFile as readFile14 } from "fs/promises";
|
|
3184
|
+
import { join as join17 } from "path";
|
|
3185
|
+
function parseGemfileLock(raw) {
|
|
3186
|
+
const out = [];
|
|
3187
|
+
const lines = raw.split("\n");
|
|
3188
|
+
let inSpecs = false;
|
|
3189
|
+
for (const line of lines) {
|
|
3190
|
+
if (line.trim() === "specs:") {
|
|
3191
|
+
inSpecs = true;
|
|
3192
|
+
continue;
|
|
3193
|
+
}
|
|
3194
|
+
if (inSpecs) {
|
|
3195
|
+
if (!line.startsWith(" ")) {
|
|
3196
|
+
inSpecs = false;
|
|
3197
|
+
continue;
|
|
3198
|
+
}
|
|
3199
|
+
const match = line.match(/^ {4}([A-Za-z0-9_\-.]+) \(([^)]+)\)/);
|
|
3200
|
+
if (match?.[1] && match[2])
|
|
3201
|
+
out.push({ name: match[1], version: match[2] });
|
|
3202
|
+
}
|
|
3203
|
+
}
|
|
3204
|
+
return out;
|
|
3205
|
+
}
|
|
3206
|
+
function parseGemfile(raw) {
|
|
3207
|
+
const out = [];
|
|
3208
|
+
const lines = raw.split("\n");
|
|
3209
|
+
let groupDepth = 0;
|
|
3210
|
+
let inDevGroup = false;
|
|
3211
|
+
for (const rawLine of lines) {
|
|
3212
|
+
const line = rawLine.split("#")[0]?.trim() ?? "";
|
|
3213
|
+
if (!line)
|
|
3214
|
+
continue;
|
|
3215
|
+
const groupMatch = line.match(/^group\s+(:[\w,\s:]+?)\s*do\s*$/);
|
|
3216
|
+
if (groupMatch && groupMatch[1]) {
|
|
3217
|
+
groupDepth++;
|
|
3218
|
+
inDevGroup = /\b(development|test)\b/.test(groupMatch[1]);
|
|
3219
|
+
continue;
|
|
3220
|
+
}
|
|
3221
|
+
if (line === "end" && groupDepth > 0) {
|
|
3222
|
+
groupDepth--;
|
|
3223
|
+
if (groupDepth === 0)
|
|
3224
|
+
inDevGroup = false;
|
|
3225
|
+
continue;
|
|
3226
|
+
}
|
|
3227
|
+
const gemMatch = line.match(/^gem\s+(['"])([A-Za-z0-9_\-.]+)\1(?:\s*,\s*(['"])([^'"]+)\3)?/);
|
|
3228
|
+
if (gemMatch?.[2]) {
|
|
3229
|
+
out.push({ name: gemMatch[2], constraint: gemMatch[4], dev: inDevGroup });
|
|
3230
|
+
}
|
|
3231
|
+
}
|
|
3232
|
+
return out;
|
|
3233
|
+
}
|
|
3234
|
+
var parseRuby = async ({ workspace_dir, workspace_rel }) => {
|
|
3235
|
+
const warnings = [];
|
|
3236
|
+
const tools = [];
|
|
3237
|
+
const lockPath = join17(workspace_dir, "Gemfile.lock");
|
|
3238
|
+
const gemfilePath = join17(workspace_dir, "Gemfile");
|
|
3239
|
+
if (await fileExists(lockPath)) {
|
|
3240
|
+
try {
|
|
3241
|
+
const raw = await readFile14(lockPath, "utf-8");
|
|
3242
|
+
const manifestFile = workspace_rel ? `${workspace_rel}/Gemfile.lock` : "Gemfile.lock";
|
|
3243
|
+
const declared = /* @__PURE__ */ new Set();
|
|
3244
|
+
if (await fileExists(gemfilePath)) {
|
|
3245
|
+
try {
|
|
3246
|
+
const gemRaw = await readFile14(gemfilePath, "utf-8");
|
|
3247
|
+
for (const gem of parseGemfile(gemRaw))
|
|
3248
|
+
declared.add(gem.name);
|
|
3249
|
+
} catch {
|
|
1870
3250
|
}
|
|
1871
|
-
}
|
|
3251
|
+
}
|
|
3252
|
+
for (const spec of parseGemfileLock(raw)) {
|
|
3253
|
+
if (declared.size > 0 && !declared.has(spec.name))
|
|
3254
|
+
continue;
|
|
3255
|
+
tools.push({
|
|
3256
|
+
name: spec.name,
|
|
3257
|
+
ecosystem: "rubygems",
|
|
3258
|
+
version_constraint: void 0,
|
|
3259
|
+
resolved_version: spec.version,
|
|
3260
|
+
section: "dep",
|
|
3261
|
+
manifest_file: manifestFile,
|
|
3262
|
+
workspace_path: workspace_rel
|
|
3263
|
+
});
|
|
3264
|
+
}
|
|
3265
|
+
if (tools.length > 0)
|
|
3266
|
+
return { ecosystem: "rubygems", tools, warnings };
|
|
3267
|
+
} catch (err) {
|
|
3268
|
+
warnings.push({
|
|
3269
|
+
scope: "parser:ruby",
|
|
3270
|
+
path: lockPath,
|
|
3271
|
+
message: `Failed to parse Gemfile.lock: ${err instanceof Error ? err.message : String(err)}`
|
|
3272
|
+
});
|
|
3273
|
+
}
|
|
3274
|
+
}
|
|
3275
|
+
if (await fileExists(gemfilePath)) {
|
|
3276
|
+
try {
|
|
3277
|
+
const raw = await readFile14(gemfilePath, "utf-8");
|
|
3278
|
+
const manifestFile = workspace_rel ? `${workspace_rel}/Gemfile` : "Gemfile";
|
|
3279
|
+
for (const gem of parseGemfile(raw)) {
|
|
3280
|
+
tools.push({
|
|
3281
|
+
name: gem.name,
|
|
3282
|
+
ecosystem: "rubygems",
|
|
3283
|
+
version_constraint: gem.constraint,
|
|
3284
|
+
section: gem.dev ? "dev" : "dep",
|
|
3285
|
+
manifest_file: manifestFile,
|
|
3286
|
+
workspace_path: workspace_rel
|
|
3287
|
+
});
|
|
3288
|
+
}
|
|
3289
|
+
warnings.push({
|
|
3290
|
+
scope: "parser:ruby",
|
|
3291
|
+
path: manifestFile,
|
|
3292
|
+
message: "No Gemfile.lock \u2014 resolved_version unavailable."
|
|
3293
|
+
});
|
|
3294
|
+
} catch (err) {
|
|
3295
|
+
warnings.push({
|
|
3296
|
+
scope: "parser:ruby",
|
|
3297
|
+
path: gemfilePath,
|
|
3298
|
+
message: `Failed to parse Gemfile: ${err instanceof Error ? err.message : String(err)}`
|
|
3299
|
+
});
|
|
1872
3300
|
}
|
|
3301
|
+
}
|
|
3302
|
+
return { ecosystem: "rubygems", tools, warnings };
|
|
3303
|
+
};
|
|
1873
3304
|
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
3305
|
+
// ../../packages/tools-local/dist/discovery/parsers/swift.js
|
|
3306
|
+
init_esm_shims();
|
|
3307
|
+
import { readFile as readFile15 } from "fs/promises";
|
|
3308
|
+
import { join as join18 } from "path";
|
|
3309
|
+
function parsePackageResolved(raw) {
|
|
3310
|
+
let doc;
|
|
3311
|
+
try {
|
|
3312
|
+
doc = JSON.parse(raw);
|
|
3313
|
+
} catch {
|
|
3314
|
+
return [];
|
|
3315
|
+
}
|
|
3316
|
+
const out = [];
|
|
3317
|
+
for (const pin of doc.pins ?? []) {
|
|
3318
|
+
if (pin.identity)
|
|
3319
|
+
out.push({ name: pin.identity, version: pin.state?.version });
|
|
3320
|
+
}
|
|
3321
|
+
for (const pin of doc.object?.pins ?? []) {
|
|
3322
|
+
if (pin.package)
|
|
3323
|
+
out.push({ name: pin.package, version: pin.state?.version });
|
|
3324
|
+
}
|
|
3325
|
+
return out;
|
|
3326
|
+
}
|
|
3327
|
+
function parsePackageSwift(raw) {
|
|
3328
|
+
const out = [];
|
|
3329
|
+
const pattern = /\.package\(\s*(?:name:\s*"[^"]+"\s*,\s*)?url:\s*"([^"]+)"\s*,\s*([^)]+)\)/g;
|
|
3330
|
+
let match;
|
|
3331
|
+
while ((match = pattern.exec(raw)) !== null) {
|
|
3332
|
+
const url = match[1];
|
|
3333
|
+
const spec = match[2]?.trim();
|
|
3334
|
+
if (!url)
|
|
3335
|
+
continue;
|
|
3336
|
+
const identity = url.split("/").pop()?.replace(/\.git$/, "");
|
|
3337
|
+
if (!identity)
|
|
3338
|
+
continue;
|
|
3339
|
+
out.push({ name: identity, constraint: spec });
|
|
3340
|
+
}
|
|
3341
|
+
return out;
|
|
3342
|
+
}
|
|
3343
|
+
var parseSwift = async ({ workspace_dir, workspace_rel }) => {
|
|
3344
|
+
const warnings = [];
|
|
3345
|
+
const tools = [];
|
|
3346
|
+
const resolvedPath = join18(workspace_dir, "Package.resolved");
|
|
3347
|
+
if (await fileExists(resolvedPath)) {
|
|
3348
|
+
try {
|
|
3349
|
+
const raw = await readFile15(resolvedPath, "utf-8");
|
|
3350
|
+
const manifestFile = workspace_rel ? `${workspace_rel}/Package.resolved` : "Package.resolved";
|
|
3351
|
+
for (const pkg of parsePackageResolved(raw)) {
|
|
3352
|
+
tools.push({
|
|
3353
|
+
name: pkg.name,
|
|
3354
|
+
ecosystem: "swift-pm",
|
|
3355
|
+
resolved_version: pkg.version,
|
|
3356
|
+
section: "dep",
|
|
3357
|
+
manifest_file: manifestFile,
|
|
3358
|
+
workspace_path: workspace_rel
|
|
3359
|
+
});
|
|
3360
|
+
}
|
|
3361
|
+
if (tools.length > 0)
|
|
3362
|
+
return { ecosystem: "swift-pm", tools, warnings };
|
|
3363
|
+
} catch (err) {
|
|
3364
|
+
warnings.push({
|
|
3365
|
+
scope: "parser:swift",
|
|
3366
|
+
path: resolvedPath,
|
|
3367
|
+
message: `Failed to parse Package.resolved: ${err instanceof Error ? err.message : String(err)}`
|
|
3368
|
+
});
|
|
3369
|
+
}
|
|
3370
|
+
}
|
|
3371
|
+
const swiftPath = join18(workspace_dir, "Package.swift");
|
|
3372
|
+
if (await fileExists(swiftPath)) {
|
|
3373
|
+
try {
|
|
3374
|
+
const raw = await readFile15(swiftPath, "utf-8");
|
|
3375
|
+
const manifestFile = workspace_rel ? `${workspace_rel}/Package.swift` : "Package.swift";
|
|
3376
|
+
for (const pkg of parsePackageSwift(raw)) {
|
|
3377
|
+
tools.push({
|
|
3378
|
+
name: pkg.name,
|
|
3379
|
+
ecosystem: "swift-pm",
|
|
3380
|
+
version_constraint: pkg.constraint,
|
|
3381
|
+
section: "dep",
|
|
3382
|
+
manifest_file: manifestFile,
|
|
3383
|
+
workspace_path: workspace_rel
|
|
3384
|
+
});
|
|
3385
|
+
}
|
|
3386
|
+
warnings.push({
|
|
3387
|
+
scope: "parser:swift",
|
|
3388
|
+
path: manifestFile,
|
|
3389
|
+
message: "No Package.resolved \u2014 resolved_version unavailable."
|
|
3390
|
+
});
|
|
3391
|
+
} catch (err) {
|
|
3392
|
+
warnings.push({
|
|
3393
|
+
scope: "parser:swift",
|
|
3394
|
+
path: swiftPath,
|
|
3395
|
+
message: `Failed to parse Package.swift: ${err instanceof Error ? err.message : String(err)}`
|
|
3396
|
+
});
|
|
1877
3397
|
}
|
|
3398
|
+
}
|
|
3399
|
+
return { ecosystem: "swift-pm", tools, warnings };
|
|
3400
|
+
};
|
|
1878
3401
|
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
3402
|
+
// ../../packages/tools-local/dist/discovery/parsers/index.js
|
|
3403
|
+
var PARSERS = {
|
|
3404
|
+
npm: parseNpm,
|
|
3405
|
+
pypi: parsePypi,
|
|
3406
|
+
cargo: parseCargo,
|
|
3407
|
+
go: parseGo,
|
|
3408
|
+
rubygems: parseRuby,
|
|
3409
|
+
maven: parseMaven,
|
|
3410
|
+
gradle: parseGradle,
|
|
3411
|
+
composer: parseComposer,
|
|
3412
|
+
hex: parseMix,
|
|
3413
|
+
pub: parseDart,
|
|
3414
|
+
nuget: parseDotnet,
|
|
3415
|
+
"swift-pm": parseSwift
|
|
3416
|
+
};
|
|
3417
|
+
|
|
3418
|
+
// ../../packages/tools-local/dist/discovery/workspaces/glob.js
|
|
3419
|
+
init_esm_shims();
|
|
3420
|
+
import { readdir as readdir5 } from "fs/promises";
|
|
3421
|
+
import { join as join19, relative as relative3, sep as sep2 } from "path";
|
|
3422
|
+
async function expandWorkspaceGlobs(rootDir, patterns) {
|
|
3423
|
+
const excluded = /* @__PURE__ */ new Set();
|
|
3424
|
+
const included = /* @__PURE__ */ new Set();
|
|
3425
|
+
for (const raw of patterns) {
|
|
3426
|
+
const pattern = raw.trim();
|
|
3427
|
+
if (!pattern)
|
|
3428
|
+
continue;
|
|
3429
|
+
const negated = pattern.startsWith("!");
|
|
3430
|
+
const clean = negated ? pattern.slice(1) : pattern;
|
|
3431
|
+
const normalised = clean.replace(/\\/g, "/");
|
|
3432
|
+
const matches = await matchPattern(rootDir, normalised);
|
|
3433
|
+
const target = negated ? excluded : included;
|
|
3434
|
+
for (const m of matches)
|
|
3435
|
+
target.add(m);
|
|
1884
3436
|
}
|
|
3437
|
+
return [...included].filter((p) => !excluded.has(p)).sort();
|
|
1885
3438
|
}
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
if (isLive) startPolling(); else stopPolling();
|
|
3439
|
+
async function matchPattern(rootDir, pattern) {
|
|
3440
|
+
const parts = pattern.split("/").filter(Boolean);
|
|
3441
|
+
const results = [];
|
|
3442
|
+
await walkPattern(rootDir, rootDir, parts, 0, results);
|
|
3443
|
+
return results;
|
|
1892
3444
|
}
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
3445
|
+
async function walkPattern(rootDir, currentDir, parts, index, out) {
|
|
3446
|
+
if (index >= parts.length) {
|
|
3447
|
+
if (await isDir(currentDir))
|
|
3448
|
+
out.push(currentDir);
|
|
3449
|
+
return;
|
|
3450
|
+
}
|
|
3451
|
+
const segment = parts[index];
|
|
3452
|
+
if (!segment)
|
|
3453
|
+
return;
|
|
3454
|
+
if (segment === "**") {
|
|
3455
|
+
await walkPattern(rootDir, currentDir, parts, index + 1, out);
|
|
3456
|
+
try {
|
|
3457
|
+
const entries = await readdir5(currentDir, { withFileTypes: true });
|
|
3458
|
+
for (const entry of entries) {
|
|
3459
|
+
if (!entry.isDirectory() || IGNORED_DIRS.has(entry.name))
|
|
3460
|
+
continue;
|
|
3461
|
+
await walkPattern(rootDir, join19(currentDir, entry.name), parts, index, out);
|
|
3462
|
+
}
|
|
3463
|
+
} catch {
|
|
3464
|
+
}
|
|
3465
|
+
return;
|
|
3466
|
+
}
|
|
3467
|
+
if (segment.includes("*")) {
|
|
3468
|
+
const re = globSegmentToRegex(segment);
|
|
3469
|
+
try {
|
|
3470
|
+
const entries = await readdir5(currentDir, { withFileTypes: true });
|
|
3471
|
+
for (const entry of entries) {
|
|
3472
|
+
if (!entry.isDirectory() || IGNORED_DIRS.has(entry.name))
|
|
3473
|
+
continue;
|
|
3474
|
+
if (re.test(entry.name)) {
|
|
3475
|
+
await walkPattern(rootDir, join19(currentDir, entry.name), parts, index + 1, out);
|
|
3476
|
+
}
|
|
3477
|
+
}
|
|
3478
|
+
} catch {
|
|
3479
|
+
}
|
|
3480
|
+
return;
|
|
3481
|
+
}
|
|
3482
|
+
await walkPattern(rootDir, join19(currentDir, segment), parts, index + 1, out);
|
|
1898
3483
|
}
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
document.getElementById('intervalLabel').textContent = v + 's';
|
|
1903
|
-
if (isLive) { stopPolling(); startPolling(); }
|
|
3484
|
+
function globSegmentToRegex(segment) {
|
|
3485
|
+
const escaped = segment.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
3486
|
+
return new RegExp(`^${escaped}$`);
|
|
1904
3487
|
}
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
fetchEvents();
|
|
1909
|
-
pollHandle = setInterval(fetchEvents, pollIntervalMs);
|
|
3488
|
+
function toRelPosix(projectRoot, absPath) {
|
|
3489
|
+
const rel = relative3(projectRoot, absPath);
|
|
3490
|
+
return rel.split(sep2).join("/");
|
|
1910
3491
|
}
|
|
1911
3492
|
|
|
1912
|
-
|
|
1913
|
-
|
|
3493
|
+
// ../../packages/tools-local/dist/discovery/workspaces/walker.js
|
|
3494
|
+
init_esm_shims();
|
|
3495
|
+
import { readFile as readFile16 } from "fs/promises";
|
|
3496
|
+
import { join as join20 } from "path";
|
|
3497
|
+
import { parse as parseToml3 } from "smol-toml";
|
|
3498
|
+
import { parse as parseYaml3 } from "yaml";
|
|
3499
|
+
async function discoverWorkspaces(projectRoot, maxDepth = 5) {
|
|
3500
|
+
const warnings = [];
|
|
3501
|
+
const discovered = /* @__PURE__ */ new Set([projectRoot]);
|
|
3502
|
+
const visited = /* @__PURE__ */ new Set();
|
|
3503
|
+
const queue = [{ dir: projectRoot, depth: 0 }];
|
|
3504
|
+
while (queue.length > 0) {
|
|
3505
|
+
const { dir, depth } = queue.shift();
|
|
3506
|
+
if (visited.has(dir) || depth > maxDepth)
|
|
3507
|
+
continue;
|
|
3508
|
+
visited.add(dir);
|
|
3509
|
+
const globs = await readWorkspaceGlobs(dir, warnings);
|
|
3510
|
+
if (globs.length === 0)
|
|
3511
|
+
continue;
|
|
3512
|
+
const expanded = await expandWorkspaceGlobs(dir, globs);
|
|
3513
|
+
for (const sub of expanded) {
|
|
3514
|
+
if (!discovered.has(sub)) {
|
|
3515
|
+
discovered.add(sub);
|
|
3516
|
+
queue.push({ dir: sub, depth: depth + 1 });
|
|
3517
|
+
}
|
|
3518
|
+
}
|
|
3519
|
+
}
|
|
3520
|
+
return { paths: [...discovered].sort(), warnings };
|
|
1914
3521
|
}
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
3522
|
+
async function readWorkspaceGlobs(dir, warnings) {
|
|
3523
|
+
const globs = [];
|
|
3524
|
+
const pnpmPath = join20(dir, "pnpm-workspace.yaml");
|
|
3525
|
+
if (await fileExists(pnpmPath)) {
|
|
3526
|
+
try {
|
|
3527
|
+
const doc = parseYaml3(await readFile16(pnpmPath, "utf-8"));
|
|
3528
|
+
if (Array.isArray(doc.packages))
|
|
3529
|
+
globs.push(...doc.packages);
|
|
3530
|
+
} catch (err) {
|
|
3531
|
+
warnings.push({
|
|
3532
|
+
scope: "workspace:pnpm",
|
|
3533
|
+
path: pnpmPath,
|
|
3534
|
+
message: `Failed to parse pnpm-workspace.yaml: ${err instanceof Error ? err.message : String(err)}`
|
|
3535
|
+
});
|
|
3536
|
+
}
|
|
3537
|
+
}
|
|
3538
|
+
const pkgPath = join20(dir, "package.json");
|
|
3539
|
+
if (await fileExists(pkgPath)) {
|
|
3540
|
+
try {
|
|
3541
|
+
const doc = JSON.parse(await readFile16(pkgPath, "utf-8"));
|
|
3542
|
+
if (Array.isArray(doc.workspaces)) {
|
|
3543
|
+
globs.push(...doc.workspaces);
|
|
3544
|
+
} else if (doc.workspaces && Array.isArray(doc.workspaces.packages)) {
|
|
3545
|
+
globs.push(...doc.workspaces.packages);
|
|
3546
|
+
}
|
|
3547
|
+
} catch (err) {
|
|
3548
|
+
warnings.push({
|
|
3549
|
+
scope: "workspace:package-json",
|
|
3550
|
+
path: pkgPath,
|
|
3551
|
+
message: `Failed to parse package.json#workspaces: ${err instanceof Error ? err.message : String(err)}`
|
|
3552
|
+
});
|
|
3553
|
+
}
|
|
3554
|
+
}
|
|
3555
|
+
const cargoPath = join20(dir, "Cargo.toml");
|
|
3556
|
+
if (await fileExists(cargoPath)) {
|
|
3557
|
+
try {
|
|
3558
|
+
const doc = parseToml3(await readFile16(cargoPath, "utf-8"));
|
|
3559
|
+
if (Array.isArray(doc.workspace?.members))
|
|
3560
|
+
globs.push(...doc.workspace.members);
|
|
3561
|
+
} catch (err) {
|
|
3562
|
+
warnings.push({
|
|
3563
|
+
scope: "workspace:cargo",
|
|
3564
|
+
path: cargoPath,
|
|
3565
|
+
message: `Failed to parse Cargo workspace: ${err instanceof Error ? err.message : String(err)}`
|
|
3566
|
+
});
|
|
3567
|
+
}
|
|
3568
|
+
}
|
|
3569
|
+
const goWorkPath = join20(dir, "go.work");
|
|
3570
|
+
if (await fileExists(goWorkPath)) {
|
|
3571
|
+
try {
|
|
3572
|
+
const raw = await readFile16(goWorkPath, "utf-8");
|
|
3573
|
+
const useMatch = raw.match(/use\s*\(([^)]*)\)/s);
|
|
3574
|
+
if (useMatch?.[1]) {
|
|
3575
|
+
for (const line of useMatch[1].split("\n")) {
|
|
3576
|
+
const trimmed = line.trim().replace(/^['"]|['"]$/g, "");
|
|
3577
|
+
if (trimmed && !trimmed.startsWith("//"))
|
|
3578
|
+
globs.push(trimmed);
|
|
3579
|
+
}
|
|
3580
|
+
} else {
|
|
3581
|
+
for (const line of raw.split("\n")) {
|
|
3582
|
+
const m = line.match(/^\s*use\s+(.+)$/);
|
|
3583
|
+
if (m?.[1])
|
|
3584
|
+
globs.push(m[1].trim().replace(/^['"]|['"]$/g, ""));
|
|
3585
|
+
}
|
|
3586
|
+
}
|
|
3587
|
+
} catch (err) {
|
|
3588
|
+
warnings.push({
|
|
3589
|
+
scope: "workspace:go",
|
|
3590
|
+
path: goWorkPath,
|
|
3591
|
+
message: `Failed to parse go.work: ${err instanceof Error ? err.message : String(err)}`
|
|
3592
|
+
});
|
|
3593
|
+
}
|
|
3594
|
+
}
|
|
3595
|
+
const lernaPath = join20(dir, "lerna.json");
|
|
3596
|
+
if (await fileExists(lernaPath)) {
|
|
3597
|
+
try {
|
|
3598
|
+
const doc = JSON.parse(await readFile16(lernaPath, "utf-8"));
|
|
3599
|
+
if (Array.isArray(doc.packages))
|
|
3600
|
+
globs.push(...doc.packages);
|
|
3601
|
+
} catch (err) {
|
|
3602
|
+
warnings.push({
|
|
3603
|
+
scope: "workspace:lerna",
|
|
3604
|
+
path: lernaPath,
|
|
3605
|
+
message: `Failed to parse lerna.json: ${err instanceof Error ? err.message : String(err)}`
|
|
3606
|
+
});
|
|
3607
|
+
}
|
|
3608
|
+
}
|
|
3609
|
+
const nxPath = join20(dir, "nx.json");
|
|
3610
|
+
if (await fileExists(nxPath)) {
|
|
3611
|
+
try {
|
|
3612
|
+
const doc = JSON.parse(await readFile16(nxPath, "utf-8"));
|
|
3613
|
+
const base = doc.workspaceLayout?.projectsDir ?? "packages";
|
|
3614
|
+
globs.push(`${base}/*`);
|
|
3615
|
+
} catch (err) {
|
|
3616
|
+
warnings.push({
|
|
3617
|
+
scope: "workspace:nx",
|
|
3618
|
+
path: nxPath,
|
|
3619
|
+
message: `Failed to parse nx.json: ${err instanceof Error ? err.message : String(err)}`
|
|
3620
|
+
});
|
|
3621
|
+
}
|
|
3622
|
+
}
|
|
3623
|
+
return globs;
|
|
1919
3624
|
}
|
|
1920
3625
|
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
3626
|
+
// ../../packages/tools-local/dist/discovery/scan-project.js
|
|
3627
|
+
var logger7 = (0, import_errors9.createMcpLogger)({ name: "@toolcairn/tools:scan-project" });
|
|
3628
|
+
async function scanProject(projectRoot, options = {}) {
|
|
3629
|
+
const start = Date.now();
|
|
3630
|
+
const { batchResolve, maxDepth = 5 } = options;
|
|
3631
|
+
const absRoot = resolve(projectRoot);
|
|
3632
|
+
const warnings = [];
|
|
3633
|
+
logger7.info({ projectRoot: absRoot }, "Starting project scan");
|
|
3634
|
+
const { paths: workspaceAbs, warnings: wsWarnings } = await discoverWorkspaces(absRoot, maxDepth);
|
|
3635
|
+
warnings.push(...wsWarnings);
|
|
3636
|
+
const allDetected = [];
|
|
3637
|
+
const ecosystemsScanned = /* @__PURE__ */ new Set();
|
|
3638
|
+
const parsersFailed = [];
|
|
3639
|
+
const subprojects = [];
|
|
3640
|
+
const parseTasks = [];
|
|
3641
|
+
for (const wsDir of workspaceAbs) {
|
|
3642
|
+
const wsRel = toRelPosix(absRoot, wsDir);
|
|
3643
|
+
const ecosystems = await detectEcosystems(wsDir);
|
|
3644
|
+
for (const eco of ecosystems) {
|
|
3645
|
+
ecosystemsScanned.add(eco);
|
|
3646
|
+
const parser = PARSERS[eco];
|
|
3647
|
+
parseTasks.push(parser({ workspace_dir: wsDir, workspace_rel: wsRel, project_root: absRoot }).then((result) => {
|
|
3648
|
+
allDetected.push(...result.tools);
|
|
3649
|
+
warnings.push(...result.warnings);
|
|
3650
|
+
if (result.tools.length > 0 && wsRel !== "") {
|
|
3651
|
+
const existing = subprojects.find((s) => s.path === wsRel && s.ecosystem === eco);
|
|
3652
|
+
if (!existing) {
|
|
3653
|
+
subprojects.push({
|
|
3654
|
+
path: wsRel,
|
|
3655
|
+
manifest: primaryManifestForEcosystem(eco),
|
|
3656
|
+
ecosystem: eco
|
|
3657
|
+
});
|
|
3658
|
+
}
|
|
3659
|
+
}
|
|
3660
|
+
}).catch((err) => {
|
|
3661
|
+
parsersFailed.push(`${eco}@${wsRel || "."}`);
|
|
3662
|
+
warnings.push({
|
|
3663
|
+
scope: `parser:${eco}`,
|
|
3664
|
+
path: wsRel || ".",
|
|
3665
|
+
message: `Parser crashed: ${err instanceof Error ? err.message : String(err)}`
|
|
3666
|
+
});
|
|
3667
|
+
}));
|
|
3668
|
+
}
|
|
1930
3669
|
}
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
3670
|
+
await Promise.all(parseTasks);
|
|
3671
|
+
const mergedMap = /* @__PURE__ */ new Map();
|
|
3672
|
+
for (const dep of allDetected) {
|
|
3673
|
+
const key = `${dep.ecosystem}:${dep.name}`;
|
|
3674
|
+
const location = {
|
|
3675
|
+
workspace_path: dep.workspace_path,
|
|
3676
|
+
manifest_file: dep.manifest_file,
|
|
3677
|
+
section: dep.section,
|
|
3678
|
+
ecosystem: dep.ecosystem,
|
|
3679
|
+
version_constraint: dep.version_constraint,
|
|
3680
|
+
resolved_version: dep.resolved_version
|
|
3681
|
+
};
|
|
3682
|
+
const existing = mergedMap.get(key);
|
|
3683
|
+
if (existing) {
|
|
3684
|
+
const sameLoc = existing.locations.some((l) => l.workspace_path === location.workspace_path && l.manifest_file === location.manifest_file && l.section === location.section);
|
|
3685
|
+
if (!sameLoc)
|
|
3686
|
+
existing.locations.push(location);
|
|
3687
|
+
} else {
|
|
3688
|
+
mergedMap.set(key, { name: dep.name, ecosystem: dep.ecosystem, locations: [location] });
|
|
3689
|
+
}
|
|
1936
3690
|
}
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
3691
|
+
const workspaceRels = workspaceAbs.map((abs) => toRelPosix(absRoot, abs));
|
|
3692
|
+
const languages = await detectLanguages(absRoot, workspaceRels);
|
|
3693
|
+
const resolveInputs = [...mergedMap.values()].map(({ name: name2, ecosystem }) => ({ name: name2, ecosystem }));
|
|
3694
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
3695
|
+
const methods = /* @__PURE__ */ new Map();
|
|
3696
|
+
const githubUrls = /* @__PURE__ */ new Map();
|
|
3697
|
+
if (batchResolve && resolveInputs.length > 0) {
|
|
3698
|
+
try {
|
|
3699
|
+
const r = await batchResolve(resolveInputs);
|
|
3700
|
+
for (const res of r.results) {
|
|
3701
|
+
const key = `${res.input.ecosystem}:${res.input.name}`;
|
|
3702
|
+
resolved.set(key, res);
|
|
3703
|
+
}
|
|
3704
|
+
for (const [k, v] of r.methods)
|
|
3705
|
+
methods.set(k, v);
|
|
3706
|
+
for (const [k, v] of r.githubUrls)
|
|
3707
|
+
githubUrls.set(k, v);
|
|
3708
|
+
warnings.push(...r.warnings);
|
|
3709
|
+
} catch (err) {
|
|
3710
|
+
warnings.push({
|
|
3711
|
+
scope: "batch-resolve",
|
|
3712
|
+
message: `Failed to resolve tools against graph: ${err instanceof Error ? err.message : String(err)}. Falling back to local classification.`
|
|
3713
|
+
});
|
|
3714
|
+
}
|
|
3715
|
+
} else if (!batchResolve) {
|
|
3716
|
+
warnings.push({
|
|
3717
|
+
scope: "batch-resolve",
|
|
3718
|
+
message: "No batchResolve client provided \u2014 running in offline-only mode; all tools classified as non_oss."
|
|
3719
|
+
});
|
|
3720
|
+
}
|
|
3721
|
+
const frameworks = detectFrameworks(allDetected, resolved);
|
|
3722
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3723
|
+
const confirmed = [];
|
|
3724
|
+
let toolsResolvedCount = 0;
|
|
3725
|
+
for (const { name: name2, ecosystem, locations } of mergedMap.values()) {
|
|
3726
|
+
const key = `${ecosystem}:${name2}`;
|
|
3727
|
+
const graph = resolved.get(key);
|
|
3728
|
+
const matchMethod = methods.get(key) ?? "none";
|
|
3729
|
+
const matched = graph?.matched === true;
|
|
3730
|
+
if (matched)
|
|
3731
|
+
toolsResolvedCount++;
|
|
3732
|
+
const source = matched ? "toolcairn" : "non_oss";
|
|
3733
|
+
const canonical = graph?.tool?.canonical_name;
|
|
3734
|
+
const categories = graph?.tool?.categories;
|
|
3735
|
+
const github_url = githubUrls.get(key);
|
|
3736
|
+
const version = locations.find((l) => l.resolved_version)?.resolved_version ?? locations[0]?.version_constraint;
|
|
3737
|
+
confirmed.push({
|
|
3738
|
+
name: name2,
|
|
3739
|
+
source,
|
|
3740
|
+
github_url,
|
|
3741
|
+
version,
|
|
3742
|
+
chosen_at: now,
|
|
3743
|
+
chosen_reason: "Auto-detected from manifest during toolcairn_init scan",
|
|
3744
|
+
alternatives_considered: [],
|
|
3745
|
+
canonical_name: canonical,
|
|
3746
|
+
categories,
|
|
3747
|
+
match_method: matchMethod,
|
|
3748
|
+
locations
|
|
3749
|
+
});
|
|
3750
|
+
}
|
|
3751
|
+
confirmed.sort((a, b) => {
|
|
3752
|
+
const rank = (t) => t.source === "toolcairn" ? 0 : 1;
|
|
3753
|
+
if (rank(a) !== rank(b))
|
|
3754
|
+
return rank(a) - rank(b);
|
|
3755
|
+
return a.name.localeCompare(b.name);
|
|
3756
|
+
});
|
|
3757
|
+
subprojects.sort((a, b) => a.path.localeCompare(b.path));
|
|
3758
|
+
const name = await inferProjectName(absRoot);
|
|
3759
|
+
const scan_metadata = {
|
|
3760
|
+
ecosystems_scanned: [...ecosystemsScanned].sort(),
|
|
3761
|
+
parsers_failed: parsersFailed.sort(),
|
|
3762
|
+
tools_resolved: toolsResolvedCount,
|
|
3763
|
+
tools_unresolved: confirmed.length - toolsResolvedCount,
|
|
3764
|
+
duration_ms: Date.now() - start,
|
|
3765
|
+
completed_at: now
|
|
3766
|
+
};
|
|
3767
|
+
logger7.info({
|
|
3768
|
+
projectRoot: absRoot,
|
|
3769
|
+
workspaces: workspaceAbs.length,
|
|
3770
|
+
ecosystems: scan_metadata.ecosystems_scanned,
|
|
3771
|
+
tools: confirmed.length,
|
|
3772
|
+
resolved: toolsResolvedCount,
|
|
3773
|
+
languages: languages.map((l) => l.name),
|
|
3774
|
+
frameworks: frameworks.map((f) => f.name),
|
|
3775
|
+
duration_ms: scan_metadata.duration_ms
|
|
3776
|
+
}, "Project scan complete");
|
|
3777
|
+
return {
|
|
3778
|
+
name,
|
|
3779
|
+
languages,
|
|
3780
|
+
frameworks,
|
|
3781
|
+
subprojects,
|
|
3782
|
+
tools: confirmed,
|
|
3783
|
+
warnings,
|
|
3784
|
+
scan_metadata
|
|
3785
|
+
};
|
|
3786
|
+
}
|
|
3787
|
+
function primaryManifestForEcosystem(ecosystem) {
|
|
3788
|
+
switch (ecosystem) {
|
|
3789
|
+
case "npm":
|
|
3790
|
+
return "package.json";
|
|
3791
|
+
case "pypi":
|
|
3792
|
+
return "pyproject.toml";
|
|
3793
|
+
case "cargo":
|
|
3794
|
+
return "Cargo.toml";
|
|
3795
|
+
case "go":
|
|
3796
|
+
return "go.mod";
|
|
3797
|
+
case "rubygems":
|
|
3798
|
+
return "Gemfile";
|
|
3799
|
+
case "maven":
|
|
3800
|
+
return "pom.xml";
|
|
3801
|
+
case "gradle":
|
|
3802
|
+
return "build.gradle";
|
|
3803
|
+
case "composer":
|
|
3804
|
+
return "composer.json";
|
|
3805
|
+
case "hex":
|
|
3806
|
+
return "mix.exs";
|
|
3807
|
+
case "pub":
|
|
3808
|
+
return "pubspec.yaml";
|
|
3809
|
+
case "nuget":
|
|
3810
|
+
return "*.csproj";
|
|
3811
|
+
case "swift-pm":
|
|
3812
|
+
return "Package.swift";
|
|
3813
|
+
}
|
|
3814
|
+
}
|
|
3815
|
+
async function inferProjectName(projectRoot) {
|
|
3816
|
+
const pkgPath = resolve(projectRoot, "package.json");
|
|
3817
|
+
if (await fileExists(pkgPath)) {
|
|
3818
|
+
try {
|
|
3819
|
+
const doc = JSON.parse(await readFile17(pkgPath, "utf-8"));
|
|
3820
|
+
if (doc.name)
|
|
3821
|
+
return doc.name;
|
|
3822
|
+
} catch {
|
|
3823
|
+
}
|
|
3824
|
+
}
|
|
3825
|
+
return basename(projectRoot);
|
|
1940
3826
|
}
|
|
1941
3827
|
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
3828
|
+
// ../../packages/tools-local/dist/templates/agent-instructions.js
|
|
3829
|
+
init_esm_shims();
|
|
3830
|
+
var IS_WINDOWS = process.platform === "win32";
|
|
3831
|
+
var TOOLCAIRN_MCP_ENTRY = IS_WINDOWS ? {
|
|
3832
|
+
toolcairn: {
|
|
3833
|
+
command: "cmd",
|
|
3834
|
+
args: ["/c", "npx", "-y", "@neurynae/toolcairn-mcp"]
|
|
1949
3835
|
}
|
|
1950
|
-
|
|
3836
|
+
} : {
|
|
3837
|
+
toolcairn: {
|
|
3838
|
+
command: "npx",
|
|
3839
|
+
args: ["-y", "@neurynae/toolcairn-mcp"]
|
|
3840
|
+
}
|
|
3841
|
+
};
|
|
3842
|
+
var CORE_RULES = `
|
|
3843
|
+
## ToolCairn \u2014 Tool Intelligence MCP
|
|
1951
3844
|
|
|
1952
|
-
|
|
1953
|
-
const existingIds = new Set(Array.from(feed.querySelectorAll('.event-row')).map(r => r.dataset.id));
|
|
1954
|
-
const currentIds = new Set(allEvents.map(e => e.id));
|
|
1955
|
-
existingIds.forEach(id => { if (!currentIds.has(id)) feed.querySelector(\`[data-id="\${id}"]\`)?.remove(); });
|
|
3845
|
+
ToolCairn is your tool intelligence co-pilot. It has a graph database of 500+ indexed tools with health signals, compatibility data, and known issue tracking. Always use it before recommending any tool, library, or framework.
|
|
1956
3846
|
|
|
1957
|
-
|
|
1958
|
-
for (const ev of allEvents) {
|
|
1959
|
-
if (feed.querySelector(\`[data-id="\${ev.id}"]\`)) continue;
|
|
1960
|
-
const row = document.createElement('div');
|
|
1961
|
-
row.className = 'event-row' + (selectedId === ev.id ? ' selected' : '');
|
|
1962
|
-
row.dataset.id = ev.id;
|
|
1963
|
-
row.onclick = () => selectEvent(ev.id);
|
|
3847
|
+
### The Server Owns .toolcairn/ (v0.10.0+)
|
|
1964
3848
|
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
<span class="time">\${fmtTime(ev.created_at)}</span>
|
|
1969
|
-
<span class="tool">\${ev.tool_name}</span>
|
|
1970
|
-
<span class="summary">\${summary}</span>
|
|
1971
|
-
<span class="dur">\${ev.duration_ms}ms</span>
|
|
1972
|
-
<span class="badge \${badgeClass}">\${ev.status}</span>
|
|
1973
|
-
\`;
|
|
3849
|
+
The MCP server reads and writes \`.toolcairn/config.json\` and
|
|
3850
|
+
\`.toolcairn/audit-log.jsonl\` atomically under a cross-process lock.
|
|
3851
|
+
NEVER read or write these files directly \u2014 call the MCP tools instead.
|
|
1974
3852
|
|
|
1975
|
-
|
|
1976
|
-
const firstRow = feed.querySelector('.event-row');
|
|
1977
|
-
if (firstRow) feed.insertBefore(row, firstRow);
|
|
1978
|
-
else feed.appendChild(row);
|
|
1979
|
-
}
|
|
1980
|
-
}
|
|
3853
|
+
### Workflow for Tool Selection
|
|
1981
3854
|
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
3855
|
+
1. **Session start**: Call \`read_project_config\` with the project_root. If it returns
|
|
3856
|
+
\`status: "not_initialized"\`, call \`toolcairn_init\` which auto-discovers the stack
|
|
3857
|
+
(parses manifests across 12 ecosystems, classifies against the ToolCairn graph, and
|
|
3858
|
+
writes \`.toolcairn/config.json\`). Then apply the returned setup_steps for CLAUDE.md
|
|
3859
|
+
+ .mcp.json + .gitignore.
|
|
3860
|
+
2. **Before recommending any tool**: Call \`search_tools\` first. Never recommend from
|
|
3861
|
+
training data alone.
|
|
3862
|
+
3. **When user describes a use case**: Call \`classify_prompt\`, then \`refine_requirement\`
|
|
3863
|
+
if tool selection is needed.
|
|
3864
|
+
4. **When a tool is selected / replaced / dropped**: Call \`update_project_config\` with
|
|
3865
|
+
project_root + action \u2014 the server atomically updates config.json and appends to
|
|
3866
|
+
audit-log.jsonl.
|
|
3867
|
+
5. **When encountering an error with a tool**: Call \`check_issue\` before debugging \u2014
|
|
3868
|
+
it may be a known issue with an open GitHub ticket.
|
|
3869
|
+
6. **When user asks to compare tools**: Call \`compare_tools\`.
|
|
3870
|
+
7. **When user chooses a non-indexed/proprietary tool**: Call \`update_project_config\`
|
|
3871
|
+
with \`data: { source: "non_oss" }\`.
|
|
1990
3872
|
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
3873
|
+
### Available ToolCairn MCP Tools
|
|
3874
|
+
|
|
3875
|
+
| Tool | When to use |
|
|
3876
|
+
|------|------------|
|
|
3877
|
+
| \`classify_prompt\` | User describes a task \u2014 determine if tool search needed |
|
|
3878
|
+
| \`refine_requirement\` | Decompose vague use case into searchable tool needs |
|
|
3879
|
+
| \`search_tools\` | Find the best tool for a specific need |
|
|
3880
|
+
| \`search_tools_respond\` | Answer clarification questions from search_tools |
|
|
3881
|
+
| \`get_stack\` | Get recommended tool stack for a use case |
|
|
3882
|
+
| \`check_issue\` | Check if an error is a known tool bug before debugging |
|
|
3883
|
+
| \`check_compatibility\` | Check if two tools work well together |
|
|
3884
|
+
| \`compare_tools\` | Compare two tools with health and graph data |
|
|
3885
|
+
| \`report_outcome\` | Report whether a recommended tool worked (improves future results) |
|
|
3886
|
+
| \`toolcairn_init\` | One-call project bootstrap (auto-discovers + writes config.json) |
|
|
3887
|
+
| \`read_project_config\` | Read .toolcairn/config.json from disk (returns structured snapshot) |
|
|
3888
|
+
| \`update_project_config\` | Apply a mutation (add_tool / remove_tool / update_tool / add_evaluation) |
|
|
3889
|
+
| \`suggest_graph_update\` | Suggest a new tool or relationship for the ToolCairn graph |
|
|
3890
|
+
|
|
3891
|
+
### Rules
|
|
3892
|
+
|
|
3893
|
+
- NEVER recommend a tool without first checking ToolCairn unless the tool is already in the project config
|
|
3894
|
+
- Always call \`read_project_config\` at the start of a session
|
|
3895
|
+
- NEVER write to .toolcairn/config.json or .toolcairn/audit-log.jsonl yourself \u2014 the server owns them
|
|
3896
|
+
- Prefer open-source tools from ToolCairn index; flag proprietary alternatives clearly
|
|
3897
|
+
- After selecting a tool, always call \`update_project_config\` to persist it
|
|
3898
|
+
- After using a tool successfully/unsuccessfully, call \`report_outcome\`
|
|
3899
|
+
`;
|
|
3900
|
+
function getClaudeInstructions() {
|
|
3901
|
+
return {
|
|
3902
|
+
file_path: "CLAUDE.md",
|
|
3903
|
+
mode: "append",
|
|
3904
|
+
content: CORE_RULES
|
|
3905
|
+
};
|
|
1998
3906
|
}
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
const html = sorted.map(([tool, count]) => \`
|
|
2006
|
-
<div class="bar-row">
|
|
2007
|
-
<span class="bar-label">\${tool}</span>
|
|
2008
|
-
<div class="bar-track"><div class="bar-fill" style="width:\${count/max*100}%"></div></div>
|
|
2009
|
-
<span class="bar-count">\${count}</span>
|
|
2010
|
-
</div>
|
|
2011
|
-
\`).join('');
|
|
2012
|
-
document.getElementById('toolChart').innerHTML = html || '<span style="color:var(--muted);font-size:12px">No data yet</span>';
|
|
3907
|
+
function getCursorInstructions() {
|
|
3908
|
+
return {
|
|
3909
|
+
file_path: ".cursorrules",
|
|
3910
|
+
mode: "append",
|
|
3911
|
+
content: CORE_RULES
|
|
3912
|
+
};
|
|
2013
3913
|
}
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
insights.push({ tool: ev.tool_name, text: 'Issue check ran \u2014 may have prevented a debug loop', time: ev.created_at });
|
|
2021
|
-
}
|
|
2022
|
-
if (m.had_deprecation_warning) {
|
|
2023
|
-
insights.push({ tool: ev.tool_name, text: 'Deprecated/unmaintained tool detected in results', time: ev.created_at });
|
|
2024
|
-
}
|
|
2025
|
-
if (m.auto_graduated) {
|
|
2026
|
-
insights.push({ tool: 'suggest_graph_update', text: 'New edge auto-graduated to graph (confidence \u22650.8)', time: ev.created_at });
|
|
2027
|
-
}
|
|
2028
|
-
if (m.had_non_indexed_guidance) {
|
|
2029
|
-
insights.push({ tool: ev.tool_name, text: 'Non-indexed tool detected \u2014 non-OSS guidance provided', time: ev.created_at });
|
|
2030
|
-
}
|
|
2031
|
-
if (m.recommendation) {
|
|
2032
|
-
insights.push({ tool: 'compare_tools', text: \`Tool comparison recommended: \${m.recommendation}\`, time: ev.created_at });
|
|
2033
|
-
}
|
|
2034
|
-
}
|
|
2035
|
-
const list = document.getElementById('insightsList');
|
|
2036
|
-
if (insights.length === 0) {
|
|
2037
|
-
list.innerHTML = '<li style="color:var(--muted);font-size:12px">No insights yet</li>';
|
|
2038
|
-
return;
|
|
2039
|
-
}
|
|
2040
|
-
list.innerHTML = insights.slice(0, 8).map(i => \`
|
|
2041
|
-
<li class="insight-item">
|
|
2042
|
-
<div class="i-tool">\${i.tool}</div>
|
|
2043
|
-
<div class="i-text">\${i.text}</div>
|
|
2044
|
-
</li>
|
|
2045
|
-
\`).join('');
|
|
3914
|
+
function getWindsurfInstructions() {
|
|
3915
|
+
return {
|
|
3916
|
+
file_path: ".windsurfrules",
|
|
3917
|
+
mode: "append",
|
|
3918
|
+
content: CORE_RULES
|
|
3919
|
+
};
|
|
2046
3920
|
}
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
const content = document.getElementById('detailContent');
|
|
2055
|
-
panel.style.display = 'block';
|
|
2056
|
-
const m = ev.metadata || {};
|
|
2057
|
-
const rows = [
|
|
2058
|
-
['Tool', ev.tool_name],
|
|
2059
|
-
['Status', ev.status],
|
|
2060
|
-
['Duration', ev.duration_ms + 'ms'],
|
|
2061
|
-
['Time', new Date(ev.created_at).toLocaleString()],
|
|
2062
|
-
ev.query_id ? ['Session ID', ev.query_id.slice(0, 8) + '...'] : null,
|
|
2063
|
-
...Object.entries(m).filter(([k]) => k !== 'tool').map(([k, v]) => [k, String(v)])
|
|
2064
|
-
].filter(Boolean);
|
|
2065
|
-
content.innerHTML = rows.map(([k, v]) => {
|
|
2066
|
-
const cls = v === 'true' || v === 'ok' ? 'green' : v === 'false' || v === 'error' ? 'red' : '';
|
|
2067
|
-
return \`<div class="kv"><span class="k">\${k}</span><span class="v \${cls}">\${v}</span></div>\`;
|
|
2068
|
-
}).join('');
|
|
3921
|
+
function getCopilotInstructions() {
|
|
3922
|
+
return {
|
|
3923
|
+
file_path: ".github/copilot-instructions.md",
|
|
3924
|
+
mode: "create",
|
|
3925
|
+
content: `# GitHub Copilot Instructions
|
|
3926
|
+
${CORE_RULES}`
|
|
3927
|
+
};
|
|
2069
3928
|
}
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
3929
|
+
function getCopilotCliInstructions() {
|
|
3930
|
+
return {
|
|
3931
|
+
file_path: ".github/copilot-instructions.md",
|
|
3932
|
+
mode: "append",
|
|
3933
|
+
content: CORE_RULES
|
|
3934
|
+
};
|
|
2076
3935
|
}
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
}
|
|
2083
|
-
startPolling();
|
|
3936
|
+
function getOpenCodeInstructions() {
|
|
3937
|
+
return {
|
|
3938
|
+
file_path: "AGENTS.md",
|
|
3939
|
+
mode: "append",
|
|
3940
|
+
content: CORE_RULES
|
|
3941
|
+
};
|
|
2084
3942
|
}
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
3943
|
+
function getGenericInstructions() {
|
|
3944
|
+
return {
|
|
3945
|
+
file_path: "AI_INSTRUCTIONS.md",
|
|
3946
|
+
mode: "create",
|
|
3947
|
+
content: `# AI Assistant Instructions
|
|
3948
|
+
${CORE_RULES}`
|
|
3949
|
+
};
|
|
3950
|
+
}
|
|
3951
|
+
function getInstructionsForAgent(agent) {
|
|
3952
|
+
switch (agent) {
|
|
3953
|
+
case "claude":
|
|
3954
|
+
return getClaudeInstructions();
|
|
3955
|
+
case "cursor":
|
|
3956
|
+
return getCursorInstructions();
|
|
3957
|
+
case "windsurf":
|
|
3958
|
+
return getWindsurfInstructions();
|
|
3959
|
+
case "copilot":
|
|
3960
|
+
return getCopilotInstructions();
|
|
3961
|
+
case "copilot-cli":
|
|
3962
|
+
return getCopilotCliInstructions();
|
|
3963
|
+
case "opencode":
|
|
3964
|
+
return getOpenCodeInstructions();
|
|
3965
|
+
case "generic":
|
|
3966
|
+
return getGenericInstructions();
|
|
3967
|
+
}
|
|
3968
|
+
}
|
|
3969
|
+
function getMcpConfigEntry(serverPath) {
|
|
3970
|
+
if (serverPath) {
|
|
3971
|
+
return {
|
|
3972
|
+
toolcairn: {
|
|
3973
|
+
command: "node",
|
|
3974
|
+
args: [serverPath]
|
|
3975
|
+
}
|
|
3976
|
+
};
|
|
3977
|
+
}
|
|
3978
|
+
return TOOLCAIRN_MCP_ENTRY;
|
|
3979
|
+
}
|
|
3980
|
+
function getOpenCodeMcpEntry(serverPath) {
|
|
3981
|
+
if (serverPath) {
|
|
3982
|
+
return {
|
|
3983
|
+
toolcairn: {
|
|
3984
|
+
type: "local",
|
|
3985
|
+
command: ["node", serverPath],
|
|
3986
|
+
enabled: true
|
|
3987
|
+
}
|
|
3988
|
+
};
|
|
3989
|
+
}
|
|
3990
|
+
const command = IS_WINDOWS ? ["cmd", "/c", "npx", "-y", "@neurynae/toolcairn-mcp"] : ["npx", "-y", "@neurynae/toolcairn-mcp"];
|
|
3991
|
+
return {
|
|
3992
|
+
toolcairn: {
|
|
3993
|
+
type: "local",
|
|
3994
|
+
command,
|
|
3995
|
+
enabled: true
|
|
3996
|
+
}
|
|
3997
|
+
};
|
|
2088
3998
|
}
|
|
2089
3999
|
|
|
2090
4000
|
// ../../packages/tools-local/dist/handlers/toolcairn-init.js
|
|
2091
|
-
var
|
|
2092
|
-
async function handleToolcairnInit(args) {
|
|
4001
|
+
var logger8 = (0, import_errors10.createMcpLogger)({ name: "@toolcairn/tools:toolcairn-init" });
|
|
4002
|
+
async function handleToolcairnInit(args, deps = {}) {
|
|
2093
4003
|
try {
|
|
2094
|
-
|
|
4004
|
+
logger8.info({ agent: args.agent, project_root: args.project_root }, "toolcairn_init called");
|
|
4005
|
+
const scan = await scanProject(args.project_root, { batchResolve: deps.batchResolve });
|
|
4006
|
+
const audit = {
|
|
4007
|
+
action: "init",
|
|
4008
|
+
tool: "__project__",
|
|
4009
|
+
reason: `Auto-discovered via toolcairn_init: ${scan.tools.length} tools across ${scan.scan_metadata.ecosystems_scanned.length} ecosystems`
|
|
4010
|
+
};
|
|
4011
|
+
const { config: config5, audit_entry, bootstrapped, migrated } = await mutateConfig(args.project_root, (cfg) => {
|
|
4012
|
+
cfg.project.name = scan.name;
|
|
4013
|
+
cfg.project.languages = scan.languages;
|
|
4014
|
+
cfg.project.frameworks = scan.frameworks;
|
|
4015
|
+
cfg.project.subprojects = scan.subprojects;
|
|
4016
|
+
cfg.tools.confirmed = scan.tools;
|
|
4017
|
+
cfg.scan_metadata = scan.scan_metadata;
|
|
4018
|
+
}, audit);
|
|
2095
4019
|
const instructions = getInstructionsForAgent(args.agent);
|
|
2096
4020
|
const isOpenCode = args.agent === "opencode";
|
|
2097
4021
|
const mcpConfigEntry = isOpenCode ? getOpenCodeMcpEntry(args.server_path) : getMcpConfigEntry(args.server_path);
|
|
2098
4022
|
const mcpConfigFile = isOpenCode ? "opencode.json" : ".mcp.json";
|
|
2099
|
-
const hasMcpJson = args.detected_files?.some((f) => f === mcpConfigFile || f.endsWith(`/${mcpConfigFile}`));
|
|
2100
|
-
const hasInstructionFile = args.detected_files?.some((f) => f.endsWith(instructions.file_path));
|
|
2101
|
-
const hasToolcairnConfig = args.detected_files?.some((f) => f.includes(".toolcairn/config.json"));
|
|
2102
|
-
const hasTrackerHtml = args.detected_files?.some((f) => f.includes(".toolcairn/tracker.html"));
|
|
2103
|
-
const eventsPath = `${args.project_root}/.toolcairn/events.jsonl`;
|
|
2104
|
-
const setupSteps = [];
|
|
2105
|
-
let step = 1;
|
|
2106
|
-
setupSteps.push({
|
|
2107
|
-
step: step++,
|
|
2108
|
-
action: hasInstructionFile ? "append" : "create",
|
|
2109
|
-
file: instructions.file_path,
|
|
2110
|
-
content: instructions.content,
|
|
2111
|
-
note: hasInstructionFile ? `Append the content to your existing ${instructions.file_path}` : `Create ${instructions.file_path} with the content`
|
|
2112
|
-
});
|
|
2113
4023
|
const mcpContent = isOpenCode ? JSON.stringify({ mcp: mcpConfigEntry }, null, 2) : JSON.stringify({ mcpServers: mcpConfigEntry }, null, 2);
|
|
2114
|
-
const
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
step: step++,
|
|
2142
|
-
action: "append",
|
|
2143
|
-
file: ".gitignore",
|
|
2144
|
-
content: "\n# ToolCairn\n.toolcairn/events.jsonl\n",
|
|
2145
|
-
note: "Add .toolcairn/events.jsonl to .gitignore (the tracker event log)"
|
|
2146
|
-
});
|
|
2147
|
-
const agentFileLabel = {
|
|
2148
|
-
claude: "CLAUDE.md",
|
|
2149
|
-
cursor: ".cursorrules",
|
|
2150
|
-
windsurf: ".windsurfrules",
|
|
2151
|
-
copilot: ".github/copilot-instructions.md",
|
|
2152
|
-
"copilot-cli": ".github/copilot-instructions.md",
|
|
2153
|
-
opencode: "AGENTS.md",
|
|
2154
|
-
generic: "AI_INSTRUCTIONS.md"
|
|
4024
|
+
const setupSteps = [
|
|
4025
|
+
{
|
|
4026
|
+
step: 1,
|
|
4027
|
+
action: "append-or-create",
|
|
4028
|
+
file: instructions.file_path,
|
|
4029
|
+
content: instructions.content,
|
|
4030
|
+
note: `Append the ToolCairn rules block to ${instructions.file_path} (or create it if missing).`
|
|
4031
|
+
},
|
|
4032
|
+
{
|
|
4033
|
+
step: 2,
|
|
4034
|
+
action: "merge-or-create",
|
|
4035
|
+
file: mcpConfigFile,
|
|
4036
|
+
content: mcpContent,
|
|
4037
|
+
note: isOpenCode ? `Merge the toolcairn entry into ${mcpConfigFile} under "mcp".` : `Merge the toolcairn entry into ${mcpConfigFile} under "mcpServers".`
|
|
4038
|
+
},
|
|
4039
|
+
{
|
|
4040
|
+
step: 3,
|
|
4041
|
+
action: "append",
|
|
4042
|
+
file: ".gitignore",
|
|
4043
|
+
content: "\n# ToolCairn\n.toolcairn/events.jsonl\n.toolcairn/audit-log.jsonl\n.toolcairn/audit-log.archive.jsonl\n.toolcairn/config.lock\n",
|
|
4044
|
+
note: "Ignore runtime/audit files. config.json should be committed so teammates share tool intelligence."
|
|
4045
|
+
}
|
|
4046
|
+
];
|
|
4047
|
+
const tool_counts = {
|
|
4048
|
+
total: config5.tools.confirmed.length,
|
|
4049
|
+
indexed: config5.tools.confirmed.filter((t) => t.source === "toolcairn").length,
|
|
4050
|
+
non_oss: config5.tools.confirmed.filter((t) => t.source === "non_oss").length
|
|
2155
4051
|
};
|
|
2156
4052
|
return okResult({
|
|
2157
4053
|
agent: args.agent,
|
|
2158
|
-
instruction_file:
|
|
2159
|
-
|
|
4054
|
+
instruction_file: instructions.file_path,
|
|
4055
|
+
config_path: ".toolcairn/config.json",
|
|
4056
|
+
audit_log_path: ".toolcairn/audit-log.jsonl",
|
|
4057
|
+
events_path: ".toolcairn/events.jsonl",
|
|
2160
4058
|
mcp_config_entry: mcpConfigEntry,
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
});
|
|
2171
|
-
} catch (e) {
|
|
2172
|
-
logger3.error({ err: e }, "toolcairn_init failed");
|
|
2173
|
-
return errResult("init_error", e instanceof Error ? e.message : String(e));
|
|
2174
|
-
}
|
|
2175
|
-
}
|
|
2176
|
-
|
|
2177
|
-
// ../../packages/tools-local/dist/handlers/init-project-config.js
|
|
2178
|
-
init_esm_shims();
|
|
2179
|
-
var import_errors6 = __toESM(require_dist2(), 1);
|
|
2180
|
-
var logger4 = (0, import_errors6.createMcpLogger)({ name: "@toolcairn/tools:init-project-config" });
|
|
2181
|
-
async function handleInitProjectConfig(args) {
|
|
2182
|
-
try {
|
|
2183
|
-
logger4.info({ project: args.project_name }, "init_project_config called");
|
|
2184
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2185
|
-
const confirmedTools = (args.detected_tools ?? []).map((t) => ({
|
|
2186
|
-
name: t.name,
|
|
2187
|
-
source: t.source,
|
|
2188
|
-
version: t.version,
|
|
2189
|
-
chosen_at: now,
|
|
2190
|
-
chosen_reason: "Auto-detected from project files during toolcairn_init",
|
|
2191
|
-
alternatives_considered: []
|
|
2192
|
-
}));
|
|
2193
|
-
const config5 = {
|
|
2194
|
-
version: "1.0",
|
|
2195
|
-
project: {
|
|
2196
|
-
name: args.project_name,
|
|
2197
|
-
language: args.language,
|
|
2198
|
-
framework: args.framework
|
|
2199
|
-
},
|
|
2200
|
-
tools: {
|
|
2201
|
-
confirmed: confirmedTools,
|
|
2202
|
-
pending_evaluation: []
|
|
4059
|
+
setup_steps: setupSteps,
|
|
4060
|
+
scan_summary: {
|
|
4061
|
+
project_name: scan.name,
|
|
4062
|
+
languages: scan.languages.map((l) => ({ name: l.name, file_count: l.file_count })),
|
|
4063
|
+
frameworks: scan.frameworks,
|
|
4064
|
+
subprojects: scan.subprojects,
|
|
4065
|
+
tool_counts,
|
|
4066
|
+
warnings: scan.warnings,
|
|
4067
|
+
scan_metadata: scan.scan_metadata
|
|
2203
4068
|
},
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
timestamp: now,
|
|
2209
|
-
reason: `Project config initialized for ${args.project_name}`
|
|
2210
|
-
}
|
|
2211
|
-
]
|
|
2212
|
-
};
|
|
2213
|
-
const config_json = JSON.stringify(config5, null, 2);
|
|
2214
|
-
return okResult({
|
|
2215
|
-
config_json,
|
|
2216
|
-
file_path: ".toolcairn/config.json",
|
|
2217
|
-
instructions: "Create the directory .toolcairn/ in your project root (if it does not exist), then write this config_json content to .toolcairn/config.json. Also add .toolcairn/ to .gitignore if not already present.",
|
|
2218
|
-
confirmed_count: confirmedTools.length,
|
|
2219
|
-
next_step: confirmedTools.length > 0 ? "Config initialized with auto-detected tools. Use search_tools to find any additional tools you need." : "Config initialized. Use classify_prompt \u2192 refine_requirement \u2192 search_tools to discover tools for your project."
|
|
4069
|
+
bootstrapped,
|
|
4070
|
+
migrated,
|
|
4071
|
+
last_audit_entry: audit_entry,
|
|
4072
|
+
next_steps: "Config written. Apply the setup_steps above (CLAUDE.md rules + .mcp.json merge + .gitignore). Then proceed with normal tool calls \u2014 the server owns .toolcairn/ going forward."
|
|
2220
4073
|
});
|
|
2221
4074
|
} catch (e) {
|
|
2222
|
-
|
|
2223
|
-
return errResult("
|
|
4075
|
+
logger8.error({ err: e }, "toolcairn_init failed");
|
|
4076
|
+
return errResult("init_error", e instanceof Error ? e.message : String(e));
|
|
2224
4077
|
}
|
|
2225
4078
|
}
|
|
2226
4079
|
|
|
2227
4080
|
// ../../packages/tools-local/dist/handlers/read-project-config.js
|
|
2228
4081
|
init_esm_shims();
|
|
2229
|
-
var
|
|
2230
|
-
var
|
|
4082
|
+
var import_errors11 = __toESM(require_dist2(), 1);
|
|
4083
|
+
var logger9 = (0, import_errors11.createMcpLogger)({ name: "@toolcairn/tools:read-project-config" });
|
|
2231
4084
|
var STALENESS_THRESHOLD_DAYS = 90;
|
|
2232
4085
|
function daysSince(isoDate) {
|
|
2233
4086
|
return (Date.now() - new Date(isoDate).getTime()) / (1e3 * 60 * 60 * 24);
|
|
2234
4087
|
}
|
|
2235
4088
|
async function handleReadProjectConfig(args) {
|
|
2236
4089
|
try {
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
4090
|
+
logger9.info({ project_root: args.project_root }, "read_project_config called");
|
|
4091
|
+
const { config: initial, corrupt_backup_path } = await readConfig(args.project_root);
|
|
4092
|
+
if (!initial) {
|
|
4093
|
+
return okResult({
|
|
4094
|
+
status: "not_initialized",
|
|
4095
|
+
project_root: args.project_root,
|
|
4096
|
+
config_path: joinConfigPath(args.project_root),
|
|
4097
|
+
audit_log_path: joinAuditPath(args.project_root),
|
|
4098
|
+
corrupt_backup_path,
|
|
4099
|
+
agent_instructions: corrupt_backup_path ? `.toolcairn/config.json was unparseable \u2014 moved to ${corrupt_backup_path}. Call toolcairn_init with the project_root to re-discover and write a fresh config.` : "No .toolcairn/config.json present. Call toolcairn_init with the project_root to auto-discover the project and bootstrap the config."
|
|
4100
|
+
});
|
|
2243
4101
|
}
|
|
2244
|
-
|
|
2245
|
-
|
|
4102
|
+
let config5 = initial;
|
|
4103
|
+
let migrated = false;
|
|
4104
|
+
if (initial.version === "1.0") {
|
|
4105
|
+
const result = await mutateConfig(args.project_root, () => {
|
|
4106
|
+
}, {
|
|
4107
|
+
action: "migrate",
|
|
4108
|
+
tool: "__schema__",
|
|
4109
|
+
reason: "Lazy migration on first read after server upgrade"
|
|
4110
|
+
});
|
|
4111
|
+
config5 = result.config;
|
|
4112
|
+
migrated = true;
|
|
2246
4113
|
}
|
|
2247
4114
|
const confirmedToolNames = config5.tools.confirmed.map((t) => t.name);
|
|
2248
4115
|
const pendingToolNames = config5.tools.pending_evaluation.map((t) => t.name);
|
|
@@ -2261,8 +4128,35 @@ async function handleReadProjectConfig(args) {
|
|
|
2261
4128
|
});
|
|
2262
4129
|
const non_oss_tools = config5.tools.confirmed.filter((t) => t.source === "non_oss").map((t) => t.name);
|
|
2263
4130
|
const toolcairn_indexed_tools = config5.tools.confirmed.filter((t) => t.source === "toolcairn" || t.source === "toolpilot").map((t) => t.name);
|
|
4131
|
+
const include_locations = args.include_locations === true;
|
|
4132
|
+
const confirmed_tools_detail = include_locations ? config5.tools.confirmed.map((t) => ({
|
|
4133
|
+
name: t.name,
|
|
4134
|
+
source: t.source,
|
|
4135
|
+
canonical_name: t.canonical_name,
|
|
4136
|
+
categories: t.categories ?? [],
|
|
4137
|
+
match_method: t.match_method ?? "none",
|
|
4138
|
+
github_url: t.github_url,
|
|
4139
|
+
locations: t.locations ?? []
|
|
4140
|
+
})) : void 0;
|
|
4141
|
+
const instructions_lines = [
|
|
4142
|
+
`Project: ${config5.project.name}`,
|
|
4143
|
+
config5.project.languages && config5.project.languages.length > 0 ? `Languages: ${config5.project.languages.map((l) => `${l.name} (${l.file_count} files)`).join(", ")}` : "",
|
|
4144
|
+
config5.project.frameworks && config5.project.frameworks.length > 0 ? `Frameworks: ${config5.project.frameworks.map((f) => `${f.name}@${f.workspace}`).join(", ")}` : "",
|
|
4145
|
+
`Confirmed tools (${confirmedToolNames.length}): ${confirmedToolNames.join(", ") || "none"}`,
|
|
4146
|
+
"When recommending tools, skip any already in confirmed_tools.",
|
|
4147
|
+
non_oss_tools.length > 0 ? `Non-OSS tools in project (handle separately): ${non_oss_tools.join(", ")}` : "",
|
|
4148
|
+
staleTools.length > 0 ? `Tools that may be stale \u2014 worth re-checking: ${staleTools.map((t) => t.name).join(", ")}` : ""
|
|
4149
|
+
].filter(Boolean);
|
|
2264
4150
|
return okResult({
|
|
2265
|
-
|
|
4151
|
+
status: "ready",
|
|
4152
|
+
schema_version: config5.version,
|
|
4153
|
+
migrated,
|
|
4154
|
+
project: {
|
|
4155
|
+
name: config5.project.name,
|
|
4156
|
+
languages: config5.project.languages ?? [],
|
|
4157
|
+
frameworks: config5.project.frameworks ?? [],
|
|
4158
|
+
subprojects: config5.project.subprojects ?? []
|
|
4159
|
+
},
|
|
2266
4160
|
confirmed_tools: confirmedToolNames,
|
|
2267
4161
|
pending_tools: pendingToolNames,
|
|
2268
4162
|
non_oss_tools,
|
|
@@ -2270,129 +4164,124 @@ async function handleReadProjectConfig(args) {
|
|
|
2270
4164
|
stale_tools: staleTools,
|
|
2271
4165
|
total_confirmed: confirmedToolNames.length,
|
|
2272
4166
|
total_pending: pendingToolNames.length,
|
|
2273
|
-
last_audit_entry: config5.
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
"When recommending tools, skip any already in confirmed_tools.",
|
|
2278
|
-
non_oss_tools.length > 0 ? `Non-OSS tools in project (handle separately): ${non_oss_tools.join(", ")}` : "",
|
|
2279
|
-
staleTools.length > 0 ? `These tools may be stale and worth re-checking: ${staleTools.map((t) => t.name).join(", ")}` : ""
|
|
2280
|
-
].filter(Boolean).join("\n")
|
|
4167
|
+
last_audit_entry: config5.last_audit_entry ?? null,
|
|
4168
|
+
scan_metadata: config5.scan_metadata ?? null,
|
|
4169
|
+
confirmed_tools_detail,
|
|
4170
|
+
agent_instructions: instructions_lines.join("\n")
|
|
2281
4171
|
});
|
|
2282
4172
|
} catch (e) {
|
|
2283
|
-
|
|
4173
|
+
logger9.error({ err: e }, "read_project_config failed");
|
|
2284
4174
|
return errResult("read_config_error", e instanceof Error ? e.message : String(e));
|
|
2285
4175
|
}
|
|
2286
4176
|
}
|
|
2287
4177
|
|
|
2288
4178
|
// ../../packages/tools-local/dist/handlers/update-project-config.js
|
|
2289
4179
|
init_esm_shims();
|
|
2290
|
-
var
|
|
2291
|
-
var
|
|
4180
|
+
var import_errors12 = __toESM(require_dist2(), 1);
|
|
4181
|
+
var logger10 = (0, import_errors12.createMcpLogger)({ name: "@toolcairn/tools:update-project-config" });
|
|
2292
4182
|
async function handleUpdateProjectConfig(args) {
|
|
2293
4183
|
try {
|
|
2294
|
-
|
|
2295
|
-
let config5;
|
|
2296
|
-
try {
|
|
2297
|
-
config5 = JSON.parse(args.current_config);
|
|
2298
|
-
} catch {
|
|
2299
|
-
return errResult("parse_error", "current_config is not valid JSON");
|
|
2300
|
-
}
|
|
2301
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4184
|
+
logger10.info({ project_root: args.project_root, action: args.action, tool: args.tool_name }, "update_project_config called");
|
|
2302
4185
|
const data = args.data ?? {};
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
config5.tools.pending_evaluation = config5.tools.pending_evaluation.filter((t) => t.name !== args.tool_name);
|
|
2331
|
-
config5.audit_log.push({
|
|
2332
|
-
action: "remove_tool",
|
|
2333
|
-
tool: args.tool_name,
|
|
2334
|
-
timestamp: now,
|
|
2335
|
-
reason: data.reason ?? "Removed from project"
|
|
2336
|
-
});
|
|
2337
|
-
break;
|
|
2338
|
-
}
|
|
2339
|
-
case "update_tool": {
|
|
2340
|
-
const idx = config5.tools.confirmed.findIndex((t) => t.name === args.tool_name);
|
|
2341
|
-
if (idx === -1) {
|
|
2342
|
-
return errResult("not_found", `Tool "${args.tool_name}" not found in confirmed tools`);
|
|
4186
|
+
let notFound = false;
|
|
4187
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4188
|
+
const audit = {
|
|
4189
|
+
action: args.action,
|
|
4190
|
+
tool: args.tool_name,
|
|
4191
|
+
reason: data.reason ?? data.chosen_reason ?? defaultReasonFor(args.action)
|
|
4192
|
+
};
|
|
4193
|
+
const { config: config5, audit_entry, bootstrapped } = await mutateConfig(args.project_root, (cfg) => {
|
|
4194
|
+
switch (args.action) {
|
|
4195
|
+
case "add_tool": {
|
|
4196
|
+
cfg.tools.pending_evaluation = cfg.tools.pending_evaluation.filter((t) => t.name !== args.tool_name);
|
|
4197
|
+
if (!cfg.tools.confirmed.some((t) => t.name === args.tool_name)) {
|
|
4198
|
+
const tool = {
|
|
4199
|
+
name: args.tool_name,
|
|
4200
|
+
source: data.source ?? "toolcairn",
|
|
4201
|
+
github_url: data.github_url,
|
|
4202
|
+
version: data.version,
|
|
4203
|
+
chosen_at: now,
|
|
4204
|
+
chosen_reason: data.chosen_reason ?? "Selected via ToolCairn",
|
|
4205
|
+
alternatives_considered: data.alternatives_considered ?? [],
|
|
4206
|
+
query_id: data.query_id,
|
|
4207
|
+
notes: data.notes,
|
|
4208
|
+
locations: []
|
|
4209
|
+
};
|
|
4210
|
+
cfg.tools.confirmed.push(tool);
|
|
4211
|
+
}
|
|
4212
|
+
break;
|
|
2343
4213
|
}
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
4214
|
+
case "remove_tool": {
|
|
4215
|
+
cfg.tools.confirmed = cfg.tools.confirmed.filter((t) => t.name !== args.tool_name);
|
|
4216
|
+
cfg.tools.pending_evaluation = cfg.tools.pending_evaluation.filter((t) => t.name !== args.tool_name);
|
|
4217
|
+
break;
|
|
2347
4218
|
}
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
name: args.tool_name,
|
|
2367
|
-
category: data.category ?? "other",
|
|
2368
|
-
added_at: now
|
|
4219
|
+
case "update_tool": {
|
|
4220
|
+
const idx = cfg.tools.confirmed.findIndex((t) => t.name === args.tool_name);
|
|
4221
|
+
if (idx === -1) {
|
|
4222
|
+
notFound = true;
|
|
4223
|
+
return;
|
|
4224
|
+
}
|
|
4225
|
+
const existing = cfg.tools.confirmed[idx];
|
|
4226
|
+
if (!existing) {
|
|
4227
|
+
notFound = true;
|
|
4228
|
+
return;
|
|
4229
|
+
}
|
|
4230
|
+
cfg.tools.confirmed[idx] = {
|
|
4231
|
+
...existing,
|
|
4232
|
+
...data.version !== void 0 ? { version: data.version } : {},
|
|
4233
|
+
...data.notes !== void 0 ? { notes: data.notes } : {},
|
|
4234
|
+
...data.chosen_reason !== void 0 ? { chosen_reason: data.chosen_reason } : {},
|
|
4235
|
+
...data.alternatives_considered !== void 0 ? { alternatives_considered: data.alternatives_considered } : {},
|
|
4236
|
+
last_verified: now
|
|
2369
4237
|
};
|
|
2370
|
-
|
|
4238
|
+
break;
|
|
4239
|
+
}
|
|
4240
|
+
case "add_evaluation": {
|
|
4241
|
+
const inConfirmed = cfg.tools.confirmed.some((t) => t.name === args.tool_name);
|
|
4242
|
+
const inPending = cfg.tools.pending_evaluation.some((t) => t.name === args.tool_name);
|
|
4243
|
+
if (!inConfirmed && !inPending) {
|
|
4244
|
+
const pending = {
|
|
4245
|
+
name: args.tool_name,
|
|
4246
|
+
category: data.category ?? "other",
|
|
4247
|
+
added_at: now
|
|
4248
|
+
};
|
|
4249
|
+
cfg.tools.pending_evaluation.push(pending);
|
|
4250
|
+
}
|
|
4251
|
+
break;
|
|
2371
4252
|
}
|
|
2372
|
-
config5.audit_log.push({
|
|
2373
|
-
action: "add_evaluation",
|
|
2374
|
-
tool: args.tool_name,
|
|
2375
|
-
timestamp: now,
|
|
2376
|
-
reason: data.reason ?? "Added for evaluation"
|
|
2377
|
-
});
|
|
2378
|
-
break;
|
|
2379
4253
|
}
|
|
4254
|
+
}, audit);
|
|
4255
|
+
if (notFound) {
|
|
4256
|
+
return errResult("not_found", `Tool "${args.tool_name}" is not in the confirmed list \u2014 cannot update.`);
|
|
2380
4257
|
}
|
|
2381
|
-
const updated_config_json = JSON.stringify(config5, null, 2);
|
|
2382
4258
|
return okResult({
|
|
2383
|
-
updated_config_json,
|
|
2384
|
-
file_path: ".toolcairn/config.json",
|
|
2385
4259
|
action_applied: args.action,
|
|
2386
4260
|
tool_name: args.tool_name,
|
|
2387
4261
|
confirmed_count: config5.tools.confirmed.length,
|
|
2388
4262
|
pending_count: config5.tools.pending_evaluation.length,
|
|
2389
|
-
|
|
4263
|
+
last_audit_entry: audit_entry,
|
|
4264
|
+
bootstrapped,
|
|
4265
|
+
config_path: ".toolcairn/config.json",
|
|
4266
|
+
audit_log_path: ".toolcairn/audit-log.jsonl"
|
|
2390
4267
|
});
|
|
2391
4268
|
} catch (e) {
|
|
2392
|
-
|
|
4269
|
+
logger10.error({ err: e }, "update_project_config failed");
|
|
2393
4270
|
return errResult("update_config_error", e instanceof Error ? e.message : String(e));
|
|
2394
4271
|
}
|
|
2395
4272
|
}
|
|
4273
|
+
function defaultReasonFor(action) {
|
|
4274
|
+
switch (action) {
|
|
4275
|
+
case "add_tool":
|
|
4276
|
+
return "Added via ToolCairn recommendation";
|
|
4277
|
+
case "remove_tool":
|
|
4278
|
+
return "Removed from project";
|
|
4279
|
+
case "update_tool":
|
|
4280
|
+
return "Tool details updated";
|
|
4281
|
+
case "add_evaluation":
|
|
4282
|
+
return "Added for evaluation";
|
|
4283
|
+
}
|
|
4284
|
+
}
|
|
2396
4285
|
|
|
2397
4286
|
// src/server.prod.ts
|
|
2398
4287
|
import { z as z2 } from "zod";
|
|
@@ -2400,10 +4289,10 @@ import { z as z2 } from "zod";
|
|
|
2400
4289
|
// src/middleware/event-logger.ts
|
|
2401
4290
|
init_esm_shims();
|
|
2402
4291
|
var import_config = __toESM(require_dist(), 1);
|
|
2403
|
-
var
|
|
2404
|
-
import { appendFile, mkdir as
|
|
4292
|
+
var import_errors13 = __toESM(require_dist2(), 1);
|
|
4293
|
+
import { appendFile as appendFile2, mkdir as mkdir6 } from "fs/promises";
|
|
2405
4294
|
import { dirname } from "path";
|
|
2406
|
-
var
|
|
4295
|
+
var logger11 = (0, import_errors13.createMcpLogger)({ name: "@toolcairn/mcp-server:event-logger" });
|
|
2407
4296
|
function isTrackingEnabled() {
|
|
2408
4297
|
return process.env.TOOLCAIRN_TRACKING_ENABLED !== "false";
|
|
2409
4298
|
}
|
|
@@ -2443,11 +4332,11 @@ function extractMetadata(toolName, result) {
|
|
|
2443
4332
|
}
|
|
2444
4333
|
async function writeToFile(eventsPath, event) {
|
|
2445
4334
|
try {
|
|
2446
|
-
await
|
|
2447
|
-
await
|
|
4335
|
+
await mkdir6(dirname(eventsPath), { recursive: true });
|
|
4336
|
+
await appendFile2(eventsPath, `${JSON.stringify(event)}
|
|
2448
4337
|
`, "utf-8");
|
|
2449
4338
|
} catch (e) {
|
|
2450
|
-
|
|
4339
|
+
logger11.warn({ err: e, path: eventsPath }, "Failed to write event to JSONL file");
|
|
2451
4340
|
}
|
|
2452
4341
|
}
|
|
2453
4342
|
async function sendToApi(event) {
|
|
@@ -2469,7 +4358,7 @@ async function sendToApi(event) {
|
|
|
2469
4358
|
})
|
|
2470
4359
|
});
|
|
2471
4360
|
} catch (e) {
|
|
2472
|
-
|
|
4361
|
+
logger11.debug({ err: e }, "Failed to send event to API \u2014 non-fatal");
|
|
2473
4362
|
}
|
|
2474
4363
|
}
|
|
2475
4364
|
function withEventLogging(toolName, handler) {
|
|
@@ -2510,35 +4399,47 @@ function withEventLogging(toolName, handler) {
|
|
|
2510
4399
|
}
|
|
2511
4400
|
|
|
2512
4401
|
// src/server.prod.ts
|
|
2513
|
-
var
|
|
4402
|
+
var logger12 = (0, import_errors14.createMcpLogger)({ name: "@toolcairn/mcp-server:prod" });
|
|
2514
4403
|
var SETUP_INSTRUCTIONS = `
|
|
2515
4404
|
# ToolCairn \u2014 Agent Setup Instructions
|
|
2516
4405
|
|
|
2517
4406
|
You are connected to the ToolCairn MCP server. ToolCairn is your active tool intelligence co-pilot.
|
|
2518
4407
|
|
|
4408
|
+
## The Server Owns .toolcairn/ (v0.10.0+)
|
|
4409
|
+
|
|
4410
|
+
The MCP server reads and writes \`.toolcairn/config.json\` + \`.toolcairn/audit-log.jsonl\`
|
|
4411
|
+
atomically under a cross-process lock. You never touch those files directly.
|
|
4412
|
+
|
|
2519
4413
|
## On Every Session Start \u2014 Do This First
|
|
2520
4414
|
|
|
2521
|
-
|
|
2522
|
-
|
|
4415
|
+
Call \`read_project_config\` with \`project_root\` (absolute path to the user's project).
|
|
4416
|
+
- If it returns \`status: "not_initialized"\`: the project has no config yet. Call
|
|
4417
|
+
\`toolcairn_init\` with \`agent\` (your type) + \`project_root\`. The server walks
|
|
4418
|
+
every workspace, parses manifests across 12 ecosystems, classifies tools against
|
|
4419
|
+
the ToolCairn graph, and atomically writes \`.toolcairn/config.json\`.
|
|
4420
|
+
After that, apply the returned \`setup_steps\` (append rules to CLAUDE.md,
|
|
4421
|
+
merge the toolcairn entry into .mcp.json, update .gitignore).
|
|
4422
|
+
- If it returns \`status: "ready"\`: you have the full project snapshot. Review
|
|
4423
|
+
\`stale_tools\` / \`pending_tools\` and proceed with work.
|
|
4424
|
+
|
|
4425
|
+
## Applying Mutations
|
|
2523
4426
|
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
4427
|
+
When the user selects / replaces / drops a tool, call \`update_project_config\`
|
|
4428
|
+
with \`project_root\`, \`action\` (add_tool / remove_tool / update_tool / add_evaluation),
|
|
4429
|
+
and \`tool_name\`. The server:
|
|
4430
|
+
- atomically updates \`.toolcairn/config.json\`,
|
|
4431
|
+
- appends a new line to \`.toolcairn/audit-log.jsonl\` (FIFO-archived at 1000 entries),
|
|
4432
|
+
- returns the new \`last_audit_entry\` for your record.
|
|
2529
4433
|
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
1. List the project root files to detect the tech stack.
|
|
2533
|
-
2. Call \`toolcairn_init\` with your agent type, project root, and detected files.
|
|
2534
|
-
This appends ToolCairn rules to CLAUDE.md (or equivalent) and sets up the tracker.
|
|
2535
|
-
3. Call \`init_project_config\` with the project name, language, framework, and detected tools.
|
|
2536
|
-
4. Write the returned \`config_json\` to \`.toolcairn/config.json\` (overwrites the empty scaffold).
|
|
4434
|
+
Do NOT construct or write these files yourself \u2014 you do not have the cross-process
|
|
4435
|
+
lock that protects them.
|
|
2537
4436
|
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
4437
|
+
## Schema Migration
|
|
4438
|
+
|
|
4439
|
+
Configs written by v0.9.x are on schema 1.0. The first \`read_project_config\` or
|
|
4440
|
+
\`update_project_config\` call after upgrade migrates in place to 1.1 (languages \u2192
|
|
4441
|
+
array, frameworks \u2192 array, \`audit_log[]\` relocated from config.json to
|
|
4442
|
+
audit-log.jsonl). The migration is logged as an audit entry.
|
|
2542
4443
|
|
|
2543
4444
|
## When to Use ToolCairn Tools
|
|
2544
4445
|
|
|
@@ -2564,9 +4465,9 @@ async function addToolsToServer(server) {
|
|
|
2564
4465
|
apiKey: creds.client_id,
|
|
2565
4466
|
accessToken: creds.access_token
|
|
2566
4467
|
});
|
|
2567
|
-
|
|
4468
|
+
logger12.info({ user: creds.user_email }, "Registering production tools");
|
|
2568
4469
|
function wrap(toolName, fn) {
|
|
2569
|
-
return withEventLogging(toolName, (0,
|
|
4470
|
+
return withEventLogging(toolName, (0, import_errors14.withErrorHandling)(toolName, logger12, fn));
|
|
2570
4471
|
}
|
|
2571
4472
|
server.registerTool(
|
|
2572
4473
|
"classify_prompt",
|
|
@@ -2582,29 +4483,20 @@ async function addToolsToServer(server) {
|
|
|
2582
4483
|
server.registerTool(
|
|
2583
4484
|
"toolcairn_init",
|
|
2584
4485
|
{
|
|
2585
|
-
description: "
|
|
2586
|
-
inputSchema:
|
|
4486
|
+
description: "Bootstrap ToolCairn for the current project. Walks every workspace, parses manifests across 12 ecosystems, classifies tools against the ToolCairn graph, and writes .toolcairn/config.json + audit-log.jsonl atomically. Returns setup_steps for CLAUDE.md / .mcp.json / .gitignore (agent applies those).",
|
|
4487
|
+
inputSchema: toolcairnInitSchema
|
|
2587
4488
|
},
|
|
2588
4489
|
wrap(
|
|
2589
4490
|
"toolcairn_init",
|
|
2590
|
-
async (args) => handleToolcairnInit(args
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
server.registerTool(
|
|
2594
|
-
"init_project_config",
|
|
2595
|
-
{
|
|
2596
|
-
description: "Initialize a .toolcairn/config.json file for the current project. Returns the config JSON for the agent to write to disk.",
|
|
2597
|
-
inputSchema: initProjectConfigSchema
|
|
2598
|
-
},
|
|
2599
|
-
wrap(
|
|
2600
|
-
"init_project_config",
|
|
2601
|
-
async (args) => handleInitProjectConfig(args)
|
|
4491
|
+
async (args) => handleToolcairnInit(args, {
|
|
4492
|
+
batchResolve: (items) => remote.batchResolve(items)
|
|
4493
|
+
})
|
|
2602
4494
|
)
|
|
2603
4495
|
);
|
|
2604
4496
|
server.registerTool(
|
|
2605
4497
|
"read_project_config",
|
|
2606
4498
|
{
|
|
2607
|
-
description: "
|
|
4499
|
+
description: "Read .toolcairn/config.json from disk and return the structured project snapshot: project metadata, confirmed tools, stale tools, pending evaluations, and last audit entry. Auto-migrates v1.0 configs to v1.1 on first read.",
|
|
2608
4500
|
inputSchema: readProjectConfigSchema
|
|
2609
4501
|
},
|
|
2610
4502
|
wrap(
|
|
@@ -2615,7 +4507,7 @@ async function addToolsToServer(server) {
|
|
|
2615
4507
|
server.registerTool(
|
|
2616
4508
|
"update_project_config",
|
|
2617
4509
|
{
|
|
2618
|
-
description: "Apply a mutation to .toolcairn/config.json and
|
|
4510
|
+
description: "Apply a mutation to .toolcairn/config.json (add_tool / remove_tool / update_tool / add_evaluation). The server atomically rewrites config.json and appends a new line to audit-log.jsonl under a cross-process lock. Requires project_root.",
|
|
2619
4511
|
inputSchema: updateProjectConfigSchema
|
|
2620
4512
|
},
|
|
2621
4513
|
wrap(
|
|
@@ -2795,14 +4687,14 @@ function createTransport() {
|
|
|
2795
4687
|
|
|
2796
4688
|
// src/index.prod.ts
|
|
2797
4689
|
process.env.TOOLPILOT_MODE = "production";
|
|
2798
|
-
var
|
|
4690
|
+
var logger13 = (0, import_errors15.createMcpLogger)({ name: "@toolcairn/mcp-server" });
|
|
2799
4691
|
async function main() {
|
|
2800
4692
|
await ensureProjectSetup();
|
|
2801
4693
|
const creds = await loadCredentials();
|
|
2802
4694
|
const authenticated = creds !== null && isTokenValid(creds);
|
|
2803
4695
|
let server;
|
|
2804
4696
|
if (authenticated) {
|
|
2805
|
-
|
|
4697
|
+
logger13.info({ user: creds.user_email }, "Authenticated \u2014 starting full server");
|
|
2806
4698
|
server = await buildProdServer();
|
|
2807
4699
|
} else {
|
|
2808
4700
|
let verificationUri = "https://toolcairn.neurynae.com/signup";
|
|
@@ -2812,15 +4704,15 @@ async function main() {
|
|
|
2812
4704
|
if (pending) {
|
|
2813
4705
|
verificationUri = pending.verification_uri;
|
|
2814
4706
|
userCode = pending.user_code;
|
|
2815
|
-
|
|
4707
|
+
logger13.info({ userCode }, "Resuming pending sign-in");
|
|
2816
4708
|
} else {
|
|
2817
4709
|
const codeData = await requestDeviceCode(import_config4.config.TOOLPILOT_API_URL);
|
|
2818
4710
|
verificationUri = codeData.verification_uri;
|
|
2819
4711
|
userCode = codeData.user_code;
|
|
2820
|
-
|
|
4712
|
+
logger13.info({ userCode }, "New sign-in started");
|
|
2821
4713
|
}
|
|
2822
4714
|
} catch (err) {
|
|
2823
|
-
|
|
4715
|
+
logger13.error({ err }, "Could not reach ToolCairn API \u2014 check your connection");
|
|
2824
4716
|
}
|
|
2825
4717
|
const instructions = userCode ? `# ToolCairn \u2014 Sign In Required
|
|
2826
4718
|
|
|
@@ -2852,23 +4744,23 @@ Open the URL, sign in, and confirm the code shown. All 14 tools will appear auto
|
|
|
2852
4744
|
})
|
|
2853
4745
|
);
|
|
2854
4746
|
startDeviceAuth(import_config4.config.TOOLPILOT_API_URL).then(async () => {
|
|
2855
|
-
|
|
4747
|
+
logger13.info("Sign-in complete \u2014 adding all tools to running server");
|
|
2856
4748
|
try {
|
|
2857
4749
|
await addToolsToServer(server);
|
|
2858
|
-
|
|
4750
|
+
logger13.info("All ToolCairn tools now available");
|
|
2859
4751
|
} catch (err) {
|
|
2860
|
-
|
|
4752
|
+
logger13.error({ err }, "Failed to add tools after sign-in \u2014 please reconnect");
|
|
2861
4753
|
}
|
|
2862
4754
|
}).catch((err) => {
|
|
2863
|
-
|
|
4755
|
+
logger13.error({ err }, "Sign-in failed \u2014 please try again");
|
|
2864
4756
|
});
|
|
2865
4757
|
}
|
|
2866
4758
|
const transport = createTransport();
|
|
2867
4759
|
await server.connect(transport);
|
|
2868
|
-
|
|
4760
|
+
logger13.info(authenticated ? "ToolCairn MCP ready" : "ToolCairn MCP ready (awaiting sign-in)");
|
|
2869
4761
|
}
|
|
2870
4762
|
main().catch((error) => {
|
|
2871
|
-
(0,
|
|
4763
|
+
(0, import_errors15.createMcpLogger)({ name: "@toolcairn/mcp-server" }).error(
|
|
2872
4764
|
{ err: error },
|
|
2873
4765
|
"Failed to start MCP server"
|
|
2874
4766
|
);
|