@itradingai/aiwiki 0.2.15 → 0.2.18
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 +34 -0
- package/dist/src/app.js +237 -11
- package/dist/src/context.js +173 -16
- package/dist/src/lint.js +163 -23
- package/docs/AGENT_HANDOFF.md +76 -0
- package/docs/POSITIONING_CONTEXT_COMPILER_PLAN.md +347 -0
- package/docs/README.md +1 -0
- package/docs/USAGE.md +67 -1
- package/docs/development-log.md +97 -0
- package/package.json +1 -1
- package/skill/LINT_PROTOCOL.md +12 -5
- package/skill/QUERY_PROTOCOL.md +21 -4
- package/skill/SKILL.md +54 -1
- package/skill/UPGRADE_NOTES.md +22 -0
package/dist/src/lint.js
CHANGED
|
@@ -19,14 +19,17 @@ export async function lintWorkspace(rootPath, now = new Date().toISOString()) {
|
|
|
19
19
|
...await readNotes(root, "08-outputs/outlines")
|
|
20
20
|
];
|
|
21
21
|
const issues = [];
|
|
22
|
+
issues.push(...await systemFileIssues(root));
|
|
22
23
|
const wikiSourceCards = new Set(wikiEntries.map((note) => frontmatterString(note.frontmatter, "source_card")).filter(Boolean));
|
|
23
24
|
for (const card of sourceCards) {
|
|
24
25
|
if (!wikiSourceCards.has(card.path)) {
|
|
25
26
|
issues.push({
|
|
26
27
|
severity: "warning",
|
|
27
28
|
path: card.path,
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
category: "isolated_source_card",
|
|
30
|
+
action: "reingest",
|
|
31
|
+
message: "Source Card has no matching Wiki Entry.",
|
|
32
|
+
suggestion: `Reingest or create a matching 05-wiki/source-knowledge/${path.basename(card.path)} entry.`
|
|
30
33
|
});
|
|
31
34
|
}
|
|
32
35
|
}
|
|
@@ -35,26 +38,68 @@ export async function lintWorkspace(rootPath, now = new Date().toISOString()) {
|
|
|
35
38
|
const rawFile = frontmatterString(entry.frontmatter, "raw_file");
|
|
36
39
|
const mode = frontmatterString(entry.frontmatter, "generation_mode");
|
|
37
40
|
if (!sourceCard) {
|
|
38
|
-
issues.push({
|
|
41
|
+
issues.push({
|
|
42
|
+
severity: "warning",
|
|
43
|
+
path: entry.path,
|
|
44
|
+
category: "missing_source",
|
|
45
|
+
action: "reingest",
|
|
46
|
+
message: "Wiki Entry is missing source_card.",
|
|
47
|
+
suggestion: "Add the vault path of the source card."
|
|
48
|
+
});
|
|
39
49
|
}
|
|
40
50
|
if (!rawFile) {
|
|
41
|
-
issues.push({
|
|
51
|
+
issues.push({
|
|
52
|
+
severity: "warning",
|
|
53
|
+
path: entry.path,
|
|
54
|
+
category: "missing_source",
|
|
55
|
+
action: "reingest",
|
|
56
|
+
message: "Wiki Entry is missing raw_file.",
|
|
57
|
+
suggestion: "Add the vault path of the raw source file."
|
|
58
|
+
});
|
|
42
59
|
}
|
|
43
60
|
if (mode === "deterministic_fallback") {
|
|
44
|
-
issues.push({
|
|
61
|
+
issues.push({
|
|
62
|
+
severity: "info",
|
|
63
|
+
path: entry.path,
|
|
64
|
+
category: "stale_scaffold",
|
|
65
|
+
action: "enrich",
|
|
66
|
+
message: "Wiki Entry is a deterministic fallback and only contains source trace plus a content preview.",
|
|
67
|
+
suggestion: "Ask the host Agent to enrich it with analysis or a full wiki_entry."
|
|
68
|
+
});
|
|
45
69
|
const createdAt = Date.parse(frontmatterString(entry.frontmatter, "created_at") ?? "");
|
|
46
70
|
if (Number.isFinite(createdAt) && Date.parse(now) - createdAt > 7 * 24 * 60 * 60 * 1000) {
|
|
47
|
-
issues.push({
|
|
71
|
+
issues.push({
|
|
72
|
+
severity: "warning",
|
|
73
|
+
path: entry.path,
|
|
74
|
+
category: "stale_scaffold",
|
|
75
|
+
action: "enrich",
|
|
76
|
+
message: "Fallback Wiki Entry is older than 7 days.",
|
|
77
|
+
suggestion: "Reingest with a host Agent to generate an enriched Wiki Entry."
|
|
78
|
+
});
|
|
48
79
|
}
|
|
49
80
|
}
|
|
50
81
|
if (mode === "agent_enriched") {
|
|
51
|
-
const hasSummary =
|
|
52
|
-
const hasKeyPoints =
|
|
82
|
+
const hasSummary = /##\s+.+/.test(entry.body) || Boolean(frontmatterString(entry.frontmatter, "summary"));
|
|
83
|
+
const hasKeyPoints = /-\s+/.test(entry.body);
|
|
53
84
|
if (!hasSummary) {
|
|
54
|
-
issues.push({
|
|
85
|
+
issues.push({
|
|
86
|
+
severity: "warning",
|
|
87
|
+
path: entry.path,
|
|
88
|
+
category: "weak_entry",
|
|
89
|
+
action: "enrich",
|
|
90
|
+
message: "agent_enriched Wiki Entry is missing a summary.",
|
|
91
|
+
suggestion: "Ask the host Agent to provide analysis.summary."
|
|
92
|
+
});
|
|
55
93
|
}
|
|
56
94
|
if (!hasKeyPoints) {
|
|
57
|
-
issues.push({
|
|
95
|
+
issues.push({
|
|
96
|
+
severity: "warning",
|
|
97
|
+
path: entry.path,
|
|
98
|
+
category: "weak_entry",
|
|
99
|
+
action: "enrich",
|
|
100
|
+
message: "agent_enriched Wiki Entry is missing key points.",
|
|
101
|
+
suggestion: "Ask the host Agent to provide analysis.key_points."
|
|
102
|
+
});
|
|
58
103
|
}
|
|
59
104
|
}
|
|
60
105
|
if (frontmatterBoolean(entry.frontmatter, "grounding_needs_review") === true) {
|
|
@@ -62,15 +107,24 @@ export async function lintWorkspace(rootPath, now = new Date().toISOString()) {
|
|
|
62
107
|
issues.push({
|
|
63
108
|
severity: "warning",
|
|
64
109
|
path: entry.path,
|
|
65
|
-
|
|
66
|
-
|
|
110
|
+
category: "grounding_review",
|
|
111
|
+
action: "mark_reviewed",
|
|
112
|
+
message: `Wiki Entry needs grounding review${markers.length ? `: ${markers.join(", ")}` : "."}`,
|
|
113
|
+
suggestion: "Check whether source quotes are present in Raw. Heuristic coverage risks are not confirmed facts."
|
|
67
114
|
});
|
|
68
115
|
}
|
|
69
116
|
if (frontmatterBoolean(entry.frontmatter, "represents_user_view") === true && frontmatterString(entry.frontmatter, "source_role") !== "output") {
|
|
70
|
-
issues.push({
|
|
117
|
+
issues.push({
|
|
118
|
+
severity: "warning",
|
|
119
|
+
path: entry.path,
|
|
120
|
+
category: "metadata_boundary",
|
|
121
|
+
action: "mark_reviewed",
|
|
122
|
+
message: "Only output source_role entries should represent the user's view.",
|
|
123
|
+
suggestion: "Set represents_user_view to false, or change source_role to output when it is user-authored output."
|
|
124
|
+
});
|
|
71
125
|
}
|
|
72
126
|
}
|
|
73
|
-
issues.push(...duplicateIssues(sourceCards, "source_url", "
|
|
127
|
+
issues.push(...duplicateIssues(sourceCards, "source_url", "Duplicate URL"));
|
|
74
128
|
issues.push(...duplicateTitles(allNotes));
|
|
75
129
|
issues.push(...brokenLinkIssues(root, allNotes));
|
|
76
130
|
return {
|
|
@@ -84,6 +138,15 @@ export async function lintWorkspace(rootPath, now = new Date().toISOString()) {
|
|
|
84
138
|
issues
|
|
85
139
|
};
|
|
86
140
|
}
|
|
141
|
+
export function filterLintReport(report, severity) {
|
|
142
|
+
if (!severity) {
|
|
143
|
+
return report;
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
...report,
|
|
147
|
+
issues: report.issues.filter((issue) => issue.severity === severity)
|
|
148
|
+
};
|
|
149
|
+
}
|
|
87
150
|
export async function writeLintReport(rootPath, report) {
|
|
88
151
|
const root = path.resolve(rootPath);
|
|
89
152
|
const target = safeJoin(root, "dashboards", "Lint Report.md");
|
|
@@ -92,6 +155,8 @@ export async function writeLintReport(rootPath, report) {
|
|
|
92
155
|
return relativePath(root, target);
|
|
93
156
|
}
|
|
94
157
|
export function renderLintReport(report) {
|
|
158
|
+
const counts = severityCounts(report.issues);
|
|
159
|
+
const topIssue = report.issues[0];
|
|
95
160
|
return [
|
|
96
161
|
"# AIWiki Lint Report",
|
|
97
162
|
"",
|
|
@@ -103,17 +168,49 @@ export function renderLintReport(report) {
|
|
|
103
168
|
`- Source Cards: ${report.summary.source_cards}`,
|
|
104
169
|
`- Raw Files: ${report.summary.raw_files}`,
|
|
105
170
|
`- Runs: ${report.summary.runs}`,
|
|
171
|
+
`- Errors: ${counts.error}`,
|
|
172
|
+
`- Warnings: ${counts.warning}`,
|
|
173
|
+
`- Info: ${counts.info}`,
|
|
174
|
+
`- Top Issue: ${topIssue ? formatIssueLine(topIssue) : "none"}`,
|
|
106
175
|
"",
|
|
107
|
-
"##
|
|
176
|
+
"## Suggested Handling Order",
|
|
108
177
|
"",
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
178
|
+
"- Fix error issues first.",
|
|
179
|
+
"- Review warning issues next.",
|
|
180
|
+
"- Use info issues for enrichment and cleanup backlog.",
|
|
181
|
+
"",
|
|
182
|
+
...renderIssueGroup("Errors", report.issues.filter((issue) => issue.severity === "error")),
|
|
183
|
+
...renderIssueGroup("Warnings", report.issues.filter((issue) => issue.severity === "warning")),
|
|
184
|
+
...renderIssueGroup("Info", report.issues.filter((issue) => issue.severity === "info")),
|
|
114
185
|
""
|
|
115
186
|
].join("\n");
|
|
116
187
|
}
|
|
188
|
+
export function renderLintSummary(report, reportPath) {
|
|
189
|
+
const counts = severityCounts(report.issues);
|
|
190
|
+
const topIssue = report.issues[0];
|
|
191
|
+
return [
|
|
192
|
+
`lint_summary: errors=${counts.error} warnings=${counts.warning} info=${counts.info}`,
|
|
193
|
+
`top_issue: ${topIssue ? formatIssueLine(topIssue) : "none"}`,
|
|
194
|
+
...(reportPath ? [`report: ${reportPath}`] : [])
|
|
195
|
+
].join("\n");
|
|
196
|
+
}
|
|
197
|
+
async function systemFileIssues(root) {
|
|
198
|
+
const issues = [];
|
|
199
|
+
for (const systemFile of ["_system/purpose.md", "_system/index.md", "_system/log.md"]) {
|
|
200
|
+
if (await exists(path.join(root, systemFile))) {
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
issues.push({
|
|
204
|
+
severity: "error",
|
|
205
|
+
path: systemFile,
|
|
206
|
+
category: "workspace_structure",
|
|
207
|
+
action: "repair_structure",
|
|
208
|
+
message: `Required system file is missing: ${systemFile}`,
|
|
209
|
+
suggestion: `Run aiwiki setup --path "${root}" --yes`
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
return issues;
|
|
213
|
+
}
|
|
117
214
|
async function readNotes(root, dir) {
|
|
118
215
|
const absolute = path.join(root, dir);
|
|
119
216
|
if (!(await exists(absolute))) {
|
|
@@ -163,7 +260,13 @@ function duplicateIssues(notes, field, label) {
|
|
|
163
260
|
seen.set(value, [...(seen.get(value) ?? []), note.path]);
|
|
164
261
|
}
|
|
165
262
|
return Array.from(seen.entries()).flatMap(([value, paths]) => paths.length > 1
|
|
166
|
-
? [{
|
|
263
|
+
? [{
|
|
264
|
+
severity: "warning",
|
|
265
|
+
category: "duplicate",
|
|
266
|
+
action: "mark_reviewed",
|
|
267
|
+
message: `${label}: ${value}`,
|
|
268
|
+
suggestion: paths.join(", ")
|
|
269
|
+
}]
|
|
167
270
|
: []);
|
|
168
271
|
}
|
|
169
272
|
function duplicateTitles(notes) {
|
|
@@ -175,7 +278,13 @@ function duplicateTitles(notes) {
|
|
|
175
278
|
seen.set(note.title, [...(seen.get(note.title) ?? []), note.path]);
|
|
176
279
|
}
|
|
177
280
|
return Array.from(seen.entries()).flatMap(([title, paths]) => paths.length > 1
|
|
178
|
-
? [{
|
|
281
|
+
? [{
|
|
282
|
+
severity: "info",
|
|
283
|
+
category: "duplicate_title",
|
|
284
|
+
action: "archive",
|
|
285
|
+
message: `Duplicate title: ${title}`,
|
|
286
|
+
suggestion: paths.join(", ")
|
|
287
|
+
}]
|
|
179
288
|
: []);
|
|
180
289
|
}
|
|
181
290
|
function brokenLinkIssues(root, notes) {
|
|
@@ -185,12 +294,43 @@ function brokenLinkIssues(root, notes) {
|
|
|
185
294
|
for (const link of note.body.matchAll(/\[\[([^\]|]+)(?:\|[^\]]+)?\]\]/g)) {
|
|
186
295
|
const target = link[1].replace(/\\/g, "/").replace(/\.md$/i, "");
|
|
187
296
|
if (!existing.has(target) && !isRunLocalLink(target)) {
|
|
188
|
-
issues.push({
|
|
297
|
+
issues.push({
|
|
298
|
+
severity: "error",
|
|
299
|
+
path: note.path,
|
|
300
|
+
category: "broken_link",
|
|
301
|
+
action: "fix_link",
|
|
302
|
+
message: `Broken wikilink: ${target}`,
|
|
303
|
+
suggestion: "Check whether the target file exists or update the wikilink."
|
|
304
|
+
});
|
|
189
305
|
}
|
|
190
306
|
}
|
|
191
307
|
}
|
|
192
308
|
return issues;
|
|
193
309
|
}
|
|
310
|
+
function renderIssueGroup(title, issues) {
|
|
311
|
+
return [
|
|
312
|
+
`## ${title}`,
|
|
313
|
+
"",
|
|
314
|
+
...(issues.length ? issues.map((issue) => {
|
|
315
|
+
const suggestion = issue.suggestion ? `\n - Suggested Fix: ${issue.suggestion}` : "";
|
|
316
|
+
const action = issue.action ? `\n - Action: ${issue.action}` : "";
|
|
317
|
+
return `- ${formatIssueLine(issue)}${action}${suggestion}`;
|
|
318
|
+
}) : ["- none"]),
|
|
319
|
+
""
|
|
320
|
+
];
|
|
321
|
+
}
|
|
322
|
+
function formatIssueLine(issue) {
|
|
323
|
+
const suffix = issue.path ? ` (${issue.path})` : "";
|
|
324
|
+
const category = issue.category ? ` {${issue.category}}` : "";
|
|
325
|
+
return `[${issue.severity}]${category} ${issue.message}${suffix}`;
|
|
326
|
+
}
|
|
327
|
+
function severityCounts(issues) {
|
|
328
|
+
return {
|
|
329
|
+
error: issues.filter((issue) => issue.severity === "error").length,
|
|
330
|
+
warning: issues.filter((issue) => issue.severity === "warning").length,
|
|
331
|
+
info: issues.filter((issue) => issue.severity === "info").length
|
|
332
|
+
};
|
|
333
|
+
}
|
|
194
334
|
function isRunLocalLink(target) {
|
|
195
335
|
// Run-local notes are valid trace links but are not part of the long-term note set.
|
|
196
336
|
return target.startsWith("09-runs/");
|
package/docs/AGENT_HANDOFF.md
CHANGED
|
@@ -199,6 +199,28 @@ aiwiki query "<主题>"
|
|
|
199
199
|
aiwiki lint
|
|
200
200
|
```
|
|
201
201
|
|
|
202
|
+
需要机器读取时用:
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
aiwiki lint --json
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
只想看某一级别时用:
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
aiwiki lint --severity error
|
|
212
|
+
aiwiki lint --severity warning
|
|
213
|
+
aiwiki lint --severity info
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
只做临时检查、不改 dashboard 时用:
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
aiwiki lint --no-write
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Lint 输出会包含摘要、最高优先级问题、分级报告,以及建议动作。把 `error` 当作必须先修的结构问题,把 `warning` 当作需要处理或复核的维护问题,把 `info` 当作富集、归档或后续整理 backlog。
|
|
223
|
+
|
|
202
224
|
`context` 返回 JSON,注意其中的 `generation_mode`、`quality` 和 `warnings`。如果结果是 `deterministic_fallback` / `scaffold`,回复时要说明它只是可追溯脚手架,不是高质量知识提炼。
|
|
203
225
|
|
|
204
226
|
`context` 也可能返回 grounding 字段。回复用户时可以把 `grounding_needs_review: true` 解释为“这条资料需要复核证据或覆盖度”,不要说成“AIWiki 已确认漏掉重点”。
|
|
@@ -225,3 +247,57 @@ aiwiki lint
|
|
|
225
247
|
Before ingesting, querying, or reorganizing material, read `_system/purpose.md` in the target AIWiki workspace. Treat it as the local contract for what belongs in this knowledge base, what should stay out, and how future multi-knowledge-base routing should be handled.
|
|
226
248
|
|
|
227
249
|
If the material does not fit the purpose file, do not force it into the knowledge base as confirmed knowledge. Record the mismatch, ask for review when needed, or keep it as a traceable source rather than a claim.
|
|
250
|
+
|
|
251
|
+
## Context JSON for Retrieval
|
|
252
|
+
|
|
253
|
+
When answering from AIWiki, prefer:
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
aiwiki context "<topic>" --limit 10
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Use filters when the user intent is narrow:
|
|
260
|
+
|
|
261
|
+
```bash
|
|
262
|
+
aiwiki context "<topic>" --type wiki_entries --source-role input --wiki-type source_knowledge --status active
|
|
263
|
+
aiwiki context "<topic>" --type source_cards --status to-review
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
`context` is local Markdown/frontmatter retrieval only. Do not describe it as vector search, RAG, a database query, or external web search.
|
|
267
|
+
|
|
268
|
+
Read these fields before responding:
|
|
269
|
+
|
|
270
|
+
- `query_scope`: what was searched and which filters were applied.
|
|
271
|
+
- `result_quality.total_matches` and `result_quality.has_wiki_entry`: whether the answer is grounded in a Wiki Entry or only in supporting artifacts.
|
|
272
|
+
- `recommended_next_action`: whether to answer, enrich, review grounding, or broaden the query.
|
|
273
|
+
- `match_reasons`: why each result matched, such as title, body, tags, topics, relationships, or source URL.
|
|
274
|
+
- `quality_signals`: scaffold, enriched, status, grounding, and relationship hints.
|
|
275
|
+
- `related_refs`: local wikilinks and frontmatter relationships such as `source_card`, `raw_file`, and run artifacts.
|
|
276
|
+
|
|
277
|
+
If `quality_signals` contains `quality:scaffold` or `grounding:needs_review`, tell the user the result is a traceable lead that needs enrichment or review. Do not present it as confirmed final knowledge.
|
|
278
|
+
|
|
279
|
+
## Agent Skill Upgrade Flow
|
|
280
|
+
|
|
281
|
+
When the user asks you to install, update, upgrade, or repair AIWiki integration, run:
|
|
282
|
+
|
|
283
|
+
```bash
|
|
284
|
+
aiwiki agent sync --yes
|
|
285
|
+
aiwiki agent check
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
Use JSON when you need exact state:
|
|
289
|
+
|
|
290
|
+
```bash
|
|
291
|
+
aiwiki agent sync --json --yes
|
|
292
|
+
aiwiki agent check --json
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
Interpret sync results:
|
|
296
|
+
|
|
297
|
+
- `installed`: target did not exist and now has the packaged skill.
|
|
298
|
+
- `current`: installed skill already matches the packaged skill.
|
|
299
|
+
- `updated`: installed skill differed; the old file was backed up and replaced.
|
|
300
|
+
- `would_install` / `would_update`: dry-run preview only.
|
|
301
|
+
- `unsupported`: no safe automatic target is known; use `aiwiki prompt agent`.
|
|
302
|
+
|
|
303
|
+
After sync, report the target path, backup path when present, and that the user may need to restart or reload the Agent. Do not claim the new skill is active until the Agent has reloaded it.
|