mirror of
https://github.com/yan-istart/IStart-Note-AI-Plugin.git
synced 2026-07-22 06:51:37 +00:00
fix(rc): address all P0/P1 review items for 2.0 RC
P0: tsconfig lib upgraded from ES6 to ES2018 (matches esbuild target,
eliminates Object.entries type risk)
P1: sourceLinks format safety
- Prompt explicitly forbids [[]] in sourceLinks
- ArtifactRenderer.wikiLink() strips accidental double-wrapping
- All sourceLinks rendering uses wikiLink() helper
P1: High-risk content disclaimer
- ArtifactRenderer.needsSafetyDisclaimer() detects sensitive keywords
(health/baby/medical/legal/investment) and high-risk items
- Automatically prepends warning callout to template/run output
P1: Preview shows concrete file paths
- ArtifactPreviewModal shows exact Knowledge/Artifacts/xxx.md and
Knowledge/Artifact Runs/yyyy xxx.md paths instead of '1-2 files'
P1: README Architecture tree updated with core/artifact + features/artifact
P1: README Configuration table corrected to match actual settings page
P2: YAML frontmatter escaping
- ArtifactRenderer.yamlStr() uses JSON.stringify for safe YAML scalars
- title and target fields no longer break on quotes/special chars
P2: Prompt clarifies 'do not fabricate sourceLinks'
This commit is contained in:
parent
2bf2032b5d
commit
25eb0bc04c
5 changed files with 54 additions and 18 deletions
12
README.md
12
README.md
|
|
@ -122,9 +122,9 @@ Settings are organized into three tabs:
|
|||
|
||||
| Tab | Key settings |
|
||||
| --- | --- |
|
||||
| **Knowledge** | Q&A path, Concepts path, Questions index path, Reading path, auto-classify, concept depth, vault QA source limit |
|
||||
| **Execution** | Require preview, log path, scheduled tasks, safety policy |
|
||||
| **Auxiliary** | API key, Base URL, model, output style, Baidu sync, UI preferences |
|
||||
| **Knowledge** | Q&A path, Concepts path, Questions index path, knowledge index status + rebuild |
|
||||
| **Execution** | Execution log path (read-only), scheduled tasks status (v2.1) |
|
||||
| **Auxiliary** | API key, Base URL, model, output style, Baidu sync (App ID/Secret, remote path, auto-backup) |
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -150,12 +150,14 @@ src/
|
|||
core/
|
||||
llm/ Unified LLM client + JSON extractor
|
||||
knowledge/ KnowledgeIndexService (metadata index)
|
||||
execution/ ExecutionPlan, PlanBuilder, PlanExecutor
|
||||
scheduler/ ScheduledTask types + runner (WIP)
|
||||
execution/ ExecutionPlan, PlanBuilder, PlanExecutor, PlanDraftStore
|
||||
artifact/ ExecutionArtifact types, prompt, validation, rendering
|
||||
scheduler/ ScheduledTask types + runner (disabled in v2.0)
|
||||
schema.ts SCHEMA_VERSION + helpers
|
||||
ai/ AI feature modules (assistant, classifier, planner, ...)
|
||||
features/
|
||||
assistant/ AI assistant modals
|
||||
artifact/ Artifact builder, preview, feature controller
|
||||
concept/ Concept completion + page manager
|
||||
question/ Question classify + graph manager
|
||||
reading/ Reading project manager
|
||||
|
|
|
|||
|
|
@ -20,8 +20,11 @@ const SYSTEM_PROMPT = `你是一个知识到执行资产的结构化转换助手
|
|||
3. 如果没有明确来源,标记 inferred: true。
|
||||
4. 高风险或不确定内容设置 riskLevel: "watch" 或 "high"。
|
||||
5. 不要生成医学诊断、法律结论或投资决策。
|
||||
6. 每个条目的 id 使用 "item-1", "item-2" 等顺序编号。
|
||||
7. 严格输出 JSON,不要输出解释文本。
|
||||
6. 不要编造 sourceLinks。sourceLinks 只能来自"来源内容"中出现过的路径或文档标题。只返回路径或 path|alias,不要包含 [[ ]]。
|
||||
正确:"Reading/Book/Chapter1.md|第1章"
|
||||
错误:"[[Reading/Book/Chapter1.md|第1章]]"
|
||||
7. 每个条目的 id 使用 "item-1", "item-2" 等顺序编号。
|
||||
8. 严格输出 JSON,不要输出解释文本。
|
||||
|
||||
返回格式:
|
||||
{
|
||||
|
|
|
|||
|
|
@ -26,12 +26,12 @@ export class ArtifactRenderer {
|
|||
`type: execution-artifact-${docType}`,
|
||||
`schema_version: ${SCHEMA_VERSION}`,
|
||||
`artifact_type: ${artifact.artifactType}`,
|
||||
`title: "${artifact.title}"`,
|
||||
`title: ${this.yamlStr(artifact.title)}`,
|
||||
`usage_mode: ${artifact.usageMode}`,
|
||||
`source_scope: ${artifact.sourceScope}`,
|
||||
`evidence_policy: ${artifact.evidencePolicy}`,
|
||||
];
|
||||
if (artifact.target) lines.push(`target: "${artifact.target}"`);
|
||||
if (artifact.target) lines.push(`target: ${this.yamlStr(artifact.target)}`);
|
||||
if (artifact.frequency) lines.push(`frequency: ${artifact.frequency}`);
|
||||
lines.push(`status: draft`);
|
||||
lines.push(`created_at: ${todayIso()}`);
|
||||
|
|
@ -45,7 +45,7 @@ export class ArtifactRenderer {
|
|||
`type: execution-artifact-run`,
|
||||
`schema_version: ${SCHEMA_VERSION}`,
|
||||
`artifact_type: ${artifact.artifactType}`,
|
||||
`template: "${artifact.title}"`,
|
||||
`template: ${this.yamlStr(artifact.title)}`,
|
||||
`date: ${date}`,
|
||||
`status: open`,
|
||||
"---",
|
||||
|
|
@ -65,10 +65,15 @@ export class ArtifactRenderer {
|
|||
artifact.target ? `> 对象:${artifact.target}` : null,
|
||||
artifact.frequency ? `> 频率:${artifact.frequency}` : null,
|
||||
artifact.sourceLinks.length > 0
|
||||
? `> 来源:${artifact.sourceLinks.map((l) => `[[${l}]]`).join("、")}`
|
||||
? `> 来源:${artifact.sourceLinks.map((l) => this.wikiLink(l)).join("、")}`
|
||||
: null,
|
||||
].filter(Boolean).join("\n");
|
||||
|
||||
// Safety disclaimer for sensitive domains
|
||||
const disclaimer = this.needsSafetyDisclaimer(artifact)
|
||||
? `\n> [!warning] 使用边界\n> 该执行资产用于记录、观察和复盘,不构成医学、法律或投资建议。出现异常情况请咨询专业人士。\n`
|
||||
: "";
|
||||
|
||||
// Group items by category
|
||||
const grouped = this.groupByCategory(artifact.items);
|
||||
const sections: string[] = [];
|
||||
|
|
@ -87,7 +92,7 @@ export class ArtifactRenderer {
|
|||
inferredSection = `\n## 未验证项\n\n> [!warning]\n> 以下 ${inferredItems.length} 个条目由 AI 推断生成,未找到明确来源,建议人工确认。\n\n${inferredItems.map((i) => `- ${i.title}`).join("\n")}\n`;
|
||||
}
|
||||
|
||||
return `${title}\n\n${meta}\n${sections.join("\n")}${inferredSection}`;
|
||||
return `${title}\n\n${meta}${disclaimer}\n${sections.join("\n")}${inferredSection}`;
|
||||
}
|
||||
|
||||
private renderItem(item: ArtifactItem, isRun: boolean): string {
|
||||
|
|
@ -98,7 +103,7 @@ export class ArtifactRenderer {
|
|||
lines.push(`${checkbox} ${item.title}${riskMark}`);
|
||||
|
||||
if (item.sourceLinks.length > 0) {
|
||||
lines.push(` - 依据:${item.sourceLinks.map((l) => `[[${l}]]`).join("、")}`);
|
||||
lines.push(` - 依据:${item.sourceLinks.map((l) => this.wikiLink(l)).join("、")}`);
|
||||
} else if (item.inferred) {
|
||||
lines.push(` - 依据:*AI 推断,建议确认*`);
|
||||
}
|
||||
|
|
@ -120,6 +125,23 @@ export class ArtifactRenderer {
|
|||
return `\n## 今日问题\n\n- \n\n## 明日调整\n\n- \n`;
|
||||
}
|
||||
|
||||
/** Safely encode a string for YAML (uses JSON quoting). */
|
||||
private yamlStr(value: string): string {
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
|
||||
private needsSafetyDisclaimer(artifact: ExecutionArtifact): boolean {
|
||||
const sensitiveKeywords = [
|
||||
"婴儿", "母亲", "健康", "医疗", "诊断", "症状", "用药", "治疗",
|
||||
"法律", "合同", "诉讼", "投资", "理财", "基金", "股票",
|
||||
"baby", "infant", "health", "medical", "diagnosis", "legal", "investment",
|
||||
];
|
||||
const text = `${artifact.title} ${artifact.target ?? ""}`.toLowerCase();
|
||||
const hasHighRisk = artifact.items.some((i) => i.riskLevel === "high" || i.riskLevel === "watch");
|
||||
const hasSensitiveKeyword = sensitiveKeywords.some((k) => text.includes(k));
|
||||
return hasHighRisk || hasSensitiveKeyword;
|
||||
}
|
||||
|
||||
private groupByCategory(items: ArtifactItem[]): Map<string, ArtifactItem[]> {
|
||||
const map = new Map<string, ArtifactItem[]>();
|
||||
for (const item of items) {
|
||||
|
|
@ -129,4 +151,13 @@ export class ArtifactRenderer {
|
|||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/** Ensure a link string becomes a proper [[wikilink]] without double-wrapping. */
|
||||
private wikiLink(link: string): string {
|
||||
const trimmed = link.trim();
|
||||
if (trimmed.startsWith("[[") && trimmed.endsWith("]]")) return trimmed;
|
||||
// Strip accidental [[ ]] if partially wrapped
|
||||
const cleaned = trimmed.replace(/^\[\[/, "").replace(/\]\]$/, "");
|
||||
return `[[${cleaned}]]`;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,10 +35,10 @@ export class ArtifactPreviewModal extends Modal {
|
|||
if (stats.highRisk > 0) statsEl.createSpan({ text: `高关注:${stats.highRisk}`, attr: { style: "color: var(--text-error);" } });
|
||||
|
||||
// File impact notice
|
||||
contentEl.createEl("p", {
|
||||
text: `保存后将创建 1-2 个文件(模板 + 可选的今日记录)`,
|
||||
attr: { style: "font-size: 12px; color: var(--text-muted); margin-bottom: 8px;" },
|
||||
});
|
||||
const safeName = this.artifact.title.replace(/[\\/:*?"<>|#[\]]/g, "-").slice(0, 50);
|
||||
contentEl.createEl("div", {
|
||||
attr: { style: "font-size: 12px; color: var(--text-muted); margin-bottom: 8px; padding: 6px 8px; background: var(--background-secondary); border-radius: 4px;" },
|
||||
}).innerHTML = `将创建:<br>• <code>Knowledge/Artifacts/${safeName}.md</code><br>• <code>Knowledge/Artifact Runs/${new Date().toISOString().slice(0, 10)} ${safeName}.md</code>(仅"保存并生成今日记录"时)`;
|
||||
|
||||
// Warnings
|
||||
if (this.validation.warnings.length > 0) {
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
"strictFunctionTypes": true,
|
||||
"strictNullChecks": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"lib": ["ES6", "DOM"]
|
||||
"lib": ["ES2018", "DOM"]
|
||||
},
|
||||
"include": ["src/**/*.ts"]
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue