czon 0.9.0 → 0.9.1
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.
|
@@ -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
|
}
|
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. 头部格式
|