@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/cli.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  resolveHost,
7
7
  resolveNeatVersion,
8
8
  writeDaemonRecord
9
- } from "./chunk-L4SZIIER.js";
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-RQQUI3NQ.js";
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 path15 from "path";
91
+ import path16 from "path";
92
92
  import os4 from "os";
93
- import { promises as fs13 } from "fs";
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 runInstall = options.runInstall ?? runPackageManagerInstall;
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 runInstall(cmd);
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
- console.log(`neat hooks: installed the search-nudge hook`);
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
- console.log("restart Claude Code to load the hook. On a Grep/Glob or a Bash grep,");
4940
- console.log("your agent will now be nudged to query NEAT first.");
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-nudge hook and write the");
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("The hook is a gentle, non-blocking nudge \u2014 searches still run. It is");
4960
- console.log("Claude-Code-specific; other harnesses get the same steer from the guidance.");
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/codex-cli.ts
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 path12.resolve(override);
5190
+ if (override && override.length > 0) return path13.resolve(override);
5024
5191
  const home = process.env.HOME ?? process.env.USERPROFILE ?? os2.homedir();
5025
- return path12.join(home, ".codex", "config.toml");
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 path12.resolve(override);
5030
- return path12.join(process.cwd(), "AGENTS.md");
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 fs11.readFile(configPath, "utf8");
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 fs11.readFile(agentsPath, "utf8");
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 fs11.mkdir(path12.dirname(configPath), { recursive: true });
5214
- await fs11.writeFile(configPath, config.text, "utf8");
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 fs11.mkdir(path12.dirname(agentsPath), { recursive: true });
5221
- await fs11.writeFile(agentsPath, agents.text, "utf8");
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 usage2() {
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
- usage2();
5426
+ usage3();
5260
5427
  return 0;
5261
5428
  default:
5262
5429
  console.error(`neat codex: unknown flag "${arg}"`);
5263
- usage2();
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 path13 from "path";
5444
+ import path14 from "path";
5278
5445
  import os3 from "os";
5279
- import { promises as fs12 } from "fs";
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 ? path13.resolve(xdg) : path13.join(homeDir(), ".config");
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 ? path13.resolve(v) : void 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") ?? path13.join(homeDir(), ".cursor", "mcp.json"),
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") ?? path13.join(homeDir(), ".codeium", "windsurf", "mcp_config.json"),
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") ?? path13.join(homeDir(), ".gemini", "settings.json"),
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") ?? path13.join(homeDir(), ".qwen", "settings.json"),
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") ?? path13.join(homeDir(), ".aws", "amazonq", "mcp.json"),
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") ?? path13.join(process.cwd(), ".roo", "mcp.json"),
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 path13.join(appData, "Zed", "settings.json");
5540
+ if (appData && appData.length > 0) return path14.join(appData, "Zed", "settings.json");
5374
5541
  }
5375
- return path13.join(homeDir(), ".config", "zed", "settings.json");
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") ?? path13.join(xdgConfigDir(), "opencode", "opencode.json"),
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") ?? path13.join(xdgConfigDir(), "crush", "crush.json"),
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 fs12.readFile(mcpPath, "utf8");
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 ? path13.join(opts.projectDir, client.rulesFileName) : "";
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 fs12.readFile(rulesPath, "utf8");
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 fs12.mkdir(path13.dirname(mcpPath), { recursive: true });
5544
- await fs12.writeFile(mcpPath, mcp.text, "utf8");
5710
+ await fs13.mkdir(path14.dirname(mcpPath), { recursive: true });
5711
+ await fs13.writeFile(mcpPath, mcp.text, "utf8");
5545
5712
  if (hasRules) {
5546
- await fs12.mkdir(path13.dirname(rulesPath), { recursive: true });
5547
- await fs12.writeFile(rulesPath, newRules, "utf8");
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 usage3(client) {
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
- usage3(client);
5758
+ usage4(client);
5592
5759
  return 0;
5593
5760
  default:
5594
5761
  console.error(`neat ${client.id}: unknown flag "${arg}"`);
5595
- usage3(client);
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(path16) {
5804
+ async get(path17) {
5638
5805
  let res;
5639
5806
  try {
5640
- res = await fetch(`${root}${path16}`, {
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 ${path16}: ${body}`,
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(path16, body) {
5825
+ async post(path17, body) {
5659
5826
  let res;
5660
5827
  try {
5661
- res = await fetch(`${root}${path16}`, {
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 ${path16}: ${text}`,
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 path16 = projectPath(
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(path16);
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 path16 = projectPath(
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(path16);
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 path16 = projectPath(
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(path16);
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 path16 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
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(path16);
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 path14 from "path";
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}${path14.sep}`)) {
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 usage4() {
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 gentle Claude Code search-nudge hook and");
6735
- console.log(" writes agent-agnostic graph-first guidance for other harnesses.");
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 fs13.stat(opts.scanPath).catch(() => null);
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 = path15.join(opts.scanPath, "neat.patch");
7251
+ const patchPath = path16.join(opts.scanPath, "neat.patch");
7046
7252
  if (opts.dryRun) {
7047
- await fs13.writeFile(patchPath, patch, "utf8");
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 = path15.join(opts.scanPath, ".gitignore");
7051
- const gitignoreExists = await fs13.stat(gitignorePath).then(() => true).catch(() => false);
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
- path15.join(opts.scanPath, "neat-out")
7268
+ path16.join(opts.scanPath, "neat-out")
7063
7269
  );
7064
- const errorsPath = path15.join(path15.dirname(opts.outPath), path15.basename(projectPaths.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 fs13.writeFile(patchPath, patch, "utf8");
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 path15.resolve(override);
7389
+ if (override && override.length > 0) return path16.resolve(override);
7184
7390
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
7185
- return path15.join(home, ".claude.json");
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 fs13.readFile(target, "utf8"));
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 fs13.mkdir(path15.dirname(target), { recursive: true });
7210
- await fs13.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
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
- usage4();
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
- usage4();
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 = path15.resolve(target);
7507
+ const scanPath = path16.resolve(target);
7297
7508
  const projectExplicit = parsed.project !== null;
7298
- const projectName = projectExplicit ? project : path15.basename(scanPath);
7509
+ const projectName = projectExplicit ? project : path16.basename(scanPath);
7299
7510
  const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
7300
- const fallback = pathsForProject(projectKey, path15.join(scanPath, "neat-out")).snapshotPath;
7301
- const outPath = path15.resolve(process.env.NEAT_OUT_PATH ?? fallback);
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
- usage4();
7530
+ usage5();
7320
7531
  process.exit(2);
7321
7532
  }
7322
- const scanPath = path15.resolve(target);
7323
- const stat = await fs13.stat(scanPath).catch(() => null);
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, path15.join(scanPath, "neat-out"));
7329
- const outPath = path15.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
7330
- const errorsPath = path15.resolve(
7331
- process.env.NEAT_ERRORS_PATH ?? path15.join(path15.dirname(outPath), path15.basename(projectPaths.errorsPath))
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 = path15.resolve(
7334
- process.env.NEAT_STALE_EVENTS_PATH ?? path15.join(path15.dirname(outPath), path15.basename(projectPaths.staleEventsPath))
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 ? path15.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
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 ? path15.resolve(process.env.NEAT_HOME) : path15.join(os4.homedir(), ".neat"),
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
- usage4();
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
- usage4();
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
- usage4();
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
- usage4();
7734
+ usage5();
7524
7735
  process.exit(1);
7525
7736
  }
7526
7737
  async function tryOrchestrator(cmd, parsed) {
7527
- const scanPath = path15.resolve(cmd);
7528
- const stat = await fs13.stat(scanPath).catch(() => null);
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 : path15.basename(scanPath);
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
- usage4 as usage
8042
+ usage5 as usage
7820
8043
  };
7821
8044
  //# sourceMappingURL=cli.js.map