notex-companion 0.3.3 → 0.4.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/cli.js +78 -2
- package/dist/client.js +25 -1
- package/dist/graph.d.ts +13 -1
- package/dist/index.js +67 -2
- package/dist/ops.d.ts +9 -2
- package/dist/scoring.d.ts +2 -1
- package/dist/types.d.ts +6 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -6727,11 +6727,13 @@ function shatter(text) {
|
|
|
6727
6727
|
function terms(question) {
|
|
6728
6728
|
return [...new Set(shatter(question).filter((t) => t.length >= 3 && !STOPWORDS.has(t)))];
|
|
6729
6729
|
}
|
|
6730
|
+
var FUZZY_MATCH_EXACT_MIN_COUNT = 2;
|
|
6730
6731
|
function scoreNodes(index, queryTerms) {
|
|
6731
6732
|
const scored = [];
|
|
6732
6733
|
for (const entry of index) {
|
|
6733
6734
|
let score = 0;
|
|
6734
6735
|
let exact = false;
|
|
6736
|
+
let fuzzyMatchesByToken;
|
|
6735
6737
|
for (const term of queryTerms) {
|
|
6736
6738
|
let best = 0;
|
|
6737
6739
|
if (entry.labelLower === term) {
|
|
@@ -6744,6 +6746,16 @@ function scoreNodes(index, queryTerms) {
|
|
|
6744
6746
|
for (const tok of entry.labelTokens) {
|
|
6745
6747
|
if (tok.length >= 4 && (tok.startsWith(term) || term.startsWith(tok))) {
|
|
6746
6748
|
best = Math.max(best, 1.5);
|
|
6749
|
+
if (term.length >= 4) {
|
|
6750
|
+
if (!fuzzyMatchesByToken)
|
|
6751
|
+
fuzzyMatchesByToken = new Map;
|
|
6752
|
+
let matchedTerms = fuzzyMatchesByToken.get(tok);
|
|
6753
|
+
if (!matchedTerms) {
|
|
6754
|
+
matchedTerms = new Set;
|
|
6755
|
+
fuzzyMatchesByToken.set(tok, matchedTerms);
|
|
6756
|
+
}
|
|
6757
|
+
matchedTerms.add(term);
|
|
6758
|
+
}
|
|
6747
6759
|
} else if (term.length >= 4 && tok.includes(term)) {
|
|
6748
6760
|
best = Math.max(best, 1);
|
|
6749
6761
|
}
|
|
@@ -6761,6 +6773,14 @@ function scoreNodes(index, queryTerms) {
|
|
|
6761
6773
|
}
|
|
6762
6774
|
score += best;
|
|
6763
6775
|
}
|
|
6776
|
+
if (fuzzyMatchesByToken) {
|
|
6777
|
+
for (const matchedTerms of fuzzyMatchesByToken.values()) {
|
|
6778
|
+
if (matchedTerms.size >= FUZZY_MATCH_EXACT_MIN_COUNT) {
|
|
6779
|
+
exact = true;
|
|
6780
|
+
break;
|
|
6781
|
+
}
|
|
6782
|
+
}
|
|
6783
|
+
}
|
|
6764
6784
|
if (score > 0)
|
|
6765
6785
|
scored.push({ id: entry.id, score: Math.round(score * 100) / 100, exact });
|
|
6766
6786
|
}
|
|
@@ -6790,6 +6810,36 @@ function readHeadSha(checkoutPath) {
|
|
|
6790
6810
|
return null;
|
|
6791
6811
|
}
|
|
6792
6812
|
}
|
|
6813
|
+
var SUGGESTED_QUESTIONS_HEADING = /^## Suggested Questions\s*$/;
|
|
6814
|
+
var SECTION_HEADING = /^## /;
|
|
6815
|
+
var QUESTION_BULLET = /^- \*\*(.+)\*\*$/;
|
|
6816
|
+
var RATIONALE_LINE = /^\s*_(.+)_\s*$/;
|
|
6817
|
+
function parseSuggestedQuestions(markdown) {
|
|
6818
|
+
const lines = markdown.split(`
|
|
6819
|
+
`);
|
|
6820
|
+
const headingIndex = lines.findIndex((line) => SUGGESTED_QUESTIONS_HEADING.test(line));
|
|
6821
|
+
if (headingIndex === -1)
|
|
6822
|
+
return [];
|
|
6823
|
+
const sectionEnd = lines.findIndex((line, i) => i > headingIndex && SECTION_HEADING.test(line));
|
|
6824
|
+
const section = lines.slice(headingIndex + 1, sectionEnd === -1 ? undefined : sectionEnd);
|
|
6825
|
+
const result = [];
|
|
6826
|
+
for (let i = 0;i < section.length; i++) {
|
|
6827
|
+
const questionMatch = section[i].match(QUESTION_BULLET);
|
|
6828
|
+
if (!questionMatch)
|
|
6829
|
+
continue;
|
|
6830
|
+
const rationaleMatch = section[i + 1]?.match(RATIONALE_LINE);
|
|
6831
|
+
result.push({ question: questionMatch[1], rationale: rationaleMatch?.[1] ?? "" });
|
|
6832
|
+
}
|
|
6833
|
+
return result;
|
|
6834
|
+
}
|
|
6835
|
+
function loadSuggestedQuestions(checkoutPath) {
|
|
6836
|
+
try {
|
|
6837
|
+
const raw = readFileSync2(resolve(checkoutPath, "graphify-out/GRAPH_REPORT.md"), "utf8");
|
|
6838
|
+
return parseSuggestedQuestions(raw);
|
|
6839
|
+
} catch {
|
|
6840
|
+
return [];
|
|
6841
|
+
}
|
|
6842
|
+
}
|
|
6793
6843
|
function loadGraph(checkoutPath) {
|
|
6794
6844
|
const graphPath = resolve(checkoutPath, "graphify-out/graph.json");
|
|
6795
6845
|
let raw;
|
|
@@ -6851,7 +6901,16 @@ function loadGraph(checkoutPath) {
|
|
|
6851
6901
|
sourceFile: checkoutRelative(e.source_file),
|
|
6852
6902
|
sourceLocation: e.source_location
|
|
6853
6903
|
});
|
|
6854
|
-
return {
|
|
6904
|
+
return {
|
|
6905
|
+
stamp,
|
|
6906
|
+
nodesById,
|
|
6907
|
+
edges: doc.links,
|
|
6908
|
+
adjacency,
|
|
6909
|
+
scoreIndex,
|
|
6910
|
+
suggestedQuestions: loadSuggestedQuestions(checkoutPath),
|
|
6911
|
+
project,
|
|
6912
|
+
projectEdge
|
|
6913
|
+
};
|
|
6855
6914
|
}
|
|
6856
6915
|
|
|
6857
6916
|
// src/http.ts
|
|
@@ -7014,7 +7073,7 @@ function traverse(adjacency, allEdges, seedIds, depth, maxNodes) {
|
|
|
7014
7073
|
|
|
7015
7074
|
// src/ops.ts
|
|
7016
7075
|
var API_VERSION = "0.2.1";
|
|
7017
|
-
var CAPABILITIES = ["search", "query", "path", "node", "browse"];
|
|
7076
|
+
var CAPABILITIES = ["search", "query", "path", "node", "browse", "suggestedQuestions"];
|
|
7018
7077
|
var MAX_NODES_CEILING = 1000;
|
|
7019
7078
|
var MAX_DEPTH_CEILING = 3;
|
|
7020
7079
|
var DEFAULT_DEPTH = 1;
|
|
@@ -7032,6 +7091,9 @@ function status(index) {
|
|
|
7032
7091
|
limits: { maxNodes: MAX_NODES_CEILING, maxDepth: MAX_DEPTH_CEILING }
|
|
7033
7092
|
};
|
|
7034
7093
|
}
|
|
7094
|
+
function suggestedQuestions(index) {
|
|
7095
|
+
return { graph: index.stamp, questions: index.suggestedQuestions };
|
|
7096
|
+
}
|
|
7035
7097
|
function search(index, req) {
|
|
7036
7098
|
const limit = Math.max(0, Math.min(req.limit ?? DEFAULT_SEARCH_LIMIT, MAX_SEARCH_LIMIT));
|
|
7037
7099
|
const scored = scoreNodes(index.scoreIndex, terms(req.q)).slice(0, limit);
|
|
@@ -7227,6 +7289,8 @@ async function dispatch(req, url, opts) {
|
|
|
7227
7289
|
}
|
|
7228
7290
|
if (method === "GET" && pathname === "/v1/browse")
|
|
7229
7291
|
return browse(index, parseBrowseRequest(url.searchParams));
|
|
7292
|
+
if (method === "GET" && pathname === "/v1/suggested-questions")
|
|
7293
|
+
return suggestedQuestions(index);
|
|
7230
7294
|
throw new OpError("not_found", `No such route: ${method} ${pathname}`);
|
|
7231
7295
|
}
|
|
7232
7296
|
function decodeNodeId(raw) {
|
|
@@ -33888,6 +33952,18 @@ function createGraphTools(ctx) {
|
|
|
33888
33952
|
return toResult(outcome, text);
|
|
33889
33953
|
}
|
|
33890
33954
|
},
|
|
33955
|
+
graph_suggested_questions: {
|
|
33956
|
+
description: "Questions graphify's own analysis (GRAPH_REPORT.md) flagged as ones this graph is uniquely positioned to answer, each with a one-line rationale (e.g. high betweenness centrality, a weakly-connected community). Empty when the checkout has no GRAPH_REPORT.md.",
|
|
33957
|
+
inputSchema: {},
|
|
33958
|
+
handler: () => {
|
|
33959
|
+
const outcome = runOp(ctx, (index) => suggestedQuestions(index));
|
|
33960
|
+
if (!outcome.ok)
|
|
33961
|
+
return outcome.error;
|
|
33962
|
+
const { result } = outcome;
|
|
33963
|
+
const text = `${result.questions.length} suggested question(s)`;
|
|
33964
|
+
return toResult(outcome, text);
|
|
33965
|
+
}
|
|
33966
|
+
},
|
|
33891
33967
|
graph_search: {
|
|
33892
33968
|
description: "Literal label/path search over the loaded graph. Returns scored nodes.",
|
|
33893
33969
|
inputSchema: { q: exports_external.string().min(1), limit: exports_external.number().int().min(0).optional() },
|
package/dist/client.js
CHANGED
|
@@ -98,11 +98,13 @@ function shatter(text) {
|
|
|
98
98
|
function terms(question) {
|
|
99
99
|
return [...new Set(shatter(question).filter((t) => t.length >= 3 && !STOPWORDS.has(t)))];
|
|
100
100
|
}
|
|
101
|
+
var FUZZY_MATCH_EXACT_MIN_COUNT = 2;
|
|
101
102
|
function scoreNodes(index, queryTerms) {
|
|
102
103
|
const scored = [];
|
|
103
104
|
for (const entry of index) {
|
|
104
105
|
let score = 0;
|
|
105
106
|
let exact = false;
|
|
107
|
+
let fuzzyMatchesByToken;
|
|
106
108
|
for (const term of queryTerms) {
|
|
107
109
|
let best = 0;
|
|
108
110
|
if (entry.labelLower === term) {
|
|
@@ -115,6 +117,16 @@ function scoreNodes(index, queryTerms) {
|
|
|
115
117
|
for (const tok of entry.labelTokens) {
|
|
116
118
|
if (tok.length >= 4 && (tok.startsWith(term) || term.startsWith(tok))) {
|
|
117
119
|
best = Math.max(best, 1.5);
|
|
120
|
+
if (term.length >= 4) {
|
|
121
|
+
if (!fuzzyMatchesByToken)
|
|
122
|
+
fuzzyMatchesByToken = new Map;
|
|
123
|
+
let matchedTerms = fuzzyMatchesByToken.get(tok);
|
|
124
|
+
if (!matchedTerms) {
|
|
125
|
+
matchedTerms = new Set;
|
|
126
|
+
fuzzyMatchesByToken.set(tok, matchedTerms);
|
|
127
|
+
}
|
|
128
|
+
matchedTerms.add(term);
|
|
129
|
+
}
|
|
118
130
|
} else if (term.length >= 4 && tok.includes(term)) {
|
|
119
131
|
best = Math.max(best, 1);
|
|
120
132
|
}
|
|
@@ -132,6 +144,14 @@ function scoreNodes(index, queryTerms) {
|
|
|
132
144
|
}
|
|
133
145
|
score += best;
|
|
134
146
|
}
|
|
147
|
+
if (fuzzyMatchesByToken) {
|
|
148
|
+
for (const matchedTerms of fuzzyMatchesByToken.values()) {
|
|
149
|
+
if (matchedTerms.size >= FUZZY_MATCH_EXACT_MIN_COUNT) {
|
|
150
|
+
exact = true;
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
135
155
|
if (score > 0)
|
|
136
156
|
scored.push({ id: entry.id, score: Math.round(score * 100) / 100, exact });
|
|
137
157
|
}
|
|
@@ -196,7 +216,7 @@ function traverse(adjacency, allEdges, seedIds, depth, maxNodes) {
|
|
|
196
216
|
|
|
197
217
|
// src/ops.ts
|
|
198
218
|
var API_VERSION = "0.2.1";
|
|
199
|
-
var CAPABILITIES = ["search", "query", "path", "node", "browse"];
|
|
219
|
+
var CAPABILITIES = ["search", "query", "path", "node", "browse", "suggestedQuestions"];
|
|
200
220
|
var MAX_NODES_CEILING = 1000;
|
|
201
221
|
var MAX_DEPTH_CEILING = 3;
|
|
202
222
|
var DEFAULT_DEPTH = 1;
|
|
@@ -214,6 +234,9 @@ function status(index) {
|
|
|
214
234
|
limits: { maxNodes: MAX_NODES_CEILING, maxDepth: MAX_DEPTH_CEILING }
|
|
215
235
|
};
|
|
216
236
|
}
|
|
237
|
+
function suggestedQuestions(index) {
|
|
238
|
+
return { graph: index.stamp, questions: index.suggestedQuestions };
|
|
239
|
+
}
|
|
217
240
|
function search(index, req) {
|
|
218
241
|
const limit = Math.max(0, Math.min(req.limit ?? DEFAULT_SEARCH_LIMIT, MAX_SEARCH_LIMIT));
|
|
219
242
|
const scored = scoreNodes(index.scoreIndex, terms(req.q)).slice(0, limit);
|
|
@@ -361,6 +384,7 @@ function node(index, req) {
|
|
|
361
384
|
return { graph: index.stamp, node: index.project(raw), neighbours };
|
|
362
385
|
}
|
|
363
386
|
export {
|
|
387
|
+
suggestedQuestions,
|
|
364
388
|
status,
|
|
365
389
|
search,
|
|
366
390
|
query,
|
package/dist/graph.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ScoreIndexEntry } from "./scoring.js";
|
|
2
|
-
import type { GraphEdge, GraphNode, GraphStamp } from "./types.js";
|
|
2
|
+
import type { GraphEdge, GraphNode, GraphStamp, SuggestedQuestion } from "./types.js";
|
|
3
3
|
type RawNode = {
|
|
4
4
|
id: string;
|
|
5
5
|
label: string;
|
|
@@ -30,6 +30,7 @@ export type GraphIndex = {
|
|
|
30
30
|
/** Undirected — the graph is `"directed": false` (companion-api.md §2.2). */
|
|
31
31
|
adjacency: Map<string, Array<AdjacencyEntry>>;
|
|
32
32
|
scoreIndex: Array<ScoreIndexEntry>;
|
|
33
|
+
suggestedQuestions: Array<SuggestedQuestion>;
|
|
33
34
|
project: (n: RawNode) => GraphNode;
|
|
34
35
|
projectEdge: (e: RawEdge) => GraphEdge;
|
|
35
36
|
};
|
|
@@ -38,5 +39,16 @@ export declare function rootPrefixFor(checkoutPath: string, graphRoot: string):
|
|
|
38
39
|
/** Also used by the MCP binding (mcpTools.ts) to detect staleness at call time — re-reads HEAD,
|
|
39
40
|
* doesn't cache it, so a commit made mid-session is picked up on the next tool call. */
|
|
40
41
|
export declare function readHeadSha(checkoutPath: string): string | null;
|
|
42
|
+
/**
|
|
43
|
+
* Parses graphify's own `GRAPH_REPORT.md` "## Suggested Questions" section (a bullet list of
|
|
44
|
+
* bold question / italic one-line rationale pairs) into structured pairs. Pure and best-effort:
|
|
45
|
+
* an absent heading yields `[]`, a bullet that isn't a bold question line is skipped, and a
|
|
46
|
+
* question with no following italic line still gets a `""` rationale rather than being dropped.
|
|
47
|
+
*/
|
|
48
|
+
export declare function parseSuggestedQuestions(markdown: string): Array<SuggestedQuestion>;
|
|
49
|
+
/** Best-effort: a missing/unreadable `GRAPH_REPORT.md` (e.g. graphify was never run for
|
|
50
|
+
* suggestions, or only `graph.json` is present) degrades to `[]` rather than failing the
|
|
51
|
+
* whole graph load — suggested questions are a nice-to-have, never load-bearing. */
|
|
52
|
+
export declare function loadSuggestedQuestions(checkoutPath: string): Array<SuggestedQuestion>;
|
|
41
53
|
export declare function loadGraph(checkoutPath: string): GraphIndex;
|
|
42
54
|
export {};
|
package/dist/index.js
CHANGED
|
@@ -98,11 +98,13 @@ function shatter(text) {
|
|
|
98
98
|
function terms(question) {
|
|
99
99
|
return [...new Set(shatter(question).filter((t) => t.length >= 3 && !STOPWORDS.has(t)))];
|
|
100
100
|
}
|
|
101
|
+
var FUZZY_MATCH_EXACT_MIN_COUNT = 2;
|
|
101
102
|
function scoreNodes(index, queryTerms) {
|
|
102
103
|
const scored = [];
|
|
103
104
|
for (const entry of index) {
|
|
104
105
|
let score = 0;
|
|
105
106
|
let exact = false;
|
|
107
|
+
let fuzzyMatchesByToken;
|
|
106
108
|
for (const term of queryTerms) {
|
|
107
109
|
let best = 0;
|
|
108
110
|
if (entry.labelLower === term) {
|
|
@@ -115,6 +117,16 @@ function scoreNodes(index, queryTerms) {
|
|
|
115
117
|
for (const tok of entry.labelTokens) {
|
|
116
118
|
if (tok.length >= 4 && (tok.startsWith(term) || term.startsWith(tok))) {
|
|
117
119
|
best = Math.max(best, 1.5);
|
|
120
|
+
if (term.length >= 4) {
|
|
121
|
+
if (!fuzzyMatchesByToken)
|
|
122
|
+
fuzzyMatchesByToken = new Map;
|
|
123
|
+
let matchedTerms = fuzzyMatchesByToken.get(tok);
|
|
124
|
+
if (!matchedTerms) {
|
|
125
|
+
matchedTerms = new Set;
|
|
126
|
+
fuzzyMatchesByToken.set(tok, matchedTerms);
|
|
127
|
+
}
|
|
128
|
+
matchedTerms.add(term);
|
|
129
|
+
}
|
|
118
130
|
} else if (term.length >= 4 && tok.includes(term)) {
|
|
119
131
|
best = Math.max(best, 1);
|
|
120
132
|
}
|
|
@@ -132,6 +144,14 @@ function scoreNodes(index, queryTerms) {
|
|
|
132
144
|
}
|
|
133
145
|
score += best;
|
|
134
146
|
}
|
|
147
|
+
if (fuzzyMatchesByToken) {
|
|
148
|
+
for (const matchedTerms of fuzzyMatchesByToken.values()) {
|
|
149
|
+
if (matchedTerms.size >= FUZZY_MATCH_EXACT_MIN_COUNT) {
|
|
150
|
+
exact = true;
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
135
155
|
if (score > 0)
|
|
136
156
|
scored.push({ id: entry.id, score: Math.round(score * 100) / 100, exact });
|
|
137
157
|
}
|
|
@@ -196,7 +216,7 @@ function traverse(adjacency, allEdges, seedIds, depth, maxNodes) {
|
|
|
196
216
|
|
|
197
217
|
// src/ops.ts
|
|
198
218
|
var API_VERSION = "0.2.1";
|
|
199
|
-
var CAPABILITIES = ["search", "query", "path", "node", "browse"];
|
|
219
|
+
var CAPABILITIES = ["search", "query", "path", "node", "browse", "suggestedQuestions"];
|
|
200
220
|
var MAX_NODES_CEILING = 1000;
|
|
201
221
|
var MAX_DEPTH_CEILING = 3;
|
|
202
222
|
var DEFAULT_DEPTH = 1;
|
|
@@ -214,6 +234,9 @@ function status(index) {
|
|
|
214
234
|
limits: { maxNodes: MAX_NODES_CEILING, maxDepth: MAX_DEPTH_CEILING }
|
|
215
235
|
};
|
|
216
236
|
}
|
|
237
|
+
function suggestedQuestions(index) {
|
|
238
|
+
return { graph: index.stamp, questions: index.suggestedQuestions };
|
|
239
|
+
}
|
|
217
240
|
function search(index, req) {
|
|
218
241
|
const limit = Math.max(0, Math.min(req.limit ?? DEFAULT_SEARCH_LIMIT, MAX_SEARCH_LIMIT));
|
|
219
242
|
const scored = scoreNodes(index.scoreIndex, terms(req.q)).slice(0, limit);
|
|
@@ -387,6 +410,36 @@ function readHeadSha(checkoutPath) {
|
|
|
387
410
|
return null;
|
|
388
411
|
}
|
|
389
412
|
}
|
|
413
|
+
var SUGGESTED_QUESTIONS_HEADING = /^## Suggested Questions\s*$/;
|
|
414
|
+
var SECTION_HEADING = /^## /;
|
|
415
|
+
var QUESTION_BULLET = /^- \*\*(.+)\*\*$/;
|
|
416
|
+
var RATIONALE_LINE = /^\s*_(.+)_\s*$/;
|
|
417
|
+
function parseSuggestedQuestions(markdown) {
|
|
418
|
+
const lines = markdown.split(`
|
|
419
|
+
`);
|
|
420
|
+
const headingIndex = lines.findIndex((line) => SUGGESTED_QUESTIONS_HEADING.test(line));
|
|
421
|
+
if (headingIndex === -1)
|
|
422
|
+
return [];
|
|
423
|
+
const sectionEnd = lines.findIndex((line, i) => i > headingIndex && SECTION_HEADING.test(line));
|
|
424
|
+
const section = lines.slice(headingIndex + 1, sectionEnd === -1 ? undefined : sectionEnd);
|
|
425
|
+
const result = [];
|
|
426
|
+
for (let i = 0;i < section.length; i++) {
|
|
427
|
+
const questionMatch = section[i].match(QUESTION_BULLET);
|
|
428
|
+
if (!questionMatch)
|
|
429
|
+
continue;
|
|
430
|
+
const rationaleMatch = section[i + 1]?.match(RATIONALE_LINE);
|
|
431
|
+
result.push({ question: questionMatch[1], rationale: rationaleMatch?.[1] ?? "" });
|
|
432
|
+
}
|
|
433
|
+
return result;
|
|
434
|
+
}
|
|
435
|
+
function loadSuggestedQuestions(checkoutPath) {
|
|
436
|
+
try {
|
|
437
|
+
const raw = readFileSync(resolve(checkoutPath, "graphify-out/GRAPH_REPORT.md"), "utf8");
|
|
438
|
+
return parseSuggestedQuestions(raw);
|
|
439
|
+
} catch {
|
|
440
|
+
return [];
|
|
441
|
+
}
|
|
442
|
+
}
|
|
390
443
|
function loadGraph(checkoutPath) {
|
|
391
444
|
const graphPath = resolve(checkoutPath, "graphify-out/graph.json");
|
|
392
445
|
let raw;
|
|
@@ -448,7 +501,16 @@ function loadGraph(checkoutPath) {
|
|
|
448
501
|
sourceFile: checkoutRelative(e.source_file),
|
|
449
502
|
sourceLocation: e.source_location
|
|
450
503
|
});
|
|
451
|
-
return {
|
|
504
|
+
return {
|
|
505
|
+
stamp,
|
|
506
|
+
nodesById,
|
|
507
|
+
edges: doc.links,
|
|
508
|
+
adjacency,
|
|
509
|
+
scoreIndex,
|
|
510
|
+
suggestedQuestions: loadSuggestedQuestions(checkoutPath),
|
|
511
|
+
project,
|
|
512
|
+
projectEdge
|
|
513
|
+
};
|
|
452
514
|
}
|
|
453
515
|
// src/http.ts
|
|
454
516
|
import { timingSafeEqual } from "node:crypto";
|
|
@@ -529,6 +591,8 @@ async function dispatch(req, url, opts) {
|
|
|
529
591
|
}
|
|
530
592
|
if (method === "GET" && pathname === "/v1/browse")
|
|
531
593
|
return browse(index, parseBrowseRequest(url.searchParams));
|
|
594
|
+
if (method === "GET" && pathname === "/v1/suggested-questions")
|
|
595
|
+
return suggestedQuestions(index);
|
|
532
596
|
throw new OpError("not_found", `No such route: ${method} ${pathname}`);
|
|
533
597
|
}
|
|
534
598
|
function decodeNodeId(raw) {
|
|
@@ -795,6 +859,7 @@ function serve(opts) {
|
|
|
795
859
|
return { server, token, baseUrl, pairingLine: pairingLine(baseUrl, token) };
|
|
796
860
|
}
|
|
797
861
|
export {
|
|
862
|
+
suggestedQuestions,
|
|
798
863
|
status,
|
|
799
864
|
serve,
|
|
800
865
|
search,
|
package/dist/ops.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { GraphEdge, GraphNode, OpResponse } from "./types.js";
|
|
1
|
+
import type { GraphEdge, GraphNode, OpResponse, SuggestedQuestion } from "./types.js";
|
|
2
2
|
import type { GraphIndex } from "./graph.js";
|
|
3
3
|
/** Also the version /v1/ping reports (companion-api.md §4.1) — the two must never drift apart. */
|
|
4
4
|
export declare const API_VERSION = "0.2.1";
|
|
@@ -14,6 +14,13 @@ export type StatusResult = {
|
|
|
14
14
|
};
|
|
15
15
|
};
|
|
16
16
|
export declare function status(index: GraphIndex): OpResponse<StatusResult>;
|
|
17
|
+
export type SuggestedQuestionsResult = {
|
|
18
|
+
questions: Array<SuggestedQuestion>;
|
|
19
|
+
};
|
|
20
|
+
/** graphify's own `GRAPH_REPORT.md`-derived suggestions (graph.ts's loadSuggestedQuestions),
|
|
21
|
+
* already loaded onto the index at graph-load time — this op just echoes them alongside the
|
|
22
|
+
* stamp, same shape as every other op. */
|
|
23
|
+
export declare function suggestedQuestions(index: GraphIndex): OpResponse<SuggestedQuestionsResult>;
|
|
17
24
|
export type SearchRequest = {
|
|
18
25
|
q: string;
|
|
19
26
|
limit?: number;
|
|
@@ -66,7 +73,7 @@ export type QueryResult = {
|
|
|
66
73
|
};
|
|
67
74
|
/** See companion-api.md §4.8 / notex-mcp-server.md §5 — same buildFooter as the write paths. */
|
|
68
75
|
footer?: string;
|
|
69
|
-
/** Set when no seed cleared the seed-score floor —
|
|
76
|
+
/** Set when no seed cleared the seed-score floor — see ScoredNode's `exact` (scoring.ts). */
|
|
70
77
|
lowConfidence?: {
|
|
71
78
|
topScore: number;
|
|
72
79
|
};
|
package/dist/scoring.d.ts
CHANGED
|
@@ -10,7 +10,8 @@ export type ScoreIndexEntry = {
|
|
|
10
10
|
export type ScoredNode = {
|
|
11
11
|
id: string;
|
|
12
12
|
score: number;
|
|
13
|
-
/** Cleared the seed-score floor: at least one term matched a whole label or a whole label
|
|
13
|
+
/** Cleared the seed-score floor: at least one term matched a whole label or a whole label
|
|
14
|
+
* token, or (TBR-130) at least two independent terms each fuzzy-prefix the same label token. */
|
|
14
15
|
exact: boolean;
|
|
15
16
|
};
|
|
16
17
|
/** label tokens weigh full; path tokens weigh less — a path match is weaker evidence. */
|
package/dist/types.d.ts
CHANGED
|
@@ -21,6 +21,12 @@ export type GraphNode = {
|
|
|
21
21
|
name: string;
|
|
22
22
|
} | null;
|
|
23
23
|
};
|
|
24
|
+
/** Parsed from `graphify-out/GRAPH_REPORT.md`'s "## Suggested Questions" section — free-text,
|
|
25
|
+
* not derived from graph.json, so there is no stable id to key it by. */
|
|
26
|
+
export type SuggestedQuestion = {
|
|
27
|
+
question: string;
|
|
28
|
+
rationale: string;
|
|
29
|
+
};
|
|
24
30
|
export type GraphEdge = {
|
|
25
31
|
source: string;
|
|
26
32
|
target: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "notex-companion",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Local retrieval companion for Notex — reads a checkout's graphify-out/graph.json and serves deterministic search/query/path/node lookups over loopback HTTP and MCP stdio. No LLM, no graph building, no network beyond 127.0.0.1.",
|
|
5
5
|
"keywords": ["notex", "graphify", "mcp", "code-graph"],
|
|
6
6
|
"license": "MIT",
|