@neurynae/toolcairn-mcp 0.10.3 → 0.10.5
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 +236 -8
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -923,7 +923,7 @@ async function pollForToken(apiUrl, deviceCode, intervalSec) {
|
|
|
923
923
|
}
|
|
924
924
|
}
|
|
925
925
|
function sleep(ms) {
|
|
926
|
-
return new Promise((
|
|
926
|
+
return new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
927
927
|
}
|
|
928
928
|
|
|
929
929
|
// src/index.prod.ts
|
|
@@ -1165,6 +1165,9 @@ var import_errors21 = __toESM(require_dist2(), 1);
|
|
|
1165
1165
|
// ../../packages/tools-local/dist/auto-init.js
|
|
1166
1166
|
init_esm_shims();
|
|
1167
1167
|
var import_errors20 = __toESM(require_dist2(), 1);
|
|
1168
|
+
import { readFile as readFile28 } from "fs/promises";
|
|
1169
|
+
import { resolve as resolve3 } from "path";
|
|
1170
|
+
import writeFileAtomic3 from "write-file-atomic";
|
|
1168
1171
|
|
|
1169
1172
|
// ../../packages/tools-local/dist/config-store/index.js
|
|
1170
1173
|
init_esm_shims();
|
|
@@ -4481,6 +4484,9 @@ function getOpenCodeMcpEntry(serverPath) {
|
|
|
4481
4484
|
|
|
4482
4485
|
// ../../packages/tools-local/dist/auto-init.js
|
|
4483
4486
|
var logger18 = (0, import_errors20.createMcpLogger)({ name: "@toolcairn/tools:auto-init" });
|
|
4487
|
+
var INSTRUCTION_SENTINEL = "## ToolCairn \u2014 Tool Intelligence MCP";
|
|
4488
|
+
var GITIGNORE_SENTINEL = "# ToolCairn";
|
|
4489
|
+
var GITIGNORE_BLOCK = "\n# ToolCairn\n.toolcairn/events.jsonl\n.toolcairn/audit-log.jsonl\n.toolcairn/audit-log.archive.jsonl\n.toolcairn/config.lock\n";
|
|
4484
4490
|
async function autoInitProject(input) {
|
|
4485
4491
|
const { projectRoot, agent, batchResolve, serverPath, reason } = input;
|
|
4486
4492
|
logger18.info({ projectRoot, agent }, "autoInitProject starting");
|
|
@@ -4546,10 +4552,23 @@ async function autoInitProject(input) {
|
|
|
4546
4552
|
step: 3,
|
|
4547
4553
|
action: "append",
|
|
4548
4554
|
file: ".gitignore",
|
|
4549
|
-
content:
|
|
4555
|
+
content: GITIGNORE_BLOCK,
|
|
4550
4556
|
note: "Ignore runtime/audit files. config.json should be committed so teammates share tool intelligence."
|
|
4551
4557
|
}
|
|
4552
4558
|
];
|
|
4559
|
+
const applied_steps = input.skipSetupFileWrites ? setupSteps.map((s) => ({
|
|
4560
|
+
file: s.file,
|
|
4561
|
+
action: s.action,
|
|
4562
|
+
applied: false,
|
|
4563
|
+
reason: "skipSetupFileWrites=true"
|
|
4564
|
+
})) : await applySetupFiles(projectRoot, {
|
|
4565
|
+
agent,
|
|
4566
|
+
instructionFile: instructions.file_path,
|
|
4567
|
+
instructionContent: instructions.content,
|
|
4568
|
+
mcpConfigFile,
|
|
4569
|
+
mcpConfigEntry,
|
|
4570
|
+
isOpenCode
|
|
4571
|
+
});
|
|
4553
4572
|
const tool_counts = {
|
|
4554
4573
|
total: config5.tools.confirmed.length,
|
|
4555
4574
|
indexed: config5.tools.confirmed.filter((t) => t.source === "toolcairn").length,
|
|
@@ -4564,6 +4583,7 @@ async function autoInitProject(input) {
|
|
|
4564
4583
|
events_path: ".toolcairn/events.jsonl",
|
|
4565
4584
|
mcp_config_entry: mcpConfigEntry,
|
|
4566
4585
|
setup_steps: setupSteps,
|
|
4586
|
+
applied_steps,
|
|
4567
4587
|
scan_summary: {
|
|
4568
4588
|
project_name: scan.name,
|
|
4569
4589
|
languages: scan.languages.map((l) => ({ name: l.name, file_count: l.file_count })),
|
|
@@ -4579,6 +4599,108 @@ async function autoInitProject(input) {
|
|
|
4579
4599
|
unknown_tools: undrained
|
|
4580
4600
|
};
|
|
4581
4601
|
}
|
|
4602
|
+
async function applySetupFiles(projectRoot, args) {
|
|
4603
|
+
const results = [];
|
|
4604
|
+
results.push(await applyInstructionFile(resolve3(projectRoot, args.instructionFile), args.instructionFile, args.instructionContent));
|
|
4605
|
+
results.push(await applyMcpConfig(resolve3(projectRoot, args.mcpConfigFile), args.mcpConfigFile, args.mcpConfigEntry, args.isOpenCode));
|
|
4606
|
+
results.push(await applyGitignore(resolve3(projectRoot, ".gitignore")));
|
|
4607
|
+
return results;
|
|
4608
|
+
}
|
|
4609
|
+
async function applyInstructionFile(abs, relPath, content) {
|
|
4610
|
+
try {
|
|
4611
|
+
const exists = await fileExists(abs);
|
|
4612
|
+
if (exists) {
|
|
4613
|
+
const current = await readFile28(abs, "utf-8");
|
|
4614
|
+
if (current.includes(INSTRUCTION_SENTINEL)) {
|
|
4615
|
+
return {
|
|
4616
|
+
file: relPath,
|
|
4617
|
+
action: "append-or-create",
|
|
4618
|
+
applied: false,
|
|
4619
|
+
reason: "ToolCairn rules block already present"
|
|
4620
|
+
};
|
|
4621
|
+
}
|
|
4622
|
+
const separator = current.endsWith("\n") ? "" : "\n";
|
|
4623
|
+
await writeFileAtomic3(abs, `${current}${separator}${content}`, "utf-8");
|
|
4624
|
+
return { file: relPath, action: "append-or-create", applied: true };
|
|
4625
|
+
}
|
|
4626
|
+
await writeFileAtomic3(abs, content, "utf-8");
|
|
4627
|
+
return { file: relPath, action: "append-or-create", applied: true };
|
|
4628
|
+
} catch (err) {
|
|
4629
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
4630
|
+
logger18.warn({ err, file: relPath }, "Failed to write instruction file");
|
|
4631
|
+
return { file: relPath, action: "append-or-create", applied: false, reason };
|
|
4632
|
+
}
|
|
4633
|
+
}
|
|
4634
|
+
async function applyMcpConfig(abs, relPath, entry, isOpenCode) {
|
|
4635
|
+
try {
|
|
4636
|
+
const exists = await fileExists(abs);
|
|
4637
|
+
const topKey = isOpenCode ? "mcp" : "mcpServers";
|
|
4638
|
+
if (!exists) {
|
|
4639
|
+
const payload = { [topKey]: entry };
|
|
4640
|
+
await writeFileAtomic3(abs, `${JSON.stringify(payload, null, 2)}
|
|
4641
|
+
`, "utf-8");
|
|
4642
|
+
return { file: relPath, action: "merge-or-create", applied: true };
|
|
4643
|
+
}
|
|
4644
|
+
const raw = await readFile28(abs, "utf-8");
|
|
4645
|
+
let parsed;
|
|
4646
|
+
try {
|
|
4647
|
+
parsed = JSON.parse(raw);
|
|
4648
|
+
} catch {
|
|
4649
|
+
return {
|
|
4650
|
+
file: relPath,
|
|
4651
|
+
action: "merge-or-create",
|
|
4652
|
+
applied: false,
|
|
4653
|
+
reason: "existing file is not valid JSON \u2014 refusing to overwrite; fix manually"
|
|
4654
|
+
};
|
|
4655
|
+
}
|
|
4656
|
+
const existingServers = parsed[topKey] && typeof parsed[topKey] === "object" && !Array.isArray(parsed[topKey]) ? parsed[topKey] : {};
|
|
4657
|
+
if (existingServers.toolcairn !== void 0) {
|
|
4658
|
+
return {
|
|
4659
|
+
file: relPath,
|
|
4660
|
+
action: "merge-or-create",
|
|
4661
|
+
applied: false,
|
|
4662
|
+
reason: `${topKey}.toolcairn already present`
|
|
4663
|
+
};
|
|
4664
|
+
}
|
|
4665
|
+
const merged = {
|
|
4666
|
+
...parsed,
|
|
4667
|
+
[topKey]: { ...existingServers, ...entry }
|
|
4668
|
+
};
|
|
4669
|
+
await writeFileAtomic3(abs, `${JSON.stringify(merged, null, 2)}
|
|
4670
|
+
`, "utf-8");
|
|
4671
|
+
return { file: relPath, action: "merge-or-create", applied: true };
|
|
4672
|
+
} catch (err) {
|
|
4673
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
4674
|
+
logger18.warn({ err, file: relPath }, "Failed to write MCP config");
|
|
4675
|
+
return { file: relPath, action: "merge-or-create", applied: false, reason };
|
|
4676
|
+
}
|
|
4677
|
+
}
|
|
4678
|
+
async function applyGitignore(abs) {
|
|
4679
|
+
const relPath = ".gitignore";
|
|
4680
|
+
try {
|
|
4681
|
+
const exists = await fileExists(abs);
|
|
4682
|
+
if (!exists) {
|
|
4683
|
+
await writeFileAtomic3(abs, GITIGNORE_BLOCK.replace(/^\n/, ""), "utf-8");
|
|
4684
|
+
return { file: relPath, action: "append", applied: true };
|
|
4685
|
+
}
|
|
4686
|
+
const current = await readFile28(abs, "utf-8");
|
|
4687
|
+
if (current.includes(GITIGNORE_SENTINEL)) {
|
|
4688
|
+
return {
|
|
4689
|
+
file: relPath,
|
|
4690
|
+
action: "append",
|
|
4691
|
+
applied: false,
|
|
4692
|
+
reason: "ToolCairn gitignore block already present"
|
|
4693
|
+
};
|
|
4694
|
+
}
|
|
4695
|
+
const separator = current.endsWith("\n") ? "" : "\n";
|
|
4696
|
+
await writeFileAtomic3(abs, `${current}${separator}${GITIGNORE_BLOCK}`, "utf-8");
|
|
4697
|
+
return { file: relPath, action: "append", applied: true };
|
|
4698
|
+
} catch (err) {
|
|
4699
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
4700
|
+
logger18.warn({ err, file: relPath }, "Failed to write .gitignore");
|
|
4701
|
+
return { file: relPath, action: "append", applied: false, reason };
|
|
4702
|
+
}
|
|
4703
|
+
}
|
|
4582
4704
|
|
|
4583
4705
|
// ../../packages/tools-local/dist/handlers/toolcairn-init.js
|
|
4584
4706
|
var logger19 = (0, import_errors21.createMcpLogger)({ name: "@toolcairn/tools:toolcairn-init" });
|
|
@@ -4931,7 +5053,45 @@ async function runPostAuthInit(options = {}) {
|
|
|
4931
5053
|
});
|
|
4932
5054
|
}
|
|
4933
5055
|
}
|
|
4934
|
-
|
|
5056
|
+
if (!options.disableAutoSubmit) {
|
|
5057
|
+
for (const project of projects) {
|
|
5058
|
+
if (project.status !== "initialized") continue;
|
|
5059
|
+
const pending = (project.unknown_tools ?? []).filter((t) => !!t.github_url);
|
|
5060
|
+
if (pending.length === 0) continue;
|
|
5061
|
+
try {
|
|
5062
|
+
const outcome = await submitUnknownsToEngine(remote, pending);
|
|
5063
|
+
project.auto_submitted = outcome;
|
|
5064
|
+
const toMark = [...outcome.staged, ...outcome.already_staged, ...outcome.already_indexed];
|
|
5065
|
+
if (toMark.length > 0) {
|
|
5066
|
+
await markSuggestedInConfig(project.project_root, toMark).catch(
|
|
5067
|
+
(err) => logger22.warn(
|
|
5068
|
+
{ err, projectRoot: project.project_root },
|
|
5069
|
+
"Failed to flip suggested flags after auto-submit"
|
|
5070
|
+
)
|
|
5071
|
+
);
|
|
5072
|
+
}
|
|
5073
|
+
logger22.info(
|
|
5074
|
+
{
|
|
5075
|
+
projectRoot: project.project_root,
|
|
5076
|
+
staged: outcome.staged.length,
|
|
5077
|
+
already_staged: outcome.already_staged.length,
|
|
5078
|
+
already_indexed: outcome.already_indexed.length,
|
|
5079
|
+
rejected: outcome.rejected.length
|
|
5080
|
+
},
|
|
5081
|
+
"Auto-push to suggest_graph_update complete"
|
|
5082
|
+
);
|
|
5083
|
+
} catch (err) {
|
|
5084
|
+
logger22.warn(
|
|
5085
|
+
{ err, projectRoot: project.project_root },
|
|
5086
|
+
"Auto-push to suggest_graph_update failed \u2014 agent directive remains as fallback"
|
|
5087
|
+
);
|
|
5088
|
+
}
|
|
5089
|
+
}
|
|
5090
|
+
}
|
|
5091
|
+
const unknownTotal = projects.reduce(
|
|
5092
|
+
(sum, p) => sum + (p.unknown_tools ?? []).filter((t) => !t.suggested).length,
|
|
5093
|
+
0
|
|
5094
|
+
);
|
|
4935
5095
|
const directive = buildFirstTurnDirective(projects, unknownTotal);
|
|
4936
5096
|
return {
|
|
4937
5097
|
cwd,
|
|
@@ -4942,19 +5102,87 @@ async function runPostAuthInit(options = {}) {
|
|
|
4942
5102
|
first_turn_directive: directive
|
|
4943
5103
|
};
|
|
4944
5104
|
}
|
|
5105
|
+
async function submitUnknownsToEngine(remote, pending) {
|
|
5106
|
+
const res = await remote.suggestGraphUpdate({
|
|
5107
|
+
suggestion_type: "new_tool",
|
|
5108
|
+
data: {
|
|
5109
|
+
tools: pending.map((t) => ({ tool_name: t.name, github_url: t.github_url }))
|
|
5110
|
+
},
|
|
5111
|
+
confidence: 0.5
|
|
5112
|
+
});
|
|
5113
|
+
const textBlock = res.content?.[0];
|
|
5114
|
+
const outcome = {
|
|
5115
|
+
staged: [],
|
|
5116
|
+
already_staged: [],
|
|
5117
|
+
already_indexed: [],
|
|
5118
|
+
rejected: []
|
|
5119
|
+
};
|
|
5120
|
+
if (!textBlock || textBlock.type !== "text") return outcome;
|
|
5121
|
+
let envelope;
|
|
5122
|
+
try {
|
|
5123
|
+
envelope = JSON.parse(textBlock.text ?? "{}");
|
|
5124
|
+
} catch {
|
|
5125
|
+
return outcome;
|
|
5126
|
+
}
|
|
5127
|
+
const items = envelope.data?.results ?? [];
|
|
5128
|
+
for (const item of items) {
|
|
5129
|
+
const name = item.tool_name ?? "";
|
|
5130
|
+
if (!name) continue;
|
|
5131
|
+
if (item.already_indexed) {
|
|
5132
|
+
outcome.already_indexed.push(name);
|
|
5133
|
+
} else if (item.already_staged) {
|
|
5134
|
+
outcome.already_staged.push(name);
|
|
5135
|
+
} else if (item.staged === true) {
|
|
5136
|
+
outcome.staged.push(name);
|
|
5137
|
+
} else {
|
|
5138
|
+
outcome.rejected.push({ tool_name: name, reason: item.reason ?? "unknown" });
|
|
5139
|
+
}
|
|
5140
|
+
}
|
|
5141
|
+
return outcome;
|
|
5142
|
+
}
|
|
5143
|
+
async function markSuggestedInConfig(projectRoot, toolNames) {
|
|
5144
|
+
const wanted = new Set(toolNames);
|
|
5145
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5146
|
+
let changed = 0;
|
|
5147
|
+
await mutateConfig(
|
|
5148
|
+
projectRoot,
|
|
5149
|
+
(cfg) => {
|
|
5150
|
+
const list = cfg.tools.unknown_in_graph ?? [];
|
|
5151
|
+
for (const entry of list) {
|
|
5152
|
+
if (wanted.has(entry.name) && !entry.suggested) {
|
|
5153
|
+
entry.suggested = true;
|
|
5154
|
+
entry.suggested_at = now;
|
|
5155
|
+
changed++;
|
|
5156
|
+
}
|
|
5157
|
+
}
|
|
5158
|
+
cfg.tools.unknown_in_graph = list;
|
|
5159
|
+
},
|
|
5160
|
+
{
|
|
5161
|
+
action: "mark_suggestions_sent",
|
|
5162
|
+
tool: `__auto_submit__:${toolNames.length}`,
|
|
5163
|
+
reason: "Server auto-pushed unknown_in_graph tools to suggest_graph_update"
|
|
5164
|
+
}
|
|
5165
|
+
);
|
|
5166
|
+
if (changed === 0) {
|
|
5167
|
+
logger22.debug({ projectRoot, toolNames }, "No unknown_in_graph entries flipped");
|
|
5168
|
+
}
|
|
5169
|
+
}
|
|
4945
5170
|
function buildFirstTurnDirective(projects, unknownTotal) {
|
|
4946
5171
|
if (unknownTotal === 0) return "";
|
|
4947
|
-
const rootsWithUnknown = projects.filter(
|
|
5172
|
+
const rootsWithUnknown = projects.filter(
|
|
5173
|
+
(p) => (p.unknown_tools ?? []).filter((t) => !t.suggested).length > 0
|
|
5174
|
+
);
|
|
5175
|
+
if (rootsWithUnknown.length === 0) return "";
|
|
4948
5176
|
const roots = rootsWithUnknown.map((p) => p.project_root).join(", ");
|
|
4949
5177
|
return [
|
|
4950
5178
|
`The auto-init scan found ${unknownTotal} tool(s) not yet in the ToolCairn graph`,
|
|
4951
5179
|
`across ${rootsWithUnknown.length} project root(s): ${roots}.`,
|
|
4952
|
-
"
|
|
5180
|
+
"The server has already auto-submitted these to the admin review queue,",
|
|
5181
|
+
"but you MAY repeat the call below to confirm \u2014 engine-side dedup prevents duplicates:",
|
|
4953
5182
|
'(1) call `suggest_graph_update` with suggestion_type="new_tool" and',
|
|
4954
|
-
" data.tools=<that root's unknown_tools array
|
|
4955
|
-
" for admin review (no live-graph writes yet);",
|
|
5183
|
+
" data.tools=<that root's unknown_tools array>;",
|
|
4956
5184
|
'(2) call `update_project_config` with action="mark_suggestions_sent",',
|
|
4957
|
-
" project_root, and data.tool_names=<names that staged
|
|
5185
|
+
" project_root, and data.tool_names=<names that staged or were already_staged>."
|
|
4958
5186
|
].join(" ");
|
|
4959
5187
|
}
|
|
4960
5188
|
|