czon 0.9.0 → 0.9.2
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 +1 -0
- package/dist/commands/gen-todo-list.js +40 -0
- package/dist/commands/index.js +3 -1
- package/dist/commands/ls-files.js +7 -2
- package/dist/findEntries.js +9 -7
- package/dist/process/checkLinks.js +55 -3
- package/dist/process/scanSourceFiles.js +1 -1
- package/dist/process/todoSummary.js +126 -0
- package/package.json +1 -1
- package/prompts/summary-base.md +5 -5
- package/prompts/todo-summary.md +244 -0
package/dist/cli.js
CHANGED
|
@@ -16,6 +16,7 @@ const cli = new clipanion_1.Cli({
|
|
|
16
16
|
// 注册命令
|
|
17
17
|
cli.register(commands_1.BuildCommand);
|
|
18
18
|
cli.register(commands_1.CheckCommand);
|
|
19
|
+
cli.register(commands_1.GenTodoListCommand);
|
|
19
20
|
cli.register(commands_1.LsFilesCommand);
|
|
20
21
|
cli.register(commands_1.SummaryCommand);
|
|
21
22
|
cli.register(commands_1.ConfigGithubCommand);
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.GenTodoListCommand = void 0;
|
|
4
|
+
const clipanion_1 = require("clipanion");
|
|
5
|
+
const todoSummary_1 = require("../process/todoSummary");
|
|
6
|
+
class GenTodoListCommand extends clipanion_1.Command {
|
|
7
|
+
constructor() {
|
|
8
|
+
super(...arguments);
|
|
9
|
+
this.model = clipanion_1.Option.String('--model', 'opencode/big-pickle', {
|
|
10
|
+
description: 'OpenCode model to use for TODO extraction',
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
async execute() {
|
|
14
|
+
try {
|
|
15
|
+
await (0, todoSummary_1.processTodoSummary)(this.model);
|
|
16
|
+
return 0;
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
this.context.stderr.write(`TODO summary generation failed: ${error}\n`);
|
|
20
|
+
return 1;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
exports.GenTodoListCommand = GenTodoListCommand;
|
|
25
|
+
GenTodoListCommand.paths = [['gen', 'todo-list']];
|
|
26
|
+
GenTodoListCommand.usage = clipanion_1.Command.Usage({
|
|
27
|
+
description: 'Generate a TODO summary by extracting TODOs from all markdown files',
|
|
28
|
+
details: `
|
|
29
|
+
This command uses AI to read all markdown files in the repository,
|
|
30
|
+
extract TODO items (both explicit and implicit), determine their
|
|
31
|
+
completion status, and assign priority levels.
|
|
32
|
+
|
|
33
|
+
The generated report is saved to .czon/AIGC/TODO/todo-summary.md
|
|
34
|
+
|
|
35
|
+
Examples:
|
|
36
|
+
$ czon gen todo-list
|
|
37
|
+
$ czon gen todo-list --model opencode/gpt-4o
|
|
38
|
+
`,
|
|
39
|
+
});
|
|
40
|
+
//# sourceMappingURL=gen-todo-list.js.map
|
package/dist/commands/index.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.SummaryCommand = exports.LsFilesCommand = exports.ConfigGithubCommand = exports.CheckCommand = exports.BuildCommand = void 0;
|
|
3
|
+
exports.SummaryCommand = exports.LsFilesCommand = exports.GenTodoListCommand = exports.ConfigGithubCommand = exports.CheckCommand = exports.BuildCommand = void 0;
|
|
4
4
|
var build_1 = require("./build");
|
|
5
5
|
Object.defineProperty(exports, "BuildCommand", { enumerable: true, get: function () { return build_1.BuildCommand; } });
|
|
6
6
|
var check_1 = require("./check");
|
|
7
7
|
Object.defineProperty(exports, "CheckCommand", { enumerable: true, get: function () { return check_1.CheckCommand; } });
|
|
8
8
|
var config_github_1 = require("./config-github");
|
|
9
9
|
Object.defineProperty(exports, "ConfigGithubCommand", { enumerable: true, get: function () { return config_github_1.ConfigGithubCommand; } });
|
|
10
|
+
var gen_todo_list_1 = require("./gen-todo-list");
|
|
11
|
+
Object.defineProperty(exports, "GenTodoListCommand", { enumerable: true, get: function () { return gen_todo_list_1.GenTodoListCommand; } });
|
|
10
12
|
var ls_files_1 = require("./ls-files");
|
|
11
13
|
Object.defineProperty(exports, "LsFilesCommand", { enumerable: true, get: function () { return ls_files_1.LsFilesCommand; } });
|
|
12
14
|
var summary_1 = require("./summary");
|
|
@@ -9,10 +9,13 @@ class LsFilesCommand extends clipanion_1.Command {
|
|
|
9
9
|
this.aigc = clipanion_1.Option.Boolean('--aigc', false, {
|
|
10
10
|
description: 'Include files under .czon/AIGC directory',
|
|
11
11
|
});
|
|
12
|
+
this.allTypes = clipanion_1.Option.Boolean('--all-types', false, {
|
|
13
|
+
description: 'List all file types, not just Markdown files',
|
|
14
|
+
});
|
|
12
15
|
}
|
|
13
16
|
async execute() {
|
|
14
17
|
try {
|
|
15
|
-
const files = await (0, findEntries_1.
|
|
18
|
+
const files = await (0, findEntries_1.findEntries)(process.cwd(), { aigc: this.aigc, allTypes: this.allTypes });
|
|
16
19
|
if (files.length === 0) {
|
|
17
20
|
this.context.stdout.write('No markdown files found.\n');
|
|
18
21
|
}
|
|
@@ -35,10 +38,12 @@ LsFilesCommand.usage = clipanion_1.Command.Usage({
|
|
|
35
38
|
description: 'List all markdown files in the current directory',
|
|
36
39
|
details: `
|
|
37
40
|
This command lists all markdown files in the current directory using git.
|
|
38
|
-
It uses the same logic as the internal
|
|
41
|
+
It uses the same logic as the internal findEntries function.
|
|
42
|
+
Use --all-types to list all file types, not just Markdown.
|
|
39
43
|
|
|
40
44
|
Examples:
|
|
41
45
|
$ czon ls-files
|
|
46
|
+
$ czon ls-files --all-types
|
|
42
47
|
`,
|
|
43
48
|
});
|
|
44
49
|
//# sourceMappingURL=ls-files.js.map
|
package/dist/findEntries.js
CHANGED
|
@@ -1,23 +1,25 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.findEntries = void 0;
|
|
4
4
|
const child_process_1 = require("child_process");
|
|
5
5
|
const path_1 = require("path");
|
|
6
6
|
const util_1 = require("util");
|
|
7
7
|
const isExists_1 = require("./utils/isExists");
|
|
8
8
|
const execAsync = (0, util_1.promisify)(child_process_1.exec);
|
|
9
9
|
/**
|
|
10
|
-
* 使用git
|
|
10
|
+
* 使用git命令查找项目中的文件
|
|
11
11
|
* 使用git ls-files --others --cached --exclude-standard获取所有文件
|
|
12
|
-
* 然后过滤掉.czon
|
|
12
|
+
* 然后过滤掉.czon目录,默认只保留.md文件
|
|
13
13
|
*
|
|
14
14
|
* @param dirPath 要扫描的目录路径
|
|
15
15
|
* @param options 可选参数
|
|
16
16
|
* @param options.aigc 是否包含 .czon/AIGC 目录下的文件
|
|
17
|
-
* @
|
|
17
|
+
* @param options.allTypes 是否返回所有类型的文件(默认 false,仅返回 .md 文件)
|
|
18
|
+
* @returns Promise<string[]> 返回文件的相对路径数组
|
|
18
19
|
*/
|
|
19
|
-
const
|
|
20
|
+
const findEntries = async (dirPath, options) => {
|
|
20
21
|
const aigc = options?.aigc ?? false;
|
|
22
|
+
const allTypes = options?.allTypes ?? false;
|
|
21
23
|
// 获取git仓库的根目录
|
|
22
24
|
const gitRoot = (await execAsync('git rev-parse --show-toplevel', { cwd: dirPath })).stdout.trim();
|
|
23
25
|
// 使用git命令获取所有文件(包括已跟踪和未跟踪的文件)
|
|
@@ -31,7 +33,7 @@ const findMarkdownEntries = async (dirPath, options) => {
|
|
|
31
33
|
.split('\0') // 按空字符分割文件名
|
|
32
34
|
.filter(line => line.trim() !== '') // 移除空行
|
|
33
35
|
.filter(file => !file.startsWith('.') || (aigc && file.startsWith('.czon/AIGC/'))) // 过滤掉隐藏目录下的文件(aigc 模式下保留 .czon/AIGC/)
|
|
34
|
-
.filter(file => file.endsWith('.md')); //
|
|
36
|
+
.filter(file => allTypes || file.endsWith('.md')); // allTypes 时返回所有文件,否则只保留 .md 文件
|
|
35
37
|
// 排除文件系统中不存在的文件
|
|
36
38
|
const existingFiles = [];
|
|
37
39
|
for (const file of files) {
|
|
@@ -41,5 +43,5 @@ const findMarkdownEntries = async (dirPath, options) => {
|
|
|
41
43
|
}
|
|
42
44
|
return existingFiles;
|
|
43
45
|
};
|
|
44
|
-
exports.
|
|
46
|
+
exports.findEntries = findEntries;
|
|
45
47
|
//# sourceMappingURL=findEntries.js.map
|
|
@@ -103,11 +103,22 @@ function extractLinksWithLineNumbers(content) {
|
|
|
103
103
|
*/
|
|
104
104
|
async function checkLinks() {
|
|
105
105
|
console.log('🔍 正在扫描 Markdown 文件...');
|
|
106
|
-
const markdownFiles = await (0, findEntries_1.
|
|
106
|
+
const markdownFiles = await (0, findEntries_1.findEntries)(paths_1.INPUT_DIR, { aigc: true });
|
|
107
|
+
const allFiles = await (0, findEntries_1.findEntries)(paths_1.INPUT_DIR, { allTypes: true });
|
|
107
108
|
const issues = [];
|
|
108
109
|
// 构建已知文件集合,用于死链接检测
|
|
109
110
|
const knownFiles = new Set(markdownFiles);
|
|
110
|
-
|
|
111
|
+
// 构建全部文件集合,用于建议生成
|
|
112
|
+
const allFilesSet = new Set(allFiles);
|
|
113
|
+
// 构建 basename -> 相对路径列表 的索引,用于模糊匹配
|
|
114
|
+
const basenameIndex = new Map();
|
|
115
|
+
for (const file of allFiles) {
|
|
116
|
+
const base = path_1.default.basename(file);
|
|
117
|
+
const list = basenameIndex.get(base) ?? [];
|
|
118
|
+
list.push(file);
|
|
119
|
+
basenameIndex.set(base, list);
|
|
120
|
+
}
|
|
121
|
+
console.log(`📄 发现 ${markdownFiles.length} 个 Markdown 文件,${allFiles.length} 个总文件\n`);
|
|
111
122
|
for (const filePath of markdownFiles) {
|
|
112
123
|
const fullPath = path_1.default.join(paths_1.INPUT_DIR, filePath);
|
|
113
124
|
const content = await (0, promises_1.readFile)(fullPath, 'utf-8');
|
|
@@ -125,6 +136,13 @@ async function checkLinks() {
|
|
|
125
136
|
continue;
|
|
126
137
|
// 检查 1: 路径格式规范 — 不应使用 / 开头的绝对路径
|
|
127
138
|
if (hrefWithoutHash.startsWith('/')) {
|
|
139
|
+
// 尝试生成建议:去掉开头的 /,看是否能在已知文件中找到
|
|
140
|
+
const suggestions = [];
|
|
141
|
+
const targetRelative = hrefWithoutHash.slice(1); // 去掉开头的 /
|
|
142
|
+
if (allFilesSet.has(targetRelative)) {
|
|
143
|
+
const suggested = path_1.default.relative(path_1.default.dirname(filePath), targetRelative);
|
|
144
|
+
suggestions.push(suggested);
|
|
145
|
+
}
|
|
128
146
|
issues.push({
|
|
129
147
|
file: filePath,
|
|
130
148
|
line: link.line,
|
|
@@ -132,6 +150,7 @@ async function checkLinks() {
|
|
|
132
150
|
href: link.href,
|
|
133
151
|
type: 'absolute-path',
|
|
134
152
|
message: '不应使用 / 开头的绝对路径,请使用相对路径',
|
|
153
|
+
suggestions,
|
|
135
154
|
});
|
|
136
155
|
// 绝对路径无法正确解析,跳过死链接检测
|
|
137
156
|
continue;
|
|
@@ -147,6 +166,22 @@ async function checkLinks() {
|
|
|
147
166
|
href: link.href,
|
|
148
167
|
type: 'dead-link',
|
|
149
168
|
message: '链接指向项目目录之外',
|
|
169
|
+
suggestions: [],
|
|
170
|
+
});
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
// 检查 2: .czon/src 边界隔离 — 外部文件不能引用 .czon/src 内部的文件
|
|
174
|
+
const sourceIsInternal = filePath.startsWith('.czon/src/');
|
|
175
|
+
const targetIsInternal = resolvedRelative.startsWith('.czon/src/');
|
|
176
|
+
if (!sourceIsInternal && targetIsInternal) {
|
|
177
|
+
issues.push({
|
|
178
|
+
file: filePath,
|
|
179
|
+
line: link.line,
|
|
180
|
+
raw: link.raw,
|
|
181
|
+
href: link.href,
|
|
182
|
+
type: 'cross-boundary',
|
|
183
|
+
message: '不能引用 .czon/src 内部的文件',
|
|
184
|
+
suggestions: [],
|
|
150
185
|
});
|
|
151
186
|
continue;
|
|
152
187
|
}
|
|
@@ -158,6 +193,16 @@ async function checkLinks() {
|
|
|
158
193
|
// 再检查文件系统
|
|
159
194
|
const resolvedFullPath = path_1.default.join(paths_1.INPUT_DIR, resolvedRelative);
|
|
160
195
|
if (!(await (0, isExists_1.isExists)(resolvedFullPath))) {
|
|
196
|
+
// 通过 basename 模糊匹配,生成候选建议(最多 3 个)
|
|
197
|
+
const suggestions = [];
|
|
198
|
+
const targetBasename = path_1.default.basename(hrefWithoutHash);
|
|
199
|
+
const candidates = basenameIndex.get(targetBasename) ?? [];
|
|
200
|
+
for (const candidate of candidates) {
|
|
201
|
+
if (suggestions.length >= 3)
|
|
202
|
+
break;
|
|
203
|
+
const suggested = path_1.default.relative(path_1.default.dirname(filePath), candidate);
|
|
204
|
+
suggestions.push(suggested);
|
|
205
|
+
}
|
|
161
206
|
issues.push({
|
|
162
207
|
file: filePath,
|
|
163
208
|
line: link.line,
|
|
@@ -165,6 +210,7 @@ async function checkLinks() {
|
|
|
165
210
|
href: link.href,
|
|
166
211
|
type: 'dead-link',
|
|
167
212
|
message: '目标文件不存在',
|
|
213
|
+
suggestions,
|
|
168
214
|
});
|
|
169
215
|
}
|
|
170
216
|
}
|
|
@@ -189,19 +235,25 @@ function formatCheckResults(issues) {
|
|
|
189
235
|
for (const [file, fileIssues] of grouped) {
|
|
190
236
|
lines.push(file);
|
|
191
237
|
for (const issue of fileIssues) {
|
|
192
|
-
const icon = issue.type === 'dead-link' ? '✖' : '⚠';
|
|
238
|
+
const icon = issue.type === 'dead-link' ? '✖' : issue.type === 'cross-boundary' ? '⊘' : '⚠';
|
|
193
239
|
lines.push(` line ${issue.line}: ${icon} ${issue.raw}`);
|
|
194
240
|
lines.push(` ${issue.message}`);
|
|
241
|
+
for (const suggestion of issue.suggestions) {
|
|
242
|
+
lines.push(` 建议: ${suggestion}`);
|
|
243
|
+
}
|
|
195
244
|
}
|
|
196
245
|
lines.push('');
|
|
197
246
|
}
|
|
198
247
|
const deadCount = issues.filter(i => i.type === 'dead-link').length;
|
|
199
248
|
const formatCount = issues.filter(i => i.type === 'absolute-path').length;
|
|
249
|
+
const boundaryCount = issues.filter(i => i.type === 'cross-boundary').length;
|
|
200
250
|
const parts = [];
|
|
201
251
|
if (deadCount > 0)
|
|
202
252
|
parts.push(`${deadCount} 个死链接`);
|
|
203
253
|
if (formatCount > 0)
|
|
204
254
|
parts.push(`${formatCount} 个路径格式问题`);
|
|
255
|
+
if (boundaryCount > 0)
|
|
256
|
+
parts.push(`${boundaryCount} 个跨边界引用`);
|
|
205
257
|
lines.push(`发现 ${issues.length} 个问题(${parts.join(',')}),涉及 ${grouped.size} 个文件。`);
|
|
206
258
|
return lines.join('\n');
|
|
207
259
|
}
|
|
@@ -25,7 +25,7 @@ async function scanSourceFiles() {
|
|
|
25
25
|
console.log(`🔍 Scanning source directory...`);
|
|
26
26
|
const queue = [];
|
|
27
27
|
const isVisited = new Set();
|
|
28
|
-
const markdownFiles = await (0, findEntries_1.
|
|
28
|
+
const markdownFiles = await (0, findEntries_1.findEntries)(paths_1.INPUT_DIR, { aigc: true });
|
|
29
29
|
for (const filePath of markdownFiles) {
|
|
30
30
|
queue.push(filePath);
|
|
31
31
|
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.processTodoSummary = void 0;
|
|
4
|
+
const promises_1 = require("fs/promises");
|
|
5
|
+
const path_1 = require("path");
|
|
6
|
+
const metadata_1 = require("../metadata");
|
|
7
|
+
const paths_1 = require("../paths");
|
|
8
|
+
const opencode_1 = require("../services/opencode");
|
|
9
|
+
// Prompt 模板目录路径
|
|
10
|
+
const PROMPTS_DIR = (0, path_1.join)(__dirname, '../../prompts');
|
|
11
|
+
// 最大重试次数
|
|
12
|
+
const MAX_RETRIES = 3;
|
|
13
|
+
// TODO Summary 配置
|
|
14
|
+
const TODO_CONFIG = {
|
|
15
|
+
promptFile: 'todo-summary',
|
|
16
|
+
title: 'AI 总结:待办事项',
|
|
17
|
+
slug: 'aigc-todo-list',
|
|
18
|
+
outputRelative: '.czon/AIGC/TODO/todo-summary.md',
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* 读取 Prompt 模板文件内容
|
|
22
|
+
*/
|
|
23
|
+
const loadPromptTemplate = async (templateName) => {
|
|
24
|
+
const templatePath = (0, path_1.join)(PROMPTS_DIR, `${templateName}.md`);
|
|
25
|
+
return (0, promises_1.readFile)(templatePath, 'utf-8');
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* 获取文件的 mtime(毫秒),文件不存在则返回 null
|
|
29
|
+
*/
|
|
30
|
+
const getFileMtimeMs = async (filePath) => {
|
|
31
|
+
try {
|
|
32
|
+
const s = await (0, promises_1.stat)(filePath);
|
|
33
|
+
return s.mtimeMs;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* 生成 TODO Summary 报告
|
|
41
|
+
*/
|
|
42
|
+
const processTodoSummary = async (model) => {
|
|
43
|
+
const cwd = process.cwd();
|
|
44
|
+
// 加载 MetaData
|
|
45
|
+
await (0, metadata_1.loadMetaData)();
|
|
46
|
+
// 加载 prompt 模板
|
|
47
|
+
console.info('📖 加载 TODO Summary prompt 模板...');
|
|
48
|
+
const promptContent = await loadPromptTemplate(TODO_CONFIG.promptFile);
|
|
49
|
+
const outputPath = (0, path_1.join)(paths_1.INPUT_DIR, TODO_CONFIG.outputRelative);
|
|
50
|
+
const prompt = `
|
|
51
|
+
${promptContent}
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
# 执行任务
|
|
56
|
+
|
|
57
|
+
请严格按照上述指南,阅读本仓库中的所有 Markdown 文件,提取所有 TODO 项,生成待办事项报告。
|
|
58
|
+
|
|
59
|
+
输出文件:${TODO_CONFIG.outputRelative}
|
|
60
|
+
|
|
61
|
+
注意:
|
|
62
|
+
1. 必须阅读所有文件后再生成报告
|
|
63
|
+
2. 文件必须保存到 ${TODO_CONFIG.outputRelative}
|
|
64
|
+
3. 确保所有链接使用 ../../../ 开头的相对路径
|
|
65
|
+
4. 链接文本使用文章标题
|
|
66
|
+
`.trim();
|
|
67
|
+
// 记录发送前的 mtime
|
|
68
|
+
const mtimeBefore = await getFileMtimeMs(outputPath);
|
|
69
|
+
console.info('\n📊 开始生成 TODO Summary 报告...\n');
|
|
70
|
+
// 发送初始 prompt,获取句柄
|
|
71
|
+
const handle = await (0, opencode_1.runOpenCode)(prompt, { model, cwd });
|
|
72
|
+
// 验证 + 重试循环
|
|
73
|
+
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
74
|
+
const mtimeAfter = await getFileMtimeMs(outputPath);
|
|
75
|
+
const fileExists = mtimeAfter !== null;
|
|
76
|
+
const fileModified = mtimeBefore !== mtimeAfter;
|
|
77
|
+
if (fileExists && fileModified) {
|
|
78
|
+
console.info('✅ TODO Summary 报告生成成功\n');
|
|
79
|
+
// 预设 title 和 slug 到 MetaData
|
|
80
|
+
let fileMeta = metadata_1.MetaData.files.find(f => f.path === TODO_CONFIG.outputRelative);
|
|
81
|
+
if (!fileMeta) {
|
|
82
|
+
fileMeta = { path: TODO_CONFIG.outputRelative, links: [] };
|
|
83
|
+
metadata_1.MetaData.files.push(fileMeta);
|
|
84
|
+
}
|
|
85
|
+
fileMeta.metadata = {
|
|
86
|
+
...fileMeta.metadata,
|
|
87
|
+
title: TODO_CONFIG.title,
|
|
88
|
+
slug: TODO_CONFIG.slug,
|
|
89
|
+
};
|
|
90
|
+
// 保存 MetaData
|
|
91
|
+
await (0, metadata_1.saveMetaData)();
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
// 构造错误反馈
|
|
95
|
+
let errorMsg;
|
|
96
|
+
if (!fileExists) {
|
|
97
|
+
errorMsg = `错误:文件 ${TODO_CONFIG.outputRelative} 未生成。请立即创建并写入完整的报告内容到 ${TODO_CONFIG.outputRelative}。`;
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
errorMsg = `错误:文件 ${TODO_CONFIG.outputRelative} 未被修改。请重新生成完整内容并覆盖写入 ${TODO_CONFIG.outputRelative}。`;
|
|
101
|
+
}
|
|
102
|
+
console.warn(` ⚠️ 重试 ${attempt}/${MAX_RETRIES}: ${errorMsg}`);
|
|
103
|
+
await handle.prompt(errorMsg);
|
|
104
|
+
}
|
|
105
|
+
// 最终检查
|
|
106
|
+
const mtimeFinal = await getFileMtimeMs(outputPath);
|
|
107
|
+
if (mtimeFinal !== null && mtimeBefore !== mtimeFinal) {
|
|
108
|
+
console.info('✅ TODO Summary 报告生成成功(最终检查通过)\n');
|
|
109
|
+
// 预设 title 和 slug 到 MetaData
|
|
110
|
+
let fileMeta = metadata_1.MetaData.files.find(f => f.path === TODO_CONFIG.outputRelative);
|
|
111
|
+
if (!fileMeta) {
|
|
112
|
+
fileMeta = { path: TODO_CONFIG.outputRelative, links: [] };
|
|
113
|
+
metadata_1.MetaData.files.push(fileMeta);
|
|
114
|
+
}
|
|
115
|
+
fileMeta.metadata = {
|
|
116
|
+
...fileMeta.metadata,
|
|
117
|
+
title: TODO_CONFIG.title,
|
|
118
|
+
slug: TODO_CONFIG.slug,
|
|
119
|
+
};
|
|
120
|
+
await (0, metadata_1.saveMetaData)();
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
throw new Error(`${MAX_RETRIES} 次重试后仍未成功生成文件: ${TODO_CONFIG.outputRelative}`);
|
|
124
|
+
};
|
|
125
|
+
exports.processTodoSummary = processTodoSummary;
|
|
126
|
+
//# sourceMappingURL=todoSummary.js.map
|
package/package.json
CHANGED
package/prompts/summary-base.md
CHANGED
|
@@ -115,20 +115,20 @@
|
|
|
115
115
|
- 引用原文链接时,保证链接有效
|
|
116
116
|
- **永远链接到具体的 Markdown 文件**,不要链接目录
|
|
117
117
|
- **链接文本应当是对应的标题**,而不是文件名
|
|
118
|
-
- 由于生成到 SUMMARY 目录,引用时使用
|
|
118
|
+
- 由于生成到 `.czon/AIGC/SUMMARY/` 目录,引用时使用 `../../../` 开头的相对路径
|
|
119
119
|
|
|
120
120
|
**正确示例**:
|
|
121
121
|
|
|
122
122
|
```markdown
|
|
123
|
-
[资本持久战:个人投资者跨越阶级的战略](
|
|
124
|
-
[从创作到分发——构建AI-Native内容引擎](
|
|
123
|
+
[资本持久战:个人投资者跨越阶级的战略](../../../INSIGHTS/6.md)
|
|
124
|
+
[从创作到分发——构建AI-Native内容引擎](../../../INSIGHTS/4.md)
|
|
125
125
|
```
|
|
126
126
|
|
|
127
127
|
**错误示例**:
|
|
128
128
|
|
|
129
129
|
```markdown
|
|
130
|
-
[INSIGHTS/6.md](
|
|
131
|
-
[资本持久战](
|
|
130
|
+
[INSIGHTS/6.md](../../../INSIGHTS/6.md) ← 使用了文件名而非标题
|
|
131
|
+
[资本持久战](../../../INSIGHTS/) ← 链接到了目录
|
|
132
132
|
```
|
|
133
133
|
|
|
134
134
|
### 3. 头部格式
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
# TODO 待办事项提取报告
|
|
2
|
+
|
|
3
|
+
## 文档阅读流程(必须严格遵循)
|
|
4
|
+
|
|
5
|
+
### 核心原则
|
|
6
|
+
|
|
7
|
+
本仓库可能包含成百上千个 Markdown 文件。你必须**逐一阅读每个文件**,不得跳过。
|
|
8
|
+
为了处理大量文件,采用**渐进式阅读与摘要**策略。
|
|
9
|
+
|
|
10
|
+
### 阶段 1:获取完整文件列表
|
|
11
|
+
|
|
12
|
+
1. 执行 `npx czon@latest ls-files` 获取所有 Markdown 文件
|
|
13
|
+
2. 记录文件总数 N
|
|
14
|
+
3. 将文件列表保存为待阅读队列
|
|
15
|
+
|
|
16
|
+
### 阶段 2:分批阅读与提取
|
|
17
|
+
|
|
18
|
+
将文件分批处理。建议每批 10-20 个文件,但你可以根据文件大小和复杂度自行调整。
|
|
19
|
+
|
|
20
|
+
**对于每一批:**
|
|
21
|
+
|
|
22
|
+
1. **阅读**:使用 Read 工具逐一读取该批次的每个文件完整内容
|
|
23
|
+
2. **提取 TODO**:根据下方「TODO 提取规则」,识别并记录所有 TODO 项
|
|
24
|
+
3. **批次汇总**:将该批次提取的 TODO 项整理到「TODO 知识库」中
|
|
25
|
+
4. **累积**:将批次结果合并到全局 TODO 知识库
|
|
26
|
+
|
|
27
|
+
### 阶段 3:上下文管理
|
|
28
|
+
|
|
29
|
+
当上下文接近限制时:
|
|
30
|
+
|
|
31
|
+
1. **压缩知识库**:已提取的 TODO 条目保留核心信息(描述、来源文件、状态),丢弃详细原文引用
|
|
32
|
+
2. **保留索引**:无论如何压缩,必须保留所有文件的路径和标题索引
|
|
33
|
+
3. **继续阅读**:使用压缩后的知识库继续处理剩余文件
|
|
34
|
+
|
|
35
|
+
### 阶段 4:完整性验证
|
|
36
|
+
|
|
37
|
+
在生成报告前,必须确认:
|
|
38
|
+
|
|
39
|
+
1. 文件列表中的所有 N 个文件都已处理
|
|
40
|
+
2. 所有提取的 TODO 项都有明确的来源文件
|
|
41
|
+
|
|
42
|
+
**如果发现遗漏**:返回阶段 2 处理遗漏的文件。
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## TODO 提取规则
|
|
47
|
+
|
|
48
|
+
### 显式 TODO
|
|
49
|
+
|
|
50
|
+
文中明确表达待办意图的内容,包括但不限于以下标志词:
|
|
51
|
+
|
|
52
|
+
- "TODO"、"FIXME"、"HACK"
|
|
53
|
+
- "待办"、"待完成"、"待处理"、"待实现"
|
|
54
|
+
- "计划做"、"计划实现"、"计划添加"
|
|
55
|
+
- "接下来要做"、"接下来要"、"下一步"
|
|
56
|
+
- "需要实现"、"需要添加"、"需要完成"、"需要解决"
|
|
57
|
+
|
|
58
|
+
### 隐式 TODO
|
|
59
|
+
|
|
60
|
+
文中表达了意图或规划但尚未明确完成的事项:
|
|
61
|
+
|
|
62
|
+
- "我会..."、"我打算..."、"我准备..."
|
|
63
|
+
- "应该..."、"需要一个..."
|
|
64
|
+
- "未来会..."、"之后会..."
|
|
65
|
+
- "还没有..."、"暂时没有..."
|
|
66
|
+
- 描述了一个功能设想但没有实现的迹象
|
|
67
|
+
|
|
68
|
+
### 提取要素
|
|
69
|
+
|
|
70
|
+
对每个识别到的 TODO 项,记录以下信息:
|
|
71
|
+
|
|
72
|
+
| 字段 | 说明 |
|
|
73
|
+
| -------- | ------------------------------------ |
|
|
74
|
+
| 描述 | TODO 的简短描述(一句话概括) |
|
|
75
|
+
| 来源文件 | 文件路径和文章标题 |
|
|
76
|
+
| 原文引用 | 相关原文片段(1-2 句话) |
|
|
77
|
+
| 推断日期 | 该 TODO 提出的日期(从文章内容推断) |
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## 状态判断规则
|
|
82
|
+
|
|
83
|
+
通过交叉对比多篇文章内容,判断每个 TODO 的当前状态:
|
|
84
|
+
|
|
85
|
+
### ✅ 已完成
|
|
86
|
+
|
|
87
|
+
- 后续文章中明确提到「已完成」「已实现」「已上线」「已解决」
|
|
88
|
+
- 在代码库或产品中可以观察到相关功能已存在
|
|
89
|
+
- 相关问题在后续文章中不再被提及,且有明确的解决迹象
|
|
90
|
+
|
|
91
|
+
### 🔄 进行中
|
|
92
|
+
|
|
93
|
+
- 后续文章中提到正在做、有部分进展
|
|
94
|
+
- 存在相关的中间产物(如设计文档、原型)但未完全完成
|
|
95
|
+
- 最近的文章中仍在讨论相关内容
|
|
96
|
+
|
|
97
|
+
### ⬜ 待开始
|
|
98
|
+
|
|
99
|
+
- 仅提出了计划或想法,未见任何后续进展
|
|
100
|
+
- 没有在其他文章中被再次提及
|
|
101
|
+
- 明确标注为未来计划
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## 优先级标记规则
|
|
106
|
+
|
|
107
|
+
基于文章内容体现的作者价值观和关注领域,自动判断优先级:
|
|
108
|
+
|
|
109
|
+
### 🔴 高优先级
|
|
110
|
+
|
|
111
|
+
- 与核心业务目标直接相关:创业、投资、产品发布、收入目标
|
|
112
|
+
- 与正在活跃开发的项目直接相关(如 CZON、CZONE、EA 等)
|
|
113
|
+
- 作者在文中明确表达了紧迫感或重要性
|
|
114
|
+
- 阻塞其他工作的关键任务
|
|
115
|
+
|
|
116
|
+
### 🟡 中优先级
|
|
117
|
+
|
|
118
|
+
- 技术改进、工具优化、流程提升
|
|
119
|
+
- 对产品质量有正面影响但非紧急
|
|
120
|
+
- 作者表达了兴趣但没有明确时间要求
|
|
121
|
+
|
|
122
|
+
### 🟢 低优先级
|
|
123
|
+
|
|
124
|
+
- 探索性想法、研究性质的思考
|
|
125
|
+
- 非紧急的改进建议
|
|
126
|
+
- 「有空再做」类的想法
|
|
127
|
+
- 纯学习或实验目的
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## 输出格式
|
|
132
|
+
|
|
133
|
+
### 头部格式
|
|
134
|
+
|
|
135
|
+
```markdown
|
|
136
|
+
# AI 总结:待办事项
|
|
137
|
+
|
|
138
|
+
**AI 分析时间**:YYYY年MM月DD日
|
|
139
|
+
**基于 N 个 Markdown 文件生成**
|
|
140
|
+
**注**:本报告由 AI 自动提取,状态和优先级为 AI 推断结果,仅供参考。
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
其中 `N` 为阶段 1 中获取到的实际 Markdown 文件总数。
|
|
146
|
+
|
|
147
|
+
### 概览
|
|
148
|
+
|
|
149
|
+
```markdown
|
|
150
|
+
## 概览
|
|
151
|
+
|
|
152
|
+
| 状态 | 数量 |
|
|
153
|
+
| --------- | ----- |
|
|
154
|
+
| ⬜ 待开始 | X |
|
|
155
|
+
| 🔄 进行中 | X |
|
|
156
|
+
| ✅ 已完成 | X |
|
|
157
|
+
| **总计** | **X** |
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### TODO 列表
|
|
161
|
+
|
|
162
|
+
按状态分组展示,每组内部按优先级排序(高 → 中 → 低):
|
|
163
|
+
|
|
164
|
+
```markdown
|
|
165
|
+
## ⬜ 待开始
|
|
166
|
+
|
|
167
|
+
### 🔴 [TODO 简短描述]
|
|
168
|
+
|
|
169
|
+
- **来源**:[文章标题](../../../path/to/file.md)
|
|
170
|
+
- **日期**:YYYY-MM-DD
|
|
171
|
+
- **原文**:
|
|
172
|
+
> 引用相关原文片段
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
### 🟡 [TODO 简短描述]
|
|
177
|
+
|
|
178
|
+
...
|
|
179
|
+
|
|
180
|
+
## 🔄 进行中
|
|
181
|
+
|
|
182
|
+
...
|
|
183
|
+
|
|
184
|
+
## ✅ 已完成
|
|
185
|
+
|
|
186
|
+
...
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## 引用链接规范
|
|
192
|
+
|
|
193
|
+
- 引用原文链接时,保证链接有效
|
|
194
|
+
- **永远链接到具体的 Markdown 文件**,不要链接目录
|
|
195
|
+
- **链接文本应当是对应的标题**,而不是文件名
|
|
196
|
+
- 由于生成到 `.czon/AIGC/TODO/` 目录,引用时使用 `../../../` 开头的相对路径
|
|
197
|
+
|
|
198
|
+
**正确示例**:
|
|
199
|
+
|
|
200
|
+
```markdown
|
|
201
|
+
[资本持久战:个人投资者跨越阶级的战略](../../../INSIGHTS/6.md)
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
**错误示例**:
|
|
205
|
+
|
|
206
|
+
```markdown
|
|
207
|
+
[INSIGHTS/6.md](../../../INSIGHTS/6.md) ← 使用了文件名而非标题
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## 分段写入规则
|
|
213
|
+
|
|
214
|
+
由于报告内容可能很长,**必须分段写入**,禁止一次性写入整个报告。
|
|
215
|
+
|
|
216
|
+
### 步骤 1:写入骨架文件
|
|
217
|
+
|
|
218
|
+
首先创建报告文件,写入头部、概览占位、各状态章节标题和占位标记。
|
|
219
|
+
|
|
220
|
+
### 步骤 2:逐章节填充内容
|
|
221
|
+
|
|
222
|
+
按章节顺序,逐一替换占位标记为实际内容:
|
|
223
|
+
|
|
224
|
+
1. 每次只填充**一个章节**的内容
|
|
225
|
+
2. 单次写入内容控制在 **2000 字以内**
|
|
226
|
+
3. 如果某个章节超过 2000 字,拆分为多次写入
|
|
227
|
+
|
|
228
|
+
### 步骤 3:完整性检查
|
|
229
|
+
|
|
230
|
+
所有章节填充完毕后:
|
|
231
|
+
|
|
232
|
+
1. 读取完整文件,确认无遗漏的占位标记
|
|
233
|
+
2. 确认所有链接格式正确
|
|
234
|
+
3. 确认概览中的统计数字与实际 TODO 数量一致
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## 禁止行为
|
|
239
|
+
|
|
240
|
+
- ❌ 不得在阅读完所有文件前开始生成报告
|
|
241
|
+
- ❌ 不得跳过任何文件
|
|
242
|
+
- ❌ 不得虚构不存在的 TODO 项
|
|
243
|
+
- ❌ 不得虚构完成状态(无法判断时标记为「待开始」)
|
|
244
|
+
- ❌ 不得遗漏明确标记的 TODO(如文中出现 "TODO" 字样的条目)
|