@doquflow/cli 1.5.2 → 1.7.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.
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ // Help text for `docuflow --help` and `docuflow advanced --help`.
3
+ // No external help library — plain console.log for zero-dependency output.
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.printCoreHelp = printCoreHelp;
6
+ exports.printAdvancedHelp = printAdvancedHelp;
7
+ function printCoreHelp() {
8
+ console.log('DocuFlow — preserve decision context for AI agents.');
9
+ console.log('');
10
+ console.log('CORE');
11
+ console.log(' docuflow init Initialise .docuflow/ in this project');
12
+ console.log(' docuflow ingest <file> Add a source document to the wiki');
13
+ console.log(' docuflow query "<question>" Ask the wiki — answer with citations');
14
+ console.log(' docuflow status Show wiki health and counts');
15
+ console.log(' docuflow rewiki Migrate / re-ingest with current rules');
16
+ console.log('');
17
+ console.log('ADVANCED');
18
+ console.log(' docuflow advanced --help See watch / sync / ui / review / recent / suggest / update');
19
+ console.log('');
20
+ console.log(' docuflow --version Print version');
21
+ console.log(' docuflow --help Show this help');
22
+ }
23
+ function printAdvancedHelp() {
24
+ console.log('DocuFlow — advanced surface (sync daemons, UI, audit commands).');
25
+ console.log('');
26
+ console.log(' docuflow advanced watch [stop|status|restart] Auto-sync daemon');
27
+ console.log(' docuflow advanced sync [--ai] One-shot sync for CI / git hooks');
28
+ console.log(' docuflow advanced ui [--port N] Launch web UI dashboard');
29
+ console.log(' docuflow advanced start Alias for ui');
30
+ console.log(' docuflow advanced review Review uncommitted changes');
31
+ console.log(' docuflow advanced recent Recent work dashboard');
32
+ console.log(' docuflow advanced suggest First-steps guidance');
33
+ console.log(' docuflow advanced update Self-upgrade DocuFlow');
34
+ console.log('');
35
+ console.log('Note: every "advanced" command also works at its old top-level path');
36
+ console.log('(e.g. `docuflow watch` is still valid). The "advanced" prefix is optional.');
37
+ }
@@ -0,0 +1,115 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.run = run;
7
+ const node_fs_1 = __importDefault(require("node:fs"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const c = {
10
+ green: (s) => `\x1b[32m${s}\x1b[0m`,
11
+ yellow: (s) => `\x1b[33m${s}\x1b[0m`,
12
+ red: (s) => `\x1b[31m${s}\x1b[0m`,
13
+ cyan: (s) => `\x1b[36m${s}\x1b[0m`,
14
+ dim: (s) => `\x1b[2m${s}\x1b[0m`,
15
+ bold: (s) => `\x1b[1m${s}\x1b[0m`,
16
+ };
17
+ function loadServerTool(toolFile) {
18
+ const candidates = [
19
+ () => require(`@doquflow/server/dist/tools/${toolFile}`),
20
+ () => require(node_path_1.default.resolve(__dirname, "../../../server/dist/tools", toolFile)),
21
+ () => require(node_path_1.default.resolve(__dirname, "../../server/dist/tools", toolFile)),
22
+ ];
23
+ for (const attempt of candidates) {
24
+ try {
25
+ return attempt();
26
+ }
27
+ catch { }
28
+ }
29
+ throw new Error(`Cannot load server tool "${toolFile}". Run "npm run build" first.`);
30
+ }
31
+ async function run(options) {
32
+ const { sourceFile, all = false, dryRun = false, quiet = false } = options;
33
+ const projectPath = node_path_1.default.resolve(process.cwd());
34
+ const docuDir = node_path_1.default.join(projectPath, ".docuflow");
35
+ const sourcesDir = node_path_1.default.join(docuDir, "sources");
36
+ function info(msg) { if (!quiet)
37
+ process.stdout.write(msg + "\n"); }
38
+ if (!all && !sourceFile) {
39
+ console.error(c.red(" ✗ Provide a source filename or pass --all."));
40
+ console.error(` Usage: docuflow ingest <source.md>`);
41
+ console.error(` docuflow ingest --all`);
42
+ process.exit(2);
43
+ }
44
+ if (!node_fs_1.default.existsSync(docuDir)) {
45
+ console.error(c.red(` ✗ .docuflow/ not found at ${projectPath}`));
46
+ console.error(` Run "docuflow init" first.`);
47
+ process.exit(2);
48
+ }
49
+ if (dryRun) {
50
+ info(c.yellow(" dry-run not supported for ingest_source; use `docuflow rewiki --dry-run` for a full simulation"));
51
+ process.exit(0);
52
+ }
53
+ let ingestSource;
54
+ try {
55
+ ({ ingestSource } = loadServerTool("ingest-source"));
56
+ }
57
+ catch (e) {
58
+ console.error(c.red(` ✗ ${e.message}`));
59
+ process.exit(2);
60
+ }
61
+ // Collect files to ingest
62
+ let files;
63
+ if (all) {
64
+ if (!node_fs_1.default.existsSync(sourcesDir)) {
65
+ console.error(c.yellow(" No .docuflow/sources/ directory found — nothing to ingest."));
66
+ process.exit(0);
67
+ }
68
+ files = node_fs_1.default.readdirSync(sourcesDir).filter(f => f.endsWith(".md"));
69
+ if (files.length === 0) {
70
+ info(c.yellow(" No source files found in .docuflow/sources/"));
71
+ process.exit(0);
72
+ }
73
+ }
74
+ else {
75
+ // Single file — verify it exists
76
+ const filename = sourceFile;
77
+ const fullPath = node_path_1.default.join(sourcesDir, filename);
78
+ if (!node_fs_1.default.existsSync(fullPath)) {
79
+ console.error(c.red(` ✗ Source file not found: .docuflow/sources/${filename}`));
80
+ process.exit(3);
81
+ }
82
+ files = [filename];
83
+ }
84
+ info(c.bold(`DocuFlow ingest`));
85
+ info(` Project : ${projectPath}`);
86
+ info(` Files : ${files.length}`);
87
+ info("");
88
+ let totalCreated = 0;
89
+ let totalUpdated = 0;
90
+ let errored = 0;
91
+ for (const filename of files) {
92
+ info(c.dim(` Ingesting ${filename}…`));
93
+ try {
94
+ const result = await ingestSource({ project_path: projectPath, source_filename: filename });
95
+ totalCreated += result.pages_created ?? 0;
96
+ totalUpdated += result.pages_updated ?? 0;
97
+ info(` ${c.green("✓")} ${filename}` +
98
+ ` ${c.dim(`+${result.pages_created ?? 0} created, ~${result.pages_updated ?? 0} updated`)}`);
99
+ }
100
+ catch (e) {
101
+ errored++;
102
+ console.error(c.red(` ✗ ${filename}: ${e.message}`));
103
+ }
104
+ }
105
+ info("");
106
+ if (errored === 0) {
107
+ info(c.green(" ✓ Done.") +
108
+ ` Pages created: ${totalCreated} updated: ${totalUpdated}`);
109
+ }
110
+ else {
111
+ info(c.yellow(` Done with ${errored} error(s).`) +
112
+ ` Pages created: ${totalCreated} updated: ${totalUpdated}`);
113
+ process.exit(2);
114
+ }
115
+ }
@@ -101,59 +101,56 @@ async function copyTemplateFile(templateName, destPath) {
101
101
  }
102
102
  }
103
103
  function buildClaudeMd(projectDir) {
104
- return `# DocuFlow AI Documentation Assistant
105
-
106
- DocuFlow is an MCP server that gives you structured access to this codebase and maintains a living wiki.
107
- It is registered in your Claude Desktop config and available as MCP tools in every session.
108
-
109
- ## Codebase Scanner Tools
110
-
111
- - **read_module** — Analyse a single source file. Returns language, classes, functions, dependencies, DB tables, endpoints, config refs, and raw content (first 8 KB).
112
- - Example: \`read_module({ path: "src/UserService.cs" })\`
113
- - **list_modules** — Walk a directory and extract facts for every non-binary file. Use this to understand the full project in one call.
114
- - Example: \`list_modules({ path: "${projectDir}" })\`
115
- - **write_spec** — Persist a markdown spec to \`.docuflow/specs/<filename>.md\` and update the index.
116
- - Example: \`write_spec({ project_path: "${projectDir}", filename: "UserService", content: "# UserService\\n..." })\`
117
- - **read_specs** — Read previously written specs, optionally filtered by name.
118
- - Example: \`read_specs({ project_path: "${projectDir}" })\`
104
+ return `<!-- BEGIN DOCUFLOW -->
105
+ # DocuFlow — AI Documentation Assistant
119
106
 
120
- ## Wiki Pipeline Tools
107
+ DocuFlow preserves decision context for AI agents. Intent in, value out.
121
108
 
122
- - **ingest_source** Ingest a markdown file from \`.docuflow/sources/\` and generate wiki pages (entities, concepts).
123
- - **update_index** — Rebuild \`.docuflow/index.md\` from all wiki pages.
124
- - **list_wiki** — List all wiki pages, optionally filtered by category (entity/concept/timeline/synthesis).
125
- - **wiki_search** — BM25 search across all wiki pages. Returns ranked results with previews.
126
- - **query_wiki** — One-stop Q&A: searches wiki, synthesises an answer, returns source citations.
127
- - **synthesize_answer** — Generate a markdown synthesis from a list of specific wiki page IDs.
128
- - **save_answer_as_page** — Persist a synthesised answer back into the wiki (knowledge compounding).
129
-
130
- ## Health & Guidance Tools
109
+ ## Core tools (use these first)
131
110
 
132
- - **lint_wiki** — Health check: orphan pages, broken refs, stale content, metadata gaps. Returns a 0–100 health score.
133
- - **get_schema_guidance** Analyse what wiki pages should exist based on the schema and current state.
134
- - **preview_generation** Preview what a tool will do before running it.
111
+ - **query_wiki({ project_path, question })** Ask the wiki. Returns an answer with citations.
112
+ - **ingest_source({ project_path, source_filename })** Fold a markdown source into the wiki.
113
+ - **wiki_search({ project_path, query })** BM25 search across all pages.
114
+ - **read_module({ path })** — Read and extract facts from a single source file.
135
115
 
136
- ## Common Workflows
116
+ ## CLI — Core Commands
137
117
 
138
- ### First time — understand the codebase
139
118
  \`\`\`
140
- list_modules({ path: "${projectDir}" })
141
- read the language breakdown and dependency map
142
- write_spec each important module
119
+ docuflow query "<question>" # ask the wiki from the shell
120
+ docuflow ingest <source.md> # add a source doc to the wiki
121
+ docuflow status # wiki health and counts
122
+ docuflow rewiki # re-ingest with current rules
123
+ docuflow init # initialise .docuflow/ in this project
143
124
  \`\`\`
144
125
 
145
- ### Ongoing — answer a question
126
+ ## Workflows
127
+
128
+ ### Answer a question
146
129
  \`\`\`
147
130
  query_wiki({ project_path: "${projectDir}", question: "How does authentication work?" })
148
- → save_answer_as_page if the answer is worth keeping
149
131
  \`\`\`
150
132
 
151
- ### Maintenance check wiki health
133
+ ### Add new context
152
134
  \`\`\`
153
- lint_wiki({ project_path: "${projectDir}" })
154
- fix orphans and broken refs
135
+ # drop a markdown file in .docuflow/sources/
136
+ ingest_source({ project_path: "${projectDir}", source_filename: "auth-design.md" })
155
137
  \`\`\`
156
138
 
139
+ ## Advanced tools
140
+
141
+ Use when the core tools don't cover the workflow. Each has more parameters and side effects.
142
+
143
+ - **list_modules** — Walk a directory tree and extract facts in bulk
144
+ - **list_wiki** — Inventory pages by category, with staleness flags
145
+ - **write_spec / read_specs** — Persistent agent-written specs
146
+ - **save_answer_as_page** — Promote a synthesised answer into the wiki
147
+ - **synthesize_answer** — Combine multiple pages into a markdown synthesis
148
+ - **update_index** — Rebuild \`.docuflow/index.md\`
149
+ - **lint_wiki** — Health checks: orphans, broken refs, stale content
150
+ - **get_schema_guidance** — Recommend what pages should exist
151
+ - **preview_generation** — Show what a tool will do before running
152
+ - **generate_dependency_graph** — Build the import/shared-table graph
153
+
157
154
  ## Storage Layout
158
155
 
159
156
  \`\`\`
@@ -169,20 +166,25 @@ lint_wiki({ project_path: "${projectDir}" })
169
166
  ├── index.md Auto-maintained catalog
170
167
  └── log.md Operation log
171
168
  \`\`\`
172
- `;
169
+ <!-- END DOCUFLOW -->`;
173
170
  }
174
171
  async function writeClaudeMd(projectDir) {
175
172
  const claudeMdPath = node_path_1.default.join(projectDir, "CLAUDE.md");
176
173
  const newSection = buildClaudeMd(projectDir);
177
174
  if (node_fs_1.default.existsSync(claudeMdPath)) {
178
175
  const existing = await promises_1.default.readFile(claudeMdPath, "utf8");
179
- if (existing.includes("DocuFlow")) {
180
- // Already has DocuFlow section replace it
176
+ if (existing.includes("<!-- BEGIN DOCUFLOW -->") && existing.includes("<!-- END DOCUFLOW -->")) {
177
+ // Marker-based replacement idempotent re-runs preserve surrounding content
178
+ const replaced = existing.replace(/<!-- BEGIN DOCUFLOW -->[\s\S]*?<!-- END DOCUFLOW -->/, newSection.trimEnd());
179
+ await promises_1.default.writeFile(claudeMdPath, replaced, "utf8");
180
+ }
181
+ else if (existing.includes("DocuFlow")) {
182
+ // Old format without markers — replace old DocuFlow section, add markers this time
181
183
  const withoutDocuflow = existing.replace(/\n?# DocuFlow[\s\S]*/, "").trimEnd();
182
184
  await promises_1.default.writeFile(claudeMdPath, withoutDocuflow + "\n\n" + newSection, "utf8");
183
185
  }
184
186
  else {
185
- // Append to existing CLAUDE.md
187
+ // No DocuFlow section yet — append
186
188
  await promises_1.default.appendFile(claudeMdPath, "\n\n" + newSection, "utf8");
187
189
  }
188
190
  }
@@ -0,0 +1,141 @@
1
+ "use strict";
2
+ /**
3
+ * docuflow query
4
+ *
5
+ * Calls query_wiki and renders the answer with citations.
6
+ *
7
+ * Usage:
8
+ * docuflow query "<question>"
9
+ * docuflow query "<question>" --max-sources 5
10
+ * docuflow query "<question>" --json
11
+ * docuflow query "<question>" --no-cite
12
+ * docuflow query "<question>" --save-as "<title>"
13
+ * docuflow query "<question>" --quiet
14
+ *
15
+ * Exit codes:
16
+ * 0 — answer produced
17
+ * 2 — fatal (.docuflow missing, server tool not found)
18
+ * 3 — query produced no matching sources
19
+ */
20
+ var __importDefault = (this && this.__importDefault) || function (mod) {
21
+ return (mod && mod.__esModule) ? mod : { "default": mod };
22
+ };
23
+ Object.defineProperty(exports, "__esModule", { value: true });
24
+ exports.run = run;
25
+ const node_fs_1 = __importDefault(require("node:fs"));
26
+ const node_path_1 = __importDefault(require("node:path"));
27
+ // ─── Colour helpers ────────────────────────────────────────────────────────────
28
+ const c = {
29
+ green: (s) => `\x1b[32m${s}\x1b[0m`,
30
+ yellow: (s) => `\x1b[33m${s}\x1b[0m`,
31
+ red: (s) => `\x1b[31m${s}\x1b[0m`,
32
+ cyan: (s) => `\x1b[36m${s}\x1b[0m`,
33
+ dim: (s) => `\x1b[2m${s}\x1b[0m`,
34
+ bold: (s) => `\x1b[1m${s}\x1b[0m`,
35
+ };
36
+ // ─── Dynamic server tool loader (mirrors rewiki.ts pattern) ───────────────────
37
+ function loadServerTool(toolFile) {
38
+ const candidates = [
39
+ () => require(`@doquflow/server/dist/tools/${toolFile}`),
40
+ () => require(node_path_1.default.resolve(__dirname, "../../../server/dist/tools", toolFile)),
41
+ () => require(node_path_1.default.resolve(__dirname, "../../server/dist/tools", toolFile)),
42
+ ];
43
+ for (const attempt of candidates) {
44
+ try {
45
+ return attempt();
46
+ }
47
+ catch { }
48
+ }
49
+ throw new Error(`Cannot load server tool "${toolFile}". Run "npm run build" first.`);
50
+ }
51
+ async function run(options) {
52
+ const { question, maxSources = 5, json = false, noCite = false, saveAs, quiet = false } = options;
53
+ const projectPath = node_path_1.default.resolve(process.cwd());
54
+ const docuDir = node_path_1.default.join(projectPath, ".docuflow");
55
+ function info(msg) { if (!quiet)
56
+ console.log(msg); }
57
+ // ── Validate question ───────────────────────────────────────────────────────
58
+ if (!question || question.trim() === "") {
59
+ console.error(c.red(" ✗ Question is required."));
60
+ console.error(` Usage: docuflow query "<question>"`);
61
+ process.exit(2);
62
+ }
63
+ // ── Pre-flight ──────────────────────────────────────────────────────────────
64
+ if (!node_fs_1.default.existsSync(docuDir)) {
65
+ console.error(c.red(` ✗ .docuflow/ not found at ${projectPath}`));
66
+ console.error(` Run "docuflow init" first.`);
67
+ process.exit(2);
68
+ }
69
+ // ── Load tools ──────────────────────────────────────────────────────────────
70
+ let queryWiki;
71
+ let saveAnswerAsPage;
72
+ try {
73
+ ({ queryWiki } = loadServerTool("query-wiki"));
74
+ if (saveAs) {
75
+ ({ saveAnswerAsPage } = loadServerTool("save-answer-as-page"));
76
+ }
77
+ }
78
+ catch (e) {
79
+ console.error(c.red(` ✗ ${e.message}`));
80
+ process.exit(2);
81
+ }
82
+ // ── Execute query ───────────────────────────────────────────────────────────
83
+ info(c.dim(` Searching wiki for: "${question}"`));
84
+ let result;
85
+ try {
86
+ result = await queryWiki({ project_path: projectPath, question, max_sources: maxSources });
87
+ }
88
+ catch (e) {
89
+ console.error(c.red(` ✗ Query failed: ${e.message}`));
90
+ process.exit(2);
91
+ }
92
+ if (result.error) {
93
+ console.error(c.red(` ✗ ${result.error}`));
94
+ process.exit(2);
95
+ }
96
+ // ── No sources found ────────────────────────────────────────────────────────
97
+ if (!result.source_pages || result.source_pages.length === 0) {
98
+ if (!json) {
99
+ console.error(c.yellow(" No matching sources found in the wiki."));
100
+ console.error(` Try running "docuflow rewiki" to rebuild the wiki first.`);
101
+ }
102
+ process.exit(3);
103
+ }
104
+ // ── Output ──────────────────────────────────────────────────────────────────
105
+ if (json) {
106
+ console.log(JSON.stringify({
107
+ question: result.question,
108
+ answer: result.answer,
109
+ source_pages: result.source_pages,
110
+ search_results: result.search_results,
111
+ confidence: result.confidence,
112
+ }, null, 2));
113
+ }
114
+ else if (noCite) {
115
+ console.log(result.answer);
116
+ }
117
+ else {
118
+ console.log(result.answer);
119
+ console.log("");
120
+ console.log(c.bold("## Sources"));
121
+ for (const page of result.source_pages) {
122
+ console.log(` - ${page.title} ${c.dim("(" + page.page_id + ")")}`);
123
+ }
124
+ }
125
+ // ── Save ────────────────────────────────────────────────────────────────────
126
+ if (saveAs && saveAnswerAsPage) {
127
+ try {
128
+ const saved = await saveAnswerAsPage({
129
+ project_path: projectPath,
130
+ question,
131
+ answer: result.answer,
132
+ page_title: saveAs,
133
+ source_page_ids: (result.source_pages ?? []).map((p) => p.page_id),
134
+ });
135
+ process.stderr.write(`page-id: ${saved?.page_id ?? saveAs}\n`);
136
+ }
137
+ catch (e) {
138
+ process.stderr.write(c.red(`Warning: could not save page: ${e.message}\n`));
139
+ }
140
+ }
141
+ }