@itradingai/aiwiki 0.2.18 → 0.2.19
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/README.md +7 -12
- package/dist/src/app.js +9 -17
- package/dist/src/ingest.js +52 -27
- package/dist/src/lint.js +84 -1
- package/dist/src/payload.js +25 -10
- package/dist/src/wiki-entry.js +3 -3
- package/dist/src/workspace.js +18 -9
- package/docs/20260607-aiwiki-feature-pruning-plan.md +468 -0
- package/docs/20260607-aiwiki-long-term-operating-roadmap.md +409 -0
- package/docs/AGENT_HANDOFF.md +5 -7
- package/docs/FAQ.md +4 -2
- package/docs/README.md +3 -4
- package/docs/ROADMAP.md +4 -0
- package/docs/USAGE.md +18 -12
- package/docs/development-log.md +227 -0
- package/package.json +12 -2
- package/skill/LINT_PROTOCOL.md +6 -4
- package/skill/SKILL.md +12 -5
- package/docs/POSITIONING_CONTEXT_COMPILER_PLAN.md +0 -347
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ AIWiki 是一个开源的 Agent-first 本地 LLM-wiki CLI。
|
|
|
23
23
|
用户给 URL / 正文 / 文件
|
|
24
24
|
-> 宿主 Agent 读取内容并尽量生成 analysis / wiki_entry
|
|
25
25
|
-> aiwiki ingest-agent --stdin
|
|
26
|
-
-> AIWiki 写入 Raw / Source Card / Wiki Entry / Claim / Topic / Outline /
|
|
26
|
+
-> AIWiki 写入 Raw / Source Card / Wiki Entry / Run Summary;有明确内容或请求时再写 Claim / Topic / Outline / Asset
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
### Query:从 Wiki 调度知识
|
|
@@ -55,7 +55,7 @@ aiwiki lint
|
|
|
55
55
|
我的知识库路径:F:\knowledges
|
|
56
56
|
|
|
57
57
|
请检查 Node.js >=20,执行 aiwiki setup --path "我的知识库路径" --yes,
|
|
58
|
-
然后运行 aiwiki agent
|
|
58
|
+
然后运行 aiwiki agent sync --yes 和 aiwiki agent check --json 完成宿主 Agent 对接。
|
|
59
59
|
最后执行 aiwiki doctor 和 aiwiki status,告诉我实际执行了哪些命令和还差什么手动步骤。
|
|
60
60
|
```
|
|
61
61
|
|
|
@@ -63,16 +63,15 @@ aiwiki lint
|
|
|
63
63
|
|
|
64
64
|
```bash
|
|
65
65
|
npx @itradingai/aiwiki@latest setup
|
|
66
|
-
aiwiki agent
|
|
67
|
-
aiwiki agent
|
|
66
|
+
aiwiki agent sync --yes
|
|
67
|
+
aiwiki agent check --json
|
|
68
68
|
```
|
|
69
69
|
|
|
70
70
|
### 第二步:接入宿主 Agent
|
|
71
71
|
|
|
72
72
|
```bash
|
|
73
|
-
aiwiki agent
|
|
74
|
-
aiwiki agent
|
|
75
|
-
aiwiki agent check
|
|
73
|
+
aiwiki agent sync --yes
|
|
74
|
+
aiwiki agent check --json
|
|
76
75
|
```
|
|
77
76
|
|
|
78
77
|
也可以直接输出通用协议:
|
|
@@ -118,15 +117,11 @@ aiwiki query "xxx"
|
|
|
118
117
|
```text
|
|
119
118
|
02-raw/articles/
|
|
120
119
|
03-sources/article-cards/
|
|
121
|
-
04-claims/_suggestions/
|
|
122
120
|
05-wiki/source-knowledge/
|
|
123
|
-
06-assets/_suggestions/
|
|
124
|
-
07-topics/ready/
|
|
125
|
-
08-outputs/outlines/
|
|
126
121
|
09-runs/<run-id>/
|
|
127
122
|
```
|
|
128
123
|
|
|
129
|
-
其中 `05-wiki/source-knowledge/<
|
|
124
|
+
其中 `02-raw/articles/`、`03-sources/article-cards/`、`05-wiki/source-knowledge/` 和 `09-runs/<run-id>/` 是核心产物。`04-claims/_suggestions/`、`06-assets/_suggestions/`、`07-topics/ready/`、`08-outputs/outlines/` 只在 payload 有对应内容或 `request.outputs` 明确请求时生成。
|
|
130
125
|
|
|
131
126
|
### Agent-Enriched Wiki Entry
|
|
132
127
|
|
package/dist/src/app.js
CHANGED
|
@@ -6,7 +6,7 @@ import { fileURLToPath } from "node:url";
|
|
|
6
6
|
import { flagBool, flagString, parseArgs } from "./args.js";
|
|
7
7
|
import { buildContext } from "./context.js";
|
|
8
8
|
import { deriveFileTitle, ingestFile, ingestPayload } from "./ingest.js";
|
|
9
|
-
import { filterLintReport, lintWorkspace, renderLintReport, renderLintSummary, writeLintReport } from "./lint.js";
|
|
9
|
+
import { attachAppliedSafeFixes, filterLintReport, lintWorkspace, removeEmptyOptionalDirs, renderLintReport, renderLintSummary, writeLintReport } from "./lint.js";
|
|
10
10
|
import { CliError, writeLine } from "./output.js";
|
|
11
11
|
import { confirmInit, directorySummary, doctor, exists, initWorkspace, promptForSetup, promptForInitPath, readConfig, resolveWorkspace, setDefaultWorkspace, statusSummary } from "./workspace.js";
|
|
12
12
|
export async function runCli(argv, streams = { stdout: process.stdout, stderr: process.stderr }) {
|
|
@@ -44,8 +44,8 @@ export async function runCli(argv, streams = { stdout: process.stdout, stderr: p
|
|
|
44
44
|
writeLine(streams.stdout, `默认知识库: ${defaultConfig.defaultPath}`);
|
|
45
45
|
writeLine(streams.stdout, `用户配置: ${defaultConfig.configPath}`);
|
|
46
46
|
writeLine(streams.stdout, "Obsidian 入口: dashboards/AIWiki Home.md");
|
|
47
|
-
writeLine(streams.stdout, "下一步: 运行 `aiwiki agent
|
|
48
|
-
writeLine(streams.stdout, "Agent 设置完成后:
|
|
47
|
+
writeLine(streams.stdout, "下一步: 运行 `aiwiki agent sync --yes`,同步 AIWiki 宿主 Agent 对接文件。");
|
|
48
|
+
writeLine(streams.stdout, "Agent 设置完成后: 让宿主 Agent 提供正文并调用 `aiwiki ingest-agent --stdin`,或运行 `aiwiki ingest-file --file <file>`。");
|
|
49
49
|
return 0;
|
|
50
50
|
}
|
|
51
51
|
if (command === "agent" && subcommand === "install") {
|
|
@@ -184,7 +184,8 @@ export async function runCli(argv, streams = { stdout: process.stdout, stderr: p
|
|
|
184
184
|
if (command === "lint") {
|
|
185
185
|
const root = await resolveWorkspace(flagString(args, "path"));
|
|
186
186
|
const severity = parseLintSeverity(flagString(args, "severity"));
|
|
187
|
-
const
|
|
187
|
+
const appliedSafeFixes = flagBool(args, "fix-empty-dirs") ? await removeEmptyOptionalDirs(root) : [];
|
|
188
|
+
const report = filterLintReport(attachAppliedSafeFixes(await lintWorkspace(root), appliedSafeFixes), severity);
|
|
188
189
|
if (flagBool(args, "json")) {
|
|
189
190
|
writeLine(streams.stdout, JSON.stringify(report, null, 2));
|
|
190
191
|
return 0;
|
|
@@ -268,25 +269,16 @@ function printHelp(stream) {
|
|
|
268
269
|
writeLine(stream, "用法:");
|
|
269
270
|
writeLine(stream, " aiwiki setup");
|
|
270
271
|
writeLine(stream, " aiwiki setup --path <path> --yes");
|
|
271
|
-
writeLine(stream, " aiwiki agent list");
|
|
272
|
-
writeLine(stream, " aiwiki agent install");
|
|
273
|
-
writeLine(stream, " aiwiki agent install --agent codex --yes");
|
|
274
272
|
writeLine(stream, " aiwiki agent sync --yes");
|
|
275
273
|
writeLine(stream, " aiwiki agent check --json");
|
|
276
|
-
writeLine(stream, " aiwiki
|
|
274
|
+
writeLine(stream, " aiwiki ingest-agent --stdin");
|
|
275
|
+
writeLine(stream, " aiwiki ingest-file --file <file>");
|
|
277
276
|
writeLine(stream, " aiwiki doctor");
|
|
278
277
|
writeLine(stream, " aiwiki status");
|
|
279
278
|
writeLine(stream, " aiwiki context <query>");
|
|
280
279
|
writeLine(stream, " aiwiki query <query>");
|
|
281
|
-
writeLine(stream, " aiwiki next");
|
|
282
280
|
writeLine(stream, " aiwiki lint");
|
|
283
|
-
writeLine(stream, " aiwiki
|
|
284
|
-
writeLine(stream, " aiwiki ingest-file --file <file>");
|
|
285
|
-
writeLine(stream, " aiwiki init --path <path> --yes --set-default");
|
|
286
|
-
writeLine(stream, " aiwiki config show");
|
|
287
|
-
writeLine(stream, " aiwiki ingest-agent --payload <file>");
|
|
288
|
-
writeLine(stream, " aiwiki ingest-url <url> --content-file <file>");
|
|
289
|
-
writeLine(stream, " aiwiki agent check");
|
|
281
|
+
writeLine(stream, " aiwiki lint --fix-empty-dirs --json");
|
|
290
282
|
}
|
|
291
283
|
function printAgentHelp(stream) {
|
|
292
284
|
writeLine(stream, "AIWiki Agent commands");
|
|
@@ -633,7 +625,7 @@ function printAgentPrompt(stream) {
|
|
|
633
625
|
writeLine(stream, "回复措辞:成功时说“AIWiki 已完成入库,并生成 Wiki 条目。” 如果 wiki_entry_quality=scaffold,说明该条目只是可追溯脚手架,仍需宿主 Agent 后续补全。Dataview 是可选增强,不要替用户安装插件或修改 .obsidian。");
|
|
634
626
|
writeLine(stream, "");
|
|
635
627
|
writeLine(stream, "查询:当用户要求从 AIWiki 里了解某个主题时,调用 `aiwiki context <主题>`。");
|
|
636
|
-
writeLine(stream, "
|
|
628
|
+
writeLine(stream, "整理:当用户要求检查或整理知识库时,先调用 `aiwiki lint --json`;若只有 safe fix 且用户允许整理,再调用 `aiwiki lint --fix-empty-dirs --json`,随后重跑 `aiwiki lint --json`。");
|
|
637
629
|
writeLine(stream, "");
|
|
638
630
|
writeLine(stream, "禁止:让用户保存 payload;让用户每次输入 --path;声称 AIWiki CLI 负责网页抓取;声称 AIWiki CLI 会在没有 Agent 分析字段时自动高质量总结。");
|
|
639
631
|
}
|
package/dist/src/ingest.js
CHANGED
|
@@ -30,23 +30,38 @@ export async function ingestPayload(rootPath, rawPayload) {
|
|
|
30
30
|
const contentFingerprint = createContentFingerprint(content);
|
|
31
31
|
const collisionWarnings = [];
|
|
32
32
|
await detectDuplicateContent(root, payload, contentFingerprint, collisionWarnings);
|
|
33
|
-
const
|
|
33
|
+
const optionalOutputs = optionalOutputPlan(payload);
|
|
34
|
+
const longTermTargets = await chooseLongTermTargets(root, slug, runId, collisionWarnings, optionalOutputs);
|
|
34
35
|
const links = buildArtifactLinks(root, slug, runDirName, runStartedAt, contentFingerprint, longTermTargets);
|
|
35
36
|
const grounding = buildGroundingReport(payload);
|
|
36
37
|
await writeFile(path.join(runDir, "raw.md"), contentFile(payload, content, links), generatedFiles);
|
|
37
38
|
await writeFile(path.join(runDir, "source-card.md"), sourceCard(payload, runDirName, links, grounding), generatedFiles);
|
|
38
39
|
const wikiEntryResult = renderWikiEntry(payload, links);
|
|
39
40
|
await writeFile(path.join(runDir, "wiki-entry.md"), wikiEntryResult.markdown, generatedFiles);
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
if (optionalOutputs.assets && links.assets) {
|
|
42
|
+
await writeFile(path.join(runDir, "creative-assets.md"), creativeAssets(payload, links), generatedFiles);
|
|
43
|
+
}
|
|
44
|
+
if (optionalOutputs.topics && links.topics) {
|
|
45
|
+
await writeFile(path.join(runDir, "topics.md"), topics(payload, links), generatedFiles);
|
|
46
|
+
}
|
|
47
|
+
if (optionalOutputs.outline && links.outline) {
|
|
48
|
+
await writeFile(path.join(runDir, "draft-outline.md"), outline(payload, links), generatedFiles);
|
|
49
|
+
}
|
|
43
50
|
await writeFile(longTermTargets.raw, contentFile(payload, content, links), generatedFiles);
|
|
44
51
|
await writeFile(longTermTargets.sourceCard, sourceCard(payload, runDirName, links, grounding), generatedFiles);
|
|
45
52
|
await writeFile(longTermTargets.wikiEntry, wikiEntryResult.markdown, generatedFiles);
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
53
|
+
if (optionalOutputs.claims && longTermTargets.claims) {
|
|
54
|
+
await writeFile(longTermTargets.claims, claims(payload, links, grounding), generatedFiles);
|
|
55
|
+
}
|
|
56
|
+
if (optionalOutputs.assets && longTermTargets.assets) {
|
|
57
|
+
await writeFile(longTermTargets.assets, creativeAssets(payload, links), generatedFiles);
|
|
58
|
+
}
|
|
59
|
+
if (optionalOutputs.topics && longTermTargets.topics) {
|
|
60
|
+
await writeFile(longTermTargets.topics, topics(payload, links), generatedFiles);
|
|
61
|
+
}
|
|
62
|
+
if (optionalOutputs.outline && longTermTargets.outline) {
|
|
63
|
+
await writeFile(longTermTargets.outline, outline(payload, links), generatedFiles);
|
|
64
|
+
}
|
|
50
65
|
const warnings = [...payload.warnings, ...groundingWarnings(grounding), ...collisionWarnings];
|
|
51
66
|
await writeSummary(root, runDir, payload, generatedFiles, warnings, links, grounding);
|
|
52
67
|
return { runId, runDir, generatedFiles, warnings, agentReport: buildAgentReport(root, runDir, payload, generatedFiles) };
|
|
@@ -69,7 +84,7 @@ export async function ingestFile(rootPath, filePath) {
|
|
|
69
84
|
},
|
|
70
85
|
request: {
|
|
71
86
|
mode: "ingest",
|
|
72
|
-
outputs: ["source_card", "
|
|
87
|
+
outputs: ["source_card", "wiki_entry", "processing_summary"],
|
|
73
88
|
language: "zh-CN"
|
|
74
89
|
}
|
|
75
90
|
});
|
|
@@ -77,19 +92,29 @@ export async function ingestFile(rootPath, filePath) {
|
|
|
77
92
|
export function deriveFileTitle(filePath) {
|
|
78
93
|
return path.basename(filePath, path.extname(filePath));
|
|
79
94
|
}
|
|
80
|
-
async function chooseLongTermTargets(root, slug, runId, warnings) {
|
|
95
|
+
async function chooseLongTermTargets(root, slug, runId, warnings, plan) {
|
|
81
96
|
return {
|
|
82
97
|
raw: await chooseLongTermTarget(root, "02-raw/articles", `${slug}.md`, runId, warnings),
|
|
83
98
|
sourceCard: await chooseLongTermTarget(root, "03-sources/article-cards", `${slug}.md`, runId, warnings),
|
|
84
99
|
wikiEntry: await chooseLongTermTarget(root, "05-wiki/source-knowledge", `${slug}.md`, runId, warnings),
|
|
85
|
-
claims: await chooseLongTermTarget(root, "04-claims/_suggestions", `${slug}-claims.md`, runId, warnings),
|
|
86
|
-
assets: await chooseLongTermTarget(root, "06-assets/_suggestions", `${slug}-assets.md`, runId, warnings),
|
|
87
|
-
topics: await chooseLongTermTarget(root, "07-topics/ready", `${slug}-topics.md`, runId, warnings),
|
|
88
|
-
outline: await chooseLongTermTarget(root, "08-outputs/outlines", `${slug}-outline.md`, runId, warnings)
|
|
100
|
+
claims: plan.claims ? await chooseLongTermTarget(root, "04-claims/_suggestions", `${slug}-claims.md`, runId, warnings) : undefined,
|
|
101
|
+
assets: plan.assets ? await chooseLongTermTarget(root, "06-assets/_suggestions", `${slug}-assets.md`, runId, warnings) : undefined,
|
|
102
|
+
topics: plan.topics ? await chooseLongTermTarget(root, "07-topics/ready", `${slug}-topics.md`, runId, warnings) : undefined,
|
|
103
|
+
outline: plan.outline ? await chooseLongTermTarget(root, "08-outputs/outlines", `${slug}-outline.md`, runId, warnings) : undefined
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function optionalOutputPlan(payload) {
|
|
107
|
+
const outputs = new Set(payload.request.outputs);
|
|
108
|
+
return {
|
|
109
|
+
claims: outputs.has("claims") || outputs.has("claim_suggestions") || Boolean(payload.analysis?.claims.length),
|
|
110
|
+
assets: outputs.has("creative_assets"),
|
|
111
|
+
topics: outputs.has("topics") || Boolean(payload.analysis?.topic_candidates.length),
|
|
112
|
+
outline: outputs.has("draft_outline") || Boolean(payload.analysis?.outline || payload.analysis?.suggested_links.length || payload.analysis?.reusable_judgments.length)
|
|
89
113
|
};
|
|
90
114
|
}
|
|
91
115
|
async function chooseLongTermTarget(root, dir, fileName, runId, warnings) {
|
|
92
116
|
const target = safeJoin(root, dir, fileName);
|
|
117
|
+
await fs.mkdir(path.dirname(target), { recursive: true });
|
|
93
118
|
try {
|
|
94
119
|
await fs.access(target);
|
|
95
120
|
}
|
|
@@ -260,10 +285,10 @@ function sourceCard(payload, runId, links, grounding) {
|
|
|
260
285
|
"",
|
|
261
286
|
`- Wiki 条目:${obsidianLink(links.wikiEntry, "Wiki 条目")}`,
|
|
262
287
|
`- 原文:${obsidianLink(links.raw, "原文")}`,
|
|
263
|
-
`- Claim 建议:${obsidianLink(links.claims, "Claim 建议")}
|
|
264
|
-
`- 素材建议:${obsidianLink(links.assets, "素材建议")}
|
|
265
|
-
`- 选题:${obsidianLink(links.topics, "选题")}
|
|
266
|
-
`- 大纲:${obsidianLink(links.outline, "大纲")}
|
|
288
|
+
...(links.claims ? [`- Claim 建议:${obsidianLink(links.claims, "Claim 建议")}`] : []),
|
|
289
|
+
...(links.assets ? [`- 素材建议:${obsidianLink(links.assets, "素材建议")}`] : []),
|
|
290
|
+
...(links.topics ? [`- 选题:${obsidianLink(links.topics, "选题")}`] : []),
|
|
291
|
+
...(links.outline ? [`- 大纲:${obsidianLink(links.outline, "大纲")}`] : []),
|
|
267
292
|
`- 处理记录:${obsidianLink(links.runSummary, "处理记录")}`,
|
|
268
293
|
"",
|
|
269
294
|
"## 摘要",
|
|
@@ -380,7 +405,7 @@ function topics(payload, links) {
|
|
|
380
405
|
"",
|
|
381
406
|
`- Wiki 条目:${obsidianLink(links.wikiEntry, "Wiki 条目")}`,
|
|
382
407
|
`- 资料卡:${obsidianLink(links.sourceCard, "资料卡")}`,
|
|
383
|
-
`- 大纲:${obsidianLink(links.outline, "大纲")}
|
|
408
|
+
...(links.outline ? [`- 大纲:${obsidianLink(links.outline, "大纲")}`] : []),
|
|
384
409
|
`- ${payload.source.title ?? "Untitled"}`,
|
|
385
410
|
""
|
|
386
411
|
].join("\n");
|
|
@@ -530,10 +555,10 @@ function buildArtifactLinks(root, slug, runDirName, createdAt, contentFingerprin
|
|
|
530
555
|
raw: relativePath(root, longTermTargets.raw),
|
|
531
556
|
sourceCard: relativePath(root, longTermTargets.sourceCard),
|
|
532
557
|
wikiEntry: relativePath(root, longTermTargets.wikiEntry),
|
|
533
|
-
claims: relativePath(root, longTermTargets.claims),
|
|
534
|
-
assets: relativePath(root, longTermTargets.assets),
|
|
535
|
-
topics: relativePath(root, longTermTargets.topics),
|
|
536
|
-
outline: relativePath(root, longTermTargets.outline),
|
|
558
|
+
claims: longTermTargets.claims ? relativePath(root, longTermTargets.claims) : undefined,
|
|
559
|
+
assets: longTermTargets.assets ? relativePath(root, longTermTargets.assets) : undefined,
|
|
560
|
+
topics: longTermTargets.topics ? relativePath(root, longTermTargets.topics) : undefined,
|
|
561
|
+
outline: longTermTargets.outline ? relativePath(root, longTermTargets.outline) : undefined,
|
|
537
562
|
runSummary: `09-runs/${runDirName}/processing-summary.md`
|
|
538
563
|
};
|
|
539
564
|
}
|
|
@@ -545,10 +570,10 @@ function relationshipFrontmatter(links) {
|
|
|
545
570
|
`wiki_entry: "${escapeYaml(obsidianLink(links.wikiEntry, "Wiki 条目"))}"`,
|
|
546
571
|
`source_card: "${escapeYaml(obsidianLink(links.sourceCard, "资料卡"))}"`,
|
|
547
572
|
`raw_note: "${escapeYaml(obsidianLink(links.raw, "原文"))}"`,
|
|
548
|
-
`claims_note: "${escapeYaml(obsidianLink(links.claims, "Claim 建议"))}"
|
|
549
|
-
`assets_note: "${escapeYaml(obsidianLink(links.assets, "素材建议"))}"
|
|
550
|
-
`topics_note: "${escapeYaml(obsidianLink(links.topics, "选题"))}"
|
|
551
|
-
`outline_note: "${escapeYaml(obsidianLink(links.outline, "大纲"))}"
|
|
573
|
+
...(links.claims ? [`claims_note: "${escapeYaml(obsidianLink(links.claims, "Claim 建议"))}"`] : []),
|
|
574
|
+
...(links.assets ? [`assets_note: "${escapeYaml(obsidianLink(links.assets, "素材建议"))}"`] : []),
|
|
575
|
+
...(links.topics ? [`topics_note: "${escapeYaml(obsidianLink(links.topics, "选题"))}"`] : []),
|
|
576
|
+
...(links.outline ? [`outline_note: "${escapeYaml(obsidianLink(links.outline, "大纲"))}"`] : []),
|
|
552
577
|
`run_summary: "${escapeYaml(obsidianLink(links.runSummary, "处理记录"))}"`
|
|
553
578
|
];
|
|
554
579
|
}
|
package/dist/src/lint.js
CHANGED
|
@@ -2,7 +2,7 @@ import { promises as fs } from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { frontmatterArray, frontmatterBoolean, frontmatterString, parseMarkdown } from "./frontmatter.js";
|
|
4
4
|
import { relativePath, safeJoin } from "./paths.js";
|
|
5
|
-
import { exists } from "./workspace.js";
|
|
5
|
+
import { exists, OPTIONAL_DIRS, OPTIONAL_PARENT_DIRS } from "./workspace.js";
|
|
6
6
|
export async function lintWorkspace(rootPath, now = new Date().toISOString()) {
|
|
7
7
|
const root = path.resolve(rootPath);
|
|
8
8
|
const wikiEntries = await readNotes(root, "05-wiki/source-knowledge");
|
|
@@ -20,6 +20,7 @@ export async function lintWorkspace(rootPath, now = new Date().toISOString()) {
|
|
|
20
20
|
];
|
|
21
21
|
const issues = [];
|
|
22
22
|
issues.push(...await systemFileIssues(root));
|
|
23
|
+
issues.push(...await emptyOptionalDirectoryIssues(root));
|
|
23
24
|
const wikiSourceCards = new Set(wikiEntries.map((note) => frontmatterString(note.frontmatter, "source_card")).filter(Boolean));
|
|
24
25
|
for (const card of sourceCards) {
|
|
25
26
|
if (!wikiSourceCards.has(card.path)) {
|
|
@@ -135,6 +136,7 @@ export async function lintWorkspace(rootPath, now = new Date().toISOString()) {
|
|
|
135
136
|
raw_files: rawFiles.length,
|
|
136
137
|
runs: runs.length
|
|
137
138
|
},
|
|
139
|
+
safe_fixes: safeFixSummary(issues),
|
|
138
140
|
issues
|
|
139
141
|
};
|
|
140
142
|
}
|
|
@@ -144,9 +146,31 @@ export function filterLintReport(report, severity) {
|
|
|
144
146
|
}
|
|
145
147
|
return {
|
|
146
148
|
...report,
|
|
149
|
+
safe_fixes: safeFixSummary(report.issues.filter((issue) => issue.severity === severity), report.safe_fixes.applied),
|
|
147
150
|
issues: report.issues.filter((issue) => issue.severity === severity)
|
|
148
151
|
};
|
|
149
152
|
}
|
|
153
|
+
export async function removeEmptyOptionalDirs(rootPath) {
|
|
154
|
+
const root = path.resolve(rootPath);
|
|
155
|
+
const applied = [];
|
|
156
|
+
for (const dir of [...OPTIONAL_DIRS].sort((left, right) => right.length - left.length)) {
|
|
157
|
+
if (await removeKnownEmptyDir(root, dir)) {
|
|
158
|
+
applied.push(safeFixFor(dir));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
for (const dir of [...OPTIONAL_PARENT_DIRS].sort((left, right) => right.length - left.length)) {
|
|
162
|
+
if (await removeKnownEmptyDir(root, dir)) {
|
|
163
|
+
applied.push(safeFixFor(dir));
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return applied;
|
|
167
|
+
}
|
|
168
|
+
export function attachAppliedSafeFixes(report, applied) {
|
|
169
|
+
return {
|
|
170
|
+
...report,
|
|
171
|
+
safe_fixes: safeFixSummary(report.issues, applied)
|
|
172
|
+
};
|
|
173
|
+
}
|
|
150
174
|
export async function writeLintReport(rootPath, report) {
|
|
151
175
|
const root = path.resolve(rootPath);
|
|
152
176
|
const target = safeJoin(root, "dashboards", "Lint Report.md");
|
|
@@ -171,6 +195,8 @@ export function renderLintReport(report) {
|
|
|
171
195
|
`- Errors: ${counts.error}`,
|
|
172
196
|
`- Warnings: ${counts.warning}`,
|
|
173
197
|
`- Info: ${counts.info}`,
|
|
198
|
+
`- Safe Fixes Available: ${report.safe_fixes.available}`,
|
|
199
|
+
`- Only Safe Fixes: ${report.safe_fixes.only_safe_fixes ? "yes" : "no"}`,
|
|
174
200
|
`- Top Issue: ${topIssue ? formatIssueLine(topIssue) : "none"}`,
|
|
175
201
|
"",
|
|
176
202
|
"## Suggested Handling Order",
|
|
@@ -190,6 +216,7 @@ export function renderLintSummary(report, reportPath) {
|
|
|
190
216
|
const topIssue = report.issues[0];
|
|
191
217
|
return [
|
|
192
218
|
`lint_summary: errors=${counts.error} warnings=${counts.warning} info=${counts.info}`,
|
|
219
|
+
`safe_fixes: available=${report.safe_fixes.available} applied=${report.safe_fixes.applied.length} only_safe_fixes=${report.safe_fixes.only_safe_fixes ? "yes" : "no"}`,
|
|
193
220
|
`top_issue: ${topIssue ? formatIssueLine(topIssue) : "none"}`,
|
|
194
221
|
...(reportPath ? [`report: ${reportPath}`] : [])
|
|
195
222
|
].join("\n");
|
|
@@ -211,6 +238,62 @@ async function systemFileIssues(root) {
|
|
|
211
238
|
}
|
|
212
239
|
return issues;
|
|
213
240
|
}
|
|
241
|
+
async function emptyOptionalDirectoryIssues(root) {
|
|
242
|
+
const issues = [];
|
|
243
|
+
for (const dir of [...OPTIONAL_DIRS, ...OPTIONAL_PARENT_DIRS]) {
|
|
244
|
+
if (!(await isExistingEmptyDirectory(path.join(root, dir)))) {
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
issues.push({
|
|
248
|
+
severity: "info",
|
|
249
|
+
path: dir,
|
|
250
|
+
category: "empty_optional_directory",
|
|
251
|
+
action: "remove_empty_optional_dir",
|
|
252
|
+
message: `Optional directory is empty and can be safely removed: ${dir}`,
|
|
253
|
+
suggestion: "Run aiwiki lint --fix-empty-dirs --json to remove known empty optional directories.",
|
|
254
|
+
safe_fix: safeFixFor(dir)
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
return issues;
|
|
258
|
+
}
|
|
259
|
+
async function removeKnownEmptyDir(root, dir) {
|
|
260
|
+
const target = path.join(root, dir);
|
|
261
|
+
if (!(await isExistingEmptyDirectory(target))) {
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
await fs.rmdir(target);
|
|
265
|
+
return true;
|
|
266
|
+
}
|
|
267
|
+
async function isExistingEmptyDirectory(target) {
|
|
268
|
+
try {
|
|
269
|
+
const stats = await fs.stat(target);
|
|
270
|
+
if (!stats.isDirectory()) {
|
|
271
|
+
return false;
|
|
272
|
+
}
|
|
273
|
+
return (await fs.readdir(target)).length === 0;
|
|
274
|
+
}
|
|
275
|
+
catch (error) {
|
|
276
|
+
if (error.code === "ENOENT") {
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
throw error;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
function safeFixFor(dir) {
|
|
283
|
+
return {
|
|
284
|
+
action: "remove_empty_optional_dir",
|
|
285
|
+
path: dir,
|
|
286
|
+
command: "aiwiki lint --fix-empty-dirs --json"
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
function safeFixSummary(issues, applied = []) {
|
|
290
|
+
const available = issues.filter((issue) => issue.safe_fix).length;
|
|
291
|
+
return {
|
|
292
|
+
available,
|
|
293
|
+
applied,
|
|
294
|
+
only_safe_fixes: issues.length > 0 && issues.every((issue) => Boolean(issue.safe_fix))
|
|
295
|
+
};
|
|
296
|
+
}
|
|
214
297
|
async function readNotes(root, dir) {
|
|
215
298
|
const absolute = path.join(root, dir);
|
|
216
299
|
if (!(await exists(absolute))) {
|
package/dist/src/payload.js
CHANGED
|
@@ -40,11 +40,8 @@ export function normalizePayload(raw, runStartedAt) {
|
|
|
40
40
|
const requestRaw = isRecord(raw.request) ? raw.request : {};
|
|
41
41
|
const requestedOutputs = Array.isArray(requestRaw.outputs)
|
|
42
42
|
? requestRaw.outputs.filter((item) => typeof item === "string")
|
|
43
|
-
:
|
|
44
|
-
const outputs =
|
|
45
|
-
if (fetchStatus !== "failed" && requestedOutputs.length && hasCustomOutputRequest(requestedOutputs)) {
|
|
46
|
-
warnings.push("AIWiki 会为每条输入生成完整资料产物,request.outputs 已按全量输出处理。");
|
|
47
|
-
}
|
|
43
|
+
: undefined;
|
|
44
|
+
const outputs = normalizeOutputs(requestedOutputs, fetchStatus, warnings);
|
|
48
45
|
if (typeof raw.target_kb === "string" && raw.target_kb.trim()) {
|
|
49
46
|
warnings.push(`target_kb=${raw.target_kb} 已被当前知识库流程忽略。`);
|
|
50
47
|
}
|
|
@@ -313,9 +310,27 @@ function hasAnalysisContent(analysis) {
|
|
|
313
310
|
analysis.suggested_links.length ||
|
|
314
311
|
analysis.outline);
|
|
315
312
|
}
|
|
316
|
-
function
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
313
|
+
function normalizeOutputs(outputs, fetchStatus, warnings) {
|
|
314
|
+
if (fetchStatus === "failed") {
|
|
315
|
+
return ["processing_summary"];
|
|
316
|
+
}
|
|
317
|
+
const core = ["source_card", "wiki_entry", "processing_summary"];
|
|
318
|
+
const allowed = new Set([
|
|
319
|
+
...core,
|
|
320
|
+
"claims",
|
|
321
|
+
"claim_suggestions",
|
|
322
|
+
"creative_assets",
|
|
323
|
+
"topics",
|
|
324
|
+
"draft_outline"
|
|
325
|
+
]);
|
|
326
|
+
const requested = outputs ?? core;
|
|
327
|
+
const normalized = new Set(core);
|
|
328
|
+
for (const output of requested) {
|
|
329
|
+
if (!allowed.has(output)) {
|
|
330
|
+
warnings.push(`request.outputs ignored unknown output: ${output}`);
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
normalized.add(output);
|
|
334
|
+
}
|
|
335
|
+
return [...normalized];
|
|
321
336
|
}
|
package/dist/src/wiki-entry.js
CHANGED
|
@@ -32,9 +32,9 @@ function wikiFrontmatter(payload, links, title, mode, quality, grounding) {
|
|
|
32
32
|
`source_type: "${escapeYaml(payload.source.kind)}"`,
|
|
33
33
|
`source_card: "${escapeYaml(links.sourceCard)}"`,
|
|
34
34
|
`raw_file: "${escapeYaml(links.raw)}"`,
|
|
35
|
-
`claims_file: "${escapeYaml(links.claims)}"
|
|
36
|
-
`topics_file: "${escapeYaml(links.topics)}"
|
|
37
|
-
`outline_file: "${escapeYaml(links.outline)}"
|
|
35
|
+
...(links.claims ? [`claims_file: "${escapeYaml(links.claims)}"`] : []),
|
|
36
|
+
...(links.topics ? [`topics_file: "${escapeYaml(links.topics)}"`] : []),
|
|
37
|
+
...(links.outline ? [`outline_file: "${escapeYaml(links.outline)}"`] : []),
|
|
38
38
|
`run_summary: "${escapeYaml(links.runSummary)}"`,
|
|
39
39
|
`run_id: "${escapeYaml(links.runId)}"`,
|
|
40
40
|
`content_fingerprint: "${escapeYaml(links.contentFingerprint)}"`,
|
package/dist/src/workspace.js
CHANGED
|
@@ -7,21 +7,30 @@ import { frontmatterBoolean, frontmatterString, parseMarkdown } from "./frontmat
|
|
|
7
7
|
import { CliError } from "./output.js";
|
|
8
8
|
import { relativePath } from "./paths.js";
|
|
9
9
|
export const CONFIG_FILE = "aiwiki.yaml";
|
|
10
|
-
export const
|
|
10
|
+
export const CORE_DIRS = [
|
|
11
11
|
"02-raw/articles",
|
|
12
12
|
"03-sources/article-cards",
|
|
13
|
-
"04-claims/_suggestions",
|
|
14
13
|
"05-wiki",
|
|
15
14
|
"05-wiki/source-knowledge",
|
|
16
|
-
"06-assets/_suggestions",
|
|
17
|
-
"07-topics/ready",
|
|
18
|
-
"08-outputs/outlines",
|
|
19
15
|
"09-runs",
|
|
20
16
|
"dashboards",
|
|
21
17
|
"_system/templates",
|
|
22
18
|
"_system/schemas",
|
|
23
19
|
"_system/logs"
|
|
24
20
|
];
|
|
21
|
+
export const OPTIONAL_DIRS = [
|
|
22
|
+
"04-claims/_suggestions",
|
|
23
|
+
"06-assets/_suggestions",
|
|
24
|
+
"07-topics/ready",
|
|
25
|
+
"08-outputs/outlines"
|
|
26
|
+
];
|
|
27
|
+
export const OPTIONAL_PARENT_DIRS = [
|
|
28
|
+
"04-claims",
|
|
29
|
+
"06-assets",
|
|
30
|
+
"07-topics",
|
|
31
|
+
"08-outputs"
|
|
32
|
+
];
|
|
33
|
+
export const REQUIRED_DIRS = CORE_DIRS;
|
|
25
34
|
const WORKSPACE_SEEDS = [
|
|
26
35
|
{
|
|
27
36
|
path: "_system/purpose.md",
|
|
@@ -357,7 +366,7 @@ export async function initWorkspace(rootPath) {
|
|
|
357
366
|
const root = resolveRoot(rootPath);
|
|
358
367
|
await fs.mkdir(root, { recursive: true });
|
|
359
368
|
const createdDirs = [];
|
|
360
|
-
for (const dir of
|
|
369
|
+
for (const dir of CORE_DIRS) {
|
|
361
370
|
const absolute = path.join(root, dir);
|
|
362
371
|
const existed = await exists(absolute);
|
|
363
372
|
await fs.mkdir(absolute, { recursive: true });
|
|
@@ -452,7 +461,7 @@ export async function promptForSetup(options) {
|
|
|
452
461
|
if (!options.yes) {
|
|
453
462
|
const root = resolveRoot(rootPath);
|
|
454
463
|
output.write(`将创建或补齐 AIWiki 目录: ${root}\n`);
|
|
455
|
-
for (const dir of
|
|
464
|
+
for (const dir of CORE_DIRS) {
|
|
456
465
|
output.write(` - ${dir}\n`);
|
|
457
466
|
}
|
|
458
467
|
const answer = await rl.question("确认创建?输入 y 继续: ");
|
|
@@ -483,7 +492,7 @@ async function promptForSetupFromPipe(options) {
|
|
|
483
492
|
if (!options.yes) {
|
|
484
493
|
const root = resolveRoot(rootPath);
|
|
485
494
|
output.write(`将创建或补齐 AIWiki 目录: ${root}\n`);
|
|
486
|
-
for (const dir of
|
|
495
|
+
for (const dir of CORE_DIRS) {
|
|
487
496
|
output.write(` - ${dir}\n`);
|
|
488
497
|
}
|
|
489
498
|
output.write("确认创建?输入 y 继续: ");
|
|
@@ -503,7 +512,7 @@ export async function confirmInit(rootPath) {
|
|
|
503
512
|
const rl = createInterface({ input, output });
|
|
504
513
|
try {
|
|
505
514
|
output.write(`将创建或补齐 AIWiki 目录: ${root}\n`);
|
|
506
|
-
for (const dir of
|
|
515
|
+
for (const dir of CORE_DIRS) {
|
|
507
516
|
output.write(` - ${dir}\n`);
|
|
508
517
|
}
|
|
509
518
|
const answer = await rl.question("确认创建?输入 y 继续: ");
|