@itradingai/aiwiki 0.2.16 → 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 +41 -12
- package/dist/src/app.js +226 -23
- package/dist/src/context.js +173 -16
- 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 +59 -7
- package/docs/FAQ.md +4 -2
- package/docs/README.md +3 -4
- package/docs/ROADMAP.md +4 -0
- package/docs/USAGE.md +74 -12
- package/docs/development-log.md +227 -0
- package/package.json +12 -2
- package/skill/LINT_PROTOCOL.md +16 -7
- package/skill/QUERY_PROTOCOL.md +21 -4
- package/skill/SKILL.md +65 -5
- package/skill/UPGRADE_NOTES.md +22 -0
package/dist/src/context.js
CHANGED
|
@@ -11,13 +11,28 @@ const GROUPS = [
|
|
|
11
11
|
{ key: "outlines", dir: "08-outputs/outlines", weight: 2 },
|
|
12
12
|
{ key: "raw_refs", dir: "02-raw/articles", weight: 1 }
|
|
13
13
|
];
|
|
14
|
-
|
|
14
|
+
const DEFAULT_LIMIT = 10;
|
|
15
|
+
export async function buildContext(rootPath, query, options = {}, now = new Date().toISOString()) {
|
|
15
16
|
const root = path.resolve(rootPath);
|
|
16
17
|
const tokens = tokenize(query);
|
|
18
|
+
const limit = normalizeLimit(options.limit);
|
|
19
|
+
const filters = normalizeFilters(options.filters);
|
|
17
20
|
const result = {
|
|
18
21
|
schema_version: "aiwiki.context.v1",
|
|
19
22
|
query,
|
|
20
23
|
generated_at: now,
|
|
24
|
+
query_scope: {
|
|
25
|
+
filters,
|
|
26
|
+
limit,
|
|
27
|
+
searched_groups: []
|
|
28
|
+
},
|
|
29
|
+
result_quality: {
|
|
30
|
+
total_matches: 0,
|
|
31
|
+
best_score: 0,
|
|
32
|
+
has_wiki_entry: false,
|
|
33
|
+
warnings: []
|
|
34
|
+
},
|
|
35
|
+
recommended_next_action: "broaden_query",
|
|
21
36
|
matches: {
|
|
22
37
|
wiki_entries: [],
|
|
23
38
|
source_cards: [],
|
|
@@ -26,34 +41,41 @@ export async function buildContext(rootPath, query, now = new Date().toISOString
|
|
|
26
41
|
outlines: [],
|
|
27
42
|
raw_refs: []
|
|
28
43
|
},
|
|
29
|
-
suggested_answer_structure: ["
|
|
44
|
+
suggested_answer_structure: ["topic overview", "core claims", "available evidence", "reuse judgment", "next action"],
|
|
30
45
|
warnings: []
|
|
31
46
|
};
|
|
32
47
|
if (!tokens.length) {
|
|
33
48
|
result.warnings.push("query is empty after tokenization");
|
|
49
|
+
finalizeQuality(result);
|
|
34
50
|
return result;
|
|
35
51
|
}
|
|
36
52
|
for (const group of GROUPS.filter((item) => item.key !== "raw_refs")) {
|
|
53
|
+
if (!groupAllowed(group.key, filters.type)) {
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
37
56
|
const dir = path.join(root, group.dir);
|
|
38
57
|
if (!(await exists(dir))) {
|
|
39
58
|
continue;
|
|
40
59
|
}
|
|
41
|
-
|
|
42
|
-
|
|
60
|
+
result.query_scope.searched_groups.push(group.key);
|
|
61
|
+
const matches = await searchDir(root, dir, tokens, group.weight, group.key, filters);
|
|
62
|
+
result.matches[group.key].push(...matches.slice(0, limit));
|
|
43
63
|
}
|
|
44
|
-
if (!result.matches.wiki_entries.length) {
|
|
45
|
-
result.warnings.push("
|
|
64
|
+
if (!result.matches.wiki_entries.length && groupAllowed("raw_refs", filters.type)) {
|
|
65
|
+
result.warnings.push("No Wiki Entry matched; results may come from source cards, topics, outlines, or raw references.");
|
|
46
66
|
const rawGroup = GROUPS.find((item) => item.key === "raw_refs");
|
|
47
67
|
if (rawGroup) {
|
|
48
68
|
const rawDir = path.join(root, rawGroup.dir);
|
|
49
69
|
if (await exists(rawDir)) {
|
|
50
|
-
result.
|
|
70
|
+
result.query_scope.searched_groups.push(rawGroup.key);
|
|
71
|
+
result.matches.raw_refs.push(...(await searchDir(root, rawDir, tokens, rawGroup.weight, rawGroup.key, filters)).slice(0, limit));
|
|
51
72
|
}
|
|
52
73
|
}
|
|
53
74
|
}
|
|
75
|
+
finalizeQuality(result);
|
|
54
76
|
return result;
|
|
55
77
|
}
|
|
56
|
-
async function searchDir(root, dir, tokens, weight) {
|
|
78
|
+
async function searchDir(root, dir, tokens, weight, groupKey, filters) {
|
|
57
79
|
const files = await listMarkdownFiles(dir);
|
|
58
80
|
const matches = [];
|
|
59
81
|
for (const file of files) {
|
|
@@ -61,12 +83,24 @@ async function searchDir(root, dir, tokens, weight) {
|
|
|
61
83
|
const parsed = parseMarkdown(text);
|
|
62
84
|
const rel = relativePath(root, file);
|
|
63
85
|
const title = frontmatterString(parsed.frontmatter, "title") ?? path.basename(file, ".md");
|
|
86
|
+
const type = frontmatterString(parsed.frontmatter, "type") ?? groupKey;
|
|
87
|
+
const status = frontmatterString(parsed.frontmatter, "status");
|
|
88
|
+
const sourceRole = frontmatterString(parsed.frontmatter, "source_role");
|
|
89
|
+
const wikiType = frontmatterString(parsed.frontmatter, "wiki_type");
|
|
90
|
+
if (!passesFilters({ type, groupKey, status, source_role: sourceRole, wiki_type: wikiType }, filters)) {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
const topics = frontmatterArray(parsed.frontmatter, "topics");
|
|
94
|
+
const tags = frontmatterArray(parsed.frontmatter, "tags");
|
|
95
|
+
const relatedRefs = relatedReferences(parsed.frontmatter, parsed.body);
|
|
96
|
+
const sourceUrl = frontmatterString(parsed.frontmatter, "source_url") ?? "";
|
|
64
97
|
const haystack = [
|
|
65
98
|
rel,
|
|
66
99
|
title,
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
100
|
+
sourceUrl,
|
|
101
|
+
topics.join(" "),
|
|
102
|
+
tags.join(" "),
|
|
103
|
+
relatedRefs.join(" "),
|
|
70
104
|
parsed.body
|
|
71
105
|
].join("\n").toLowerCase();
|
|
72
106
|
const hits = tokens.filter((token) => haystack.includes(token.toLowerCase())).length;
|
|
@@ -77,8 +111,9 @@ async function searchDir(root, dir, tokens, weight) {
|
|
|
77
111
|
const quality = frontmatterString(parsed.frontmatter, "quality");
|
|
78
112
|
const groundingMarkers = frontmatterArray(parsed.frontmatter, "grounding_markers");
|
|
79
113
|
const groundingNeedsReview = frontmatterBoolean(parsed.frontmatter, "grounding_needs_review");
|
|
114
|
+
const groundingEvidenceAvailable = frontmatterBoolean(parsed.frontmatter, "grounding_evidence_available");
|
|
80
115
|
const warnings = generationMode === "deterministic_fallback"
|
|
81
|
-
? ["
|
|
116
|
+
? ["This Wiki Entry is a deterministic fallback; it may need host-agent enrichment."]
|
|
82
117
|
: [];
|
|
83
118
|
if (groundingNeedsReview) {
|
|
84
119
|
warnings.push(`Grounding needs review${groundingMarkers.length ? `: ${groundingMarkers.join(", ")}` : ""}.`);
|
|
@@ -88,11 +123,19 @@ async function searchDir(root, dir, tokens, weight) {
|
|
|
88
123
|
path: rel,
|
|
89
124
|
summary: frontmatterString(parsed.frontmatter, "summary") ?? summarize(parsed.body, quality),
|
|
90
125
|
score: Number(((hits / tokens.length) * weight).toFixed(2)),
|
|
91
|
-
|
|
92
|
-
|
|
126
|
+
type,
|
|
127
|
+
topics,
|
|
128
|
+
tags,
|
|
129
|
+
source_url: sourceUrl,
|
|
130
|
+
status,
|
|
131
|
+
source_role: sourceRole,
|
|
132
|
+
wiki_type: wikiType,
|
|
133
|
+
match_reasons: matchReasons(tokens, { rel, title, body: parsed.body, topics, tags, relatedRefs, sourceUrl }),
|
|
134
|
+
quality_signals: qualitySignals({ quality, generationMode, groundingEvidenceAvailable, groundingNeedsReview, status, relatedRefs }),
|
|
135
|
+
related_refs: relatedRefs,
|
|
93
136
|
generation_mode: generationMode,
|
|
94
137
|
quality,
|
|
95
|
-
grounding_evidence_available:
|
|
138
|
+
grounding_evidence_available: groundingEvidenceAvailable,
|
|
96
139
|
grounding_needs_review: groundingNeedsReview,
|
|
97
140
|
grounding_markers: groundingMarkers,
|
|
98
141
|
warnings
|
|
@@ -125,7 +168,121 @@ function tokenize(value) {
|
|
|
125
168
|
function summarize(body, quality) {
|
|
126
169
|
const compact = body.replace(/\s+/g, " ").trim();
|
|
127
170
|
if (quality === "scaffold") {
|
|
128
|
-
return "
|
|
171
|
+
return "Only a scaffold preview is available; enrich before relying on it as a final answer.";
|
|
129
172
|
}
|
|
130
173
|
return compact.length > 180 ? `${compact.slice(0, 180)}...` : compact;
|
|
131
174
|
}
|
|
175
|
+
function normalizeLimit(value) {
|
|
176
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
177
|
+
return DEFAULT_LIMIT;
|
|
178
|
+
}
|
|
179
|
+
return Math.max(1, Math.min(50, Math.floor(value)));
|
|
180
|
+
}
|
|
181
|
+
function normalizeFilters(filters) {
|
|
182
|
+
return Object.fromEntries(Object.entries(filters ?? {})
|
|
183
|
+
.filter(([, value]) => typeof value === "string" && value.trim())
|
|
184
|
+
.map(([key, value]) => [key, String(value).trim()]));
|
|
185
|
+
}
|
|
186
|
+
function groupAllowed(groupKey, typeFilter) {
|
|
187
|
+
if (!typeFilter) {
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
return normalizeType(typeFilter) === normalizeType(groupKey);
|
|
191
|
+
}
|
|
192
|
+
function passesFilters(item, filters) {
|
|
193
|
+
if (filters.type && normalizeType(filters.type) !== normalizeType(item.type) && normalizeType(filters.type) !== normalizeType(item.groupKey)) {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
if (filters.status && filters.status !== item.status) {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
if (filters.source_role && filters.source_role !== item.source_role) {
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
if (filters.wiki_type && filters.wiki_type !== item.wiki_type) {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
function normalizeType(value) {
|
|
208
|
+
return value.replace(/-/g, "_").toLowerCase();
|
|
209
|
+
}
|
|
210
|
+
function relatedReferences(frontmatter, body) {
|
|
211
|
+
const refs = [
|
|
212
|
+
frontmatterString(frontmatter, "source_card"),
|
|
213
|
+
frontmatterString(frontmatter, "raw_file"),
|
|
214
|
+
frontmatterString(frontmatter, "claims_note"),
|
|
215
|
+
frontmatterString(frontmatter, "assets_note"),
|
|
216
|
+
frontmatterString(frontmatter, "topics_note"),
|
|
217
|
+
frontmatterString(frontmatter, "outline_note"),
|
|
218
|
+
...extractWikilinks(body)
|
|
219
|
+
].filter((value) => Boolean(value));
|
|
220
|
+
return Array.from(new Set(refs));
|
|
221
|
+
}
|
|
222
|
+
function extractWikilinks(body) {
|
|
223
|
+
const refs = [];
|
|
224
|
+
const pattern = /\[\[([^\]|]+)(?:\|[^\]]+)?\]\]/g;
|
|
225
|
+
for (const match of body.matchAll(pattern)) {
|
|
226
|
+
refs.push(match[1].trim());
|
|
227
|
+
}
|
|
228
|
+
return refs;
|
|
229
|
+
}
|
|
230
|
+
function matchReasons(tokens, fields) {
|
|
231
|
+
const reasons = new Set();
|
|
232
|
+
for (const token of tokens.map((item) => item.toLowerCase())) {
|
|
233
|
+
if (fields.title.toLowerCase().includes(token))
|
|
234
|
+
reasons.add("title");
|
|
235
|
+
if (fields.rel.toLowerCase().includes(token))
|
|
236
|
+
reasons.add("path");
|
|
237
|
+
if (fields.sourceUrl.toLowerCase().includes(token))
|
|
238
|
+
reasons.add("source_url");
|
|
239
|
+
if (fields.topics.join(" ").toLowerCase().includes(token))
|
|
240
|
+
reasons.add("topics");
|
|
241
|
+
if (fields.tags.join(" ").toLowerCase().includes(token))
|
|
242
|
+
reasons.add("tags");
|
|
243
|
+
if (fields.relatedRefs.join(" ").toLowerCase().includes(token))
|
|
244
|
+
reasons.add("relationships");
|
|
245
|
+
if (fields.body.toLowerCase().includes(token))
|
|
246
|
+
reasons.add("body");
|
|
247
|
+
}
|
|
248
|
+
return Array.from(reasons);
|
|
249
|
+
}
|
|
250
|
+
function qualitySignals(item) {
|
|
251
|
+
const signals = [];
|
|
252
|
+
if (item.quality)
|
|
253
|
+
signals.push(`quality:${item.quality}`);
|
|
254
|
+
if (item.status)
|
|
255
|
+
signals.push(`status:${item.status}`);
|
|
256
|
+
if (item.generationMode)
|
|
257
|
+
signals.push(`generation_mode:${item.generationMode}`);
|
|
258
|
+
if (item.groundingEvidenceAvailable === true)
|
|
259
|
+
signals.push("grounding:evidence_available");
|
|
260
|
+
if (item.groundingEvidenceAvailable === false)
|
|
261
|
+
signals.push("grounding:no_evidence_flag");
|
|
262
|
+
if (item.groundingNeedsReview)
|
|
263
|
+
signals.push("grounding:needs_review");
|
|
264
|
+
if (item.relatedRefs.length)
|
|
265
|
+
signals.push("relationships:present");
|
|
266
|
+
return signals;
|
|
267
|
+
}
|
|
268
|
+
function finalizeQuality(result) {
|
|
269
|
+
const all = Object.values(result.matches).flat();
|
|
270
|
+
result.result_quality = {
|
|
271
|
+
total_matches: all.length,
|
|
272
|
+
best_score: all.reduce((best, item) => Math.max(best, item.score), 0),
|
|
273
|
+
has_wiki_entry: result.matches.wiki_entries.length > 0,
|
|
274
|
+
warnings: result.warnings
|
|
275
|
+
};
|
|
276
|
+
if (!all.length) {
|
|
277
|
+
result.recommended_next_action = "broaden_query_or_ingest_source";
|
|
278
|
+
}
|
|
279
|
+
else if (!result.matches.wiki_entries.length) {
|
|
280
|
+
result.recommended_next_action = "review_source_cards_then_create_wiki_entry";
|
|
281
|
+
}
|
|
282
|
+
else if (all.some((item) => item.grounding_needs_review || item.quality === "scaffold")) {
|
|
283
|
+
result.recommended_next_action = "review_grounding_or_enrich_entry";
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
result.recommended_next_action = "use_matches_for_answer";
|
|
287
|
+
}
|
|
288
|
+
}
|
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)}"`,
|