@neat.is/core 0.9.2-dev.20260821 → 0.9.2
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/{chunk-RQQUI3NQ.js → chunk-HD5X5TWY.js} +690 -46
- package/dist/chunk-HD5X5TWY.js.map +1 -0
- package/dist/{chunk-L4SZIIER.js → chunk-TMHCS4ZY.js} +2 -2
- package/dist/cli.cjs +1148 -279
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +333 -110
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +733 -88
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +733 -88
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +732 -87
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-RQQUI3NQ.js.map +0 -1
- /package/dist/{chunk-L4SZIIER.js.map → chunk-TMHCS4ZY.js.map} +0 -0
package/dist/cli.js
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
resolveHost,
|
|
7
7
|
resolveNeatVersion,
|
|
8
8
|
writeDaemonRecord
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-TMHCS4ZY.js";
|
|
10
10
|
import {
|
|
11
11
|
buildSearchIndex
|
|
12
12
|
} from "./chunk-BC53SCT7.js";
|
|
@@ -74,7 +74,7 @@ import {
|
|
|
74
74
|
startStalenessLoop,
|
|
75
75
|
upsertConnectorEntry,
|
|
76
76
|
validateConnectorEntry
|
|
77
|
-
} from "./chunk-
|
|
77
|
+
} from "./chunk-HD5X5TWY.js";
|
|
78
78
|
import {
|
|
79
79
|
startOtelGrpcReceiver
|
|
80
80
|
} from "./chunk-ERE47MCR.js";
|
|
@@ -88,9 +88,9 @@ import {
|
|
|
88
88
|
} from "./chunk-GDGUY4T6.js";
|
|
89
89
|
|
|
90
90
|
// src/cli.ts
|
|
91
|
-
import
|
|
91
|
+
import path16 from "path";
|
|
92
92
|
import os4 from "os";
|
|
93
|
-
import { promises as
|
|
93
|
+
import { promises as fs14 } from "fs";
|
|
94
94
|
|
|
95
95
|
// src/banner.ts
|
|
96
96
|
import path from "path";
|
|
@@ -3769,7 +3769,7 @@ async function extractAndPersist(opts) {
|
|
|
3769
3769
|
}
|
|
3770
3770
|
async function applyInstallersOver(services, project, options = {}) {
|
|
3771
3771
|
const resolveManager = options.resolveManager ?? detectPackageManager;
|
|
3772
|
-
const
|
|
3772
|
+
const runInstall2 = options.runInstall ?? runPackageManagerInstall;
|
|
3773
3773
|
let instrumented = 0;
|
|
3774
3774
|
let already = 0;
|
|
3775
3775
|
let libOnly = 0;
|
|
@@ -3892,7 +3892,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
3892
3892
|
const packageManagerInstalls = [];
|
|
3893
3893
|
for (const cmd of installPlans.values()) {
|
|
3894
3894
|
console.log(`running \`${cmd.pm} ${cmd.args.join(" ")}\` in ${cmd.cwd}`);
|
|
3895
|
-
const result = await
|
|
3895
|
+
const result = await runInstall2(cmd);
|
|
3896
3896
|
packageManagerInstalls.push(result);
|
|
3897
3897
|
if (result.exitCode !== 0) {
|
|
3898
3898
|
console.error(
|
|
@@ -4869,6 +4869,9 @@ function claudeSettingsPath() {
|
|
|
4869
4869
|
function installedHookPath() {
|
|
4870
4870
|
return path11.join(neatHome(), "hooks", HOOK_FILENAME);
|
|
4871
4871
|
}
|
|
4872
|
+
function gateFlagPath() {
|
|
4873
|
+
return path11.join(neatHome(), "hooks", "gate-enabled");
|
|
4874
|
+
}
|
|
4872
4875
|
function isNeatSearchEntry(entry2) {
|
|
4873
4876
|
return (entry2.hooks ?? []).some(
|
|
4874
4877
|
(h) => typeof h.command === "string" && h.command.includes(HOOK_FILENAME)
|
|
@@ -4931,13 +4934,30 @@ async function runHooks(opts) {
|
|
|
4931
4934
|
};
|
|
4932
4935
|
await fs10.mkdir(path11.dirname(settingsFile), { recursive: true });
|
|
4933
4936
|
await fs10.writeFile(settingsFile, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
4934
|
-
|
|
4937
|
+
const flag = gateFlagPath();
|
|
4938
|
+
if (opts.gate) {
|
|
4939
|
+
await fs10.mkdir(path11.dirname(flag), { recursive: true });
|
|
4940
|
+
await fs10.writeFile(flag, "1\n", "utf8");
|
|
4941
|
+
} else {
|
|
4942
|
+
await fs10.rm(flag, { force: true });
|
|
4943
|
+
}
|
|
4944
|
+
const mode = opts.gate ? "GATE (deny search until you ask the graph)" : "nudge (search still runs)";
|
|
4945
|
+
console.log(`neat hooks: installed the search hook in ${opts.gate ? "gate" : "nudge"} mode`);
|
|
4935
4946
|
console.log(` script: ${scriptPath}`);
|
|
4936
4947
|
console.log(` settings: ${settingsFile} (PreToolUse \u2192 ${HOOK_MATCHER})`);
|
|
4937
4948
|
console.log(` guidance: ${guidePath}`);
|
|
4949
|
+
console.log(` mode: ${mode}`);
|
|
4938
4950
|
console.log("");
|
|
4939
|
-
|
|
4940
|
-
|
|
4951
|
+
if (opts.gate) {
|
|
4952
|
+
console.log("restart Claude Code to load the hook. A Grep/Glob or Bash grep is now DENIED");
|
|
4953
|
+
console.log('until you run `neat ask "<question>"` (or the ask MCP tool) once this session;');
|
|
4954
|
+
console.log("after that, search is allowed as a fallback. Set NEAT_SEARCH_GATE=0 to fall");
|
|
4955
|
+
console.log("back to nudge-only without re-running.");
|
|
4956
|
+
} else {
|
|
4957
|
+
console.log("restart Claude Code to load the hook. On a Grep/Glob or a Bash grep,");
|
|
4958
|
+
console.log("your agent will now be nudged to query NEAT first (the search still runs).");
|
|
4959
|
+
console.log("Re-run with --gate to hard-force the graph-first orientation.");
|
|
4960
|
+
}
|
|
4941
4961
|
console.log("");
|
|
4942
4962
|
console.log("The hook is Claude-Code-specific. For agents on other harnesses, paste");
|
|
4943
4963
|
console.log(`the guidance above into your project instructions (CLAUDE.md / AGENTS.md).`);
|
|
@@ -4949,28 +4969,36 @@ async function runHooks(opts) {
|
|
|
4949
4969
|
function usage() {
|
|
4950
4970
|
console.log("neat hooks \u2014 wire NEAT into your agent so it queries the graph before grepping");
|
|
4951
4971
|
console.log("");
|
|
4952
|
-
console.log(" --apply install the Claude Code search
|
|
4972
|
+
console.log(" --apply install the Claude Code search hook and write the");
|
|
4953
4973
|
console.log(" graph-first guidance to ~/.neat/, merging into");
|
|
4954
4974
|
console.log(" ~/.claude/settings.json without touching your other hooks");
|
|
4975
|
+
console.log(" --gate with --apply, enable hard-gate mode: DENY Grep/Glob/grep-Bash");
|
|
4976
|
+
console.log(" until `neat ask` has run this session (default is nudge-only).");
|
|
4977
|
+
console.log(" Toggle off at run time with NEAT_SEARCH_GATE=0.");
|
|
4955
4978
|
console.log(" --print-hook print the hook script to stdout");
|
|
4956
4979
|
console.log(" --print-guide print the agent-agnostic graph-first guidance to stdout");
|
|
4957
4980
|
console.log(" --print-settings print the settings.json PreToolUse block --apply would add");
|
|
4958
4981
|
console.log("");
|
|
4959
|
-
console.log("
|
|
4960
|
-
console.log("
|
|
4982
|
+
console.log("By default the hook is a gentle, non-blocking nudge \u2014 searches still run.");
|
|
4983
|
+
console.log("--gate turns it into a hard forcing mechanism. It is Claude-Code-specific;");
|
|
4984
|
+
console.log("other harnesses get the same steer from the graph-first guidance.");
|
|
4961
4985
|
}
|
|
4962
4986
|
async function runHooksCommand(args) {
|
|
4963
4987
|
const opts = {
|
|
4964
4988
|
apply: false,
|
|
4965
4989
|
printHook: false,
|
|
4966
4990
|
printGuide: false,
|
|
4967
|
-
printSettings: false
|
|
4991
|
+
printSettings: false,
|
|
4992
|
+
gate: false
|
|
4968
4993
|
};
|
|
4969
4994
|
for (const arg of args) {
|
|
4970
4995
|
switch (arg) {
|
|
4971
4996
|
case "--apply":
|
|
4972
4997
|
opts.apply = true;
|
|
4973
4998
|
break;
|
|
4999
|
+
case "--gate":
|
|
5000
|
+
opts.gate = true;
|
|
5001
|
+
break;
|
|
4974
5002
|
case "--print-hook":
|
|
4975
5003
|
opts.printHook = true;
|
|
4976
5004
|
break;
|
|
@@ -4999,10 +5027,149 @@ async function runHooksCommand(args) {
|
|
|
4999
5027
|
}
|
|
5000
5028
|
}
|
|
5001
5029
|
|
|
5002
|
-
// src/
|
|
5030
|
+
// src/claude-cli.ts
|
|
5003
5031
|
import path12 from "path";
|
|
5004
|
-
import os2 from "os";
|
|
5005
5032
|
import { promises as fs11 } from "fs";
|
|
5033
|
+
var NEAT_SECTION_HEADING = "## neat";
|
|
5034
|
+
var NEAT_DIRECTIVE_BODY = `This project has NEAT wired in: a live, fused semantic graph of the system \u2014
|
|
5035
|
+
code and runtime behaviour (OpenTelemetry) in one model, every fact tagged with
|
|
5036
|
+
its provenance (EXTRACTED / OBSERVED / INFERRED / STALE) and a confidence.
|
|
5037
|
+
|
|
5038
|
+
For ANY question about this system's behaviour, dependencies, failures, root
|
|
5039
|
+
cause, or blast radius, call \`neat ask\` FIRST \u2014 before Read, Grep, Glob, or Bash:
|
|
5040
|
+
|
|
5041
|
+
neat ask "why is checkout failing?"
|
|
5042
|
+
neat ask "what breaks if I change the orders table?"
|
|
5043
|
+
neat ask "what does the payments service depend on at runtime?"
|
|
5044
|
+
|
|
5045
|
+
\`ask\` resolves your question to graph nodes and routes it to the right traversal
|
|
5046
|
+
\u2014 root cause, dependencies, observed runtime calls, incidents, divergences,
|
|
5047
|
+
blast radius \u2014 and returns one compact, provenance-tagged answer. The same door
|
|
5048
|
+
is the \`ask\` MCP tool (\`mcp__neat__ask\`) when NEAT's MCP server is wired in.
|
|
5049
|
+
|
|
5050
|
+
The graph is live and fused: it is faster and more accurate than scanning files,
|
|
5051
|
+
and it can tell you what the system actually does at runtime, not only what the
|
|
5052
|
+
source declares. Fall back to Read/Grep only when the graph does not have what
|
|
5053
|
+
you need \u2014 comments, string literals, config minutiae. Ask the graph first,
|
|
5054
|
+
then scan.
|
|
5055
|
+
|
|
5056
|
+
If \`neat ask\` errors, the daemon may not be running (\`neat list\`) \u2014 start it
|
|
5057
|
+
with \`neat <path>\`, then re-ask.`;
|
|
5058
|
+
function neatSection() {
|
|
5059
|
+
return `${NEAT_SECTION_HEADING}
|
|
5060
|
+
|
|
5061
|
+
${NEAT_DIRECTIVE_BODY}
|
|
5062
|
+
`;
|
|
5063
|
+
}
|
|
5064
|
+
function claudeMdPath() {
|
|
5065
|
+
const override = process.env.NEAT_CLAUDE_MD;
|
|
5066
|
+
if (override && override.length > 0) return path12.resolve(override);
|
|
5067
|
+
return path12.join(process.cwd(), "CLAUDE.md");
|
|
5068
|
+
}
|
|
5069
|
+
function splitAroundSection(raw) {
|
|
5070
|
+
const lines = raw.split("\n");
|
|
5071
|
+
const startIdx = lines.findIndex((l) => l.replace(/\s+$/, "") === NEAT_SECTION_HEADING);
|
|
5072
|
+
if (startIdx === -1) {
|
|
5073
|
+
return { before: raw.replace(/\n*$/, ""), after: "", found: false };
|
|
5074
|
+
}
|
|
5075
|
+
let endIdx = lines.length;
|
|
5076
|
+
for (let i = startIdx + 1; i < lines.length; i++) {
|
|
5077
|
+
if (/^#{1,2}\s+/.test(lines[i] ?? "")) {
|
|
5078
|
+
endIdx = i;
|
|
5079
|
+
break;
|
|
5080
|
+
}
|
|
5081
|
+
}
|
|
5082
|
+
const before = lines.slice(0, startIdx).join("\n").replace(/\n*$/, "");
|
|
5083
|
+
const after = lines.slice(endIdx).join("\n").replace(/^\n*/, "");
|
|
5084
|
+
return { before, after, found: true };
|
|
5085
|
+
}
|
|
5086
|
+
function compose(before, after) {
|
|
5087
|
+
const parts = [];
|
|
5088
|
+
if (before.length > 0) parts.push(before);
|
|
5089
|
+
parts.push(neatSection().replace(/\n+$/, ""));
|
|
5090
|
+
if (after.length > 0) parts.push(after);
|
|
5091
|
+
return parts.join("\n\n").replace(/\n*$/, "") + "\n";
|
|
5092
|
+
}
|
|
5093
|
+
async function readIfExists(file) {
|
|
5094
|
+
try {
|
|
5095
|
+
return await fs11.readFile(file, "utf8");
|
|
5096
|
+
} catch (err) {
|
|
5097
|
+
if (err.code === "ENOENT") return null;
|
|
5098
|
+
throw err;
|
|
5099
|
+
}
|
|
5100
|
+
}
|
|
5101
|
+
async function runInstall() {
|
|
5102
|
+
const file = claudeMdPath();
|
|
5103
|
+
const raw = await readIfExists(file) ?? "";
|
|
5104
|
+
const { before, after, found } = splitAroundSection(raw);
|
|
5105
|
+
const next = compose(before, after);
|
|
5106
|
+
await fs11.mkdir(path12.dirname(file), { recursive: true });
|
|
5107
|
+
await fs11.writeFile(file, next, "utf8");
|
|
5108
|
+
const verb = raw.length === 0 ? "created" : found ? "refreshed" : "added";
|
|
5109
|
+
console.log(`neat claude: ${verb} the \`${NEAT_SECTION_HEADING}\` section in ${file}`);
|
|
5110
|
+
console.log("Your agent will now reach for `neat ask` before Read/Grep/Bash. Restart the");
|
|
5111
|
+
console.log("session (or reload CLAUDE.md) to pick it up.");
|
|
5112
|
+
return { exitCode: 0 };
|
|
5113
|
+
}
|
|
5114
|
+
async function runUninstall() {
|
|
5115
|
+
const file = claudeMdPath();
|
|
5116
|
+
const raw = await readIfExists(file);
|
|
5117
|
+
if (raw === null) {
|
|
5118
|
+
console.log(`neat claude: no CLAUDE.md at ${file} \u2014 nothing to remove.`);
|
|
5119
|
+
return { exitCode: 0 };
|
|
5120
|
+
}
|
|
5121
|
+
const { before, after, found } = splitAroundSection(raw);
|
|
5122
|
+
if (!found) {
|
|
5123
|
+
console.log(`neat claude: no \`${NEAT_SECTION_HEADING}\` section in ${file} \u2014 nothing to remove.`);
|
|
5124
|
+
return { exitCode: 0 };
|
|
5125
|
+
}
|
|
5126
|
+
const remaining = [before, after].filter((s) => s.length > 0).join("\n\n");
|
|
5127
|
+
const next = remaining.length > 0 ? remaining.replace(/\n*$/, "") + "\n" : "";
|
|
5128
|
+
await fs11.writeFile(file, next, "utf8");
|
|
5129
|
+
console.log(`neat claude: removed the \`${NEAT_SECTION_HEADING}\` section from ${file}.`);
|
|
5130
|
+
return { exitCode: 0 };
|
|
5131
|
+
}
|
|
5132
|
+
function usage2() {
|
|
5133
|
+
console.log("neat claude \u2014 make the query-first directive always-on in Claude Code");
|
|
5134
|
+
console.log("");
|
|
5135
|
+
console.log(" install write (or refresh) a `## neat` section in ./CLAUDE.md so your");
|
|
5136
|
+
console.log(" agent reaches for `neat ask` before Read/Grep/Bash");
|
|
5137
|
+
console.log(" uninstall remove the `## neat` section from ./CLAUDE.md");
|
|
5138
|
+
console.log(" print print the directive block to stdout (for a manual paste)");
|
|
5139
|
+
console.log("");
|
|
5140
|
+
console.log("Idempotent: re-running install replaces its own section, never duplicates it.");
|
|
5141
|
+
console.log("Target file overridable via NEAT_CLAUDE_MD.");
|
|
5142
|
+
}
|
|
5143
|
+
async function runClaudeCommand(args) {
|
|
5144
|
+
const sub = args[0];
|
|
5145
|
+
if (sub === "-h" || sub === "--help" || sub === void 0) {
|
|
5146
|
+
usage2();
|
|
5147
|
+
return sub === void 0 ? 2 : 0;
|
|
5148
|
+
}
|
|
5149
|
+
try {
|
|
5150
|
+
switch (sub) {
|
|
5151
|
+
case "install":
|
|
5152
|
+
return (await runInstall()).exitCode;
|
|
5153
|
+
case "uninstall":
|
|
5154
|
+
return (await runUninstall()).exitCode;
|
|
5155
|
+
case "print":
|
|
5156
|
+
process.stdout.write(neatSection());
|
|
5157
|
+
return 0;
|
|
5158
|
+
default:
|
|
5159
|
+
console.error(`neat claude: unknown subcommand "${sub}"`);
|
|
5160
|
+
usage2();
|
|
5161
|
+
return 2;
|
|
5162
|
+
}
|
|
5163
|
+
} catch (err) {
|
|
5164
|
+
console.error(`neat claude: ${err.message}`);
|
|
5165
|
+
return 1;
|
|
5166
|
+
}
|
|
5167
|
+
}
|
|
5168
|
+
|
|
5169
|
+
// src/codex-cli.ts
|
|
5170
|
+
import path13 from "path";
|
|
5171
|
+
import os2 from "os";
|
|
5172
|
+
import { promises as fs12 } from "fs";
|
|
5006
5173
|
import { isDeepStrictEqual } from "util";
|
|
5007
5174
|
import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
|
|
5008
5175
|
var CODEX_MCP_SERVER = {
|
|
@@ -5020,14 +5187,14 @@ var NEAT_GRAPH_FIRST_START = "<!-- neat:graph-first -->";
|
|
|
5020
5187
|
var NEAT_GRAPH_FIRST_END = "<!-- /neat:graph-first -->";
|
|
5021
5188
|
function codexConfigPath() {
|
|
5022
5189
|
const override = process.env.NEAT_CODEX_CONFIG;
|
|
5023
|
-
if (override && override.length > 0) return
|
|
5190
|
+
if (override && override.length > 0) return path13.resolve(override);
|
|
5024
5191
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? os2.homedir();
|
|
5025
|
-
return
|
|
5192
|
+
return path13.join(home, ".codex", "config.toml");
|
|
5026
5193
|
}
|
|
5027
5194
|
function agentsFilePath() {
|
|
5028
5195
|
const override = process.env.NEAT_CODEX_AGENTS;
|
|
5029
|
-
if (override && override.length > 0) return
|
|
5030
|
-
return
|
|
5196
|
+
if (override && override.length > 0) return path13.resolve(override);
|
|
5197
|
+
return path13.join(process.cwd(), "AGENTS.md");
|
|
5031
5198
|
}
|
|
5032
5199
|
function isTableHeader(line) {
|
|
5033
5200
|
return /^\s*\[\[?[^\]]+\]\]?\s*$/.test(line);
|
|
@@ -5161,7 +5328,7 @@ async function runCodex(opts) {
|
|
|
5161
5328
|
const agentsPath = agentsFilePath();
|
|
5162
5329
|
let configRaw = "";
|
|
5163
5330
|
try {
|
|
5164
|
-
configRaw = await
|
|
5331
|
+
configRaw = await fs12.readFile(configPath, "utf8");
|
|
5165
5332
|
} catch (err) {
|
|
5166
5333
|
if (err.code !== "ENOENT") {
|
|
5167
5334
|
console.error(`neat codex: failed to read ${configPath} \u2014 ${err.message}`);
|
|
@@ -5170,7 +5337,7 @@ async function runCodex(opts) {
|
|
|
5170
5337
|
}
|
|
5171
5338
|
let agentsRaw = "";
|
|
5172
5339
|
try {
|
|
5173
|
-
agentsRaw = await
|
|
5340
|
+
agentsRaw = await fs12.readFile(agentsPath, "utf8");
|
|
5174
5341
|
} catch (err) {
|
|
5175
5342
|
if (err.code !== "ENOENT") {
|
|
5176
5343
|
console.error(`neat codex: failed to read ${agentsPath} \u2014 ${err.message}`);
|
|
@@ -5210,15 +5377,15 @@ async function runCodex(opts) {
|
|
|
5210
5377
|
return { exitCode: 0 };
|
|
5211
5378
|
}
|
|
5212
5379
|
if (config.changed) {
|
|
5213
|
-
await
|
|
5214
|
-
await
|
|
5380
|
+
await fs12.mkdir(path13.dirname(configPath), { recursive: true });
|
|
5381
|
+
await fs12.writeFile(configPath, config.text, "utf8");
|
|
5215
5382
|
console.log(`neat codex: wrote [mcp_servers.neat] to ${configPath}`);
|
|
5216
5383
|
} else {
|
|
5217
5384
|
console.log(`neat codex: ${configPath} already has NEAT's MCP server`);
|
|
5218
5385
|
}
|
|
5219
5386
|
if (agents.changed) {
|
|
5220
|
-
await
|
|
5221
|
-
await
|
|
5387
|
+
await fs12.mkdir(path13.dirname(agentsPath), { recursive: true });
|
|
5388
|
+
await fs12.writeFile(agentsPath, agents.text, "utf8");
|
|
5222
5389
|
console.log(`neat codex: wrote the graph-first block to ${agentsPath}`);
|
|
5223
5390
|
} else {
|
|
5224
5391
|
console.log(`neat codex: ${agentsPath} already has the graph-first block`);
|
|
@@ -5228,7 +5395,7 @@ async function runCodex(opts) {
|
|
|
5228
5395
|
console.log("points the server at the local daemon \u2014 edit it for a non-default one.");
|
|
5229
5396
|
return { exitCode: 0 };
|
|
5230
5397
|
}
|
|
5231
|
-
function
|
|
5398
|
+
function usage3() {
|
|
5232
5399
|
console.log("neat codex \u2014 install NEAT into the OpenAI Codex CLI (MCP server + AGENTS.md)");
|
|
5233
5400
|
console.log("");
|
|
5234
5401
|
console.log(" (no flag) plan: print what would change, write nothing");
|
|
@@ -5256,11 +5423,11 @@ async function runCodexCommand(args) {
|
|
|
5256
5423
|
break;
|
|
5257
5424
|
case "-h":
|
|
5258
5425
|
case "--help":
|
|
5259
|
-
|
|
5426
|
+
usage3();
|
|
5260
5427
|
return 0;
|
|
5261
5428
|
default:
|
|
5262
5429
|
console.error(`neat codex: unknown flag "${arg}"`);
|
|
5263
|
-
|
|
5430
|
+
usage3();
|
|
5264
5431
|
return 2;
|
|
5265
5432
|
}
|
|
5266
5433
|
}
|
|
@@ -5274,9 +5441,9 @@ async function runCodexCommand(args) {
|
|
|
5274
5441
|
}
|
|
5275
5442
|
|
|
5276
5443
|
// src/editors-cli.ts
|
|
5277
|
-
import
|
|
5444
|
+
import path14 from "path";
|
|
5278
5445
|
import os3 from "os";
|
|
5279
|
-
import { promises as
|
|
5446
|
+
import { promises as fs13 } from "fs";
|
|
5280
5447
|
import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
|
|
5281
5448
|
import * as jsonc from "jsonc-parser";
|
|
5282
5449
|
var NEAT_MCP_SERVER = {
|
|
@@ -5300,17 +5467,17 @@ function homeDir() {
|
|
|
5300
5467
|
}
|
|
5301
5468
|
function xdgConfigDir() {
|
|
5302
5469
|
const xdg = process.env.XDG_CONFIG_HOME;
|
|
5303
|
-
return xdg && xdg.length > 0 ?
|
|
5470
|
+
return xdg && xdg.length > 0 ? path14.resolve(xdg) : path14.join(homeDir(), ".config");
|
|
5304
5471
|
}
|
|
5305
5472
|
function envOverride(name) {
|
|
5306
5473
|
const v = process.env[name];
|
|
5307
|
-
return v && v.length > 0 ?
|
|
5474
|
+
return v && v.length > 0 ? path14.resolve(v) : void 0;
|
|
5308
5475
|
}
|
|
5309
5476
|
var CURSOR_CLIENT = {
|
|
5310
5477
|
id: "cursor",
|
|
5311
5478
|
label: "Cursor",
|
|
5312
5479
|
docsUrl: "https://docs.cursor.com/context/mcp",
|
|
5313
|
-
mcpConfigPath: () => envOverride("NEAT_CURSOR_CONFIG") ??
|
|
5480
|
+
mcpConfigPath: () => envOverride("NEAT_CURSOR_CONFIG") ?? path14.join(homeDir(), ".cursor", "mcp.json"),
|
|
5314
5481
|
mcpContainerKey: "mcpServers",
|
|
5315
5482
|
format: "json",
|
|
5316
5483
|
// Cursor still reads a single `.cursorrules` at the project root (the modern
|
|
@@ -5322,7 +5489,7 @@ var DEVIN_CLIENT = {
|
|
|
5322
5489
|
id: "devin",
|
|
5323
5490
|
label: "Devin Desktop (Cascade)",
|
|
5324
5491
|
docsUrl: "https://docs.devin.ai/desktop/cascade/mcp",
|
|
5325
|
-
mcpConfigPath: () => envOverride("NEAT_DEVIN_CONFIG") ??
|
|
5492
|
+
mcpConfigPath: () => envOverride("NEAT_DEVIN_CONFIG") ?? path14.join(homeDir(), ".codeium", "windsurf", "mcp_config.json"),
|
|
5326
5493
|
mcpContainerKey: "mcpServers",
|
|
5327
5494
|
format: "json",
|
|
5328
5495
|
rulesFileName: ".windsurfrules"
|
|
@@ -5331,7 +5498,7 @@ var GEMINI_CLIENT = {
|
|
|
5331
5498
|
id: "gemini",
|
|
5332
5499
|
label: "Gemini CLI",
|
|
5333
5500
|
docsUrl: "https://github.com/google-gemini/gemini-cli/blob/main/docs/tools/mcp-server.md",
|
|
5334
|
-
mcpConfigPath: () => envOverride("NEAT_GEMINI_CONFIG") ??
|
|
5501
|
+
mcpConfigPath: () => envOverride("NEAT_GEMINI_CONFIG") ?? path14.join(homeDir(), ".gemini", "settings.json"),
|
|
5335
5502
|
mcpContainerKey: "mcpServers",
|
|
5336
5503
|
format: "json",
|
|
5337
5504
|
rulesFileName: "GEMINI.md"
|
|
@@ -5340,7 +5507,7 @@ var QWEN_CLIENT = {
|
|
|
5340
5507
|
id: "qwen",
|
|
5341
5508
|
label: "Qwen Code",
|
|
5342
5509
|
docsUrl: "https://qwenlm.github.io/qwen-code-docs/en/users/features/mcp/",
|
|
5343
|
-
mcpConfigPath: () => envOverride("NEAT_QWEN_CONFIG") ??
|
|
5510
|
+
mcpConfigPath: () => envOverride("NEAT_QWEN_CONFIG") ?? path14.join(homeDir(), ".qwen", "settings.json"),
|
|
5344
5511
|
mcpContainerKey: "mcpServers",
|
|
5345
5512
|
format: "json",
|
|
5346
5513
|
rulesFileName: "QWEN.md"
|
|
@@ -5349,7 +5516,7 @@ var AMAZONQ_CLIENT = {
|
|
|
5349
5516
|
id: "amazonq",
|
|
5350
5517
|
label: "Amazon Q Developer CLI",
|
|
5351
5518
|
docsUrl: "https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/command-line-mcp-configuration.html",
|
|
5352
|
-
mcpConfigPath: () => envOverride("NEAT_AMAZONQ_CONFIG") ??
|
|
5519
|
+
mcpConfigPath: () => envOverride("NEAT_AMAZONQ_CONFIG") ?? path14.join(homeDir(), ".aws", "amazonq", "mcp.json"),
|
|
5353
5520
|
mcpContainerKey: "mcpServers",
|
|
5354
5521
|
format: "json"
|
|
5355
5522
|
};
|
|
@@ -5357,7 +5524,7 @@ var ROOCODE_CLIENT = {
|
|
|
5357
5524
|
id: "roocode",
|
|
5358
5525
|
label: "Roo Code",
|
|
5359
5526
|
docsUrl: "https://roocodeinc.github.io/Roo-Code/features/mcp/using-mcp-in-roo",
|
|
5360
|
-
mcpConfigPath: () => envOverride("NEAT_ROOCODE_CONFIG") ??
|
|
5527
|
+
mcpConfigPath: () => envOverride("NEAT_ROOCODE_CONFIG") ?? path14.join(process.cwd(), ".roo", "mcp.json"),
|
|
5361
5528
|
mcpContainerKey: "mcpServers",
|
|
5362
5529
|
format: "json"
|
|
5363
5530
|
};
|
|
@@ -5370,9 +5537,9 @@ var ZED_CLIENT = {
|
|
|
5370
5537
|
if (override) return override;
|
|
5371
5538
|
if (process.platform === "win32") {
|
|
5372
5539
|
const appData = process.env.APPDATA;
|
|
5373
|
-
if (appData && appData.length > 0) return
|
|
5540
|
+
if (appData && appData.length > 0) return path14.join(appData, "Zed", "settings.json");
|
|
5374
5541
|
}
|
|
5375
|
-
return
|
|
5542
|
+
return path14.join(homeDir(), ".config", "zed", "settings.json");
|
|
5376
5543
|
},
|
|
5377
5544
|
mcpContainerKey: "context_servers",
|
|
5378
5545
|
format: "jsonc",
|
|
@@ -5382,7 +5549,7 @@ var OPENCODE_CLIENT = {
|
|
|
5382
5549
|
id: "opencode",
|
|
5383
5550
|
label: "OpenCode",
|
|
5384
5551
|
docsUrl: "https://opencode.ai/docs/mcp-servers/",
|
|
5385
|
-
mcpConfigPath: () => envOverride("NEAT_OPENCODE_CONFIG") ??
|
|
5552
|
+
mcpConfigPath: () => envOverride("NEAT_OPENCODE_CONFIG") ?? path14.join(xdgConfigDir(), "opencode", "opencode.json"),
|
|
5386
5553
|
mcpContainerKey: "mcp",
|
|
5387
5554
|
format: "json",
|
|
5388
5555
|
serverEntry: NEAT_OPENCODE_SERVER,
|
|
@@ -5392,7 +5559,7 @@ var CRUSH_CLIENT = {
|
|
|
5392
5559
|
id: "crush",
|
|
5393
5560
|
label: "Crush",
|
|
5394
5561
|
docsUrl: "https://charmbracelet-crush.mintlify.app/configuration/mcp",
|
|
5395
|
-
mcpConfigPath: () => envOverride("NEAT_CRUSH_CONFIG") ??
|
|
5562
|
+
mcpConfigPath: () => envOverride("NEAT_CRUSH_CONFIG") ?? path14.join(xdgConfigDir(), "crush", "crush.json"),
|
|
5396
5563
|
mcpContainerKey: "mcp",
|
|
5397
5564
|
format: "json",
|
|
5398
5565
|
serverEntry: NEAT_CRUSH_SERVER,
|
|
@@ -5455,7 +5622,7 @@ async function planMcp(client, mcpPath) {
|
|
|
5455
5622
|
const serverEntry = client.serverEntry ?? NEAT_MCP_SERVER;
|
|
5456
5623
|
let raw = "";
|
|
5457
5624
|
try {
|
|
5458
|
-
raw = await
|
|
5625
|
+
raw = await fs13.readFile(mcpPath, "utf8");
|
|
5459
5626
|
} catch (err) {
|
|
5460
5627
|
const e = err;
|
|
5461
5628
|
if (e.code === "ENOENT") {
|
|
@@ -5497,7 +5664,7 @@ async function runEditorInstall(client, opts) {
|
|
|
5497
5664
|
const mcpPath = client.mcpConfigPath();
|
|
5498
5665
|
const serverEntry = client.serverEntry ?? NEAT_MCP_SERVER;
|
|
5499
5666
|
const hasRules = typeof client.rulesFileName === "string";
|
|
5500
|
-
const rulesPath = hasRules ?
|
|
5667
|
+
const rulesPath = hasRules ? path14.join(opts.projectDir, client.rulesFileName) : "";
|
|
5501
5668
|
const mcp = await planMcp(client, mcpPath);
|
|
5502
5669
|
if (mcp === null) return { exitCode: 1 };
|
|
5503
5670
|
let existingRules = "";
|
|
@@ -5506,7 +5673,7 @@ async function runEditorInstall(client, opts) {
|
|
|
5506
5673
|
let block = "";
|
|
5507
5674
|
if (hasRules) {
|
|
5508
5675
|
try {
|
|
5509
|
-
existingRules = await
|
|
5676
|
+
existingRules = await fs13.readFile(rulesPath, "utf8");
|
|
5510
5677
|
} catch (err) {
|
|
5511
5678
|
if (err.code !== "ENOENT") {
|
|
5512
5679
|
console.error(`neat ${client.id}: failed to read ${rulesPath} \u2014 ${err.message}`);
|
|
@@ -5540,11 +5707,11 @@ async function runEditorInstall(client, opts) {
|
|
|
5540
5707
|
);
|
|
5541
5708
|
return { exitCode: 0 };
|
|
5542
5709
|
}
|
|
5543
|
-
await
|
|
5544
|
-
await
|
|
5710
|
+
await fs13.mkdir(path14.dirname(mcpPath), { recursive: true });
|
|
5711
|
+
await fs13.writeFile(mcpPath, mcp.text, "utf8");
|
|
5545
5712
|
if (hasRules) {
|
|
5546
|
-
await
|
|
5547
|
-
await
|
|
5713
|
+
await fs13.mkdir(path14.dirname(rulesPath), { recursive: true });
|
|
5714
|
+
await fs13.writeFile(rulesPath, newRules, "utf8");
|
|
5548
5715
|
}
|
|
5549
5716
|
console.log(`neat ${client.id}: wired NEAT into ${client.label}`);
|
|
5550
5717
|
console.log(` MCP server: ${mcpPath} (${client.mcpContainerKey}.neat \u2192 npx -y @neat.is/mcp)`);
|
|
@@ -5557,7 +5724,7 @@ async function runEditorInstall(client, opts) {
|
|
|
5557
5724
|
function indent(text) {
|
|
5558
5725
|
return text.split("\n").map((line) => line.length > 0 ? ` ${line}` : line).join("\n");
|
|
5559
5726
|
}
|
|
5560
|
-
function
|
|
5727
|
+
function usage4(client) {
|
|
5561
5728
|
const hasRules = typeof client.rulesFileName === "string";
|
|
5562
5729
|
console.log(
|
|
5563
5730
|
hasRules ? `neat ${client.id} \u2014 install NEAT's MCP server + graph-first guidance into ${client.label}` : `neat ${client.id} \u2014 install NEAT's MCP server into ${client.label}`
|
|
@@ -5588,11 +5755,11 @@ async function runEditorCommand(clientId, args, projectDir = process.cwd()) {
|
|
|
5588
5755
|
break;
|
|
5589
5756
|
case "-h":
|
|
5590
5757
|
case "--help":
|
|
5591
|
-
|
|
5758
|
+
usage4(client);
|
|
5592
5759
|
return 0;
|
|
5593
5760
|
default:
|
|
5594
5761
|
console.error(`neat ${client.id}: unknown flag "${arg}"`);
|
|
5595
|
-
|
|
5762
|
+
usage4(client);
|
|
5596
5763
|
return 2;
|
|
5597
5764
|
}
|
|
5598
5765
|
}
|
|
@@ -5634,10 +5801,10 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
5634
5801
|
const root = baseUrl.replace(/\/$/, "");
|
|
5635
5802
|
const authHeader = bearerToken && bearerToken.length > 0 ? { authorization: `Bearer ${bearerToken}` } : {};
|
|
5636
5803
|
return {
|
|
5637
|
-
async get(
|
|
5804
|
+
async get(path17) {
|
|
5638
5805
|
let res;
|
|
5639
5806
|
try {
|
|
5640
|
-
res = await fetch(`${root}${
|
|
5807
|
+
res = await fetch(`${root}${path17}`, {
|
|
5641
5808
|
headers: { ...authHeader }
|
|
5642
5809
|
});
|
|
5643
5810
|
} catch (err) {
|
|
@@ -5649,16 +5816,16 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
5649
5816
|
const body = await res.text().catch(() => "");
|
|
5650
5817
|
throw new HttpError(
|
|
5651
5818
|
res.status,
|
|
5652
|
-
`${res.status} ${res.statusText} on GET ${
|
|
5819
|
+
`${res.status} ${res.statusText} on GET ${path17}: ${body}`,
|
|
5653
5820
|
body
|
|
5654
5821
|
);
|
|
5655
5822
|
}
|
|
5656
5823
|
return await res.json();
|
|
5657
5824
|
},
|
|
5658
|
-
async post(
|
|
5825
|
+
async post(path17, body) {
|
|
5659
5826
|
let res;
|
|
5660
5827
|
try {
|
|
5661
|
-
res = await fetch(`${root}${
|
|
5828
|
+
res = await fetch(`${root}${path17}`, {
|
|
5662
5829
|
method: "POST",
|
|
5663
5830
|
headers: { "content-type": "application/json", ...authHeader },
|
|
5664
5831
|
body: JSON.stringify(body)
|
|
@@ -5672,7 +5839,7 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
5672
5839
|
const text = await res.text().catch(() => "");
|
|
5673
5840
|
throw new HttpError(
|
|
5674
5841
|
res.status,
|
|
5675
|
-
`${res.status} ${res.statusText} on POST ${
|
|
5842
|
+
`${res.status} ${res.statusText} on POST ${path17}: ${text}`,
|
|
5676
5843
|
text
|
|
5677
5844
|
);
|
|
5678
5845
|
}
|
|
@@ -5686,12 +5853,12 @@ function projectPath(project, suffix) {
|
|
|
5686
5853
|
}
|
|
5687
5854
|
async function runRootCause(client, input) {
|
|
5688
5855
|
const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
|
|
5689
|
-
const
|
|
5856
|
+
const path17 = projectPath(
|
|
5690
5857
|
input.project,
|
|
5691
5858
|
`/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
|
|
5692
5859
|
);
|
|
5693
5860
|
try {
|
|
5694
|
-
const result = await client.get(
|
|
5861
|
+
const result = await client.get(path17);
|
|
5695
5862
|
const arrowPath = result.traversalPath.join(" \u2190 ");
|
|
5696
5863
|
const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
|
|
5697
5864
|
const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
|
|
@@ -5717,12 +5884,12 @@ async function runRootCause(client, input) {
|
|
|
5717
5884
|
}
|
|
5718
5885
|
async function runBlastRadius(client, input) {
|
|
5719
5886
|
const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
|
|
5720
|
-
const
|
|
5887
|
+
const path17 = projectPath(
|
|
5721
5888
|
input.project,
|
|
5722
5889
|
`/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
|
|
5723
5890
|
);
|
|
5724
5891
|
try {
|
|
5725
|
-
const result = await client.get(
|
|
5892
|
+
const result = await client.get(path17);
|
|
5726
5893
|
if (result.totalAffected === 0) {
|
|
5727
5894
|
return {
|
|
5728
5895
|
summary: `${result.origin} has no dependents. Nothing else would break if it failed.`
|
|
@@ -5756,12 +5923,12 @@ function formatBlastEntry(n) {
|
|
|
5756
5923
|
}
|
|
5757
5924
|
async function runDependencies(client, input) {
|
|
5758
5925
|
const depth = input.depth ?? 3;
|
|
5759
|
-
const
|
|
5926
|
+
const path17 = projectPath(
|
|
5760
5927
|
input.project,
|
|
5761
5928
|
`/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
|
|
5762
5929
|
);
|
|
5763
5930
|
try {
|
|
5764
|
-
const result = await client.get(
|
|
5931
|
+
const result = await client.get(path17);
|
|
5765
5932
|
if (result.total === 0) {
|
|
5766
5933
|
return {
|
|
5767
5934
|
summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
|
|
@@ -5853,9 +6020,9 @@ function formatDuration(ms) {
|
|
|
5853
6020
|
return `${Math.round(h / 24)}d`;
|
|
5854
6021
|
}
|
|
5855
6022
|
async function runIncidents(client, input) {
|
|
5856
|
-
const
|
|
6023
|
+
const path17 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
|
|
5857
6024
|
try {
|
|
5858
|
-
const body = await client.get(
|
|
6025
|
+
const body = await client.get(path17);
|
|
5859
6026
|
const events = body.events;
|
|
5860
6027
|
if (events.length === 0) {
|
|
5861
6028
|
return {
|
|
@@ -6096,6 +6263,33 @@ async function runDivergences(client, input) {
|
|
|
6096
6263
|
provenance: "composite (EXTRACTED + OBSERVED)"
|
|
6097
6264
|
};
|
|
6098
6265
|
}
|
|
6266
|
+
async function runAsk(client, input) {
|
|
6267
|
+
const result = await client.get(
|
|
6268
|
+
projectPath(input.project, `/graph/ask?q=${encodeURIComponent(input.question)}`)
|
|
6269
|
+
);
|
|
6270
|
+
const blockLines = [];
|
|
6271
|
+
if (result.matched.length > 0) {
|
|
6272
|
+
blockLines.push(
|
|
6273
|
+
`Matched: ${result.matched.map((m) => `${m.nodeId} [${m.via} ${m.score.toFixed(2)}]`).join(", ")}`
|
|
6274
|
+
);
|
|
6275
|
+
blockLines.push(`Intent: ${result.intent}`);
|
|
6276
|
+
} else if (result.scope === "global") {
|
|
6277
|
+
blockLines.push(`Graph-wide answer (${result.intent}) \u2014 no entity named.`);
|
|
6278
|
+
}
|
|
6279
|
+
for (const section of result.sections) {
|
|
6280
|
+
blockLines.push("", section.heading + ":");
|
|
6281
|
+
for (const fact of section.facts) {
|
|
6282
|
+
const tag = fact.provenance ? ` [${fact.provenance}${fact.confidence !== void 0 ? ` ${fact.confidence.toFixed(2)}` : ""}]` : fact.confidence !== void 0 ? ` [confidence ${fact.confidence.toFixed(2)}]` : "";
|
|
6283
|
+
blockLines.push(` \u2022 ${fact.text}${tag}`);
|
|
6284
|
+
}
|
|
6285
|
+
}
|
|
6286
|
+
return {
|
|
6287
|
+
summary: result.answer,
|
|
6288
|
+
block: blockLines.join("\n").trim(),
|
|
6289
|
+
...result.confidence !== void 0 ? { confidence: result.confidence } : {},
|
|
6290
|
+
...result.provenance.length > 0 ? { provenance: result.provenance } : {}
|
|
6291
|
+
};
|
|
6292
|
+
}
|
|
6099
6293
|
function formatFooter(confidence, provenance) {
|
|
6100
6294
|
const c = confidence === void 0 ? "n/a" : confidence.toFixed(2);
|
|
6101
6295
|
const p = provenance === void 0 ? "n/a" : Array.isArray(provenance) ? [...new Set(provenance)].join(", ") : provenance;
|
|
@@ -6508,7 +6702,7 @@ function sleep(ms, signal) {
|
|
|
6508
6702
|
}
|
|
6509
6703
|
|
|
6510
6704
|
// src/cli-verbs.ts
|
|
6511
|
-
import
|
|
6705
|
+
import path15 from "path";
|
|
6512
6706
|
async function resolveProjectEntry(opts) {
|
|
6513
6707
|
const entries = await listProjects();
|
|
6514
6708
|
if (opts.project) {
|
|
@@ -6518,7 +6712,7 @@ async function resolveProjectEntry(opts) {
|
|
|
6518
6712
|
const cwd = opts.cwd ?? process.cwd();
|
|
6519
6713
|
const resolvedCwd = await normalizeProjectPath(cwd);
|
|
6520
6714
|
for (const entry2 of entries) {
|
|
6521
|
-
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${
|
|
6715
|
+
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${path15.sep}`)) {
|
|
6522
6716
|
return entry2;
|
|
6523
6717
|
}
|
|
6524
6718
|
}
|
|
@@ -6683,7 +6877,7 @@ function isNpxInvocation() {
|
|
|
6683
6877
|
function commandPrefix() {
|
|
6684
6878
|
return isNpxInvocation() ? "npx neat.is" : "neat";
|
|
6685
6879
|
}
|
|
6686
|
-
function
|
|
6880
|
+
function usage5() {
|
|
6687
6881
|
const neat = commandPrefix();
|
|
6688
6882
|
console.log("Installed via npx? Prefix commands with `npx neat.is`, or install once: `npm i -g neat.is`.");
|
|
6689
6883
|
console.log("");
|
|
@@ -6731,13 +6925,21 @@ function usage4() {
|
|
|
6731
6925
|
console.log(" --print-config print the JSON snippet to stdout");
|
|
6732
6926
|
console.log(" --apply merge mcpServers.neat into ~/.claude.json");
|
|
6733
6927
|
console.log(" hooks Wire NEAT into your agent so it queries the graph before");
|
|
6734
|
-
console.log(" grepping. Installs a
|
|
6735
|
-
console.log("
|
|
6928
|
+
console.log(" grepping. Installs a Claude Code search hook (nudge by default,");
|
|
6929
|
+
console.log(" or a hard gate) and agent-agnostic graph-first guidance.");
|
|
6736
6930
|
console.log(" Flags:");
|
|
6737
6931
|
console.log(" --apply install the hook + guidance");
|
|
6932
|
+
console.log(" --gate with --apply: DENY search until `neat ask`");
|
|
6933
|
+
console.log(" has run this session (default: nudge-only)");
|
|
6738
6934
|
console.log(" --print-hook print the hook script");
|
|
6739
6935
|
console.log(" --print-guide print the graph-first guidance");
|
|
6740
6936
|
console.log(" --print-settings print the settings.json block --apply adds");
|
|
6937
|
+
console.log(" claude Write the query-first directive into ./CLAUDE.md as a `## neat`");
|
|
6938
|
+
console.log(" section so an agent reaches for `neat ask` before Read/Grep/Bash.");
|
|
6939
|
+
console.log(" Subcommands:");
|
|
6940
|
+
console.log(" install write (or refresh) the `## neat` section");
|
|
6941
|
+
console.log(" uninstall remove the `## neat` section");
|
|
6942
|
+
console.log(" print print the directive block to stdout");
|
|
6741
6943
|
console.log(" codex Install NEAT into the OpenAI Codex CLI: add [mcp_servers.neat]");
|
|
6742
6944
|
console.log(" to ~/.codex/config.toml and the graph-first block to ./AGENTS.md.");
|
|
6743
6945
|
console.log(" Plan by default; --apply to write.");
|
|
@@ -6814,6 +7016,10 @@ function usage4() {
|
|
|
6814
7016
|
console.log(" run time; the config file is written owner-only (0600).");
|
|
6815
7017
|
console.log("");
|
|
6816
7018
|
console.log("query commands (mirror the MCP tools, ADR-050):");
|
|
7019
|
+
console.log(" ask <question> Plain-language door: resolves the question to");
|
|
7020
|
+
console.log(" nodes and routes it to the right traversal, with");
|
|
7021
|
+
console.log(" a compact provenance-tagged answer. Reach here first.");
|
|
7022
|
+
console.log(` example: ${neat} ask "why is checkout failing?"`);
|
|
6817
7023
|
console.log(" root-cause <node-id> Walk inbound edges to find what broke first.");
|
|
6818
7024
|
console.log(` example: ${neat} root-cause service:<name>`);
|
|
6819
7025
|
console.log(" blast-radius <node-id> BFS inbound \u2014 the dependents that break if this dies.");
|
|
@@ -7033,7 +7239,7 @@ async function buildPatchSections(services, project) {
|
|
|
7033
7239
|
}
|
|
7034
7240
|
async function runInit(opts) {
|
|
7035
7241
|
const written = [];
|
|
7036
|
-
const stat = await
|
|
7242
|
+
const stat = await fs14.stat(opts.scanPath).catch(() => null);
|
|
7037
7243
|
if (!stat || !stat.isDirectory()) {
|
|
7038
7244
|
console.error(`neat init: ${opts.scanPath} is not a directory`);
|
|
7039
7245
|
return { exitCode: 2, writtenFiles: written };
|
|
@@ -7042,13 +7248,13 @@ async function runInit(opts) {
|
|
|
7042
7248
|
printDiscoveryReport(opts, services);
|
|
7043
7249
|
const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
|
|
7044
7250
|
const patch = renderPatch(sections);
|
|
7045
|
-
const patchPath =
|
|
7251
|
+
const patchPath = path16.join(opts.scanPath, "neat.patch");
|
|
7046
7252
|
if (opts.dryRun) {
|
|
7047
|
-
await
|
|
7253
|
+
await fs14.writeFile(patchPath, patch, "utf8");
|
|
7048
7254
|
written.push(patchPath);
|
|
7049
7255
|
console.log(`dry-run: patch written to ${patchPath}`);
|
|
7050
|
-
const gitignorePath =
|
|
7051
|
-
const gitignoreExists = await
|
|
7256
|
+
const gitignorePath = path16.join(opts.scanPath, ".gitignore");
|
|
7257
|
+
const gitignoreExists = await fs14.stat(gitignorePath).then(() => true).catch(() => false);
|
|
7052
7258
|
const verb = gitignoreExists ? "append" : "create";
|
|
7053
7259
|
console.log(`dry-run: would ${verb} ${gitignorePath} (add neat-out/)`);
|
|
7054
7260
|
console.log("rerun without --dry-run to register and snapshot.");
|
|
@@ -7059,9 +7265,9 @@ async function runInit(opts) {
|
|
|
7059
7265
|
const graph = getGraph(graphKey);
|
|
7060
7266
|
const projectPaths = pathsForProject(
|
|
7061
7267
|
graphKey,
|
|
7062
|
-
|
|
7268
|
+
path16.join(opts.scanPath, "neat-out")
|
|
7063
7269
|
);
|
|
7064
|
-
const errorsPath =
|
|
7270
|
+
const errorsPath = path16.join(path16.dirname(opts.outPath), path16.basename(projectPaths.errorsPath));
|
|
7065
7271
|
const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
|
|
7066
7272
|
await saveGraphToDisk(graph, opts.outPath);
|
|
7067
7273
|
written.push(opts.outPath);
|
|
@@ -7140,7 +7346,7 @@ async function runInit(opts) {
|
|
|
7140
7346
|
console.log("Run `npm install` (or your language equivalent) to refresh lockfiles.");
|
|
7141
7347
|
}
|
|
7142
7348
|
} else {
|
|
7143
|
-
await
|
|
7349
|
+
await fs14.writeFile(patchPath, patch, "utf8");
|
|
7144
7350
|
written.push(patchPath);
|
|
7145
7351
|
}
|
|
7146
7352
|
}
|
|
@@ -7180,9 +7386,9 @@ var CLAUDE_SKILL_CONFIG = {
|
|
|
7180
7386
|
};
|
|
7181
7387
|
function claudeConfigPath() {
|
|
7182
7388
|
const override = process.env.NEAT_CLAUDE_CONFIG;
|
|
7183
|
-
if (override && override.length > 0) return
|
|
7389
|
+
if (override && override.length > 0) return path16.resolve(override);
|
|
7184
7390
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
7185
|
-
return
|
|
7391
|
+
return path16.join(home, ".claude.json");
|
|
7186
7392
|
}
|
|
7187
7393
|
async function runSkill(opts) {
|
|
7188
7394
|
const snippet = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
|
|
@@ -7194,7 +7400,7 @@ async function runSkill(opts) {
|
|
|
7194
7400
|
const target = claudeConfigPath();
|
|
7195
7401
|
let existing = {};
|
|
7196
7402
|
try {
|
|
7197
|
-
existing = JSON.parse(await
|
|
7403
|
+
existing = JSON.parse(await fs14.readFile(target, "utf8"));
|
|
7198
7404
|
} catch (err) {
|
|
7199
7405
|
if (err.code !== "ENOENT") {
|
|
7200
7406
|
console.error(`neat skill: failed to read ${target} \u2014 ${err.message}`);
|
|
@@ -7206,8 +7412,8 @@ async function runSkill(opts) {
|
|
|
7206
7412
|
...existing,
|
|
7207
7413
|
mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
|
|
7208
7414
|
};
|
|
7209
|
-
await
|
|
7210
|
-
await
|
|
7415
|
+
await fs14.mkdir(path16.dirname(target), { recursive: true });
|
|
7416
|
+
await fs14.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
7211
7417
|
console.log(`neat skill: wrote mcpServers.neat to ${target}`);
|
|
7212
7418
|
console.log("restart Claude Code to pick up the new MCP server.");
|
|
7213
7419
|
console.log("");
|
|
@@ -7234,7 +7440,7 @@ async function main() {
|
|
|
7234
7440
|
const argv = process.argv.slice(2);
|
|
7235
7441
|
const cmd0 = argv[0];
|
|
7236
7442
|
if (cmd0 === "-h" || cmd0 === "--help") {
|
|
7237
|
-
|
|
7443
|
+
usage5();
|
|
7238
7444
|
process.exit(0);
|
|
7239
7445
|
}
|
|
7240
7446
|
if (cmd0 === "--version" || cmd0 === "-v" || cmd0 === "version") {
|
|
@@ -7256,6 +7462,11 @@ async function main() {
|
|
|
7256
7462
|
if (code !== 0) process.exit(code);
|
|
7257
7463
|
return;
|
|
7258
7464
|
}
|
|
7465
|
+
if (cmd0 === "claude") {
|
|
7466
|
+
const code = await runClaudeCommand(argv.slice(1));
|
|
7467
|
+
if (code !== 0) process.exit(code);
|
|
7468
|
+
return;
|
|
7469
|
+
}
|
|
7259
7470
|
const EDITOR_VERBS = [
|
|
7260
7471
|
"cursor",
|
|
7261
7472
|
"devin",
|
|
@@ -7286,19 +7497,19 @@ async function main() {
|
|
|
7286
7497
|
const target = positional[0];
|
|
7287
7498
|
if (!target) {
|
|
7288
7499
|
console.error("neat init: missing <path>");
|
|
7289
|
-
|
|
7500
|
+
usage5();
|
|
7290
7501
|
process.exit(2);
|
|
7291
7502
|
}
|
|
7292
7503
|
if (apply6 && dryRun) {
|
|
7293
7504
|
console.error("neat init: --apply and --dry-run are mutually exclusive");
|
|
7294
7505
|
process.exit(2);
|
|
7295
7506
|
}
|
|
7296
|
-
const scanPath =
|
|
7507
|
+
const scanPath = path16.resolve(target);
|
|
7297
7508
|
const projectExplicit = parsed.project !== null;
|
|
7298
|
-
const projectName = projectExplicit ? project :
|
|
7509
|
+
const projectName = projectExplicit ? project : path16.basename(scanPath);
|
|
7299
7510
|
const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
|
|
7300
|
-
const fallback = pathsForProject(projectKey,
|
|
7301
|
-
const outPath =
|
|
7511
|
+
const fallback = pathsForProject(projectKey, path16.join(scanPath, "neat-out")).snapshotPath;
|
|
7512
|
+
const outPath = path16.resolve(process.env.NEAT_OUT_PATH ?? fallback);
|
|
7302
7513
|
const result = await runInit({
|
|
7303
7514
|
scanPath,
|
|
7304
7515
|
outPath,
|
|
@@ -7316,24 +7527,24 @@ async function main() {
|
|
|
7316
7527
|
const target = positional[0];
|
|
7317
7528
|
if (!target) {
|
|
7318
7529
|
console.error("neat watch: missing <path>");
|
|
7319
|
-
|
|
7530
|
+
usage5();
|
|
7320
7531
|
process.exit(2);
|
|
7321
7532
|
}
|
|
7322
|
-
const scanPath =
|
|
7323
|
-
const stat = await
|
|
7533
|
+
const scanPath = path16.resolve(target);
|
|
7534
|
+
const stat = await fs14.stat(scanPath).catch(() => null);
|
|
7324
7535
|
if (!stat || !stat.isDirectory()) {
|
|
7325
7536
|
console.error(`neat watch: ${scanPath} is not a directory`);
|
|
7326
7537
|
process.exit(2);
|
|
7327
7538
|
}
|
|
7328
|
-
const projectPaths = pathsForProject(project,
|
|
7329
|
-
const outPath =
|
|
7330
|
-
const errorsPath =
|
|
7331
|
-
process.env.NEAT_ERRORS_PATH ??
|
|
7539
|
+
const projectPaths = pathsForProject(project, path16.join(scanPath, "neat-out"));
|
|
7540
|
+
const outPath = path16.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
|
|
7541
|
+
const errorsPath = path16.resolve(
|
|
7542
|
+
process.env.NEAT_ERRORS_PATH ?? path16.join(path16.dirname(outPath), path16.basename(projectPaths.errorsPath))
|
|
7332
7543
|
);
|
|
7333
|
-
const staleEventsPath =
|
|
7334
|
-
process.env.NEAT_STALE_EVENTS_PATH ??
|
|
7544
|
+
const staleEventsPath = path16.resolve(
|
|
7545
|
+
process.env.NEAT_STALE_EVENTS_PATH ?? path16.join(path16.dirname(outPath), path16.basename(projectPaths.staleEventsPath))
|
|
7335
7546
|
);
|
|
7336
|
-
const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ?
|
|
7547
|
+
const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? path16.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
|
|
7337
7548
|
const handle = await startWatch(getGraph(project), {
|
|
7338
7549
|
scanPath,
|
|
7339
7550
|
outPath,
|
|
@@ -7342,7 +7553,7 @@ async function main() {
|
|
|
7342
7553
|
project,
|
|
7343
7554
|
// Resolve NEAT_HOME so a `neat watch` picks up connectors added to
|
|
7344
7555
|
// ~/.neat/connectors.json (#871). Same resolution the rest of the CLI uses.
|
|
7345
|
-
neatHome: process.env.NEAT_HOME ?
|
|
7556
|
+
neatHome: process.env.NEAT_HOME ? path16.resolve(process.env.NEAT_HOME) : path16.join(os4.homedir(), ".neat"),
|
|
7346
7557
|
...embeddingsCachePath ? { embeddingsCachePath } : {},
|
|
7347
7558
|
host: process.env.HOST ?? "0.0.0.0",
|
|
7348
7559
|
port: Number(process.env.PORT ?? 8080),
|
|
@@ -7378,7 +7589,7 @@ async function main() {
|
|
|
7378
7589
|
const name = positional[0];
|
|
7379
7590
|
if (!name) {
|
|
7380
7591
|
console.error("neat pause: missing <name>");
|
|
7381
|
-
|
|
7592
|
+
usage5();
|
|
7382
7593
|
process.exit(2);
|
|
7383
7594
|
}
|
|
7384
7595
|
const daemon = await findDaemonByProject(name);
|
|
@@ -7403,7 +7614,7 @@ async function main() {
|
|
|
7403
7614
|
const name = positional[0];
|
|
7404
7615
|
if (!name) {
|
|
7405
7616
|
console.error("neat resume: missing <name>");
|
|
7406
|
-
|
|
7617
|
+
usage5();
|
|
7407
7618
|
process.exit(2);
|
|
7408
7619
|
}
|
|
7409
7620
|
const daemon = await findDaemonByProject(name);
|
|
@@ -7433,7 +7644,7 @@ async function main() {
|
|
|
7433
7644
|
const name = positional[0];
|
|
7434
7645
|
if (!name) {
|
|
7435
7646
|
console.error("neat uninstall: missing <name>");
|
|
7436
|
-
|
|
7647
|
+
usage5();
|
|
7437
7648
|
process.exit(2);
|
|
7438
7649
|
}
|
|
7439
7650
|
const daemon = await findDaemonByProject(name);
|
|
@@ -7520,15 +7731,15 @@ async function main() {
|
|
|
7520
7731
|
return;
|
|
7521
7732
|
}
|
|
7522
7733
|
console.error(`neat: unknown command "${cmd}"`);
|
|
7523
|
-
|
|
7734
|
+
usage5();
|
|
7524
7735
|
process.exit(1);
|
|
7525
7736
|
}
|
|
7526
7737
|
async function tryOrchestrator(cmd, parsed) {
|
|
7527
|
-
const scanPath =
|
|
7528
|
-
const stat = await
|
|
7738
|
+
const scanPath = path16.resolve(cmd);
|
|
7739
|
+
const stat = await fs14.stat(scanPath).catch(() => null);
|
|
7529
7740
|
if (!stat || !stat.isDirectory()) return null;
|
|
7530
7741
|
const projectExplicit = parsed.project !== null;
|
|
7531
|
-
const projectName = projectExplicit ? parsed.project :
|
|
7742
|
+
const projectName = projectExplicit ? parsed.project : path16.basename(scanPath);
|
|
7532
7743
|
const result = await runOrchestrator({
|
|
7533
7744
|
scanPath,
|
|
7534
7745
|
project: projectName,
|
|
@@ -7550,7 +7761,10 @@ var QUERY_VERBS = /* @__PURE__ */ new Set([
|
|
|
7550
7761
|
"stale-edges",
|
|
7551
7762
|
"policies",
|
|
7552
7763
|
// Tenth verb (ADR-060) — amends ADR-050's locked allowlist of nine.
|
|
7553
|
-
"divergences"
|
|
7764
|
+
"divergences",
|
|
7765
|
+
// Twelfth verb (ADR-198) — the plain-language door over the whole tool
|
|
7766
|
+
// surface. Amends ADR-050's locked allowlist the same way `divergences` did.
|
|
7767
|
+
"ask"
|
|
7554
7768
|
]);
|
|
7555
7769
|
function resolveProjectFlag(parsed) {
|
|
7556
7770
|
if (parsed.project) return parsed.project;
|
|
@@ -7672,6 +7886,15 @@ async function runQueryVerb(cmd, parsed) {
|
|
|
7672
7886
|
makeWork = (project) => runSearch(client, { query: q, ...project ? { project } : {} });
|
|
7673
7887
|
break;
|
|
7674
7888
|
}
|
|
7889
|
+
case "ask": {
|
|
7890
|
+
const question = positional.join(" ").trim();
|
|
7891
|
+
if (!question) {
|
|
7892
|
+
console.error("neat ask: missing <question>");
|
|
7893
|
+
return 2;
|
|
7894
|
+
}
|
|
7895
|
+
makeWork = (project) => runAsk(client, { question, ...project ? { project } : {} });
|
|
7896
|
+
break;
|
|
7897
|
+
}
|
|
7675
7898
|
case "diff": {
|
|
7676
7899
|
const against = parsed.against ?? parsed.since;
|
|
7677
7900
|
if (!against) {
|
|
@@ -7816,6 +8039,6 @@ export {
|
|
|
7816
8039
|
runMonitorVerb,
|
|
7817
8040
|
runQueryVerb,
|
|
7818
8041
|
runSkill,
|
|
7819
|
-
|
|
8042
|
+
usage5 as usage
|
|
7820
8043
|
};
|
|
7821
8044
|
//# sourceMappingURL=cli.js.map
|