notex-companion 0.3.3 → 0.4.0
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 +58 -2
- package/dist/client.js +5 -1
- package/dist/graph.d.ts +13 -1
- package/dist/index.js +47 -2
- package/dist/ops.d.ts +8 -1
- package/dist/types.d.ts +6 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -6790,6 +6790,36 @@ function readHeadSha(checkoutPath) {
|
|
|
6790
6790
|
return null;
|
|
6791
6791
|
}
|
|
6792
6792
|
}
|
|
6793
|
+
var SUGGESTED_QUESTIONS_HEADING = /^## Suggested Questions\s*$/;
|
|
6794
|
+
var SECTION_HEADING = /^## /;
|
|
6795
|
+
var QUESTION_BULLET = /^- \*\*(.+)\*\*$/;
|
|
6796
|
+
var RATIONALE_LINE = /^\s*_(.+)_\s*$/;
|
|
6797
|
+
function parseSuggestedQuestions(markdown) {
|
|
6798
|
+
const lines = markdown.split(`
|
|
6799
|
+
`);
|
|
6800
|
+
const headingIndex = lines.findIndex((line) => SUGGESTED_QUESTIONS_HEADING.test(line));
|
|
6801
|
+
if (headingIndex === -1)
|
|
6802
|
+
return [];
|
|
6803
|
+
const sectionEnd = lines.findIndex((line, i) => i > headingIndex && SECTION_HEADING.test(line));
|
|
6804
|
+
const section = lines.slice(headingIndex + 1, sectionEnd === -1 ? undefined : sectionEnd);
|
|
6805
|
+
const result = [];
|
|
6806
|
+
for (let i = 0;i < section.length; i++) {
|
|
6807
|
+
const questionMatch = section[i].match(QUESTION_BULLET);
|
|
6808
|
+
if (!questionMatch)
|
|
6809
|
+
continue;
|
|
6810
|
+
const rationaleMatch = section[i + 1]?.match(RATIONALE_LINE);
|
|
6811
|
+
result.push({ question: questionMatch[1], rationale: rationaleMatch?.[1] ?? "" });
|
|
6812
|
+
}
|
|
6813
|
+
return result;
|
|
6814
|
+
}
|
|
6815
|
+
function loadSuggestedQuestions(checkoutPath) {
|
|
6816
|
+
try {
|
|
6817
|
+
const raw = readFileSync2(resolve(checkoutPath, "graphify-out/GRAPH_REPORT.md"), "utf8");
|
|
6818
|
+
return parseSuggestedQuestions(raw);
|
|
6819
|
+
} catch {
|
|
6820
|
+
return [];
|
|
6821
|
+
}
|
|
6822
|
+
}
|
|
6793
6823
|
function loadGraph(checkoutPath) {
|
|
6794
6824
|
const graphPath = resolve(checkoutPath, "graphify-out/graph.json");
|
|
6795
6825
|
let raw;
|
|
@@ -6851,7 +6881,16 @@ function loadGraph(checkoutPath) {
|
|
|
6851
6881
|
sourceFile: checkoutRelative(e.source_file),
|
|
6852
6882
|
sourceLocation: e.source_location
|
|
6853
6883
|
});
|
|
6854
|
-
return {
|
|
6884
|
+
return {
|
|
6885
|
+
stamp,
|
|
6886
|
+
nodesById,
|
|
6887
|
+
edges: doc.links,
|
|
6888
|
+
adjacency,
|
|
6889
|
+
scoreIndex,
|
|
6890
|
+
suggestedQuestions: loadSuggestedQuestions(checkoutPath),
|
|
6891
|
+
project,
|
|
6892
|
+
projectEdge
|
|
6893
|
+
};
|
|
6855
6894
|
}
|
|
6856
6895
|
|
|
6857
6896
|
// src/http.ts
|
|
@@ -7014,7 +7053,7 @@ function traverse(adjacency, allEdges, seedIds, depth, maxNodes) {
|
|
|
7014
7053
|
|
|
7015
7054
|
// src/ops.ts
|
|
7016
7055
|
var API_VERSION = "0.2.1";
|
|
7017
|
-
var CAPABILITIES = ["search", "query", "path", "node", "browse"];
|
|
7056
|
+
var CAPABILITIES = ["search", "query", "path", "node", "browse", "suggestedQuestions"];
|
|
7018
7057
|
var MAX_NODES_CEILING = 1000;
|
|
7019
7058
|
var MAX_DEPTH_CEILING = 3;
|
|
7020
7059
|
var DEFAULT_DEPTH = 1;
|
|
@@ -7032,6 +7071,9 @@ function status(index) {
|
|
|
7032
7071
|
limits: { maxNodes: MAX_NODES_CEILING, maxDepth: MAX_DEPTH_CEILING }
|
|
7033
7072
|
};
|
|
7034
7073
|
}
|
|
7074
|
+
function suggestedQuestions(index) {
|
|
7075
|
+
return { graph: index.stamp, questions: index.suggestedQuestions };
|
|
7076
|
+
}
|
|
7035
7077
|
function search(index, req) {
|
|
7036
7078
|
const limit = Math.max(0, Math.min(req.limit ?? DEFAULT_SEARCH_LIMIT, MAX_SEARCH_LIMIT));
|
|
7037
7079
|
const scored = scoreNodes(index.scoreIndex, terms(req.q)).slice(0, limit);
|
|
@@ -7227,6 +7269,8 @@ async function dispatch(req, url, opts) {
|
|
|
7227
7269
|
}
|
|
7228
7270
|
if (method === "GET" && pathname === "/v1/browse")
|
|
7229
7271
|
return browse(index, parseBrowseRequest(url.searchParams));
|
|
7272
|
+
if (method === "GET" && pathname === "/v1/suggested-questions")
|
|
7273
|
+
return suggestedQuestions(index);
|
|
7230
7274
|
throw new OpError("not_found", `No such route: ${method} ${pathname}`);
|
|
7231
7275
|
}
|
|
7232
7276
|
function decodeNodeId(raw) {
|
|
@@ -33888,6 +33932,18 @@ function createGraphTools(ctx) {
|
|
|
33888
33932
|
return toResult(outcome, text);
|
|
33889
33933
|
}
|
|
33890
33934
|
},
|
|
33935
|
+
graph_suggested_questions: {
|
|
33936
|
+
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.",
|
|
33937
|
+
inputSchema: {},
|
|
33938
|
+
handler: () => {
|
|
33939
|
+
const outcome = runOp(ctx, (index) => suggestedQuestions(index));
|
|
33940
|
+
if (!outcome.ok)
|
|
33941
|
+
return outcome.error;
|
|
33942
|
+
const { result } = outcome;
|
|
33943
|
+
const text = `${result.questions.length} suggested question(s)`;
|
|
33944
|
+
return toResult(outcome, text);
|
|
33945
|
+
}
|
|
33946
|
+
},
|
|
33891
33947
|
graph_search: {
|
|
33892
33948
|
description: "Literal label/path search over the loaded graph. Returns scored nodes.",
|
|
33893
33949
|
inputSchema: { q: exports_external.string().min(1), limit: exports_external.number().int().min(0).optional() },
|
package/dist/client.js
CHANGED
|
@@ -196,7 +196,7 @@ function traverse(adjacency, allEdges, seedIds, depth, maxNodes) {
|
|
|
196
196
|
|
|
197
197
|
// src/ops.ts
|
|
198
198
|
var API_VERSION = "0.2.1";
|
|
199
|
-
var CAPABILITIES = ["search", "query", "path", "node", "browse"];
|
|
199
|
+
var CAPABILITIES = ["search", "query", "path", "node", "browse", "suggestedQuestions"];
|
|
200
200
|
var MAX_NODES_CEILING = 1000;
|
|
201
201
|
var MAX_DEPTH_CEILING = 3;
|
|
202
202
|
var DEFAULT_DEPTH = 1;
|
|
@@ -214,6 +214,9 @@ function status(index) {
|
|
|
214
214
|
limits: { maxNodes: MAX_NODES_CEILING, maxDepth: MAX_DEPTH_CEILING }
|
|
215
215
|
};
|
|
216
216
|
}
|
|
217
|
+
function suggestedQuestions(index) {
|
|
218
|
+
return { graph: index.stamp, questions: index.suggestedQuestions };
|
|
219
|
+
}
|
|
217
220
|
function search(index, req) {
|
|
218
221
|
const limit = Math.max(0, Math.min(req.limit ?? DEFAULT_SEARCH_LIMIT, MAX_SEARCH_LIMIT));
|
|
219
222
|
const scored = scoreNodes(index.scoreIndex, terms(req.q)).slice(0, limit);
|
|
@@ -361,6 +364,7 @@ function node(index, req) {
|
|
|
361
364
|
return { graph: index.stamp, node: index.project(raw), neighbours };
|
|
362
365
|
}
|
|
363
366
|
export {
|
|
367
|
+
suggestedQuestions,
|
|
364
368
|
status,
|
|
365
369
|
search,
|
|
366
370
|
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
|
@@ -196,7 +196,7 @@ function traverse(adjacency, allEdges, seedIds, depth, maxNodes) {
|
|
|
196
196
|
|
|
197
197
|
// src/ops.ts
|
|
198
198
|
var API_VERSION = "0.2.1";
|
|
199
|
-
var CAPABILITIES = ["search", "query", "path", "node", "browse"];
|
|
199
|
+
var CAPABILITIES = ["search", "query", "path", "node", "browse", "suggestedQuestions"];
|
|
200
200
|
var MAX_NODES_CEILING = 1000;
|
|
201
201
|
var MAX_DEPTH_CEILING = 3;
|
|
202
202
|
var DEFAULT_DEPTH = 1;
|
|
@@ -214,6 +214,9 @@ function status(index) {
|
|
|
214
214
|
limits: { maxNodes: MAX_NODES_CEILING, maxDepth: MAX_DEPTH_CEILING }
|
|
215
215
|
};
|
|
216
216
|
}
|
|
217
|
+
function suggestedQuestions(index) {
|
|
218
|
+
return { graph: index.stamp, questions: index.suggestedQuestions };
|
|
219
|
+
}
|
|
217
220
|
function search(index, req) {
|
|
218
221
|
const limit = Math.max(0, Math.min(req.limit ?? DEFAULT_SEARCH_LIMIT, MAX_SEARCH_LIMIT));
|
|
219
222
|
const scored = scoreNodes(index.scoreIndex, terms(req.q)).slice(0, limit);
|
|
@@ -387,6 +390,36 @@ function readHeadSha(checkoutPath) {
|
|
|
387
390
|
return null;
|
|
388
391
|
}
|
|
389
392
|
}
|
|
393
|
+
var SUGGESTED_QUESTIONS_HEADING = /^## Suggested Questions\s*$/;
|
|
394
|
+
var SECTION_HEADING = /^## /;
|
|
395
|
+
var QUESTION_BULLET = /^- \*\*(.+)\*\*$/;
|
|
396
|
+
var RATIONALE_LINE = /^\s*_(.+)_\s*$/;
|
|
397
|
+
function parseSuggestedQuestions(markdown) {
|
|
398
|
+
const lines = markdown.split(`
|
|
399
|
+
`);
|
|
400
|
+
const headingIndex = lines.findIndex((line) => SUGGESTED_QUESTIONS_HEADING.test(line));
|
|
401
|
+
if (headingIndex === -1)
|
|
402
|
+
return [];
|
|
403
|
+
const sectionEnd = lines.findIndex((line, i) => i > headingIndex && SECTION_HEADING.test(line));
|
|
404
|
+
const section = lines.slice(headingIndex + 1, sectionEnd === -1 ? undefined : sectionEnd);
|
|
405
|
+
const result = [];
|
|
406
|
+
for (let i = 0;i < section.length; i++) {
|
|
407
|
+
const questionMatch = section[i].match(QUESTION_BULLET);
|
|
408
|
+
if (!questionMatch)
|
|
409
|
+
continue;
|
|
410
|
+
const rationaleMatch = section[i + 1]?.match(RATIONALE_LINE);
|
|
411
|
+
result.push({ question: questionMatch[1], rationale: rationaleMatch?.[1] ?? "" });
|
|
412
|
+
}
|
|
413
|
+
return result;
|
|
414
|
+
}
|
|
415
|
+
function loadSuggestedQuestions(checkoutPath) {
|
|
416
|
+
try {
|
|
417
|
+
const raw = readFileSync(resolve(checkoutPath, "graphify-out/GRAPH_REPORT.md"), "utf8");
|
|
418
|
+
return parseSuggestedQuestions(raw);
|
|
419
|
+
} catch {
|
|
420
|
+
return [];
|
|
421
|
+
}
|
|
422
|
+
}
|
|
390
423
|
function loadGraph(checkoutPath) {
|
|
391
424
|
const graphPath = resolve(checkoutPath, "graphify-out/graph.json");
|
|
392
425
|
let raw;
|
|
@@ -448,7 +481,16 @@ function loadGraph(checkoutPath) {
|
|
|
448
481
|
sourceFile: checkoutRelative(e.source_file),
|
|
449
482
|
sourceLocation: e.source_location
|
|
450
483
|
});
|
|
451
|
-
return {
|
|
484
|
+
return {
|
|
485
|
+
stamp,
|
|
486
|
+
nodesById,
|
|
487
|
+
edges: doc.links,
|
|
488
|
+
adjacency,
|
|
489
|
+
scoreIndex,
|
|
490
|
+
suggestedQuestions: loadSuggestedQuestions(checkoutPath),
|
|
491
|
+
project,
|
|
492
|
+
projectEdge
|
|
493
|
+
};
|
|
452
494
|
}
|
|
453
495
|
// src/http.ts
|
|
454
496
|
import { timingSafeEqual } from "node:crypto";
|
|
@@ -529,6 +571,8 @@ async function dispatch(req, url, opts) {
|
|
|
529
571
|
}
|
|
530
572
|
if (method === "GET" && pathname === "/v1/browse")
|
|
531
573
|
return browse(index, parseBrowseRequest(url.searchParams));
|
|
574
|
+
if (method === "GET" && pathname === "/v1/suggested-questions")
|
|
575
|
+
return suggestedQuestions(index);
|
|
532
576
|
throw new OpError("not_found", `No such route: ${method} ${pathname}`);
|
|
533
577
|
}
|
|
534
578
|
function decodeNodeId(raw) {
|
|
@@ -795,6 +839,7 @@ function serve(opts) {
|
|
|
795
839
|
return { server, token, baseUrl, pairingLine: pairingLine(baseUrl, token) };
|
|
796
840
|
}
|
|
797
841
|
export {
|
|
842
|
+
suggestedQuestions,
|
|
798
843
|
status,
|
|
799
844
|
serve,
|
|
800
845
|
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;
|
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.0",
|
|
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",
|