@noesis-brain/mcp-server 2.1.5 → 2.3.1
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/agent/identity.d.ts +33 -0
- package/dist/agent/identity.d.ts.map +1 -0
- package/dist/agent/identity.js +61 -0
- package/dist/agent/identity.js.map +1 -0
- package/dist/agent/runner.d.ts +103 -6
- package/dist/agent/runner.d.ts.map +1 -1
- package/dist/agent/runner.js +375 -64
- package/dist/agent/runner.js.map +1 -1
- package/dist/agent/tokenState.d.ts +81 -0
- package/dist/agent/tokenState.d.ts.map +1 -0
- package/dist/agent/tokenState.js +173 -0
- package/dist/agent/tokenState.js.map +1 -0
- package/dist/agent/webGuard.d.ts +48 -0
- package/dist/agent/webGuard.d.ts.map +1 -0
- package/dist/agent/webGuard.js +173 -0
- package/dist/agent/webGuard.js.map +1 -0
- package/dist/api/NoesisClient.d.ts +13 -1
- package/dist/api/NoesisClient.d.ts.map +1 -1
- package/dist/api/NoesisClient.js +18 -3
- package/dist/api/NoesisClient.js.map +1 -1
- package/dist/tools/claudeSessions.d.ts +72 -0
- package/dist/tools/claudeSessions.d.ts.map +1 -0
- package/dist/tools/claudeSessions.js +304 -0
- package/dist/tools/claudeSessions.js.map +1 -0
- package/dist/tools/index.d.ts +1 -6
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +166 -37
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/sessionIndex.d.ts +66 -0
- package/dist/tools/sessionIndex.d.ts.map +1 -0
- package/dist/tools/sessionIndex.js +241 -0
- package/dist/tools/sessionIndex.js.map +1 -0
- package/package.json +1 -1
- package/scripts/noesis-capture-watcher.mjs +722 -107
- package/scripts/start-agent.sh +51 -12
- package/skill-templates/noesis-refine-relations.md +163 -0
package/scripts/start-agent.sh
CHANGED
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
# ./scripts/start-agent.sh --local # this checkout's dist/ (after npm run build)
|
|
24
24
|
# ./scripts/start-agent.sh --status # report running daemons, exit 1 if any is unfixed
|
|
25
25
|
# ./scripts/start-agent.sh --stop # stop everything, start nothing
|
|
26
|
+
# ./scripts/start-agent.sh --grade <package-dir> # grade one build; touches no process
|
|
26
27
|
|
|
27
28
|
set -euo pipefail
|
|
28
29
|
|
|
@@ -39,8 +40,11 @@ MIN_FIXED_VERSION="2.1.5"
|
|
|
39
40
|
MODE="${1:-}"
|
|
40
41
|
case "$MODE" in
|
|
41
42
|
''|--local|--stop|--status) ;;
|
|
43
|
+
--grade)
|
|
44
|
+
[ -n "${2:-}" ] || { echo "usage: $(basename "$0") --grade <package-dir>" >&2; exit 2; }
|
|
45
|
+
;;
|
|
42
46
|
*)
|
|
43
|
-
echo "usage: $(basename "$0") [--local | --status | --stop]" >&2
|
|
47
|
+
echo "usage: $(basename "$0") [--local | --status | --stop | --grade <package-dir>]" >&2
|
|
44
48
|
# Deliberately NOT a silent fallback: a typo'd flag used to start the PUBLISHED build
|
|
45
49
|
# instead of the local one — the same "wrong build served the job" signature.
|
|
46
50
|
exit 2
|
|
@@ -121,7 +125,27 @@ pkg_dir_for_pid() {
|
|
|
121
125
|
# comments before looking for code.
|
|
122
126
|
strip_js_comments() { perl -0pe 's{/\*.*?\*/}{}gs; s{^[[:space:]]*//.*$}{}gm' "$1" 2>/dev/null || true; }
|
|
123
127
|
|
|
124
|
-
#
|
|
128
|
+
# True when compiled runner.js (comments already stripped, on stdin) restricts the SDK's
|
|
129
|
+
# built-in tools. Two accepted shapes:
|
|
130
|
+
# - `tools: []` — 2.1.5 to 2.1.x: no built-ins at all;
|
|
131
|
+
# - `tools: webTools` built by `clampWebTools(` over the literal two-name list
|
|
132
|
+
# — 2.2.0+: none, or at most WebFetch + WebSearch, which have
|
|
133
|
+
# no filesystem access (web Navis; see runner.ts).
|
|
134
|
+
# Recognising ONLY the first shape graded every 2.2.0 daemon UNFIXED and the assert at the
|
|
135
|
+
# bottom then killed it: a guaranteed outage on the first restart after publishing.
|
|
136
|
+
# The version floor stays 2.1.5 — the filesystem boundary is what this guards.
|
|
137
|
+
has_tool_boundary() {
|
|
138
|
+
local code; code="$(cat)"
|
|
139
|
+
# Here-strings, not `printf | grep -q`: under pipefail an early-exiting grep can SIGPIPE
|
|
140
|
+
# the writer once the file outgrows the pipe buffer, turning a match into a failure.
|
|
141
|
+
grep -q 'canUseTool:' <<<"$code" || return 1
|
|
142
|
+
grep -q 'tools: \[\]' <<<"$code" && return 0
|
|
143
|
+
grep -q 'tools: webTools,' <<<"$code" \
|
|
144
|
+
&& grep -q 'const webTools = clampWebTools(' <<<"$code" \
|
|
145
|
+
&& grep -q "ALLOWED_WEB_TOOLS = \['WebFetch', 'WebSearch'\]" <<<"$code"
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
# Prints "<version> <FIXED|UNFIXED(reason)>" for a package directory.
|
|
125
149
|
#
|
|
126
150
|
# TWO INDEPENDENT SIGNALS, both required:
|
|
127
151
|
# - the package version is >= MIN_FIXED_VERSION (the same signal the backend's
|
|
@@ -129,25 +153,40 @@ strip_js_comments() { perl -0pe 's{/\*.*?\*/}{}gs; s{^[[:space:]]*//.*$}{}gm' "$
|
|
|
129
153
|
# - the compiled output actually contains both halves of the fix, comments removed.
|
|
130
154
|
# An earlier version compared neither: MIN_FIXED_VERSION was assigned and never used, and
|
|
131
155
|
# the content check matched documentation. "v2.1.5 FIXED" was two non-facts side by side.
|
|
132
|
-
|
|
133
|
-
local
|
|
134
|
-
dir="$(pkg_dir_for_pid "$pid")"
|
|
135
|
-
# No package dir = the `npm exec` wrapper, not a daemon. Its child is graded separately;
|
|
136
|
-
# counting the parent as "unknown, therefore exposed" cried wolf on every healthy npx
|
|
137
|
-
# launch, which is the mode that becomes correct once the fix is published.
|
|
138
|
-
if [ -z "$dir" ] || [ ! -f "$dir/package.json" ]; then echo "- PARENT"; return; fi
|
|
156
|
+
describe_dir() {
|
|
157
|
+
local dir="$1" ver ver_ok=0 code_ok=0
|
|
139
158
|
ver="$(node -e "process.stdout.write(String(require('$dir/package.json').version||'0.0.0'))" 2>/dev/null || echo '0.0.0')"
|
|
140
159
|
version_ge "$ver" "$MIN_FIXED_VERSION" && ver_ok=1
|
|
141
160
|
if [ -f "$dir/dist/agent/runner.js" ]; then
|
|
142
|
-
|
|
143
|
-
if printf '%s' "$stripped" | grep -q 'tools: \[\]' \
|
|
144
|
-
&& printf '%s' "$stripped" | grep -q 'canUseTool:'; then code_ok=1; fi
|
|
161
|
+
if strip_js_comments "$dir/dist/agent/runner.js" | has_tool_boundary; then code_ok=1; fi
|
|
145
162
|
fi
|
|
146
163
|
if [ "$ver_ok" = 1 ] && [ "$code_ok" = 1 ]; then echo "$ver FIXED"
|
|
147
164
|
elif [ "$ver_ok" = 0 ]; then echo "$ver UNFIXED(v<$MIN_FIXED_VERSION)"
|
|
148
165
|
else echo "$ver UNFIXED(code)"; fi
|
|
149
166
|
}
|
|
150
167
|
|
|
168
|
+
# Prints "<version> <FIXED|UNFIXED(reason)|PARENT>" for a pid.
|
|
169
|
+
describe_pid() {
|
|
170
|
+
local dir
|
|
171
|
+
dir="$(pkg_dir_for_pid "$1")"
|
|
172
|
+
# No package dir = the `npm exec` wrapper, not a daemon. Its child is graded separately;
|
|
173
|
+
# counting the parent as "unknown, therefore exposed" cried wolf on every healthy npx
|
|
174
|
+
# launch, which is the mode that becomes correct once the fix is published.
|
|
175
|
+
if [ -z "$dir" ] || [ ! -f "$dir/package.json" ]; then echo "- PARENT"; return; fi
|
|
176
|
+
describe_dir "$dir"
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
# --grade <dir>: grade one package directory and touch no process. The test suite runs
|
|
180
|
+
# this against a fresh compile of runner.ts, so a change the check above stops recognising
|
|
181
|
+
# fails a test instead of killing every user's daemon on their next restart.
|
|
182
|
+
if [ "$MODE" = "--grade" ]; then
|
|
183
|
+
grade_dir="$(cd "$2" && pwd)"
|
|
184
|
+
read -r ver state <<<"$(describe_dir "$grade_dir")"
|
|
185
|
+
echo "$ver $state"
|
|
186
|
+
if [ "$state" = "FIXED" ]; then exit 0; fi
|
|
187
|
+
exit 1
|
|
188
|
+
fi
|
|
189
|
+
|
|
151
190
|
# --status: the one command that answers "is my machine exposed right now?".
|
|
152
191
|
if [ "$MODE" = "--status" ]; then
|
|
153
192
|
exposed=0 total=0
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Refine the typed relations (Related Notes) of a SET of Noesis notes so the Graph view shows real structure. Scopes by timeframe, topic, catalog or signal — "last two weeks", "notes about BTC", "BTO printing in the last month", "points over 100". Proposes a diff per note and writes only on approval. Use when the user says 'refine relations', 'refine note relations', 'fix my graph', 'link my notes', 'these notes have no relations', or names a batch of notes to connect.
|
|
3
|
+
argument-hint: "[recent <N> days|weeks|months] [topic <words>] [catalog <NAME>] [points over <N>] [<note path | note ID | query>]"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /noesis-refine-relations — Batch relation refinement
|
|
7
|
+
|
|
8
|
+
You are executing `/noesis-refine-relations`. It selects a **set** of notes, works out which
|
|
9
|
+
other notes each one should formally link to, shows you the diff, and writes only what you
|
|
10
|
+
approve.
|
|
11
|
+
|
|
12
|
+
Relations live in the Noesis **database**, not in the markdown. This skill therefore never
|
|
13
|
+
edits a note body and never needs a sync.
|
|
14
|
+
|
|
15
|
+
## What this is NOT
|
|
16
|
+
|
|
17
|
+
The app already has a per-note **"Suggest relations"** in the graph's node menu. It reasons
|
|
18
|
+
over ≤20 candidates carrying only a title and a short description — **it never reads note
|
|
19
|
+
bodies**, and it runs on one note at a time.
|
|
20
|
+
|
|
21
|
+
This skill is the complement: many notes at once, and it *reads the content*, so it can find
|
|
22
|
+
the links a note states in its own text (cited file paths, note URLs, quoted titles). Where
|
|
23
|
+
the in-app suggester is the right tool for one note, say so and stop.
|
|
24
|
+
|
|
25
|
+
## Step 0 — Route on the argument
|
|
26
|
+
|
|
27
|
+
The raw arguments string is: `$ARGUMENTS`
|
|
28
|
+
|
|
29
|
+
First match wins:
|
|
30
|
+
|
|
31
|
+
| Argument looks like | Mode |
|
|
32
|
+
|---|---|
|
|
33
|
+
| "last N days/weeks/months", "recent", "this week" | **RECENT** |
|
|
34
|
+
| a topic phrase *and* a timeframe | **TOPIC ∩ RECENT** |
|
|
35
|
+
| a topic phrase ("about BTC", "BTO printing") | **TOPIC** |
|
|
36
|
+
| "points over N", "score above N", "favorite", "pinned", "starred" | **SIGNAL** |
|
|
37
|
+
| "catalog X" | **CATALOG** |
|
|
38
|
+
| a note ID, a `.md` path, or a bare query | **SINGLE** |
|
|
39
|
+
| empty | **ASK** — `AskUserQuestion` offering the modes above |
|
|
40
|
+
|
|
41
|
+
State the mode and the resolved parameters before doing anything.
|
|
42
|
+
|
|
43
|
+
## Step 1 — Resolve the note set
|
|
44
|
+
|
|
45
|
+
- **RECENT** — `mcp__noesis__list_notes(recent: <N_DAYS>, limit: 100)`.
|
|
46
|
+
- **TOPIC** — `mcp__noesis__search_semantic(query, limit: 20)` **and**
|
|
47
|
+
`mcp__noesis__search_notes(query, limit: 50)`; union by ID.
|
|
48
|
+
- **TOPIC ∩ RECENT** — run both of the above and intersect.
|
|
49
|
+
- **SIGNAL** — `list_notes(limit: 100)`, then filter client-side on the `⭐` and `[N pts]`
|
|
50
|
+
markers in the output.
|
|
51
|
+
- **CATALOG** — `list_notes(catalog: <NAME>, limit: 100)`.
|
|
52
|
+
- **SINGLE** — resolve directly (`get_note` by id/path, or `search_notes` then pick).
|
|
53
|
+
|
|
54
|
+
Then **always**:
|
|
55
|
+
|
|
56
|
+
1. **Drop capture notes** — anything under a `captures/` folder is an auto-pushed session
|
|
57
|
+
mirror, not a note the user authored. Never link them unless explicitly asked.
|
|
58
|
+
2. **Cap the batch.** Default to 15 notes. If the scope resolved to more, take the 15 most
|
|
59
|
+
recently modified and **say how many you dropped** — never present a truncated sweep as
|
|
60
|
+
a complete one.
|
|
61
|
+
3. Print the candidate table (ID, title, path, current relation count) and confirm the set
|
|
62
|
+
before moving on.
|
|
63
|
+
|
|
64
|
+
### Selection limits you must respect
|
|
65
|
+
|
|
66
|
+
These are properties of the MCP tools, not preferences:
|
|
67
|
+
|
|
68
|
+
- `list_notes` caps `limit` at **100** server-side and exposes no offset. A scope wider than
|
|
69
|
+
100 notes cannot be enumerated in one call — narrow it and say so.
|
|
70
|
+
- When `recent` is set, `root` and `catalog` are **silently ignored**. Combined filters must
|
|
71
|
+
be applied client-side.
|
|
72
|
+
- `list_notes` prints `⭐` and `[N pts]` but **not the note ID**, so resolve IDs from paths
|
|
73
|
+
before writing anything.
|
|
74
|
+
- `search_semantic` and `find_similar_notes` need embeddings and fail without a Gemini key.
|
|
75
|
+
Degrade to keyword-only and note it; never abort the run over it.
|
|
76
|
+
|
|
77
|
+
## Step 2 — Per note: build the proposed set
|
|
78
|
+
|
|
79
|
+
For each note in the confirmed set, in order:
|
|
80
|
+
|
|
81
|
+
**2a. Read the current relations.** `mcp__noesis__get_note(id)`. This is the only reliable
|
|
82
|
+
read — use it every time, even when you expect none.
|
|
83
|
+
|
|
84
|
+
**2b. Gather candidates**, in one parallel batch:
|
|
85
|
+
- `mcp__noesis__find_similar_notes(note_id, limit: 15)`
|
|
86
|
+
- `mcp__noesis__search_semantic(query: title + description, limit: 10)`
|
|
87
|
+
- **The body scan — the highest-precision source, and the reason this skill exists.**
|
|
88
|
+
Read the note content and extract every note it already cites:
|
|
89
|
+
- file paths ending in `.md` (absolute, `~/Noesis/...`, or bare filenames)
|
|
90
|
+
- `noesisbrain.com/notes/<id>` URLs — the `<id>` is the target, use it directly
|
|
91
|
+
- quoted note titles
|
|
92
|
+
Resolve each to an ID with `mcp__noesis__search_notes`. An edge grounded in a line of the
|
|
93
|
+
note's own text is worth more than any similarity score; keep the line as evidence.
|
|
94
|
+
|
|
95
|
+
**2c. Filter.** Drop captures, drop the note itself (the MCP write path does **not** reject
|
|
96
|
+
self-links), drop anything that fails to resolve. Never invent an ID — if a cited note cannot
|
|
97
|
+
be resolved, report it as unresolved rather than guessing.
|
|
98
|
+
|
|
99
|
+
**2d. Choose a type** per edge, from exactly these five:
|
|
100
|
+
`related` · `references` · `implements` · `extends` · `supersedes`.
|
|
101
|
+
Default to `references` when the note merely cites the target, `related` when the connection
|
|
102
|
+
is thematic.
|
|
103
|
+
|
|
104
|
+
**2e. Union with the existing set from 2a.** Keep every existing entry unless the user has
|
|
105
|
+
explicitly asked to prune. This matters — see the write contract below.
|
|
106
|
+
|
|
107
|
+
**2f. Apply a cap.** 2–5 relations for a focused note; up to 8 for a comprehensive
|
|
108
|
+
architecture, workflow, or strategy note. **Tracker, sprint and index notes are exempt** —
|
|
109
|
+
being a hub is their job, and a tracker citing 25 notes should link to 25.
|
|
110
|
+
|
|
111
|
+
## Step 3 — Propose
|
|
112
|
+
|
|
113
|
+
Print, per note:
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
Note 2923 BTO 12.3 Aug Sprint 0 edges 0 -> 25
|
|
117
|
+
= keep references 4424 IDEA-1579 HTTPS support
|
|
118
|
+
+ add references 3611 BPLAT-14016 UltraLite Simple Serialization
|
|
119
|
+
evidence: "my-git/issues/BPLAT-14016-phoenix-tsc-ultralite...md"
|
|
120
|
+
+ add related 2924 Epsilon Branch Merge Convention
|
|
121
|
+
evidence: similar 0.81, both cover the Epsilon merge workflow
|
|
122
|
+
! unresolved: "support-https-print-portal-investigation.md" (no match)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Then ask once, for the whole batch, via `AskUserQuestion`: apply all / pick per note / cancel.
|
|
126
|
+
**Never write before this point.**
|
|
127
|
+
|
|
128
|
+
## Step 4 — Write
|
|
129
|
+
|
|
130
|
+
For each approved note, issue **exactly one** `mcp__noesis__update_relations` call carrying
|
|
131
|
+
the **complete** desired set.
|
|
132
|
+
|
|
133
|
+
> **The write REPLACES the note's whole relation set.** It clears the column and stores what
|
|
134
|
+
> you send. One call per relation — a pattern that exists elsewhere in this repo's older
|
|
135
|
+
> commands — leaves the note with only the last relation written and silently deletes the
|
|
136
|
+
> rest. Send the union from 2e, always.
|
|
137
|
+
|
|
138
|
+
No inverse relation is created on the target, and you must not try to write one. Backlinks
|
|
139
|
+
are **derived**: the app scans every note's one-way array to build the graph, so a single
|
|
140
|
+
edge already shows up on both notes — as "linked to" on the source and "linked by" on the
|
|
141
|
+
target. Writing the mirror by hand would double-store derived data and turn every edge into
|
|
142
|
+
"mutual", destroying the direction the user can currently see.
|
|
143
|
+
|
|
144
|
+
Do **not** write a "Related Notes" section into the markdown body. Relations are database
|
|
145
|
+
metadata; a section in the file is a stale duplicate. If a previous run left one, remove it.
|
|
146
|
+
|
|
147
|
+
## Step 5 — Report
|
|
148
|
+
|
|
149
|
+
A table of note · edges before → after · skipped-and-why, plus every unresolved citation.
|
|
150
|
+
Then suggest `mcp__noesis__get_relation_graph(note_id, depth: 1)` or the Graph view to
|
|
151
|
+
confirm the shape.
|
|
152
|
+
|
|
153
|
+
## Implementation notes
|
|
154
|
+
|
|
155
|
+
- **Read before write, every time.** The write is a replace; skipping 2a loses relations.
|
|
156
|
+
- **The body scan is the differentiator.** If you skip it, this skill is just a slower
|
|
157
|
+
version of the in-app suggester.
|
|
158
|
+
- **Evidence or no edge.** Every proposed link needs either a quoted line from the note or a
|
|
159
|
+
similarity score. "These feel related" is not a reason.
|
|
160
|
+
- **Sequential per note.** Parallel `get_note` batches race the API rate limit; the
|
|
161
|
+
*candidate gathering* inside one note (2b) is the part to parallelize.
|
|
162
|
+
- **Report every cap.** A truncated batch, a dropped candidate, an unresolved citation — all
|
|
163
|
+
of it goes in the Step 5 report. A sweep that hides what it skipped is worse than no sweep.
|