@neat.is/core 0.5.1 → 0.5.3
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-BI3XKGVG.js → chunk-GJHEZC5K.js} +56 -21
- package/dist/chunk-GJHEZC5K.js.map +1 -0
- package/dist/{chunk-DPEPI2N6.js → chunk-X2AMX3QZ.js} +32 -14
- package/dist/chunk-X2AMX3QZ.js.map +1 -0
- package/dist/cli.cjs +383 -121
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +267 -59
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +85 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +85 -32
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +55 -20
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-BI3XKGVG.js.map +0 -1
- package/dist/chunk-DPEPI2N6.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
resolveNeatVersion,
|
|
10
10
|
validateConnectorEntry,
|
|
11
11
|
writeDaemonRecord
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-X2AMX3QZ.js";
|
|
13
13
|
import {
|
|
14
14
|
buildSearchIndex
|
|
15
15
|
} from "./chunk-BIY46Q6U.js";
|
|
@@ -68,7 +68,7 @@ import {
|
|
|
68
68
|
startPersistLoop,
|
|
69
69
|
startStalenessLoop,
|
|
70
70
|
upsertConnectorEntry
|
|
71
|
-
} from "./chunk-
|
|
71
|
+
} from "./chunk-GJHEZC5K.js";
|
|
72
72
|
import {
|
|
73
73
|
startOtelGrpcReceiver
|
|
74
74
|
} from "./chunk-I72HTUOG.js";
|
|
@@ -82,8 +82,8 @@ import {
|
|
|
82
82
|
} from "./chunk-CFDPIMRP.js";
|
|
83
83
|
|
|
84
84
|
// src/cli.ts
|
|
85
|
-
import
|
|
86
|
-
import { promises as
|
|
85
|
+
import path10 from "path";
|
|
86
|
+
import { promises as fs8 } from "fs";
|
|
87
87
|
|
|
88
88
|
// src/banner.ts
|
|
89
89
|
import path from "path";
|
|
@@ -810,7 +810,7 @@ import semver from "semver";
|
|
|
810
810
|
|
|
811
811
|
// src/installers/templates.ts
|
|
812
812
|
var OTEL_INIT_HEADER = "// Generated by `neat init --apply` (ADR-069). OpenTelemetry auto-instrumentation hook.";
|
|
813
|
-
var OTEL_INIT_STAMP = "// neat-template-version:
|
|
813
|
+
var OTEL_INIT_STAMP = "// neat-template-version: 7 \u2014 OTel init degrades to no-OBSERVED instead of crashing the host app when @opentelemetry deps are absent (#820); daemon.json endpoint resolution (ADR-096) + layered file-first capture (ADR-090).";
|
|
814
814
|
var OTEL_OTLP_HEADERS_JS = "if (process.env.NEAT_OTEL_TOKEN) process.env.OTEL_EXPORTER_OTLP_HEADERS ||= 'Authorization=Bearer ' + process.env.NEAT_OTEL_TOKEN";
|
|
815
815
|
var OTEL_OTLP_PROTOCOL_JS = "process.env.OTEL_EXPORTER_OTLP_PROTOCOL ||= 'http/json'";
|
|
816
816
|
var OTEL_ENDPOINT_RESOLVER_CJS = `;(function () {
|
|
@@ -1146,6 +1146,14 @@ ${OTEL_ENDPOINT_RESOLVER_CJS}
|
|
|
1146
1146
|
${OTEL_OTLP_PROTOCOL_JS}
|
|
1147
1147
|
${OTEL_OTLP_HEADERS_JS}
|
|
1148
1148
|
|
|
1149
|
+
// Instrumentation is ambient \u2014 it must never break the host app. If the
|
|
1150
|
+
// @opentelemetry packages aren't installed (a package-manager install that
|
|
1151
|
+
// failed or hasn't run yet), degrade to running WITHOUT OBSERVED rather than
|
|
1152
|
+
// crashing the process on a missing module (#820). The require-based flavor can
|
|
1153
|
+
// guard this at runtime; do so around the whole SDK setup, not just the
|
|
1154
|
+
// requires, so a broken instrumentation registration can't take the app down
|
|
1155
|
+
// either.
|
|
1156
|
+
try {
|
|
1149
1157
|
const { NodeSDK } = require('@opentelemetry/sdk-node')
|
|
1150
1158
|
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node')
|
|
1151
1159
|
const { trace, context } = require('@opentelemetry/api')
|
|
@@ -1157,6 +1165,14 @@ __INSTRUMENTATION_BLOCK__
|
|
|
1157
1165
|
const sdk = new NodeSDK({ instrumentations })
|
|
1158
1166
|
sdk.start()
|
|
1159
1167
|
${neatWireCaptureSource(false)}
|
|
1168
|
+
} catch (__neatOtelErr) {
|
|
1169
|
+
const __neatMsg = String((__neatOtelErr && __neatOtelErr.message) || __neatOtelErr)
|
|
1170
|
+
if (/Cannot find module|MODULE_NOT_FOUND/.test(__neatMsg)) {
|
|
1171
|
+
console.warn('[neat] OpenTelemetry is not active: its packages are not installed, so this app is running without OBSERVED tracing. Run your package manager install and restart to enable it.')
|
|
1172
|
+
} else {
|
|
1173
|
+
console.warn('[neat] OpenTelemetry failed to start; the app is running without OBSERVED tracing: ' + __neatMsg)
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1160
1176
|
`;
|
|
1161
1177
|
var OTEL_INIT_ESM = `${OTEL_INIT_HEADER}
|
|
1162
1178
|
${OTEL_INIT_STAMP}
|
|
@@ -3836,8 +3852,181 @@ async function runConnectorCommand(rawArgs, deps = {}) {
|
|
|
3836
3852
|
}
|
|
3837
3853
|
}
|
|
3838
3854
|
|
|
3839
|
-
// src/cli
|
|
3855
|
+
// src/hooks-cli.ts
|
|
3840
3856
|
import path8 from "path";
|
|
3857
|
+
import os from "os";
|
|
3858
|
+
import { promises as fs7 } from "fs";
|
|
3859
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
3860
|
+
var HOOK_FILENAME = "neat-search-nudge.mjs";
|
|
3861
|
+
var GUIDE_FILENAME = "GRAPH_FIRST.md";
|
|
3862
|
+
var GUIDE_INSTALL_NAME = "neat-graph-first.md";
|
|
3863
|
+
var HOOK_MATCHER = "Grep|Glob|Bash";
|
|
3864
|
+
function moduleDir() {
|
|
3865
|
+
return typeof __dirname !== "undefined" ? __dirname : path8.dirname(fileURLToPath3(import.meta.url));
|
|
3866
|
+
}
|
|
3867
|
+
async function readSkillAsset(rel) {
|
|
3868
|
+
const here = moduleDir();
|
|
3869
|
+
const candidates = [
|
|
3870
|
+
path8.resolve(here, "../../claude-skill", rel),
|
|
3871
|
+
path8.resolve(here, "../../../claude-skill", rel),
|
|
3872
|
+
path8.resolve(here, "../claude-skill", rel)
|
|
3873
|
+
];
|
|
3874
|
+
for (const candidate of candidates) {
|
|
3875
|
+
try {
|
|
3876
|
+
return await fs7.readFile(candidate, "utf8");
|
|
3877
|
+
} catch {
|
|
3878
|
+
}
|
|
3879
|
+
}
|
|
3880
|
+
throw new Error(
|
|
3881
|
+
`neat hooks: could not find @neat.is/claude-skill/${rel} \u2014 is the package installed?`
|
|
3882
|
+
);
|
|
3883
|
+
}
|
|
3884
|
+
function neatHome() {
|
|
3885
|
+
const override = process.env.NEAT_HOME;
|
|
3886
|
+
if (override && override.length > 0) return path8.resolve(override);
|
|
3887
|
+
return path8.join(os.homedir(), ".neat");
|
|
3888
|
+
}
|
|
3889
|
+
function claudeSettingsPath() {
|
|
3890
|
+
const override = process.env.NEAT_CLAUDE_SETTINGS;
|
|
3891
|
+
if (override && override.length > 0) return path8.resolve(override);
|
|
3892
|
+
const home = process.env.HOME ?? process.env.USERPROFILE ?? os.homedir();
|
|
3893
|
+
return path8.join(home, ".claude", "settings.json");
|
|
3894
|
+
}
|
|
3895
|
+
function installedHookPath() {
|
|
3896
|
+
return path8.join(neatHome(), "hooks", HOOK_FILENAME);
|
|
3897
|
+
}
|
|
3898
|
+
function isNeatSearchEntry(entry2) {
|
|
3899
|
+
return (entry2.hooks ?? []).some(
|
|
3900
|
+
(h) => typeof h.command === "string" && h.command.includes(HOOK_FILENAME)
|
|
3901
|
+
);
|
|
3902
|
+
}
|
|
3903
|
+
function neatHookEntry(command) {
|
|
3904
|
+
return { matcher: HOOK_MATCHER, hooks: [{ type: "command", command }] };
|
|
3905
|
+
}
|
|
3906
|
+
function hookCommand(scriptPath) {
|
|
3907
|
+
return `node "${scriptPath}"`;
|
|
3908
|
+
}
|
|
3909
|
+
async function runHooks(opts) {
|
|
3910
|
+
if (opts.printHook) {
|
|
3911
|
+
process.stdout.write(await readSkillAsset(`hooks/${HOOK_FILENAME}`));
|
|
3912
|
+
return { exitCode: 0 };
|
|
3913
|
+
}
|
|
3914
|
+
if (opts.printGuide) {
|
|
3915
|
+
process.stdout.write(await readSkillAsset(GUIDE_FILENAME));
|
|
3916
|
+
return { exitCode: 0 };
|
|
3917
|
+
}
|
|
3918
|
+
if (opts.printSettings) {
|
|
3919
|
+
const block = {
|
|
3920
|
+
hooks: { PreToolUse: [neatHookEntry(hookCommand(installedHookPath()))] }
|
|
3921
|
+
};
|
|
3922
|
+
process.stdout.write(JSON.stringify(block, null, 2) + "\n");
|
|
3923
|
+
return { exitCode: 0 };
|
|
3924
|
+
}
|
|
3925
|
+
if (opts.apply) {
|
|
3926
|
+
const hookScript = await readSkillAsset(`hooks/${HOOK_FILENAME}`);
|
|
3927
|
+
const guide = await readSkillAsset(GUIDE_FILENAME);
|
|
3928
|
+
const scriptPath = installedHookPath();
|
|
3929
|
+
await fs7.mkdir(path8.dirname(scriptPath), { recursive: true });
|
|
3930
|
+
await fs7.writeFile(scriptPath, hookScript, { mode: 493 });
|
|
3931
|
+
const guidePath = path8.join(neatHome(), GUIDE_INSTALL_NAME);
|
|
3932
|
+
await fs7.writeFile(guidePath, guide, "utf8");
|
|
3933
|
+
const settingsFile = claudeSettingsPath();
|
|
3934
|
+
let settings = {};
|
|
3935
|
+
try {
|
|
3936
|
+
settings = JSON.parse(await fs7.readFile(settingsFile, "utf8"));
|
|
3937
|
+
} catch (err) {
|
|
3938
|
+
if (err.code !== "ENOENT") {
|
|
3939
|
+
console.error(
|
|
3940
|
+
`neat hooks: failed to read ${settingsFile} \u2014 ${err.message}`
|
|
3941
|
+
);
|
|
3942
|
+
return { exitCode: 1 };
|
|
3943
|
+
}
|
|
3944
|
+
}
|
|
3945
|
+
const hooks = settings.hooks ?? {};
|
|
3946
|
+
const preToolUse = Array.isArray(hooks.PreToolUse) ? [...hooks.PreToolUse] : [];
|
|
3947
|
+
const command = hookCommand(scriptPath);
|
|
3948
|
+
const existingIdx = preToolUse.findIndex(isNeatSearchEntry);
|
|
3949
|
+
if (existingIdx >= 0) {
|
|
3950
|
+
preToolUse[existingIdx] = neatHookEntry(command);
|
|
3951
|
+
} else {
|
|
3952
|
+
preToolUse.push(neatHookEntry(command));
|
|
3953
|
+
}
|
|
3954
|
+
const merged = {
|
|
3955
|
+
...settings,
|
|
3956
|
+
hooks: { ...hooks, PreToolUse: preToolUse }
|
|
3957
|
+
};
|
|
3958
|
+
await fs7.mkdir(path8.dirname(settingsFile), { recursive: true });
|
|
3959
|
+
await fs7.writeFile(settingsFile, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
3960
|
+
console.log(`neat hooks: installed the search-nudge hook`);
|
|
3961
|
+
console.log(` script: ${scriptPath}`);
|
|
3962
|
+
console.log(` settings: ${settingsFile} (PreToolUse \u2192 ${HOOK_MATCHER})`);
|
|
3963
|
+
console.log(` guidance: ${guidePath}`);
|
|
3964
|
+
console.log("");
|
|
3965
|
+
console.log("restart Claude Code to load the hook. On a Grep/Glob or a Bash grep,");
|
|
3966
|
+
console.log("your agent will now be nudged to query NEAT first.");
|
|
3967
|
+
console.log("");
|
|
3968
|
+
console.log("The hook is Claude-Code-specific. For agents on other harnesses, paste");
|
|
3969
|
+
console.log(`the guidance above into your project instructions (CLAUDE.md / AGENTS.md).`);
|
|
3970
|
+
return { exitCode: 0 };
|
|
3971
|
+
}
|
|
3972
|
+
usage();
|
|
3973
|
+
return { exitCode: 0 };
|
|
3974
|
+
}
|
|
3975
|
+
function usage() {
|
|
3976
|
+
console.log("neat hooks \u2014 wire NEAT into your agent so it queries the graph before grepping");
|
|
3977
|
+
console.log("");
|
|
3978
|
+
console.log(" --apply install the Claude Code search-nudge hook and write the");
|
|
3979
|
+
console.log(" graph-first guidance to ~/.neat/, merging into");
|
|
3980
|
+
console.log(" ~/.claude/settings.json without touching your other hooks");
|
|
3981
|
+
console.log(" --print-hook print the hook script to stdout");
|
|
3982
|
+
console.log(" --print-guide print the agent-agnostic graph-first guidance to stdout");
|
|
3983
|
+
console.log(" --print-settings print the settings.json PreToolUse block --apply would add");
|
|
3984
|
+
console.log("");
|
|
3985
|
+
console.log("The hook is a gentle, non-blocking nudge \u2014 searches still run. It is");
|
|
3986
|
+
console.log("Claude-Code-specific; other harnesses get the same steer from the guidance.");
|
|
3987
|
+
}
|
|
3988
|
+
async function runHooksCommand(args) {
|
|
3989
|
+
const opts = {
|
|
3990
|
+
apply: false,
|
|
3991
|
+
printHook: false,
|
|
3992
|
+
printGuide: false,
|
|
3993
|
+
printSettings: false
|
|
3994
|
+
};
|
|
3995
|
+
for (const arg of args) {
|
|
3996
|
+
switch (arg) {
|
|
3997
|
+
case "--apply":
|
|
3998
|
+
opts.apply = true;
|
|
3999
|
+
break;
|
|
4000
|
+
case "--print-hook":
|
|
4001
|
+
opts.printHook = true;
|
|
4002
|
+
break;
|
|
4003
|
+
case "--print-guide":
|
|
4004
|
+
opts.printGuide = true;
|
|
4005
|
+
break;
|
|
4006
|
+
case "--print-settings":
|
|
4007
|
+
opts.printSettings = true;
|
|
4008
|
+
break;
|
|
4009
|
+
case "-h":
|
|
4010
|
+
case "--help":
|
|
4011
|
+
usage();
|
|
4012
|
+
return 0;
|
|
4013
|
+
default:
|
|
4014
|
+
console.error(`neat hooks: unknown flag "${arg}"`);
|
|
4015
|
+
usage();
|
|
4016
|
+
return 2;
|
|
4017
|
+
}
|
|
4018
|
+
}
|
|
4019
|
+
try {
|
|
4020
|
+
const { exitCode } = await runHooks(opts);
|
|
4021
|
+
return exitCode;
|
|
4022
|
+
} catch (err) {
|
|
4023
|
+
console.error(err.message);
|
|
4024
|
+
return 1;
|
|
4025
|
+
}
|
|
4026
|
+
}
|
|
4027
|
+
|
|
4028
|
+
// src/cli-verbs.ts
|
|
4029
|
+
import path9 from "path";
|
|
3841
4030
|
|
|
3842
4031
|
// src/cli-client.ts
|
|
3843
4032
|
import { Provenance as Provenance2 } from "@neat.is/types";
|
|
@@ -3865,10 +4054,10 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
3865
4054
|
const root = baseUrl.replace(/\/$/, "");
|
|
3866
4055
|
const authHeader = bearerToken && bearerToken.length > 0 ? { authorization: `Bearer ${bearerToken}` } : {};
|
|
3867
4056
|
return {
|
|
3868
|
-
async get(
|
|
4057
|
+
async get(path11) {
|
|
3869
4058
|
let res;
|
|
3870
4059
|
try {
|
|
3871
|
-
res = await fetch(`${root}${
|
|
4060
|
+
res = await fetch(`${root}${path11}`, {
|
|
3872
4061
|
headers: { ...authHeader }
|
|
3873
4062
|
});
|
|
3874
4063
|
} catch (err) {
|
|
@@ -3880,16 +4069,16 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
3880
4069
|
const body = await res.text().catch(() => "");
|
|
3881
4070
|
throw new HttpError(
|
|
3882
4071
|
res.status,
|
|
3883
|
-
`${res.status} ${res.statusText} on GET ${
|
|
4072
|
+
`${res.status} ${res.statusText} on GET ${path11}: ${body}`,
|
|
3884
4073
|
body
|
|
3885
4074
|
);
|
|
3886
4075
|
}
|
|
3887
4076
|
return await res.json();
|
|
3888
4077
|
},
|
|
3889
|
-
async post(
|
|
4078
|
+
async post(path11, body) {
|
|
3890
4079
|
let res;
|
|
3891
4080
|
try {
|
|
3892
|
-
res = await fetch(`${root}${
|
|
4081
|
+
res = await fetch(`${root}${path11}`, {
|
|
3893
4082
|
method: "POST",
|
|
3894
4083
|
headers: { "content-type": "application/json", ...authHeader },
|
|
3895
4084
|
body: JSON.stringify(body)
|
|
@@ -3903,7 +4092,7 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
3903
4092
|
const text = await res.text().catch(() => "");
|
|
3904
4093
|
throw new HttpError(
|
|
3905
4094
|
res.status,
|
|
3906
|
-
`${res.status} ${res.statusText} on POST ${
|
|
4095
|
+
`${res.status} ${res.statusText} on POST ${path11}: ${text}`,
|
|
3907
4096
|
text
|
|
3908
4097
|
);
|
|
3909
4098
|
}
|
|
@@ -3917,12 +4106,12 @@ function projectPath(project, suffix) {
|
|
|
3917
4106
|
}
|
|
3918
4107
|
async function runRootCause(client, input) {
|
|
3919
4108
|
const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
|
|
3920
|
-
const
|
|
4109
|
+
const path11 = projectPath(
|
|
3921
4110
|
input.project,
|
|
3922
4111
|
`/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
|
|
3923
4112
|
);
|
|
3924
4113
|
try {
|
|
3925
|
-
const result = await client.get(
|
|
4114
|
+
const result = await client.get(path11);
|
|
3926
4115
|
const arrowPath = result.traversalPath.join(" \u2190 ");
|
|
3927
4116
|
const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
|
|
3928
4117
|
const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
|
|
@@ -3948,12 +4137,12 @@ async function runRootCause(client, input) {
|
|
|
3948
4137
|
}
|
|
3949
4138
|
async function runBlastRadius(client, input) {
|
|
3950
4139
|
const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
|
|
3951
|
-
const
|
|
4140
|
+
const path11 = projectPath(
|
|
3952
4141
|
input.project,
|
|
3953
4142
|
`/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
|
|
3954
4143
|
);
|
|
3955
4144
|
try {
|
|
3956
|
-
const result = await client.get(
|
|
4145
|
+
const result = await client.get(path11);
|
|
3957
4146
|
if (result.totalAffected === 0) {
|
|
3958
4147
|
return {
|
|
3959
4148
|
summary: `${result.origin} has no dependents. Nothing else would break if it failed.`
|
|
@@ -3987,12 +4176,12 @@ function formatBlastEntry(n) {
|
|
|
3987
4176
|
}
|
|
3988
4177
|
async function runDependencies(client, input) {
|
|
3989
4178
|
const depth = input.depth ?? 3;
|
|
3990
|
-
const
|
|
4179
|
+
const path11 = projectPath(
|
|
3991
4180
|
input.project,
|
|
3992
4181
|
`/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
|
|
3993
4182
|
);
|
|
3994
4183
|
try {
|
|
3995
|
-
const result = await client.get(
|
|
4184
|
+
const result = await client.get(path11);
|
|
3996
4185
|
if (result.total === 0) {
|
|
3997
4186
|
return {
|
|
3998
4187
|
summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
|
|
@@ -4084,9 +4273,9 @@ function formatDuration(ms) {
|
|
|
4084
4273
|
return `${Math.round(h / 24)}d`;
|
|
4085
4274
|
}
|
|
4086
4275
|
async function runIncidents(client, input) {
|
|
4087
|
-
const
|
|
4276
|
+
const path11 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
|
|
4088
4277
|
try {
|
|
4089
|
-
const body = await client.get(
|
|
4278
|
+
const body = await client.get(path11);
|
|
4090
4279
|
const events = body.events;
|
|
4091
4280
|
if (events.length === 0) {
|
|
4092
4281
|
return {
|
|
@@ -4376,7 +4565,7 @@ async function resolveProjectEntry(opts) {
|
|
|
4376
4565
|
const cwd = opts.cwd ?? process.cwd();
|
|
4377
4566
|
const resolvedCwd = await normalizeProjectPath(cwd);
|
|
4378
4567
|
for (const entry2 of entries) {
|
|
4379
|
-
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${
|
|
4568
|
+
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${path9.sep}`)) {
|
|
4380
4569
|
return entry2;
|
|
4381
4570
|
}
|
|
4382
4571
|
}
|
|
@@ -4541,7 +4730,7 @@ function isNpxInvocation() {
|
|
|
4541
4730
|
function commandPrefix() {
|
|
4542
4731
|
return isNpxInvocation() ? "npx neat.is" : "neat";
|
|
4543
4732
|
}
|
|
4544
|
-
function
|
|
4733
|
+
function usage2() {
|
|
4545
4734
|
const neat = commandPrefix();
|
|
4546
4735
|
console.log("Installed via npx? Prefix commands with `npx neat.is`, or install once: `npm i -g neat.is`.");
|
|
4547
4736
|
console.log("");
|
|
@@ -4578,6 +4767,14 @@ function usage() {
|
|
|
4578
4767
|
console.log(" Flags:");
|
|
4579
4768
|
console.log(" --print-config print the JSON snippet to stdout");
|
|
4580
4769
|
console.log(" --apply merge mcpServers.neat into ~/.claude.json");
|
|
4770
|
+
console.log(" hooks Wire NEAT into your agent so it queries the graph before");
|
|
4771
|
+
console.log(" grepping. Installs a gentle Claude Code search-nudge hook and");
|
|
4772
|
+
console.log(" writes agent-agnostic graph-first guidance for other harnesses.");
|
|
4773
|
+
console.log(" Flags:");
|
|
4774
|
+
console.log(" --apply install the hook + guidance");
|
|
4775
|
+
console.log(" --print-hook print the hook script");
|
|
4776
|
+
console.log(" --print-guide print the graph-first guidance");
|
|
4777
|
+
console.log(" --print-settings print the settings.json block --apply adds");
|
|
4581
4778
|
console.log(" deploy Detect the deploy substrate, generate NEAT_AUTH_TOKEN,");
|
|
4582
4779
|
console.log(" emit a docker-compose / systemd / docker run artifact, and");
|
|
4583
4780
|
console.log(" print the OTel env-vars block to paste into your platform.");
|
|
@@ -4822,7 +5019,7 @@ async function buildPatchSections(services, project) {
|
|
|
4822
5019
|
}
|
|
4823
5020
|
async function runInit(opts) {
|
|
4824
5021
|
const written = [];
|
|
4825
|
-
const stat = await
|
|
5022
|
+
const stat = await fs8.stat(opts.scanPath).catch(() => null);
|
|
4826
5023
|
if (!stat || !stat.isDirectory()) {
|
|
4827
5024
|
console.error(`neat init: ${opts.scanPath} is not a directory`);
|
|
4828
5025
|
return { exitCode: 2, writtenFiles: written };
|
|
@@ -4831,13 +5028,13 @@ async function runInit(opts) {
|
|
|
4831
5028
|
printDiscoveryReport(opts, services);
|
|
4832
5029
|
const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
|
|
4833
5030
|
const patch = renderPatch(sections);
|
|
4834
|
-
const patchPath =
|
|
5031
|
+
const patchPath = path10.join(opts.scanPath, "neat.patch");
|
|
4835
5032
|
if (opts.dryRun) {
|
|
4836
|
-
await
|
|
5033
|
+
await fs8.writeFile(patchPath, patch, "utf8");
|
|
4837
5034
|
written.push(patchPath);
|
|
4838
5035
|
console.log(`dry-run: patch written to ${patchPath}`);
|
|
4839
|
-
const gitignorePath =
|
|
4840
|
-
const gitignoreExists = await
|
|
5036
|
+
const gitignorePath = path10.join(opts.scanPath, ".gitignore");
|
|
5037
|
+
const gitignoreExists = await fs8.stat(gitignorePath).then(() => true).catch(() => false);
|
|
4841
5038
|
const verb = gitignoreExists ? "append" : "create";
|
|
4842
5039
|
console.log(`dry-run: would ${verb} ${gitignorePath} (add neat-out/)`);
|
|
4843
5040
|
console.log("rerun without --dry-run to register and snapshot.");
|
|
@@ -4848,9 +5045,9 @@ async function runInit(opts) {
|
|
|
4848
5045
|
const graph = getGraph(graphKey);
|
|
4849
5046
|
const projectPaths = pathsForProject(
|
|
4850
5047
|
graphKey,
|
|
4851
|
-
|
|
5048
|
+
path10.join(opts.scanPath, "neat-out")
|
|
4852
5049
|
);
|
|
4853
|
-
const errorsPath =
|
|
5050
|
+
const errorsPath = path10.join(path10.dirname(opts.outPath), path10.basename(projectPaths.errorsPath));
|
|
4854
5051
|
const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
|
|
4855
5052
|
await saveGraphToDisk(graph, opts.outPath);
|
|
4856
5053
|
written.push(opts.outPath);
|
|
@@ -4929,7 +5126,7 @@ async function runInit(opts) {
|
|
|
4929
5126
|
console.log("Run `npm install` (or your language equivalent) to refresh lockfiles.");
|
|
4930
5127
|
}
|
|
4931
5128
|
} else {
|
|
4932
|
-
await
|
|
5129
|
+
await fs8.writeFile(patchPath, patch, "utf8");
|
|
4933
5130
|
written.push(patchPath);
|
|
4934
5131
|
}
|
|
4935
5132
|
}
|
|
@@ -4969,9 +5166,9 @@ var CLAUDE_SKILL_CONFIG = {
|
|
|
4969
5166
|
};
|
|
4970
5167
|
function claudeConfigPath() {
|
|
4971
5168
|
const override = process.env.NEAT_CLAUDE_CONFIG;
|
|
4972
|
-
if (override && override.length > 0) return
|
|
5169
|
+
if (override && override.length > 0) return path10.resolve(override);
|
|
4973
5170
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
4974
|
-
return
|
|
5171
|
+
return path10.join(home, ".claude.json");
|
|
4975
5172
|
}
|
|
4976
5173
|
async function runSkill(opts) {
|
|
4977
5174
|
const snippet = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
|
|
@@ -4983,7 +5180,7 @@ async function runSkill(opts) {
|
|
|
4983
5180
|
const target = claudeConfigPath();
|
|
4984
5181
|
let existing = {};
|
|
4985
5182
|
try {
|
|
4986
|
-
existing = JSON.parse(await
|
|
5183
|
+
existing = JSON.parse(await fs8.readFile(target, "utf8"));
|
|
4987
5184
|
} catch (err) {
|
|
4988
5185
|
if (err.code !== "ENOENT") {
|
|
4989
5186
|
console.error(`neat skill: failed to read ${target} \u2014 ${err.message}`);
|
|
@@ -4995,10 +5192,13 @@ async function runSkill(opts) {
|
|
|
4995
5192
|
...existing,
|
|
4996
5193
|
mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
|
|
4997
5194
|
};
|
|
4998
|
-
await
|
|
4999
|
-
await
|
|
5195
|
+
await fs8.mkdir(path10.dirname(target), { recursive: true });
|
|
5196
|
+
await fs8.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
5000
5197
|
console.log(`neat skill: wrote mcpServers.neat to ${target}`);
|
|
5001
5198
|
console.log("restart Claude Code to pick up the new MCP server.");
|
|
5199
|
+
console.log("");
|
|
5200
|
+
console.log("Tip: run `neat hooks --apply` to also install the search-nudge hook, so");
|
|
5201
|
+
console.log("your agent reaches for the graph before it grep-scans the repo.");
|
|
5002
5202
|
return { exitCode: 0 };
|
|
5003
5203
|
}
|
|
5004
5204
|
console.log("neat skill \u2014 Claude Code MCP drop-in for NEAT");
|
|
@@ -5011,13 +5211,16 @@ async function runSkill(opts) {
|
|
|
5011
5211
|
console.log("");
|
|
5012
5212
|
console.log("The MCP server reads NEAT_CORE_URL for the daemon URL \u2014 point it at a");
|
|
5013
5213
|
console.log("non-default daemon by editing that value in the generated config.");
|
|
5214
|
+
console.log("");
|
|
5215
|
+
console.log("See also `neat hooks --apply` \u2014 the search-nudge hook + graph-first guidance");
|
|
5216
|
+
console.log("that steer an agent to query the graph before falling back to text search.");
|
|
5014
5217
|
return { exitCode: 0 };
|
|
5015
5218
|
}
|
|
5016
5219
|
async function main() {
|
|
5017
5220
|
const argv = process.argv.slice(2);
|
|
5018
5221
|
const cmd0 = argv[0];
|
|
5019
5222
|
if (cmd0 === "-h" || cmd0 === "--help") {
|
|
5020
|
-
|
|
5223
|
+
usage2();
|
|
5021
5224
|
process.exit(0);
|
|
5022
5225
|
}
|
|
5023
5226
|
if (cmd0 === "--version" || cmd0 === "-v" || cmd0 === "version") {
|
|
@@ -5029,6 +5232,11 @@ async function main() {
|
|
|
5029
5232
|
if (code !== 0) process.exit(code);
|
|
5030
5233
|
return;
|
|
5031
5234
|
}
|
|
5235
|
+
if (cmd0 === "hooks") {
|
|
5236
|
+
const code = await runHooksCommand(argv.slice(1));
|
|
5237
|
+
if (code !== 0) process.exit(code);
|
|
5238
|
+
return;
|
|
5239
|
+
}
|
|
5032
5240
|
const argvParsed = parseArgs(argv);
|
|
5033
5241
|
if (argvParsed.positional.length === 0) {
|
|
5034
5242
|
const orchestratorCode2 = await tryOrchestrator(process.cwd(), argvParsed);
|
|
@@ -5043,19 +5251,19 @@ async function main() {
|
|
|
5043
5251
|
const target = positional[0];
|
|
5044
5252
|
if (!target) {
|
|
5045
5253
|
console.error("neat init: missing <path>");
|
|
5046
|
-
|
|
5254
|
+
usage2();
|
|
5047
5255
|
process.exit(2);
|
|
5048
5256
|
}
|
|
5049
5257
|
if (apply3 && dryRun) {
|
|
5050
5258
|
console.error("neat init: --apply and --dry-run are mutually exclusive");
|
|
5051
5259
|
process.exit(2);
|
|
5052
5260
|
}
|
|
5053
|
-
const scanPath =
|
|
5261
|
+
const scanPath = path10.resolve(target);
|
|
5054
5262
|
const projectExplicit = parsed.project !== null;
|
|
5055
|
-
const projectName = projectExplicit ? project :
|
|
5263
|
+
const projectName = projectExplicit ? project : path10.basename(scanPath);
|
|
5056
5264
|
const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
|
|
5057
|
-
const fallback = pathsForProject(projectKey,
|
|
5058
|
-
const outPath =
|
|
5265
|
+
const fallback = pathsForProject(projectKey, path10.join(scanPath, "neat-out")).snapshotPath;
|
|
5266
|
+
const outPath = path10.resolve(process.env.NEAT_OUT_PATH ?? fallback);
|
|
5059
5267
|
const result = await runInit({
|
|
5060
5268
|
scanPath,
|
|
5061
5269
|
outPath,
|
|
@@ -5073,24 +5281,24 @@ async function main() {
|
|
|
5073
5281
|
const target = positional[0];
|
|
5074
5282
|
if (!target) {
|
|
5075
5283
|
console.error("neat watch: missing <path>");
|
|
5076
|
-
|
|
5284
|
+
usage2();
|
|
5077
5285
|
process.exit(2);
|
|
5078
5286
|
}
|
|
5079
|
-
const scanPath =
|
|
5080
|
-
const stat = await
|
|
5287
|
+
const scanPath = path10.resolve(target);
|
|
5288
|
+
const stat = await fs8.stat(scanPath).catch(() => null);
|
|
5081
5289
|
if (!stat || !stat.isDirectory()) {
|
|
5082
5290
|
console.error(`neat watch: ${scanPath} is not a directory`);
|
|
5083
5291
|
process.exit(2);
|
|
5084
5292
|
}
|
|
5085
|
-
const projectPaths = pathsForProject(project,
|
|
5086
|
-
const outPath =
|
|
5087
|
-
const errorsPath =
|
|
5088
|
-
process.env.NEAT_ERRORS_PATH ??
|
|
5293
|
+
const projectPaths = pathsForProject(project, path10.join(scanPath, "neat-out"));
|
|
5294
|
+
const outPath = path10.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
|
|
5295
|
+
const errorsPath = path10.resolve(
|
|
5296
|
+
process.env.NEAT_ERRORS_PATH ?? path10.join(path10.dirname(outPath), path10.basename(projectPaths.errorsPath))
|
|
5089
5297
|
);
|
|
5090
|
-
const staleEventsPath =
|
|
5091
|
-
process.env.NEAT_STALE_EVENTS_PATH ??
|
|
5298
|
+
const staleEventsPath = path10.resolve(
|
|
5299
|
+
process.env.NEAT_STALE_EVENTS_PATH ?? path10.join(path10.dirname(outPath), path10.basename(projectPaths.staleEventsPath))
|
|
5092
5300
|
);
|
|
5093
|
-
const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ?
|
|
5301
|
+
const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? path10.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
|
|
5094
5302
|
const handle = await startWatch(getGraph(project), {
|
|
5095
5303
|
scanPath,
|
|
5096
5304
|
outPath,
|
|
@@ -5132,7 +5340,7 @@ async function main() {
|
|
|
5132
5340
|
const name = positional[0];
|
|
5133
5341
|
if (!name) {
|
|
5134
5342
|
console.error("neat pause: missing <name>");
|
|
5135
|
-
|
|
5343
|
+
usage2();
|
|
5136
5344
|
process.exit(2);
|
|
5137
5345
|
}
|
|
5138
5346
|
const daemon = await findDaemonByProject(name);
|
|
@@ -5157,7 +5365,7 @@ async function main() {
|
|
|
5157
5365
|
const name = positional[0];
|
|
5158
5366
|
if (!name) {
|
|
5159
5367
|
console.error("neat resume: missing <name>");
|
|
5160
|
-
|
|
5368
|
+
usage2();
|
|
5161
5369
|
process.exit(2);
|
|
5162
5370
|
}
|
|
5163
5371
|
const daemon = await findDaemonByProject(name);
|
|
@@ -5187,7 +5395,7 @@ async function main() {
|
|
|
5187
5395
|
const name = positional[0];
|
|
5188
5396
|
if (!name) {
|
|
5189
5397
|
console.error("neat uninstall: missing <name>");
|
|
5190
|
-
|
|
5398
|
+
usage2();
|
|
5191
5399
|
process.exit(2);
|
|
5192
5400
|
}
|
|
5193
5401
|
const daemon = await findDaemonByProject(name);
|
|
@@ -5269,15 +5477,15 @@ async function main() {
|
|
|
5269
5477
|
return;
|
|
5270
5478
|
}
|
|
5271
5479
|
console.error(`neat: unknown command "${cmd}"`);
|
|
5272
|
-
|
|
5480
|
+
usage2();
|
|
5273
5481
|
process.exit(1);
|
|
5274
5482
|
}
|
|
5275
5483
|
async function tryOrchestrator(cmd, parsed) {
|
|
5276
|
-
const scanPath =
|
|
5277
|
-
const stat = await
|
|
5484
|
+
const scanPath = path10.resolve(cmd);
|
|
5485
|
+
const stat = await fs8.stat(scanPath).catch(() => null);
|
|
5278
5486
|
if (!stat || !stat.isDirectory()) return null;
|
|
5279
5487
|
const projectExplicit = parsed.project !== null;
|
|
5280
|
-
const projectName = projectExplicit ? parsed.project :
|
|
5488
|
+
const projectName = projectExplicit ? parsed.project : path10.basename(scanPath);
|
|
5281
5489
|
const result = await runOrchestrator({
|
|
5282
5490
|
scanPath,
|
|
5283
5491
|
project: projectName,
|
|
@@ -5533,6 +5741,6 @@ export {
|
|
|
5533
5741
|
runInit,
|
|
5534
5742
|
runQueryVerb,
|
|
5535
5743
|
runSkill,
|
|
5536
|
-
usage
|
|
5744
|
+
usage2 as usage
|
|
5537
5745
|
};
|
|
5538
5746
|
//# sourceMappingURL=cli.js.map
|