@doquflow/cli 1.6.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/dist/commands/doctor.js +270 -0
- package/dist/commands/help.js +38 -0
- package/dist/commands/ingest.js +118 -0
- package/dist/commands/init.js +46 -50
- package/dist/commands/query.js +144 -0
- package/dist/commands/recent.js +72 -205
- package/dist/commands/rewiki.js +6 -3
- package/dist/commands/sync.js +10 -5
- package/dist/commands/ui.js +1 -1
- package/dist/commands/update.js +1 -1
- package/dist/commands/watch.js +8 -5
- package/dist/index.js +154 -153
- package/package.json +4 -3
- package/ui-dist/assets/index-BMnRdqwa.js +44 -0
- package/ui-dist/assets/index-Cnq2PhDd.js +44 -0
- package/ui-dist/index.html +1 -1
|
@@ -0,0 +1,144 @@
|
|
|
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/core/dist/tools/${toolFile}`),
|
|
40
|
+
() => require(node_path_1.default.resolve(__dirname, "../../../core/dist/tools", toolFile)),
|
|
41
|
+
() => require(node_path_1.default.resolve(__dirname, "../../core/dist/tools", toolFile)),
|
|
42
|
+
() => require(`@doquflow/studio/dist/tools/${toolFile}`),
|
|
43
|
+
() => require(node_path_1.default.resolve(__dirname, "../../../studio/dist/tools", toolFile)),
|
|
44
|
+
() => require(node_path_1.default.resolve(__dirname, "../../studio/dist/tools", toolFile)),
|
|
45
|
+
];
|
|
46
|
+
for (const attempt of candidates) {
|
|
47
|
+
try {
|
|
48
|
+
return attempt();
|
|
49
|
+
}
|
|
50
|
+
catch { }
|
|
51
|
+
}
|
|
52
|
+
throw new Error(`Cannot load server tool "${toolFile}". Run "npm run build" first.`);
|
|
53
|
+
}
|
|
54
|
+
async function run(options) {
|
|
55
|
+
const { question, maxSources = 5, json = false, noCite = false, saveAs, quiet = false } = options;
|
|
56
|
+
const projectPath = node_path_1.default.resolve(process.cwd());
|
|
57
|
+
const docuDir = node_path_1.default.join(projectPath, ".docuflow");
|
|
58
|
+
function info(msg) { if (!quiet)
|
|
59
|
+
console.log(msg); }
|
|
60
|
+
// ── Validate question ───────────────────────────────────────────────────────
|
|
61
|
+
if (!question || question.trim() === "") {
|
|
62
|
+
console.error(c.red(" ✗ Question is required."));
|
|
63
|
+
console.error(` Usage: docuflow query "<question>"`);
|
|
64
|
+
process.exit(2);
|
|
65
|
+
}
|
|
66
|
+
// ── Pre-flight ──────────────────────────────────────────────────────────────
|
|
67
|
+
if (!node_fs_1.default.existsSync(docuDir)) {
|
|
68
|
+
console.error(c.red(` ✗ .docuflow/ not found at ${projectPath}`));
|
|
69
|
+
console.error(` Run "docuflow init" first.`);
|
|
70
|
+
process.exit(2);
|
|
71
|
+
}
|
|
72
|
+
// ── Load tools ──────────────────────────────────────────────────────────────
|
|
73
|
+
let queryWiki;
|
|
74
|
+
let saveAnswerAsPage;
|
|
75
|
+
try {
|
|
76
|
+
({ queryWiki } = loadServerTool("query-wiki"));
|
|
77
|
+
if (saveAs) {
|
|
78
|
+
({ saveAnswerAsPage } = loadServerTool("save-answer-as-page"));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
catch (e) {
|
|
82
|
+
console.error(c.red(` ✗ ${e.message}`));
|
|
83
|
+
process.exit(2);
|
|
84
|
+
}
|
|
85
|
+
// ── Execute query ───────────────────────────────────────────────────────────
|
|
86
|
+
info(c.dim(` Searching wiki for: "${question}"`));
|
|
87
|
+
let result;
|
|
88
|
+
try {
|
|
89
|
+
result = await queryWiki({ project_path: projectPath, question, max_sources: maxSources });
|
|
90
|
+
}
|
|
91
|
+
catch (e) {
|
|
92
|
+
console.error(c.red(` ✗ Query failed: ${e.message}`));
|
|
93
|
+
process.exit(2);
|
|
94
|
+
}
|
|
95
|
+
if (result.error) {
|
|
96
|
+
console.error(c.red(` ✗ ${result.error}`));
|
|
97
|
+
process.exit(2);
|
|
98
|
+
}
|
|
99
|
+
// ── No sources found ────────────────────────────────────────────────────────
|
|
100
|
+
if (!result.source_pages || result.source_pages.length === 0) {
|
|
101
|
+
if (!json) {
|
|
102
|
+
console.error(c.yellow(" No matching sources found in the wiki."));
|
|
103
|
+
console.error(` Try running "docuflow rewiki" to rebuild the wiki first.`);
|
|
104
|
+
}
|
|
105
|
+
process.exit(3);
|
|
106
|
+
}
|
|
107
|
+
// ── Output ──────────────────────────────────────────────────────────────────
|
|
108
|
+
if (json) {
|
|
109
|
+
console.log(JSON.stringify({
|
|
110
|
+
question: result.question,
|
|
111
|
+
answer: result.answer,
|
|
112
|
+
source_pages: result.source_pages,
|
|
113
|
+
search_results: result.search_results,
|
|
114
|
+
confidence: result.confidence,
|
|
115
|
+
}, null, 2));
|
|
116
|
+
}
|
|
117
|
+
else if (noCite) {
|
|
118
|
+
console.log(result.answer);
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
console.log(result.answer);
|
|
122
|
+
console.log("");
|
|
123
|
+
console.log(c.bold("## Sources"));
|
|
124
|
+
for (const page of result.source_pages) {
|
|
125
|
+
console.log(` - ${page.title} ${c.dim("(" + page.page_id + ")")}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// ── Save ────────────────────────────────────────────────────────────────────
|
|
129
|
+
if (saveAs && saveAnswerAsPage) {
|
|
130
|
+
try {
|
|
131
|
+
const saved = await saveAnswerAsPage({
|
|
132
|
+
project_path: projectPath,
|
|
133
|
+
question,
|
|
134
|
+
answer: result.answer,
|
|
135
|
+
page_title: saveAs,
|
|
136
|
+
source_page_ids: (result.source_pages ?? []).map((p) => p.page_id),
|
|
137
|
+
});
|
|
138
|
+
process.stderr.write(`page-id: ${saved?.page_id ?? saveAs}\n`);
|
|
139
|
+
}
|
|
140
|
+
catch (e) {
|
|
141
|
+
process.stderr.write(c.red(`Warning: could not save page: ${e.message}\n`));
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
package/dist/commands/recent.js
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* docuflow recent
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* Shows a dashboard of recent activity:
|
|
6
|
+
* - Recent git commits (last N days)
|
|
7
|
+
* - Recent wiki activity from .docuflow/log.md
|
|
8
8
|
*/
|
|
9
9
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
10
10
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
@@ -14,106 +14,27 @@ exports.run = run;
|
|
|
14
14
|
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
15
15
|
const node_path_1 = __importDefault(require("node:path"));
|
|
16
16
|
const node_child_process_1 = require("node:child_process");
|
|
17
|
-
// ── ANSI helpers
|
|
17
|
+
// ── ANSI helpers ──────────────────────────────────────────────────────────────
|
|
18
18
|
const c = {
|
|
19
|
-
green: (s) => `\x1b[32m${s}\x1b[0m`,
|
|
20
|
-
yellow: (s) => `\x1b[33m${s}\x1b[0m`,
|
|
21
|
-
red: (s) => `\x1b[31m${s}\x1b[0m`,
|
|
22
19
|
dim: (s) => `\x1b[2m${s}\x1b[0m`,
|
|
23
20
|
bold: (s) => `\x1b[1m${s}\x1b[0m`,
|
|
21
|
+
cyan: (s) => `\x1b[36m${s}\x1b[0m`,
|
|
24
22
|
};
|
|
25
|
-
function colourStatus(status, text) {
|
|
26
|
-
if (status === "approved")
|
|
27
|
-
return c.green(text);
|
|
28
|
-
if (status === "needs-work")
|
|
29
|
-
return c.yellow(text);
|
|
30
|
-
if (status === "rejected")
|
|
31
|
-
return c.red(text);
|
|
32
|
-
return c.dim(text);
|
|
33
|
-
}
|
|
34
|
-
function normaliseStatus(raw) {
|
|
35
|
-
// Strip emojis (✅ ⚠️ ⏳ ❌) and other non-printable/non-ASCII characters
|
|
36
|
-
const stripped = raw.replace(/[^\x20-\x7E]/g, " ").trim().toLowerCase();
|
|
37
|
-
if (stripped.includes("approved"))
|
|
38
|
-
return "approved";
|
|
39
|
-
if (stripped.includes("needs-work") || stripped.includes("needs_work"))
|
|
40
|
-
return "needs-work";
|
|
41
|
-
if (stripped.includes("rejected"))
|
|
42
|
-
return "rejected";
|
|
43
|
-
if (stripped.includes("pending"))
|
|
44
|
-
return "pending";
|
|
45
|
-
return stripped.split(/\s+/)[0] || "pending";
|
|
46
|
-
}
|
|
47
|
-
function truncate(s, max) {
|
|
48
|
-
return s.length > max ? s.slice(0, max - 1) + "…" : s;
|
|
49
|
-
}
|
|
50
23
|
function formatDateYMD(d) {
|
|
51
24
|
return d.toISOString().slice(0, 10);
|
|
52
25
|
}
|
|
53
|
-
// ── Git
|
|
54
|
-
function
|
|
26
|
+
// ── Git commits (recent, date-filtered) ──────────────────────────────────────
|
|
27
|
+
function recentCommits(days) {
|
|
55
28
|
try {
|
|
56
|
-
const out = (0, node_child_process_1.execSync)(`git log --oneline --
|
|
29
|
+
const out = (0, node_child_process_1.execSync)(`git log --oneline --since="${days} days ago"`, { encoding: "utf8", stdio: ["pipe", "pipe", "ignore"] });
|
|
57
30
|
return out.trim().split("\n").filter(Boolean);
|
|
58
31
|
}
|
|
59
32
|
catch {
|
|
60
33
|
return [];
|
|
61
34
|
}
|
|
62
35
|
}
|
|
63
|
-
// ──
|
|
64
|
-
|
|
65
|
-
let content;
|
|
66
|
-
try {
|
|
67
|
-
content = await promises_1.default.readFile(specPath, "utf8");
|
|
68
|
-
}
|
|
69
|
-
catch {
|
|
70
|
-
return {};
|
|
71
|
-
}
|
|
72
|
-
let title = "";
|
|
73
|
-
let feature = "";
|
|
74
|
-
let status = "pending";
|
|
75
|
-
for (const line of content.split("\n")) {
|
|
76
|
-
// First H1 heading: "# TASK-YYYYMMDD-HHMMSS: <title>"
|
|
77
|
-
if (!title && line.startsWith("# TASK-")) {
|
|
78
|
-
const colonIdx = line.indexOf(":");
|
|
79
|
-
if (colonIdx !== -1) {
|
|
80
|
-
title = line.slice(colonIdx + 1).trim();
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
// **Feature**: value
|
|
84
|
-
if (!feature && line.startsWith("**Feature**:")) {
|
|
85
|
-
feature = line.slice("**Feature**:".length).trim().slice(0, 120).trimEnd();
|
|
86
|
-
}
|
|
87
|
-
// **Status**: value
|
|
88
|
-
if (line.startsWith("**Status**:")) {
|
|
89
|
-
const raw = line.slice("**Status**:".length).trim();
|
|
90
|
-
status = normaliseStatus(raw);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
return { title, feature, status };
|
|
94
|
-
}
|
|
95
|
-
// ── Review parser ────────────────────────────────────────────────────────────
|
|
96
|
-
async function parseReview(reviewPath) {
|
|
97
|
-
let content;
|
|
98
|
-
try {
|
|
99
|
-
content = await promises_1.default.readFile(reviewPath, "utf8");
|
|
100
|
-
}
|
|
101
|
-
catch {
|
|
102
|
-
return { score: null, reviewVerdict: null };
|
|
103
|
-
}
|
|
104
|
-
let score = null;
|
|
105
|
-
let reviewVerdict = null;
|
|
106
|
-
const scoreMatch = content.match(/Score:\s*(\d+\/\d+)/);
|
|
107
|
-
if (scoreMatch)
|
|
108
|
-
score = scoreMatch[1];
|
|
109
|
-
const verdictMatch = content.match(/Verdict:\s*(APPROVED|NEEDS[\s_]WORK|REJECTED)/i);
|
|
110
|
-
if (verdictMatch) {
|
|
111
|
-
reviewVerdict = verdictMatch[1].toUpperCase().replace(/[\s-]+/g, "_");
|
|
112
|
-
}
|
|
113
|
-
return { score, reviewVerdict };
|
|
114
|
-
}
|
|
115
|
-
// ── Wiki log parser ──────────────────────────────────────────────────────────
|
|
116
|
-
// log.md format: ## [2026-05-01T13:49:38.141Z] operation | description
|
|
36
|
+
// ── Wiki log parsing ──────────────────────────────────────────────────────────
|
|
37
|
+
const headingRe = /^##\s+\[([^\]]+)\]\s+([^|]+?)(?:\s*\|\s*(.+))?$/;
|
|
117
38
|
async function parseWikiLog(logPath, since) {
|
|
118
39
|
let content;
|
|
119
40
|
try {
|
|
@@ -123,73 +44,80 @@ async function parseWikiLog(logPath, since) {
|
|
|
123
44
|
return [];
|
|
124
45
|
}
|
|
125
46
|
const entries = [];
|
|
126
|
-
const headingRe = /^##\s+\[([^\]]+)\]\s+([^|]+)\|\s*(.+)$/;
|
|
127
47
|
for (const line of content.split("\n")) {
|
|
128
|
-
const m = line.
|
|
129
|
-
if (!m)
|
|
48
|
+
const m = headingRe.exec(line.trim());
|
|
49
|
+
if (!m) {
|
|
50
|
+
// Legacy pipe-delimited: timestamp | tool | target | delta
|
|
51
|
+
const parts = line.split("|").map(p => p.trim());
|
|
52
|
+
if (parts.length >= 3 && parts[0].includes("T")) {
|
|
53
|
+
const d = new Date(parts[0]);
|
|
54
|
+
if (!isNaN(d.getTime()) && d >= since) {
|
|
55
|
+
entries.push({ date: formatDateYMD(d), operation: parts[1] ?? "", file: parts[2] ?? "" });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
130
58
|
continue;
|
|
131
|
-
const rawDate = m[1].trim();
|
|
132
|
-
const operation = m[2].trim();
|
|
133
|
-
const file = m[3].trim();
|
|
134
|
-
const dateVal = new Date(rawDate);
|
|
135
|
-
if (!isNaN(dateVal.getTime()) && dateVal >= since) {
|
|
136
|
-
entries.push({ date: rawDate.slice(0, 10), operation, file });
|
|
137
59
|
}
|
|
60
|
+
const d = new Date(m[1]);
|
|
61
|
+
if (isNaN(d.getTime()) || d < since)
|
|
62
|
+
continue;
|
|
63
|
+
entries.push({ date: formatDateYMD(d), operation: (m[2] ?? "").trim(), file: (m[3] ?? "").trim() });
|
|
138
64
|
}
|
|
139
65
|
return entries;
|
|
140
66
|
}
|
|
141
|
-
// ── Renderers
|
|
142
|
-
function renderTable(
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
console.log(
|
|
146
|
-
console.log(` ${
|
|
147
|
-
console.log(
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
const row = ` ${t.id.padEnd(24)} ${title} ${status} ${score}`;
|
|
153
|
-
console.log(colourStatus(t.status, row));
|
|
67
|
+
// ── Renderers ─────────────────────────────────────────────────────────────────
|
|
68
|
+
function renderTable(commits, wikiEntries, days) {
|
|
69
|
+
const fromDate = formatDateYMD(new Date(Date.now() - days * 86400000));
|
|
70
|
+
const toDate = formatDateYMD(new Date());
|
|
71
|
+
console.log("");
|
|
72
|
+
console.log(c.bold(`DocuFlow — Recent Activity (${fromDate} → ${toDate})`));
|
|
73
|
+
console.log("");
|
|
74
|
+
console.log(c.bold(`Git Commits (last ${days} days)`));
|
|
75
|
+
console.log("─".repeat(60));
|
|
76
|
+
if (commits.length === 0) {
|
|
77
|
+
console.log(c.dim(" No commits in this period."));
|
|
154
78
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
console.log(` ${line}`);
|
|
79
|
+
else {
|
|
80
|
+
for (const line of commits) {
|
|
81
|
+
const sha = line.slice(0, 7);
|
|
82
|
+
const msg = line.slice(8);
|
|
83
|
+
console.log(` ${c.cyan(sha)} ${msg}`);
|
|
161
84
|
}
|
|
162
85
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
86
|
+
console.log("");
|
|
87
|
+
console.log(c.bold(`Wiki Activity (last ${days} days)`));
|
|
88
|
+
console.log("─".repeat(60));
|
|
89
|
+
if (wikiEntries.length === 0) {
|
|
90
|
+
console.log(c.dim(" No wiki activity in this period."));
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
166
93
|
for (const e of wikiEntries) {
|
|
167
|
-
console.log(` ${e.date} ${e.operation}
|
|
94
|
+
console.log(` ${c.dim(e.date)} ${e.operation}${e.file ? " " + c.dim(e.file) : ""}`);
|
|
168
95
|
}
|
|
169
96
|
}
|
|
97
|
+
console.log("");
|
|
170
98
|
}
|
|
171
|
-
function renderMarkdown(
|
|
172
|
-
|
|
99
|
+
function renderMarkdown(commits, wikiEntries, days) {
|
|
100
|
+
const fromDate = formatDateYMD(new Date(Date.now() - days * 86400000));
|
|
101
|
+
const toDate = formatDateYMD(new Date());
|
|
102
|
+
console.log(`# DocuFlow — Recent Activity (${fromDate} → ${toDate})`);
|
|
103
|
+
console.log("");
|
|
104
|
+
console.log(`## Git Commits (last ${days} days)`);
|
|
173
105
|
console.log("");
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
for (const t of tasks) {
|
|
177
|
-
const title = truncate(t.title || t.feature, 40);
|
|
178
|
-
const score = t.score ?? "—";
|
|
179
|
-
console.log(`| ${t.id} | ${title} | ${t.status} | ${score} |`);
|
|
106
|
+
if (commits.length === 0) {
|
|
107
|
+
console.log("_No commits in this period._");
|
|
180
108
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
console.log(`## Git Commits (${allCommits.length} matching)`);
|
|
184
|
-
console.log("");
|
|
185
|
-
for (const line of allCommits) {
|
|
109
|
+
else {
|
|
110
|
+
for (const line of commits) {
|
|
186
111
|
console.log(`- ${line}`);
|
|
187
112
|
}
|
|
188
113
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
114
|
+
console.log("");
|
|
115
|
+
console.log(`## Wiki Activity (last ${days} days)`);
|
|
116
|
+
console.log("");
|
|
117
|
+
if (wikiEntries.length === 0) {
|
|
118
|
+
console.log("_No wiki activity in this period._");
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
193
121
|
console.log("| Date | Operation | File |");
|
|
194
122
|
console.log("|------|-----------|------|");
|
|
195
123
|
for (const e of wikiEntries) {
|
|
@@ -197,80 +125,19 @@ function renderMarkdown(tasks, allCommits, wikiEntries, days, fromDate, toDate)
|
|
|
197
125
|
}
|
|
198
126
|
}
|
|
199
127
|
}
|
|
200
|
-
// ── Main entry point
|
|
128
|
+
// ── Main entry point ──────────────────────────────────────────────────────────
|
|
201
129
|
async function run(opts = { days: 7, format: "table" }) {
|
|
202
130
|
const days = Math.max(1, isNaN(opts.days) ? 7 : opts.days);
|
|
203
131
|
const format = opts.format ?? "table";
|
|
204
|
-
const isMd = format === "md";
|
|
205
132
|
const cwd = process.cwd();
|
|
206
|
-
const specsDir = node_path_1.default.join(cwd, ".devloop", "specs");
|
|
207
133
|
const logPath = node_path_1.default.join(cwd, ".docuflow", "log.md");
|
|
208
|
-
const
|
|
209
|
-
const
|
|
210
|
-
// Collect spec files
|
|
211
|
-
let specFiles = [];
|
|
212
|
-
try {
|
|
213
|
-
const entries = await promises_1.default.readdir(specsDir);
|
|
214
|
-
specFiles = entries
|
|
215
|
-
.filter(f => /^TASK-\d{8}-\d{6}\.md$/.test(f))
|
|
216
|
-
.map(f => node_path_1.default.join(specsDir, f));
|
|
217
|
-
}
|
|
218
|
-
catch {
|
|
219
|
-
console.log(`No tasks found in the last ${days} days.`);
|
|
220
|
-
return;
|
|
221
|
-
}
|
|
222
|
-
// Filter by mtime and parse each spec
|
|
223
|
-
const tasks = [];
|
|
224
|
-
for (const specPath of specFiles) {
|
|
225
|
-
let stat;
|
|
226
|
-
try {
|
|
227
|
-
stat = await promises_1.default.stat(specPath);
|
|
228
|
-
}
|
|
229
|
-
catch {
|
|
230
|
-
continue;
|
|
231
|
-
}
|
|
232
|
-
if (stat.mtime < since)
|
|
233
|
-
continue;
|
|
234
|
-
const stem = node_path_1.default.basename(specPath, ".md"); // TASK-YYYYMMDD-HHMMSS
|
|
235
|
-
const specFields = await parseSpec(specPath);
|
|
236
|
-
if (!specFields.title && !specFields.feature)
|
|
237
|
-
continue; // unreadable
|
|
238
|
-
const reviewPath = node_path_1.default.join(specsDir, `${stem}-review.md`);
|
|
239
|
-
const { score, reviewVerdict } = await parseReview(reviewPath);
|
|
240
|
-
// Override status from review verdict if present
|
|
241
|
-
let status = specFields.status ?? "pending";
|
|
242
|
-
if (reviewVerdict === "APPROVED")
|
|
243
|
-
status = "approved";
|
|
244
|
-
else if (reviewVerdict === "NEEDS_WORK")
|
|
245
|
-
status = "needs-work";
|
|
246
|
-
else if (reviewVerdict === "REJECTED")
|
|
247
|
-
status = "rejected";
|
|
248
|
-
const commits = gitLog(stem);
|
|
249
|
-
tasks.push({
|
|
250
|
-
id: stem,
|
|
251
|
-
title: specFields.title ?? "",
|
|
252
|
-
feature: specFields.feature ?? "",
|
|
253
|
-
status,
|
|
254
|
-
score,
|
|
255
|
-
reviewVerdict,
|
|
256
|
-
commits,
|
|
257
|
-
specMtime: stat.mtime,
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
// Sort newest first
|
|
261
|
-
tasks.sort((a, b) => b.specMtime.getTime() - a.specMtime.getTime());
|
|
262
|
-
if (tasks.length === 0) {
|
|
263
|
-
console.log(`No tasks found in the last ${days} days.`);
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
266
|
-
const fromDate = formatDateYMD(since);
|
|
267
|
-
const toDate = formatDateYMD(now);
|
|
268
|
-
const allCommits = tasks.flatMap(t => t.commits);
|
|
134
|
+
const since = new Date(Date.now() - days * 24 * 60 * 60 * 1000);
|
|
135
|
+
const commits = recentCommits(days);
|
|
269
136
|
const wikiEntries = await parseWikiLog(logPath, since);
|
|
270
|
-
if (
|
|
271
|
-
renderMarkdown(
|
|
137
|
+
if (format === "md") {
|
|
138
|
+
renderMarkdown(commits, wikiEntries, days);
|
|
272
139
|
}
|
|
273
140
|
else {
|
|
274
|
-
renderTable(
|
|
141
|
+
renderTable(commits, wikiEntries, days);
|
|
275
142
|
}
|
|
276
143
|
}
|
package/dist/commands/rewiki.js
CHANGED
|
@@ -36,9 +36,12 @@ const c = {
|
|
|
36
36
|
// ─── Dynamic server tool loader (mirrors sync.ts pattern) ─────────────────────
|
|
37
37
|
function loadServerTool(toolFile) {
|
|
38
38
|
const candidates = [
|
|
39
|
-
() => require(`@doquflow/
|
|
40
|
-
() => require(node_path_1.default.resolve(__dirname, "../../../
|
|
41
|
-
() => require(node_path_1.default.resolve(__dirname, "../../
|
|
39
|
+
() => require(`@doquflow/core/dist/tools/${toolFile}`),
|
|
40
|
+
() => require(node_path_1.default.resolve(__dirname, "../../../core/dist/tools", toolFile)),
|
|
41
|
+
() => require(node_path_1.default.resolve(__dirname, "../../core/dist/tools", toolFile)),
|
|
42
|
+
() => require(`@doquflow/studio/dist/tools/${toolFile}`),
|
|
43
|
+
() => require(node_path_1.default.resolve(__dirname, "../../../studio/dist/tools", toolFile)),
|
|
44
|
+
() => require(node_path_1.default.resolve(__dirname, "../../studio/dist/tools", toolFile)),
|
|
42
45
|
];
|
|
43
46
|
for (const attempt of candidates) {
|
|
44
47
|
try {
|
package/dist/commands/sync.js
CHANGED
|
@@ -39,9 +39,14 @@ const c = {
|
|
|
39
39
|
// ─── Dynamic server tool loader ────────────────────────────────────────────────
|
|
40
40
|
function loadServerTool(toolFile) {
|
|
41
41
|
const candidates = [
|
|
42
|
-
|
|
43
|
-
() => require(
|
|
44
|
-
() => require(node_path_1.default.resolve(__dirname, "
|
|
42
|
+
// v2.0 — 4 core tools live in @doquflow/core
|
|
43
|
+
() => require(`@doquflow/core/dist/tools/${toolFile}`),
|
|
44
|
+
() => require(node_path_1.default.resolve(__dirname, "../../../core/dist/tools", toolFile)),
|
|
45
|
+
() => require(node_path_1.default.resolve(__dirname, "../../core/dist/tools", toolFile)),
|
|
46
|
+
// v2.0 — 11 advanced tools live in @doquflow/studio
|
|
47
|
+
() => require(`@doquflow/studio/dist/tools/${toolFile}`),
|
|
48
|
+
() => require(node_path_1.default.resolve(__dirname, "../../../studio/dist/tools", toolFile)),
|
|
49
|
+
() => require(node_path_1.default.resolve(__dirname, "../../studio/dist/tools", toolFile)),
|
|
45
50
|
];
|
|
46
51
|
for (const attempt of candidates) {
|
|
47
52
|
try {
|
|
@@ -135,10 +140,10 @@ function runClaudeSync(prompt, projectPath, allowDangerousPermissions = false) {
|
|
|
135
140
|
// Build the MCP config pointing to the local server binary
|
|
136
141
|
let serverBin;
|
|
137
142
|
try {
|
|
138
|
-
serverBin = require.resolve("@doquflow/
|
|
143
|
+
serverBin = require.resolve("@doquflow/studio/dist/mcp/index.js");
|
|
139
144
|
}
|
|
140
145
|
catch {
|
|
141
|
-
serverBin = node_path_1.default.resolve(__dirname, "../../
|
|
146
|
+
serverBin = node_path_1.default.resolve(__dirname, "../../studio/dist/mcp/index.js");
|
|
142
147
|
}
|
|
143
148
|
const mcpConfig = JSON.stringify({
|
|
144
149
|
mcpServers: {
|
package/dist/commands/ui.js
CHANGED
|
@@ -27,7 +27,7 @@ const init_1 = require("./init");
|
|
|
27
27
|
const watch_1 = require("./watch");
|
|
28
28
|
function loadTool(file, exportName) {
|
|
29
29
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
30
|
-
return require(`@doquflow/
|
|
30
|
+
return require(`@doquflow/studio/dist/tools/${file}`)[exportName];
|
|
31
31
|
}
|
|
32
32
|
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
33
33
|
async function findDocuflowProjects(scanRoot) {
|
package/dist/commands/update.js
CHANGED
|
@@ -102,7 +102,7 @@ async function run(opts = {}) {
|
|
|
102
102
|
process.exit(result.status ?? 1);
|
|
103
103
|
}
|
|
104
104
|
console.log(`\n✓ Updated to ${latest}.`);
|
|
105
|
-
console.log(` This refreshed the CLI, bundled UI (ui-dist), and the @doquflow/
|
|
105
|
+
console.log(` This refreshed the CLI, bundled UI (ui-dist), and the @doquflow/core + @doquflow/studio dependencies.`);
|
|
106
106
|
const stale = await isPortInUse(DEFAULT_PORT);
|
|
107
107
|
if (stale) {
|
|
108
108
|
console.log('');
|
package/dist/commands/watch.js
CHANGED
|
@@ -54,9 +54,12 @@ const node_child_process_1 = require("node:child_process");
|
|
|
54
54
|
// ─── Dynamic server tool loader ────────────────────────────────────────────────
|
|
55
55
|
function loadServerTool(toolFile) {
|
|
56
56
|
const candidates = [
|
|
57
|
-
() => require(`@doquflow/
|
|
58
|
-
() => require(node_path_1.default.resolve(__dirname, "../../../
|
|
59
|
-
() => require(node_path_1.default.resolve(__dirname, "../../
|
|
57
|
+
() => require(`@doquflow/core/dist/tools/${toolFile}`),
|
|
58
|
+
() => require(node_path_1.default.resolve(__dirname, "../../../core/dist/tools", toolFile)),
|
|
59
|
+
() => require(node_path_1.default.resolve(__dirname, "../../core/dist/tools", toolFile)),
|
|
60
|
+
() => require(`@doquflow/studio/dist/tools/${toolFile}`),
|
|
61
|
+
() => require(node_path_1.default.resolve(__dirname, "../../../studio/dist/tools", toolFile)),
|
|
62
|
+
() => require(node_path_1.default.resolve(__dirname, "../../studio/dist/tools", toolFile)),
|
|
60
63
|
];
|
|
61
64
|
for (const attempt of candidates) {
|
|
62
65
|
try {
|
|
@@ -202,10 +205,10 @@ let _allowDangerousPermissions = false;
|
|
|
202
205
|
function runClaudeCLI(prompt, timeoutMs = 120_000) {
|
|
203
206
|
let serverBin;
|
|
204
207
|
try {
|
|
205
|
-
serverBin = require.resolve("@doquflow/
|
|
208
|
+
serverBin = require.resolve("@doquflow/studio/dist/mcp/index.js");
|
|
206
209
|
}
|
|
207
210
|
catch {
|
|
208
|
-
serverBin = node_path_1.default.resolve(__dirname, "../../
|
|
211
|
+
serverBin = node_path_1.default.resolve(__dirname, "../../studio/dist/mcp/index.js");
|
|
209
212
|
}
|
|
210
213
|
const mcpConfig = JSON.stringify({
|
|
211
214
|
mcpServers: { docuflow: { type: "stdio", command: process.execPath, args: [serverBin] } }
|