@doquflow/server 1.7.0 → 2.0.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/README.md +24 -72
- package/dist/index.d.ts +2 -0
- package/dist/index.js +6 -328
- package/package.json +5 -7
- package/dist/category-dir.js +0 -12
- package/dist/extractor-rules.js +0 -166
- package/dist/extractor-stoplist.js +0 -89
- package/dist/extractor.js +0 -171
- package/dist/filesystem.js +0 -132
- package/dist/language-map.js +0 -58
- package/dist/tools/answer-synthesis.js +0 -189
- package/dist/tools/build-graph.js +0 -111
- package/dist/tools/generate-dependency-graph.js +0 -162
- package/dist/tools/get-schema-guidance.js +0 -213
- package/dist/tools/ingest-source.js +0 -272
- package/dist/tools/lint-wiki.js +0 -342
- package/dist/tools/list-modules.js +0 -50
- package/dist/tools/list-wiki.js +0 -198
- package/dist/tools/preview-generation.js +0 -228
- package/dist/tools/query-wiki.js +0 -67
- package/dist/tools/read-module.js +0 -53
- package/dist/tools/read-specs.js +0 -39
- package/dist/tools/save-answer-as-page.js +0 -91
- package/dist/tools/update-index.js +0 -157
- package/dist/tools/wiki-search.js +0 -157
- package/dist/tools/write-spec.js +0 -55
- package/dist/types.js +0 -2
package/README.md
CHANGED
|
@@ -1,82 +1,34 @@
|
|
|
1
|
-
# @doquflow/server
|
|
1
|
+
# @doquflow/server (DEPRECATED ALIAS)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This package was the original DocuFlow MCP server distribution.
|
|
4
|
+
In v2.0+ it is a thin alias that depends on `@doquflow/studio`.
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
AI Agent (Claude, Copilot, Cursor)
|
|
7
|
-
│ calls MCP tools
|
|
8
|
-
@doquflow/server ← reads files, builds wiki, answers questions
|
|
9
|
-
│ filesystem only
|
|
10
|
-
Any project on disk ← any language, any structure
|
|
11
|
-
```
|
|
6
|
+
## Why does this still exist?
|
|
12
7
|
|
|
13
|
-
|
|
8
|
+
Existing users have `@doquflow/server` registered in their `.mcp.json`
|
|
9
|
+
files. The DocuFlow project's commitment is **zero breakage** for
|
|
10
|
+
existing installs. This package will continue to install and run
|
|
11
|
+
identically through all v2.x releases.
|
|
14
12
|
|
|
15
|
-
|
|
16
|
-
npx @doquflow/cli init
|
|
17
|
-
```
|
|
13
|
+
## What you should install instead
|
|
18
14
|
|
|
19
|
-
|
|
15
|
+
- **Full surface** (UI, REST API, daemons, 15 MCP tools):
|
|
16
|
+
`npm i -g @doquflow/studio`
|
|
17
|
+
- **Irreducible value pipe** (4 MCP tools, no daemon, no UI):
|
|
18
|
+
`npm i -g @doquflow/core`
|
|
19
|
+
- **All-in-one CLI** (transitively pulls core + studio):
|
|
20
|
+
`npm i -g @doquflow/cli`
|
|
20
21
|
|
|
21
|
-
##
|
|
22
|
+
## Migration
|
|
22
23
|
|
|
23
|
-
|
|
24
|
+
See [MIGRATION.md](../../MIGRATION.md) and [release/v2.0.0.md](../../release/v2.0.0.md).
|
|
24
25
|
|
|
25
|
-
|
|
26
|
-
|------|-------|--------|
|
|
27
|
-
| `read_module` | `{ path }` | Language, classes, functions, dependencies, DB tables, endpoints, config refs, raw content |
|
|
28
|
-
| `list_modules` | `{ path, extensions? }` | All modules in a directory tree (bulk extraction, no raw content) |
|
|
29
|
-
| `write_spec` | `{ project_path, filename, content }` | Writes markdown spec to `.docuflow/specs/` |
|
|
30
|
-
| `read_specs` | `{ project_path, module_name? }` | Reads existing specs; includes `stale: boolean` per spec |
|
|
26
|
+
Run `docuflow doctor` (v2.0+) for a personalized recommendation.
|
|
31
27
|
|
|
32
|
-
|
|
28
|
+
## Deprecation timeline
|
|
33
29
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
| `wiki_search` | BM25-inspired relevance search across all wiki pages |
|
|
40
|
-
| `query_wiki` | Main interface: search + synthesize answer from wiki |
|
|
41
|
-
| `answer_synthesis` | Build structured markdown answer from selected pages |
|
|
42
|
-
| `save_answer_as_page` | Save synthesised answer back to wiki |
|
|
43
|
-
|
|
44
|
-
### Health & Guidance
|
|
45
|
-
|
|
46
|
-
| Tool | What it does |
|
|
47
|
-
|------|-------------|
|
|
48
|
-
| `lint_wiki` | Health checks: orphan pages, stale content, broken links, metadata gaps, contradictions. Returns 0-100 health score. |
|
|
49
|
-
| `get_schema_guidance` | Detect domain → recommend missing wiki pages |
|
|
50
|
-
| `preview_generation` | Show what a tool will do before running (reads real wiki state) |
|
|
51
|
-
|
|
52
|
-
### Dependency Analysis
|
|
53
|
-
|
|
54
|
-
| Tool | What it does |
|
|
55
|
-
|------|-------------|
|
|
56
|
-
| `generate_dependency_graph` | Build import/shared-table/shared-endpoint graph. Returns `nodes`, `edges`, `most_connected` top 10 (highest-risk files). |
|
|
57
|
-
|
|
58
|
-
## Languages supported
|
|
59
|
-
|
|
60
|
-
TypeScript, JavaScript, Python, Go (structs, funcs, gin/gorilla routes, GORM), Ruby/Rails (classes, ActiveRecord, Rails routes), Rust, Java, C#, PHP, Kotlin, Swift, SQL, Shell, YAML, JSON, and more.
|
|
61
|
-
|
|
62
|
-
Unknown file types return full raw content — never fails on unfamiliar files.
|
|
63
|
-
|
|
64
|
-
## Extraction engine
|
|
65
|
-
|
|
66
|
-
| Field | Detected from |
|
|
67
|
-
|-------|--------------|
|
|
68
|
-
| `classes` | `class`, `interface`, `struct`, `enum`, `record`, Go `type … struct/interface`, Ruby `module` |
|
|
69
|
-
| `functions` | Keyword-prefixed declarations, arrow functions, Go `func`, Ruby `def` |
|
|
70
|
-
| `dependencies` | `import`, `require()`, Go import blocks, Ruby require |
|
|
71
|
-
| `db_tables` | SQL `FROM/JOIN/INTO`, EF `DbSet<T>`, GORM `db.Table()`, ActiveRecord associations |
|
|
72
|
-
| `endpoints` | .NET attributes, Express/NestJS, gin/gorilla/chi routes, Rails route helpers |
|
|
73
|
-
| `config_refs` | `process.env`, `os.Getenv`, `ENV['KEY']`, `IConfiguration`, `appsettings` |
|
|
74
|
-
|
|
75
|
-
## Requirements
|
|
76
|
-
|
|
77
|
-
- Node.js 18+
|
|
78
|
-
- No API keys, no network calls, no AI dependencies
|
|
79
|
-
|
|
80
|
-
## License
|
|
81
|
-
|
|
82
|
-
MIT — [github.com/doquflows/docuflow](https://github.com/doquflows/docuflow)
|
|
30
|
+
- v2.0 — alias maintained, no behavior change
|
|
31
|
+
- v2.x — alias maintained, no behavior change
|
|
32
|
+
- v3.0 — removal *considered* based on download stats. We will publish
|
|
33
|
+
a clear deprecation notice at least one minor version before any
|
|
34
|
+
removal.
|
package/dist/index.d.ts
ADDED
package/dist/index.js
CHANGED
|
@@ -1,331 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const read_specs_1 = require("./tools/read-specs");
|
|
11
|
-
const ingest_source_1 = require("./tools/ingest-source");
|
|
12
|
-
const update_index_1 = require("./tools/update-index");
|
|
13
|
-
const list_wiki_1 = require("./tools/list-wiki");
|
|
14
|
-
const wiki_search_1 = require("./tools/wiki-search");
|
|
15
|
-
const answer_synthesis_1 = require("./tools/answer-synthesis");
|
|
16
|
-
const query_wiki_1 = require("./tools/query-wiki");
|
|
17
|
-
const save_answer_as_page_1 = require("./tools/save-answer-as-page");
|
|
18
|
-
const lint_wiki_1 = require("./tools/lint-wiki");
|
|
19
|
-
const get_schema_guidance_1 = require("./tools/get-schema-guidance");
|
|
20
|
-
const preview_generation_1 = require("./tools/preview-generation");
|
|
21
|
-
const generate_dependency_graph_1 = require("./tools/generate-dependency-graph");
|
|
22
|
-
const server = new index_js_1.Server({ name: "docuflow", version: "0.1.0" }, { capabilities: { tools: {} } });
|
|
23
|
-
server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
|
|
24
|
-
tools: [
|
|
25
|
-
{
|
|
26
|
-
name: "read_module",
|
|
27
|
-
description: "Read a single source file, detect its language, and extract classes, functions, dependencies, DB tables, endpoints, and config references. Returns raw content truncated at 8000 chars.",
|
|
28
|
-
inputSchema: {
|
|
29
|
-
type: "object",
|
|
30
|
-
properties: {
|
|
31
|
-
path: { type: "string", description: "Absolute or relative path to the source file." },
|
|
32
|
-
},
|
|
33
|
-
required: ["path"],
|
|
34
|
-
},
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
name: "list_modules",
|
|
38
|
-
description: "Walk a project directory and return extracted facts for every non-binary file. Skips node_modules, dist, build, .git, vendor, obj, bin, .docuflow, *.min.js, *.map, *.lock, and files >300KB. Raw content is omitted for bulk results.",
|
|
39
|
-
inputSchema: {
|
|
40
|
-
type: "object",
|
|
41
|
-
properties: {
|
|
42
|
-
path: { type: "string", description: "Root directory to scan." },
|
|
43
|
-
extensions: {
|
|
44
|
-
type: "array",
|
|
45
|
-
items: { type: "string" },
|
|
46
|
-
description: "Optional extension filter e.g. [\".cs\",\".ts\"]. If omitted all non-binary files are included.",
|
|
47
|
-
},
|
|
48
|
-
},
|
|
49
|
-
required: ["path"],
|
|
50
|
-
},
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
name: "write_spec",
|
|
54
|
-
description: "Write a markdown spec file to <project_path>/.docuflow/specs/<filename>.md and update the index. The agent provides the full markdown content.",
|
|
55
|
-
inputSchema: {
|
|
56
|
-
type: "object",
|
|
57
|
-
properties: {
|
|
58
|
-
project_path: { type: "string", description: "Root of the project (where .docuflow/ will be created)." },
|
|
59
|
-
filename: { type: "string", description: "Name for the spec file, without extension." },
|
|
60
|
-
content: { type: "string", description: "Full markdown content to write." },
|
|
61
|
-
},
|
|
62
|
-
required: ["project_path", "filename", "content"],
|
|
63
|
-
},
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
name: "read_specs",
|
|
67
|
-
description: "Read previously written specs from <project_path>/.docuflow/specs/. Optionally filter by module name.",
|
|
68
|
-
inputSchema: {
|
|
69
|
-
type: "object",
|
|
70
|
-
properties: {
|
|
71
|
-
project_path: { type: "string", description: "Root of the project." },
|
|
72
|
-
module_name: {
|
|
73
|
-
type: "string",
|
|
74
|
-
description: "Optional: name of a specific spec to retrieve (with or without .md).",
|
|
75
|
-
},
|
|
76
|
-
},
|
|
77
|
-
required: ["project_path"],
|
|
78
|
-
},
|
|
79
|
-
},
|
|
80
|
-
{
|
|
81
|
-
name: "ingest_source",
|
|
82
|
-
description: "Ingest a markdown source document from .docuflow/sources/ and generate wiki pages (entities, concepts) with cross-references. Returns pages created and entities discovered.",
|
|
83
|
-
inputSchema: {
|
|
84
|
-
type: "object",
|
|
85
|
-
properties: {
|
|
86
|
-
project_path: { type: "string", description: "Root of the project." },
|
|
87
|
-
source_filename: {
|
|
88
|
-
type: "string",
|
|
89
|
-
description: "Filename in .docuflow/sources/ to ingest (e.g., 'overview.md').",
|
|
90
|
-
},
|
|
91
|
-
},
|
|
92
|
-
required: ["project_path", "source_filename"],
|
|
93
|
-
},
|
|
94
|
-
},
|
|
95
|
-
{
|
|
96
|
-
name: "update_index",
|
|
97
|
-
description: "Scan all wiki pages in .docuflow/wiki/ and regenerate .docuflow/index.md organized by category. Appends operation to log.md.",
|
|
98
|
-
inputSchema: {
|
|
99
|
-
type: "object",
|
|
100
|
-
properties: {
|
|
101
|
-
project_path: { type: "string", description: "Root of the project." },
|
|
102
|
-
},
|
|
103
|
-
required: ["project_path"],
|
|
104
|
-
},
|
|
105
|
-
},
|
|
106
|
-
{
|
|
107
|
-
name: "list_wiki",
|
|
108
|
-
description: "List all wiki pages in .docuflow/wiki/, optionally filtered by category. Returns metadata (title, created_at, sources, tags, stale) and page counts by category. Pages not updated in 30+ days are flagged stale:true.",
|
|
109
|
-
inputSchema: {
|
|
110
|
-
type: "object",
|
|
111
|
-
properties: {
|
|
112
|
-
project_path: { type: "string", description: "Root of the project." },
|
|
113
|
-
category: {
|
|
114
|
-
type: "string",
|
|
115
|
-
enum: ["entity", "concept", "timeline", "synthesis"],
|
|
116
|
-
description: "Optional: filter to a specific category.",
|
|
117
|
-
},
|
|
118
|
-
},
|
|
119
|
-
required: ["project_path"],
|
|
120
|
-
},
|
|
121
|
-
},
|
|
122
|
-
{
|
|
123
|
-
name: "wiki_search",
|
|
124
|
-
description: "Search the wiki for pages matching a query using relevance scoring. Returns ranked results with preview snippets and matched terms. BM25-inspired ranking weights entity pages higher.",
|
|
125
|
-
inputSchema: {
|
|
126
|
-
type: "object",
|
|
127
|
-
properties: {
|
|
128
|
-
project_path: { type: "string", description: "Root of the project." },
|
|
129
|
-
query: { type: "string", description: "Search query (e.g., 'MCP protocol design')." },
|
|
130
|
-
limit: {
|
|
131
|
-
type: "number",
|
|
132
|
-
description: "Optional: max results to return (default: 10).",
|
|
133
|
-
},
|
|
134
|
-
category: {
|
|
135
|
-
type: "string",
|
|
136
|
-
enum: ["entity", "concept", "timeline", "synthesis"],
|
|
137
|
-
description: "Optional: filter to a specific category.",
|
|
138
|
-
},
|
|
139
|
-
},
|
|
140
|
-
required: ["project_path", "query"],
|
|
141
|
-
},
|
|
142
|
-
},
|
|
143
|
-
{
|
|
144
|
-
name: "synthesize_answer",
|
|
145
|
-
description: "Generate a synthesis answer from multiple wiki pages. Extracts relevant sentences, key concepts, and builds a markdown answer with citations.",
|
|
146
|
-
inputSchema: {
|
|
147
|
-
type: "object",
|
|
148
|
-
properties: {
|
|
149
|
-
project_path: { type: "string", description: "Root of the project." },
|
|
150
|
-
query: { type: "string", description: "The question being answered." },
|
|
151
|
-
source_page_ids: {
|
|
152
|
-
type: "array",
|
|
153
|
-
items: { type: "string" },
|
|
154
|
-
description: "List of wiki page IDs to synthesize from.",
|
|
155
|
-
},
|
|
156
|
-
},
|
|
157
|
-
required: ["project_path", "query", "source_page_ids"],
|
|
158
|
-
},
|
|
159
|
-
},
|
|
160
|
-
{
|
|
161
|
-
name: "query_wiki",
|
|
162
|
-
description: "Ask a question against the wiki. Automatically searches for relevant pages, synthesizes an answer, and returns source pages with confidence score. One-stop tool for querying accumulated knowledge.",
|
|
163
|
-
inputSchema: {
|
|
164
|
-
type: "object",
|
|
165
|
-
properties: {
|
|
166
|
-
project_path: { type: "string", description: "Root of the project." },
|
|
167
|
-
question: { type: "string", description: "The question to ask (e.g., 'How does the MCP protocol work?')." },
|
|
168
|
-
max_sources: {
|
|
169
|
-
type: "number",
|
|
170
|
-
description: "Optional: max source pages to use in synthesis (default: 5).",
|
|
171
|
-
},
|
|
172
|
-
},
|
|
173
|
-
required: ["project_path", "question"],
|
|
174
|
-
},
|
|
175
|
-
},
|
|
176
|
-
{
|
|
177
|
-
name: "save_answer_as_page",
|
|
178
|
-
description: "Save a generated answer as a new wiki page. Allows query results to compound back into the knowledge base, growing the wiki with new synthesis pages. Automatically adds frontmatter and updates log.md.",
|
|
179
|
-
inputSchema: {
|
|
180
|
-
type: "object",
|
|
181
|
-
properties: {
|
|
182
|
-
project_path: { type: "string", description: "Root of the project." },
|
|
183
|
-
question: { type: "string", description: "The original question that was answered." },
|
|
184
|
-
answer: { type: "string", description: "The markdown answer text to save." },
|
|
185
|
-
page_title: { type: "string", description: "Title for the new page (e.g., 'How MCP Protocol Works')." },
|
|
186
|
-
category: {
|
|
187
|
-
type: "string",
|
|
188
|
-
enum: ["synthesis", "entity", "concept", "timeline"],
|
|
189
|
-
description: "Optional: wiki category for the page (default: synthesis).",
|
|
190
|
-
},
|
|
191
|
-
source_page_ids: {
|
|
192
|
-
type: "array",
|
|
193
|
-
items: { type: "string" },
|
|
194
|
-
description: "Optional: list of source wiki page IDs used to generate this answer.",
|
|
195
|
-
},
|
|
196
|
-
},
|
|
197
|
-
required: ["project_path", "question", "answer", "page_title"],
|
|
198
|
-
},
|
|
199
|
-
},
|
|
200
|
-
{
|
|
201
|
-
name: "lint_wiki",
|
|
202
|
-
description: "Health check wiki for quality issues: orphan pages, broken references, stale content, metadata gaps, and contradictions. Returns issues found, metrics, health score, and recommendations.",
|
|
203
|
-
inputSchema: {
|
|
204
|
-
type: "object",
|
|
205
|
-
properties: {
|
|
206
|
-
project_path: { type: "string", description: "Root of the project." },
|
|
207
|
-
check_type: {
|
|
208
|
-
type: "string",
|
|
209
|
-
enum: ["all", "orphans", "contradictions", "stale", "metadata"],
|
|
210
|
-
description: "Optional: type of check to run (default: all).",
|
|
211
|
-
},
|
|
212
|
-
},
|
|
213
|
-
required: ["project_path"],
|
|
214
|
-
},
|
|
215
|
-
},
|
|
216
|
-
{
|
|
217
|
-
name: "get_schema_guidance",
|
|
218
|
-
description: "Analyze what documents should exist based on project schema and current wiki. Removes decision fatigue by suggesting what to create next.",
|
|
219
|
-
inputSchema: {
|
|
220
|
-
type: "object",
|
|
221
|
-
properties: {
|
|
222
|
-
project_path: { type: "string", description: "Root of the project." },
|
|
223
|
-
domain: {
|
|
224
|
-
type: "string",
|
|
225
|
-
description: "Optional: domain hint (Code/Architecture, Research, Business, Personal). Auto-detected if not provided.",
|
|
226
|
-
},
|
|
227
|
-
},
|
|
228
|
-
required: ["project_path"],
|
|
229
|
-
},
|
|
230
|
-
},
|
|
231
|
-
{
|
|
232
|
-
name: "preview_generation",
|
|
233
|
-
description: "Preview what a tool will generate before running it. Removes black-box feeling by showing predicted actions, outputs, and impact.",
|
|
234
|
-
inputSchema: {
|
|
235
|
-
type: "object",
|
|
236
|
-
properties: {
|
|
237
|
-
tool_name: { type: "string", description: "Name of the tool to preview (e.g., ingest_source, query_wiki)." },
|
|
238
|
-
project_path: { type: "string", description: "Root of the project." },
|
|
239
|
-
params: { type: "object", description: "Parameters you would pass to that tool." },
|
|
240
|
-
},
|
|
241
|
-
required: ["tool_name", "project_path", "params"],
|
|
242
|
-
},
|
|
243
|
-
},
|
|
244
|
-
{
|
|
245
|
-
name: "generate_dependency_graph",
|
|
246
|
-
description: "Scan a project and build a dependency graph showing how modules import each other, which DB tables are shared, and which files are most connected. Returns nodes, edges, shared tables, and the top 10 most-connected modules. Use this to understand coupling and identify risky files to change.",
|
|
247
|
-
inputSchema: {
|
|
248
|
-
type: "object",
|
|
249
|
-
properties: {
|
|
250
|
-
project_path: { type: "string", description: "Root directory of the project to analyse." },
|
|
251
|
-
extensions: {
|
|
252
|
-
type: "array",
|
|
253
|
-
items: { type: "string" },
|
|
254
|
-
description: "Optional extension filter e.g. [\".ts\",\".go\"]. If omitted, all non-binary files are scanned.",
|
|
255
|
-
},
|
|
256
|
-
focus: {
|
|
257
|
-
type: "string",
|
|
258
|
-
description: "Optional: filter graph to neighbours of a specific file or module name (partial match, e.g. 'user-service').",
|
|
259
|
-
},
|
|
260
|
-
},
|
|
261
|
-
required: ["project_path"],
|
|
262
|
-
},
|
|
263
|
-
},
|
|
264
|
-
],
|
|
265
|
-
}));
|
|
266
|
-
server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
267
|
-
const { name, arguments: args } = request.params;
|
|
268
|
-
try {
|
|
269
|
-
let result;
|
|
270
|
-
if (name === "read_module") {
|
|
271
|
-
result = await (0, read_module_1.readModule)(args);
|
|
272
|
-
}
|
|
273
|
-
else if (name === "list_modules") {
|
|
274
|
-
result = await (0, list_modules_1.listModules)(args);
|
|
275
|
-
}
|
|
276
|
-
else if (name === "write_spec") {
|
|
277
|
-
result = await (0, write_spec_1.writeSpec)(args);
|
|
278
|
-
}
|
|
279
|
-
else if (name === "read_specs") {
|
|
280
|
-
result = await (0, read_specs_1.readSpecs)(args);
|
|
281
|
-
}
|
|
282
|
-
else if (name === "ingest_source") {
|
|
283
|
-
result = await (0, ingest_source_1.ingestSource)(args);
|
|
284
|
-
}
|
|
285
|
-
else if (name === "update_index") {
|
|
286
|
-
result = await (0, update_index_1.updateIndex)(args);
|
|
287
|
-
}
|
|
288
|
-
else if (name === "list_wiki") {
|
|
289
|
-
result = await (0, list_wiki_1.listWiki)(args);
|
|
290
|
-
}
|
|
291
|
-
else if (name === "wiki_search") {
|
|
292
|
-
result = await (0, wiki_search_1.wikiSearch)(args);
|
|
293
|
-
}
|
|
294
|
-
else if (name === "synthesize_answer") {
|
|
295
|
-
result = await (0, answer_synthesis_1.synthesizeAnswer)(args);
|
|
296
|
-
}
|
|
297
|
-
else if (name === "query_wiki") {
|
|
298
|
-
result = await (0, query_wiki_1.queryWiki)(args);
|
|
299
|
-
}
|
|
300
|
-
else if (name === "save_answer_as_page") {
|
|
301
|
-
result = await (0, save_answer_as_page_1.saveAnswerAsPage)(args);
|
|
302
|
-
}
|
|
303
|
-
else if (name === "lint_wiki") {
|
|
304
|
-
result = await (0, lint_wiki_1.lintWiki)(args);
|
|
305
|
-
}
|
|
306
|
-
else if (name === "get_schema_guidance") {
|
|
307
|
-
result = await (0, get_schema_guidance_1.getSchemataGuidance)(args);
|
|
308
|
-
}
|
|
309
|
-
else if (name === "preview_generation") {
|
|
310
|
-
result = await (0, preview_generation_1.previewGeneration)(args);
|
|
311
|
-
}
|
|
312
|
-
else if (name === "generate_dependency_graph") {
|
|
313
|
-
result = await (0, generate_dependency_graph_1.generateDependencyGraph)(args);
|
|
314
|
-
}
|
|
315
|
-
else {
|
|
316
|
-
result = { error: `Unknown tool: ${name}` };
|
|
317
|
-
}
|
|
318
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
319
|
-
}
|
|
320
|
-
catch (e) {
|
|
321
|
-
return { content: [{ type: "text", text: JSON.stringify({ error: e?.message ?? String(e) }) }] };
|
|
322
|
-
}
|
|
323
|
-
});
|
|
324
|
-
async function main() {
|
|
325
|
-
const transport = new stdio_js_1.StdioServerTransport();
|
|
326
|
-
await server.connect(transport);
|
|
327
|
-
}
|
|
328
|
-
main().catch((e) => {
|
|
329
|
-
process.stderr.write(`DocuFlow MCP fatal error: ${e?.message ?? e}\n`);
|
|
330
|
-
process.exit(1);
|
|
331
|
-
});
|
|
4
|
+
// @doquflow/server v2.0 is a back-compat alias of @doquflow/studio.
|
|
5
|
+
// New installs should use @doquflow/studio (full surface) or
|
|
6
|
+
// @doquflow/core (4-tool minimal). See release/v2.0.0.md.
|
|
7
|
+
//
|
|
8
|
+
// This entry re-uses studio's MCP server binary.
|
|
9
|
+
require("@doquflow/studio/mcp");
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@doquflow/server",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "DEPRECATED ALIAS: this package now re-exports @doquflow/studio. New installs should use @doquflow/studio or @doquflow/core. See https://github.com/doquflows/docuflow/blob/main/MIGRATION.md.",
|
|
5
5
|
"author": "Docuflow <hello@doquflows.dev>",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"homepage": "https://github.com/doquflows/docuflow",
|
|
@@ -28,15 +28,13 @@
|
|
|
28
28
|
"README.md"
|
|
29
29
|
],
|
|
30
30
|
"scripts": {
|
|
31
|
-
"build": "tsc"
|
|
32
|
-
"test": "vitest run"
|
|
31
|
+
"build": "tsc"
|
|
33
32
|
},
|
|
34
33
|
"dependencies": {
|
|
35
|
-
"@
|
|
34
|
+
"@doquflow/studio": "2.0.0"
|
|
36
35
|
},
|
|
37
36
|
"devDependencies": {
|
|
38
37
|
"@types/node": "^22.0.0",
|
|
39
|
-
"typescript": "^5.6.0"
|
|
40
|
-
"vitest": "^4.1.6"
|
|
38
|
+
"typescript": "^5.6.0"
|
|
41
39
|
}
|
|
42
40
|
}
|
package/dist/category-dir.js
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.categoryDir = categoryDir;
|
|
4
|
-
const CATEGORY_PLURAL = {
|
|
5
|
-
entity: "entities",
|
|
6
|
-
concept: "concepts",
|
|
7
|
-
timeline: "timelines",
|
|
8
|
-
synthesis: "syntheses",
|
|
9
|
-
};
|
|
10
|
-
function categoryDir(category) {
|
|
11
|
-
return CATEGORY_PLURAL[category];
|
|
12
|
-
}
|
package/dist/extractor-rules.js
DELETED
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.passesEntityRules = passesEntityRules;
|
|
4
|
-
const extractor_stoplist_1 = require("./extractor-stoplist");
|
|
5
|
-
/**
|
|
6
|
-
* Rule 1 — Stop-list rejection (fast path)
|
|
7
|
-
* Candidate is rejected when:
|
|
8
|
-
* (a) the whole normalized phrase is on the stop-list, or
|
|
9
|
-
* (b) it is a single word on the stop-list, or
|
|
10
|
-
* (c) it is a multi-word phrase whose words are *all* on the stop-list
|
|
11
|
-
* (e.g. "key components" — both "key" and "components" are generic).
|
|
12
|
-
*/
|
|
13
|
-
function ruleStopList(c) {
|
|
14
|
-
const normalized = c.name
|
|
15
|
-
.trim()
|
|
16
|
-
.toLowerCase()
|
|
17
|
-
.replace(/[^a-z0-9 ]/g, " ")
|
|
18
|
-
.replace(/\s+/g, " ")
|
|
19
|
-
.trim();
|
|
20
|
-
if (!normalized)
|
|
21
|
-
return { ok: true };
|
|
22
|
-
if (extractor_stoplist_1.ENTITY_STOPLIST.has(normalized)) {
|
|
23
|
-
return { ok: false, reason: `"${c.name}" is a generic stop-list term` };
|
|
24
|
-
}
|
|
25
|
-
const words = normalized.split(/\s+/).filter(Boolean);
|
|
26
|
-
if (words.length === 1 && extractor_stoplist_1.ENTITY_STOPLIST.has(words[0])) {
|
|
27
|
-
return { ok: false, reason: `"${c.name}" is a generic stop-list term` };
|
|
28
|
-
}
|
|
29
|
-
if (words.length > 1 && words.every((w) => extractor_stoplist_1.ENTITY_STOPLIST.has(w))) {
|
|
30
|
-
return { ok: false, reason: `"${c.name}" contains only generic stop-list terms` };
|
|
31
|
-
}
|
|
32
|
-
return { ok: true };
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* Rule 2 — No emoji-only or punctuation-only slugs (fast path)
|
|
36
|
-
* Strip emoji; reject if resulting slug is empty or only underscores/dashes.
|
|
37
|
-
*/
|
|
38
|
-
function ruleNoEmojiOrPunctSlug(c) {
|
|
39
|
-
const stripped = c.name
|
|
40
|
-
.replace(/[\p{Emoji}\p{Emoji_Modifier}\p{Emoji_Component}]/gu, "")
|
|
41
|
-
.replace(/[^a-z0-9]/gi, "_")
|
|
42
|
-
.toLowerCase();
|
|
43
|
-
if (!stripped || /^[_\-]+$/.test(stripped)) {
|
|
44
|
-
return { ok: false, reason: `"${c.name}" produces an empty or punctuation-only slug` };
|
|
45
|
-
}
|
|
46
|
-
return { ok: true };
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* Rule 3 — Structural anchor
|
|
50
|
-
* Bold-text candidates are only accepted when they sit inside a meaningful
|
|
51
|
-
* structural paragraph (not a bare bullet point with no prose).
|
|
52
|
-
* Heading candidates always pass this rule.
|
|
53
|
-
*/
|
|
54
|
-
function ruleStructuralAnchor(c) {
|
|
55
|
-
if (c.source === "heading")
|
|
56
|
-
return { ok: true };
|
|
57
|
-
const stripped = c.context.replace(/^\s*[-*+]\s+/, "").trim();
|
|
58
|
-
const wordCount = stripped.split(/\s+/).filter(Boolean).length;
|
|
59
|
-
if (wordCount < 4) {
|
|
60
|
-
return { ok: false, reason: "bold text in bare bullet with no sentence context" };
|
|
61
|
-
}
|
|
62
|
-
return { ok: true };
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* Rule 4 — Minimum token signal
|
|
66
|
-
* Must be ≥2 words, OR ≥1 word with a non-sentence-start capital (camelCase/PascalCase),
|
|
67
|
-
* OR contain a code-like separator (_, -, (), ::, .).
|
|
68
|
-
* Heading candidates are exempt — structural position grants authority.
|
|
69
|
-
*/
|
|
70
|
-
function ruleMinimumTokenSignal(c) {
|
|
71
|
-
if (c.source === "heading")
|
|
72
|
-
return { ok: true };
|
|
73
|
-
const name = c.name.trim();
|
|
74
|
-
const words = name.split(/\s+/).filter(Boolean);
|
|
75
|
-
if (words.length >= 2)
|
|
76
|
-
return { ok: true };
|
|
77
|
-
const word = words[0] ?? "";
|
|
78
|
-
const hasInternalCap = /(?<!^)[A-Z]/.test(word);
|
|
79
|
-
const hasCodeSeparator = /[_\-().::]/.test(word);
|
|
80
|
-
if (hasInternalCap || hasCodeSeparator)
|
|
81
|
-
return { ok: true };
|
|
82
|
-
return { ok: false, reason: "single generic word with no distinguishing signal" };
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* Rule 5 — Context requirement
|
|
86
|
-
* Bold text must have at least 1 sentence of real context (≥6 words).
|
|
87
|
-
* Heading candidates are exempt.
|
|
88
|
-
*/
|
|
89
|
-
function ruleContextRequirement(c) {
|
|
90
|
-
if (c.source === "heading")
|
|
91
|
-
return { ok: true };
|
|
92
|
-
const sentences = c.context.split(/[.!?]+/).filter((s) => s.trim().split(/\s+/).length >= 6);
|
|
93
|
-
if (sentences.length === 0) {
|
|
94
|
-
return { ok: false, reason: "no surrounding sentence context (≥6 words) found" };
|
|
95
|
-
}
|
|
96
|
-
return { ok: true };
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* Rule 6 — Section-heading noise patterns
|
|
100
|
-
* Reject candidates whose surface form matches well-known structural noise:
|
|
101
|
-
* numbered list items ("1. Foo"), file references ("foo.md"), question-form
|
|
102
|
-
* headings ("What is X"), preposition-led phrases ("For X"), layer/phase
|
|
103
|
-
* markers ("Layer 1"), and full sentences captured as entities ("X is a Y").
|
|
104
|
-
*/
|
|
105
|
-
function ruleSectionHeadingNoise(c) {
|
|
106
|
-
const name = c.name.trim();
|
|
107
|
-
// Numbered list items: "1. Foo", "2) Bar"
|
|
108
|
-
if (/^\d+[.)]\s/.test(name)) {
|
|
109
|
-
return { ok: false, reason: `"${name}" is a numbered list item, not an entity` };
|
|
110
|
-
}
|
|
111
|
-
// File references masquerading as entities
|
|
112
|
-
if (/\.(md|ts|tsx|js|jsx|json|ya?ml|css|scss|sh|py|go|rb)$/i.test(name)) {
|
|
113
|
-
return { ok: false, reason: `"${name}" looks like a file reference, not an entity` };
|
|
114
|
-
}
|
|
115
|
-
// Question-form headings
|
|
116
|
-
if (/^(what|how|why|where|when|who|which)\s/i.test(name)) {
|
|
117
|
-
return { ok: false, reason: `"${name}" is a question-form section heading` };
|
|
118
|
-
}
|
|
119
|
-
// Preposition-led phrases (common bullet-list openings)
|
|
120
|
-
if (/^(for|with|by|to|in|on|at|of|from|about)\s/i.test(name)) {
|
|
121
|
-
return { ok: false, reason: `"${name}" starts with a preposition (section-heading form)` };
|
|
122
|
-
}
|
|
123
|
-
// Layer / phase / step markers
|
|
124
|
-
if (/^(layer|phase|step|chapter|part|section)\s+\d/i.test(name)) {
|
|
125
|
-
return { ok: false, reason: `"${name}" is a layer/phase marker, not an entity` };
|
|
126
|
-
}
|
|
127
|
-
// Sentence-form: "X is a Y", "X is not a Y" — full sentences captured as entities
|
|
128
|
-
if (/\bis\s+(not\s+)?(a|an|the)\s+/i.test(name)) {
|
|
129
|
-
return { ok: false, reason: `"${name}" looks like a sentence, not an entity name` };
|
|
130
|
-
}
|
|
131
|
-
// Starts with emoji-as-decoration (e.g. "❌ Anti-pattern", "🔧 Storage", "✅ Allowed").
|
|
132
|
-
// These are section dividers / status markers, not entity names.
|
|
133
|
-
if (/^[\p{Emoji_Presentation}\p{Extended_Pictographic}]/u.test(name)) {
|
|
134
|
-
return { ok: false, reason: `"${name}" starts with an emoji (heading decoration, not an entity)` };
|
|
135
|
-
}
|
|
136
|
-
// "The X" pattern — descriptive references, not entity names
|
|
137
|
-
if (/^the\s+/i.test(name)) {
|
|
138
|
-
return { ok: false, reason: `"${name}" starts with "the " (descriptive reference, not an entity)` };
|
|
139
|
-
}
|
|
140
|
-
// Date strings captured as entities ("Date: 2026-04-27", "2026-04-27")
|
|
141
|
-
if (/\b\d{4}[-/_]\d{1,2}[-/_]\d{1,2}\b/.test(name)) {
|
|
142
|
-
return { ok: false, reason: `"${name}" contains a date (metadata, not an entity)` };
|
|
143
|
-
}
|
|
144
|
-
return { ok: true };
|
|
145
|
-
}
|
|
146
|
-
const RULES = [
|
|
147
|
-
ruleStopList,
|
|
148
|
-
ruleNoEmojiOrPunctSlug,
|
|
149
|
-
ruleSectionHeadingNoise,
|
|
150
|
-
ruleStructuralAnchor,
|
|
151
|
-
ruleMinimumTokenSignal,
|
|
152
|
-
ruleContextRequirement,
|
|
153
|
-
];
|
|
154
|
-
/**
|
|
155
|
-
* Run all entity quality rules against a candidate.
|
|
156
|
-
* Returns { ok: true } if the candidate passes all rules,
|
|
157
|
-
* or { ok: false, reason } for the first rule that rejects it.
|
|
158
|
-
*/
|
|
159
|
-
function passesEntityRules(candidate) {
|
|
160
|
-
for (const rule of RULES) {
|
|
161
|
-
const result = rule(candidate);
|
|
162
|
-
if (!result.ok)
|
|
163
|
-
return result;
|
|
164
|
-
}
|
|
165
|
-
return { ok: true };
|
|
166
|
-
}
|