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.findMarkdownEntries)(process.cwd(), { aigc: this.aigc });
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 findMarkdownEntries function.
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
@@ -1,23 +1,25 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.findMarkdownEntries = void 0;
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命令查找所有Markdown文件
10
+ * 使用git命令查找项目中的文件
11
11
  * 使用git ls-files --others --cached --exclude-standard获取所有文件
12
- * 然后过滤掉.czon目录和只保留.md文件
12
+ * 然后过滤掉.czon目录,默认只保留.md文件
13
13
  *
14
14
  * @param dirPath 要扫描的目录路径
15
15
  * @param options 可选参数
16
16
  * @param options.aigc 是否包含 .czon/AIGC 目录下的文件
17
- * @returns Promise<string[]> 返回Markdown文件的相对路径数组
17
+ * @param options.allTypes 是否返回所有类型的文件(默认 false,仅返回 .md 文件)
18
+ * @returns Promise<string[]> 返回文件的相对路径数组
18
19
  */
19
- const findMarkdownEntries = async (dirPath, options) => {
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')); // 只保留.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.findMarkdownEntries = findMarkdownEntries;
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.findMarkdownEntries)(paths_1.INPUT_DIR, { aigc: true });
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
- console.log(`📄 发现 ${markdownFiles.length} 个 Markdown 文件\n`);
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.findMarkdownEntries)(paths_1.INPUT_DIR, { aigc: true });
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "czon",
3
- "version": "0.9.0",
3
+ "version": "0.9.1",
4
4
  "description": "CZON - AI enhanced Markdown content engine",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -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
- [资本持久战:个人投资者跨越阶级的战略](../INSIGHTS/6.md)
124
- [从创作到分发——构建AI-Native内容引擎](../INSIGHTS/4.md)
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](../INSIGHTS/6.md) ← 使用了文件名而非标题
131
- [资本持久战](../INSIGHTS/) ← 链接到了目录
130
+ [INSIGHTS/6.md](../../../INSIGHTS/6.md) ← 使用了文件名而非标题
131
+ [资本持久战](../../../INSIGHTS/) ← 链接到了目录
132
132
  ```
133
133
 
134
134
  ### 3. 头部格式