@neat.is/core 0.10.6 → 0.10.7-dev.20261011

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.d.cts CHANGED
@@ -83,7 +83,7 @@ declare class ProjectResolutionError extends Error {
83
83
  }
84
84
  declare function isLoopbackEndpoint(endpoint: string): boolean;
85
85
  declare function resolveProjectForVerb(client: HttpClient, parsed: ParsedArgs, cwdProject?: string): Promise<string | undefined>;
86
- type ClientTargetSource = 'profile' | 'env' | 'active' | 'daemon-record' | 'default';
86
+ type ClientTargetSource = 'profile' | 'env' | 'active' | 'daemon-record' | 'sole-daemon' | 'default';
87
87
  interface ClientTarget {
88
88
  endpoint: string;
89
89
  authToken?: string;
@@ -96,6 +96,7 @@ declare function resolveClientTarget(opts?: {
96
96
  project?: string;
97
97
  profile?: string;
98
98
  }): Promise<ClientTarget>;
99
+ declare function loopbackResolutionHint(): Promise<string | undefined>;
99
100
  declare function projectForCwd(cwd?: string): Promise<string | undefined>;
100
101
  declare function cwdProjectApplies(source: ClientTargetSource): boolean;
101
102
  declare function resolveDaemonUrl(project?: string, profile?: string): Promise<string>;
@@ -103,4 +104,4 @@ declare function runQueryVerb(cmd: string, parsed: ParsedArgs): Promise<number>;
103
104
  declare function runMonitorVerb(parsed: ParsedArgs): Promise<number>;
104
105
  declare function reportFatal(err: unknown, log?: (line: unknown) => void): number;
105
106
 
106
- export { CLAUDE_SKILL_CONFIG, type ClientTarget, type ClientTargetSource, type InitOptions, type InitResult, ProjectResolutionError, QUERY_VERBS, type SkillOptions, UnknownProfileError, commandPrefix, cwdProjectApplies, isLoopbackEndpoint, isNpxInvocation, main, parseArgs, printBanner, projectForCwd, readPackageVersion, reportFatal, resolveClientTarget, resolveDaemonUrl, resolveProjectForVerb, runInit, runMonitorVerb, runQueryVerb, runSkill, usage };
107
+ export { CLAUDE_SKILL_CONFIG, type ClientTarget, type ClientTargetSource, type InitOptions, type InitResult, ProjectResolutionError, QUERY_VERBS, type SkillOptions, UnknownProfileError, commandPrefix, cwdProjectApplies, isLoopbackEndpoint, isNpxInvocation, loopbackResolutionHint, main, parseArgs, printBanner, projectForCwd, readPackageVersion, reportFatal, resolveClientTarget, resolveDaemonUrl, resolveProjectForVerb, runInit, runMonitorVerb, runQueryVerb, runSkill, usage };
package/dist/cli.d.ts CHANGED
@@ -83,7 +83,7 @@ declare class ProjectResolutionError extends Error {
83
83
  }
84
84
  declare function isLoopbackEndpoint(endpoint: string): boolean;
85
85
  declare function resolveProjectForVerb(client: HttpClient, parsed: ParsedArgs, cwdProject?: string): Promise<string | undefined>;
86
- type ClientTargetSource = 'profile' | 'env' | 'active' | 'daemon-record' | 'default';
86
+ type ClientTargetSource = 'profile' | 'env' | 'active' | 'daemon-record' | 'sole-daemon' | 'default';
87
87
  interface ClientTarget {
88
88
  endpoint: string;
89
89
  authToken?: string;
@@ -96,6 +96,7 @@ declare function resolveClientTarget(opts?: {
96
96
  project?: string;
97
97
  profile?: string;
98
98
  }): Promise<ClientTarget>;
99
+ declare function loopbackResolutionHint(): Promise<string | undefined>;
99
100
  declare function projectForCwd(cwd?: string): Promise<string | undefined>;
100
101
  declare function cwdProjectApplies(source: ClientTargetSource): boolean;
101
102
  declare function resolveDaemonUrl(project?: string, profile?: string): Promise<string>;
@@ -103,4 +104,4 @@ declare function runQueryVerb(cmd: string, parsed: ParsedArgs): Promise<number>;
103
104
  declare function runMonitorVerb(parsed: ParsedArgs): Promise<number>;
104
105
  declare function reportFatal(err: unknown, log?: (line: unknown) => void): number;
105
106
 
106
- export { CLAUDE_SKILL_CONFIG, type ClientTarget, type ClientTargetSource, type InitOptions, type InitResult, ProjectResolutionError, QUERY_VERBS, type SkillOptions, UnknownProfileError, commandPrefix, cwdProjectApplies, isLoopbackEndpoint, isNpxInvocation, main, parseArgs, printBanner, projectForCwd, readPackageVersion, reportFatal, resolveClientTarget, resolveDaemonUrl, resolveProjectForVerb, runInit, runMonitorVerb, runQueryVerb, runSkill, usage };
107
+ export { CLAUDE_SKILL_CONFIG, type ClientTarget, type ClientTargetSource, type InitOptions, type InitResult, ProjectResolutionError, QUERY_VERBS, type SkillOptions, UnknownProfileError, commandPrefix, cwdProjectApplies, isLoopbackEndpoint, isNpxInvocation, loopbackResolutionHint, main, parseArgs, printBanner, projectForCwd, readPackageVersion, reportFatal, resolveClientTarget, resolveDaemonUrl, resolveProjectForVerb, runInit, runMonitorVerb, runQueryVerb, runSkill, usage };
package/dist/cli.js CHANGED
@@ -1,4 +1,7 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ deployHintLines
4
+ } from "./chunk-AEMLUMHC.js";
2
5
  import {
3
6
  clearDaemonRecord,
4
7
  portFromListenAddress,
@@ -8,7 +11,7 @@ import {
8
11
  resolveNeatVersion,
9
12
  startK8sSubstratePolling,
10
13
  writeDaemonRecord
11
- } from "./chunk-OKM4N7GH.js";
14
+ } from "./chunk-S5S2FYCQ.js";
12
15
  import {
13
16
  buildSearchIndex,
14
17
  searchProviderFromEnv
@@ -68,11 +71,12 @@ import {
68
71
  startStalenessLoop,
69
72
  upsertConnectorEntry,
70
73
  validateConnectorEntry
71
- } from "./chunk-MW2XYLCS.js";
74
+ } from "./chunk-6K22WGO7.js";
72
75
  import {
73
76
  ProjectNameCollisionError,
74
77
  RegistryError,
75
78
  addProject,
79
+ discoverDaemons,
76
80
  findDaemonByProject,
77
81
  findProjectByPath,
78
82
  getProject,
@@ -5065,7 +5069,7 @@ async function connectorAdd(args, deps) {
5065
5069
  }
5066
5070
  const { replaced } = await upsertConnectorEntry(entry2, deps.home);
5067
5071
  const verb = replaced ? "updated" : "added";
5068
- const where = project ? `project "${project}"` : "the bootstrapping project";
5072
+ const where = project ? `project "${project}"` : "every project (no --project set)";
5069
5073
  deps.out(`${verb} connector "${id}" (${provider}) for ${where}.`);
5070
5074
  if (push) {
5071
5075
  deps.out("The drain is live \u2014 traces arrive at the daemon's OTLP receiver. No poll or restart needed.");
@@ -5128,7 +5132,7 @@ async function connectorList(deps, filterProject, json = false) {
5128
5132
  }
5129
5133
  deps.out("id provider project credential");
5130
5134
  for (const e of entries) {
5131
- const project = e.project ?? "(bootstrapping project)";
5135
+ const project = e.project ?? "(every project)";
5132
5136
  const credential = describeCredential(e.credential, deps.env).map((c) => {
5133
5137
  const status = c.status ? ` (${c.status})` : c.kind === "plaintext" ? " (plaintext)" : "";
5134
5138
  return c.field ? `${c.field}=${c.display}${status}` : `${c.display}${status}`;
@@ -6391,6 +6395,8 @@ async function runIncidents(client, input) {
6391
6395
  const blockLines = [];
6392
6396
  for (const ev of ordered) {
6393
6397
  blockLines.push(` ${ev.timestamp} \u2014 ${ev.service}: ${ev.errorMessage}`);
6398
+ if (ev.exceptionType) blockLines.push(` exception: ${ev.exceptionType}`);
6399
+ for (const l of deployHintLines(ev)) blockLines.push(` ${l}`);
6394
6400
  blockLines.push(` trace=${ev.traceId} span=${ev.spanId}`);
6395
6401
  }
6396
6402
  const target = input.nodeId ?? "the project";
@@ -7242,7 +7248,7 @@ import path15 from "path";
7242
7248
  import readline4 from "readline/promises";
7243
7249
 
7244
7250
  // src/agent-directive.generated.ts
7245
- var AGENT_DIRECTIVE = '## Query NEAT before searching files\n\nThis project has a live NEAT graph: code, data and infrastructure declarations\nfused with runtime traffic, incidents and supported provider telemetry. It can\nshow what exists, what actually ran, where a failure began, and what may break\nwhen a node changes. The graph is deterministic: the agent supplies the model;\nNEAT resolves named nodes and traverses recorded evidence rather than asking an\nLLM to infer architecture from file names.\n\nEvery claim carries provenance and confidence. `EXTRACTED` comes from recognized\nsource or configuration, `OBSERVED` from spans or supported provider signals,\n`INFERRED` from a bridged relationship, and `STALE` marks an observed edge that\nwent quiet. Missing runtime evidence does not prove a path never runs.\n\n**For questions about this system\'s behavior, structure, data dependencies,\nfailures, or change impact, call the MCP `ask` tool (`mcp__neat__ask`) before\nRead/Grep/Glob/Bash.** You do not need an exact node id. `ask` resolves names\nand routes to the relevant graph traversal, returning provenance-tagged facts.\nWhen MCP is unavailable, use the same door through the CLI:\n\n```\nnpx neat.is ask "why is checkout failing?"\nnpx neat.is ask "what breaks if I change the orders table?"\nnpx neat.is ask "what does the payments service depend on at runtime?"\n```\n\nFor a failure, ask first, then call `get_incident_card` on the named service,\nfile or symbol. The card combines the incident, likely cause, blast radius,\npolicies and divergence into a work order. Use `expand` one hop at a time and\n`relate` to test whether the suspected cause and symptom share a signal. Check\nprovenance and confidence before acting.\n\nBefore a change, use `get_blast_radius` and `check_policies` to see dependents\nand advisory architectural rules. Use `get_divergences` to compare declared\nintent with observed behavior, `get_graph_diff` to compare a saved snapshot\nwith the live graph, and `get_recent_stale_edges` when traffic goes quiet.\n`get_dependencies` maps a node\'s outgoing graph; `get_observed_dependencies`\nshows only seen runtime/provider calls. `semantic_search` finds node labels,\nnot arbitrary source text.\n\nRead source when the graph does not model what you need: comments, arbitrary\nstring literals, config details, unsupported syntax, or a repository that has\nnot been extracted or connected. If a graph answer is empty, check the daemon\nwith `npx neat.is list` or restart it with `npx neat.is up`; wire MCP with\n`npx neat.is skill --apply`.\n';
7251
+ var AGENT_DIRECTIVE = '## Let NEAT investigate before you search files\n\nThis project has NEAT. It does the investigation for this system so you can\nspend your effort on the code: what exists, what actually ran, where a failure\nbegan, and what a change will reach. It answers from a live graph that fuses\ncode, data and infrastructure declarations with runtime traffic, incidents and\nsupported provider telemetry. The graph is deterministic: you supply the\nreasoning about the fix; NEAT resolves named nodes and traverses recorded\nevidence rather than asking an LLM to infer architecture from file names.\n\nEvery claim carries provenance and confidence. `EXTRACTED` comes from recognized\nsource or configuration, `OBSERVED` from spans or supported provider signals,\n`INFERRED` from a bridged relationship, and `STALE` marks an observed edge that\nwent quiet. Missing runtime evidence does not prove a path never runs.\n\n**For questions about this system\'s behavior, structure, data dependencies,\nfailures, or change impact, call the MCP `ask` tool (`mcp__neat__ask`) before\nRead/Grep/Glob/Bash.** You do not need an exact node id. `ask` resolves names\nand routes to the relevant graph traversal, returning provenance-tagged facts.\nWhen MCP is unavailable, use the same door through the CLI:\n\n```\nnpx neat.is ask "why is checkout failing?"\nnpx neat.is ask "what breaks if I change the orders table?"\nnpx neat.is ask "what does the payments service depend on at runtime?"\n```\n\nFor a failure, call `get_incident_card` on the failing service, file or symbol\n(if you don\'t know which, `ask` what is failing first). The card is the\ninvestigation already done: the incident, its likely cause chain, blast radius,\nthe governing policies and divergence, in one work order. Use `expand` one hop\nat a time and `relate` to test whether the suspected cause and symptom share a\nsignal. Check provenance and confidence before acting.\n\nBefore a change, use `get_blast_radius` and `check_policies` to see dependents\nand advisory architectural rules. Use `get_divergences` to compare declared\nintent with observed behavior, `get_graph_diff` to compare a saved snapshot\nwith the live graph, and `get_recent_stale_edges` when traffic goes quiet.\n`get_dependencies` maps a node\'s outgoing graph; `get_observed_dependencies`\nshows only seen runtime/provider calls. `semantic_search` finds node labels,\nnot arbitrary source text.\n\nRead source when the graph does not model what you need: comments, arbitrary\nstring literals, config details, unsupported syntax, or a repository that has\nnot been extracted or connected. If a graph answer is empty, check the daemon\nwith `npx neat.is list` or restart it with `npx neat.is up`; wire MCP with\n`npx neat.is skill --apply`.\n';
7246
7252
 
7247
7253
  // src/welcome.ts
7248
7254
  var WORDMARK = [
@@ -7257,10 +7263,13 @@ function renderAgentSetupPrompt(project, restPort) {
7257
7263
  const endpoint = restPort === void 0 ? "The daemon endpoint is unavailable; run `npx neat.is up` to start it." : `The local daemon is serving it at http://127.0.0.1:${restPort}.`;
7258
7264
  return `NEAT is set up for project "${project}". ${endpoint}
7259
7265
 
7260
- NEAT models this software system as one graph: code, data, infrastructure,
7261
- runtime traffic, incidents, and supported provider telemetry fused where evidence
7262
- permits. Use it to see what exists, what actually ran, where a failure began, and
7263
- what a change could affect. Missing observations do not prove a path never runs.
7266
+ NEAT does the investigation for this system, so you can spend your effort on the
7267
+ code. When something fails, call get_incident_card on the failing service, file
7268
+ or symbol: one work order with where the failure is, the cause chain (provenance
7269
+ on every hop), what a fix reaches, the policies that govern the code, and where
7270
+ code and runtime disagree. For anything else about how the system works, ask.
7271
+ NEAT answers from a live graph that fuses the code with what actually ran; a
7272
+ path it hasn't seen run is not proof the path never runs.
7264
7273
 
7265
7274
  1. Wire NEAT's MCP server into this agent session:
7266
7275
  npx neat.is skill --apply
@@ -7271,8 +7280,10 @@ what a change could affect. Missing observations do not prove a path never runs.
7271
7280
  If the daemon is unreachable, run \`npx neat.is up\`.
7272
7281
 
7273
7282
  ${AGENT_DIRECTIVE}
7274
- As the app or tests run, traces fill the OBSERVED layer. Daemon log:
7275
- neat-out/daemon.log. Reopen this door with \`npx neat.is welcome\`.`;
7283
+ Runtime evidence arrives once the app runs with NEAT attached: for Node, load
7284
+ .env.neat first (\`set -a; . ./.env.neat; set +a\`); for Python, start under
7285
+ \`opentelemetry-instrument\`. Daemon log: neat-out/daemon.log. Reopen this
7286
+ door with \`npx neat.is welcome\`.`;
7276
7287
  }
7277
7288
  function printHeader(out) {
7278
7289
  for (const line of WORDMARK) out(line);
@@ -10041,10 +10052,15 @@ async function main() {
10041
10052
  return;
10042
10053
  }
10043
10054
  if (cmd === "sync") {
10055
+ const syncTarget = await resolveClientTarget({
10056
+ ...parsed.project ? { project: parsed.project } : {},
10057
+ ...parsed.profile ? { profile: parsed.profile } : {}
10058
+ });
10044
10059
  const result = await runSync({
10045
10060
  ...parsed.project ? { project: parsed.project } : {},
10046
10061
  ...parsed.to ? { to: parsed.to } : {},
10047
10062
  ...parsed.token ? { token: parsed.token } : {},
10063
+ daemonUrl: syncTarget.endpoint,
10048
10064
  dryRun: parsed.dryRun,
10049
10065
  noInstrument: parsed.noInstrument,
10050
10066
  sourceEdit: parsed.sourceEdit,
@@ -10203,9 +10219,28 @@ async function resolveClientTarget(opts = {}) {
10203
10219
  if (daemon) {
10204
10220
  return { endpoint: `http://localhost:${daemon.record.ports.rest}`, source: "daemon-record" };
10205
10221
  }
10222
+ } else {
10223
+ const live = (await discoverDaemons()).filter((d) => d.live);
10224
+ if (live.length === 1) {
10225
+ return {
10226
+ endpoint: `http://localhost:${live[0].record.ports.rest}`,
10227
+ source: "sole-daemon"
10228
+ };
10229
+ }
10206
10230
  }
10207
10231
  return { endpoint: "http://localhost:8080", source: "default" };
10208
10232
  }
10233
+ async function loopbackResolutionHint() {
10234
+ const live = (await discoverDaemons()).filter((d) => d.live);
10235
+ if (live.length === 0) {
10236
+ return "No NEAT daemon is running. Start one with `npx neat.is` in your project (or `neat up`), then re-run \u2014 or set NEAT_API_URL to a daemon.";
10237
+ }
10238
+ if (live.length > 1) {
10239
+ const list = live.map((d) => ` ${d.record.project} \u2192 http://localhost:${d.record.ports.rest}`).join("\n");
10240
+ return "This directory is not a registered NEAT project and several daemons are running, so NEAT cannot pick one. Run from the project directory, pass --project <name>, or set NEAT_API_URL:\n" + list;
10241
+ }
10242
+ return void 0;
10243
+ }
10209
10244
  async function projectForCwd(cwd = process.cwd()) {
10210
10245
  try {
10211
10246
  return (await findProjectByPath(cwd))?.name;
@@ -10214,7 +10249,7 @@ async function projectForCwd(cwd = process.cwd()) {
10214
10249
  }
10215
10250
  }
10216
10251
  function cwdProjectApplies(source) {
10217
- return source === "daemon-record" || source === "default";
10252
+ return source === "daemon-record" || source === "sole-daemon" || source === "default";
10218
10253
  }
10219
10254
  async function resolveDaemonUrl(project, profile) {
10220
10255
  return (await resolveClientTarget({ project, profile })).endpoint;
@@ -10236,6 +10271,11 @@ async function runQueryVerb(cmd, parsed) {
10236
10271
  }
10237
10272
  throw err;
10238
10273
  }
10274
+ if (target.source === "default" && !requestedProject && !cwdProject) {
10275
+ const hint = await loopbackResolutionHint();
10276
+ if (hint) process.stderr.write(`${hint}
10277
+ `);
10278
+ }
10239
10279
  const client = createHttpClient(target.endpoint, target.authToken);
10240
10280
  const positional = parsed.positional;
10241
10281
  let makeWork;
@@ -10482,6 +10522,7 @@ export {
10482
10522
  cwdProjectApplies,
10483
10523
  isLoopbackEndpoint,
10484
10524
  isNpxInvocation,
10525
+ loopbackResolutionHint,
10485
10526
  main,
10486
10527
  parseArgs,
10487
10528
  printBanner,