@@ -11,14 +11,18 @@ import { renderWikiMarkdown, toWikiPageSummary, type WikiPageSummary } from "./m
1111
1212export type MemoryWikiLintIssue = {
1313 severity : "error" | "warning" ;
14+ category : "structure" | "provenance" | "links" | "contradictions" | "open-questions" | "quality" ;
1415 code :
1516 | "missing-id"
1617 | "duplicate-id"
1718 | "missing-page-type"
1819 | "page-type-mismatch"
1920 | "missing-title"
2021 | "missing-source-ids"
21- | "broken-wikilink" ;
22+ | "broken-wikilink"
23+ | "contradiction-present"
24+ | "open-question"
25+ | "low-confidence" ;
2226 path : string ;
2327 message : string ;
2428} ;
@@ -27,6 +31,7 @@ export type LintMemoryWikiResult = {
2731 vaultRoot : string ;
2832 issueCount : number ;
2933 issues : MemoryWikiLintIssue [ ] ;
34+ issuesByCategory : Record < MemoryWikiLintIssue [ "category" ] , MemoryWikiLintIssue [ ] > ;
3035 reportPath : string ;
3136} ;
3237
@@ -48,6 +53,7 @@ function collectBrokenLinkIssues(pages: WikiPageSummary[]): MemoryWikiLintIssue[
4853 if ( ! validTargets . has ( linkTarget ) ) {
4954 issues . push ( {
5055 severity : "warning" ,
56+ category : "links" ,
5157 code : "broken-wikilink" ,
5258 path : page . relativePath ,
5359 message : `Broken wikilink target \`${ linkTarget } \`.` ,
@@ -66,6 +72,7 @@ function collectPageIssues(pages: WikiPageSummary[]): MemoryWikiLintIssue[] {
6672 if ( ! page . id ) {
6773 issues . push ( {
6874 severity : "error" ,
75+ category : "structure" ,
6976 code : "missing-id" ,
7077 path : page . relativePath ,
7178 message : "Missing `id` frontmatter." ,
@@ -79,13 +86,15 @@ function collectPageIssues(pages: WikiPageSummary[]): MemoryWikiLintIssue[] {
7986 if ( ! page . pageType ) {
8087 issues . push ( {
8188 severity : "error" ,
89+ category : "structure" ,
8290 code : "missing-page-type" ,
8391 path : page . relativePath ,
8492 message : "Missing `pageType` frontmatter." ,
8593 } ) ;
8694 } else if ( page . pageType !== toExpectedPageType ( page ) ) {
8795 issues . push ( {
8896 severity : "error" ,
97+ category : "structure" ,
8998 code : "page-type-mismatch" ,
9099 path : page . relativePath ,
91100 message : `Expected pageType \`${ toExpectedPageType ( page ) } \`, found \`${ page . pageType } \`.` ,
@@ -95,6 +104,7 @@ function collectPageIssues(pages: WikiPageSummary[]): MemoryWikiLintIssue[] {
95104 if ( ! page . title . trim ( ) ) {
96105 issues . push ( {
97106 severity : "error" ,
107+ category : "structure" ,
98108 code : "missing-title" ,
99109 path : page . relativePath ,
100110 message : "Missing page title." ,
@@ -104,18 +114,50 @@ function collectPageIssues(pages: WikiPageSummary[]): MemoryWikiLintIssue[] {
104114 if ( page . kind !== "source" && page . kind !== "report" && page . sourceIds . length === 0 ) {
105115 issues . push ( {
106116 severity : "warning" ,
117+ category : "provenance" ,
107118 code : "missing-source-ids" ,
108119 path : page . relativePath ,
109120 message : "Non-source page is missing `sourceIds` provenance." ,
110121 } ) ;
111122 }
123+
124+ if ( page . contradictions . length > 0 ) {
125+ issues . push ( {
126+ severity : "warning" ,
127+ category : "contradictions" ,
128+ code : "contradiction-present" ,
129+ path : page . relativePath ,
130+ message : `Page lists ${ page . contradictions . length } contradiction${ page . contradictions . length === 1 ? "" : "s" } to resolve.` ,
131+ } ) ;
132+ }
133+
134+ if ( page . questions . length > 0 ) {
135+ issues . push ( {
136+ severity : "warning" ,
137+ category : "open-questions" ,
138+ code : "open-question" ,
139+ path : page . relativePath ,
140+ message : `Page lists ${ page . questions . length } open question${ page . questions . length === 1 ? "" : "s" } .` ,
141+ } ) ;
142+ }
143+
144+ if ( typeof page . confidence === "number" && page . confidence < 0.5 ) {
145+ issues . push ( {
146+ severity : "warning" ,
147+ category : "quality" ,
148+ code : "low-confidence" ,
149+ path : page . relativePath ,
150+ message : `Page confidence is low (${ page . confidence . toFixed ( 2 ) } ).` ,
151+ } ) ;
152+ }
112153 }
113154
114155 for ( const [ id , matches ] of pagesById . entries ( ) ) {
115156 if ( matches . length > 1 ) {
116157 for ( const match of matches ) {
117158 issues . push ( {
118159 severity : "error" ,
160+ category : "structure" ,
119161 code : "duplicate-id" ,
120162 path : match . relativePath ,
121163 message : `Duplicate page id \`${ id } \`.` ,
@@ -128,13 +170,27 @@ function collectPageIssues(pages: WikiPageSummary[]): MemoryWikiLintIssue[] {
128170 return issues . toSorted ( ( left , right ) => left . path . localeCompare ( right . path ) ) ;
129171}
130172
173+ function buildIssuesByCategory (
174+ issues : MemoryWikiLintIssue [ ] ,
175+ ) : Record < MemoryWikiLintIssue [ "category" ] , MemoryWikiLintIssue [ ] > {
176+ return {
177+ structure : issues . filter ( ( issue ) => issue . category === "structure" ) ,
178+ provenance : issues . filter ( ( issue ) => issue . category === "provenance" ) ,
179+ links : issues . filter ( ( issue ) => issue . category === "links" ) ,
180+ contradictions : issues . filter ( ( issue ) => issue . category === "contradictions" ) ,
181+ "open-questions" : issues . filter ( ( issue ) => issue . category === "open-questions" ) ,
182+ quality : issues . filter ( ( issue ) => issue . category === "quality" ) ,
183+ } ;
184+ }
185+
131186function buildLintReportBody ( issues : MemoryWikiLintIssue [ ] ) : string {
132187 if ( issues . length === 0 ) {
133188 return "No issues found." ;
134189 }
135190
136191 const errors = issues . filter ( ( issue ) => issue . severity === "error" ) ;
137192 const warnings = issues . filter ( ( issue ) => issue . severity === "warning" ) ;
193+ const byCategory = buildIssuesByCategory ( issues ) ;
138194 const lines = [ `- Errors: ${ errors . length } ` , `- Warnings: ${ warnings . length } ` ] ;
139195
140196 if ( errors . length > 0 ) {
@@ -151,6 +207,27 @@ function buildLintReportBody(issues: MemoryWikiLintIssue[]): string {
151207 }
152208 }
153209
210+ if ( byCategory . contradictions . length > 0 ) {
211+ lines . push ( "" , "### Contradictions" ) ;
212+ for ( const issue of byCategory . contradictions ) {
213+ lines . push ( `- \`${ issue . path } \`: ${ issue . message } ` ) ;
214+ }
215+ }
216+
217+ if ( byCategory [ "open-questions" ] . length > 0 ) {
218+ lines . push ( "" , "### Open Questions" ) ;
219+ for ( const issue of byCategory [ "open-questions" ] ) {
220+ lines . push ( `- \`${ issue . path } \`: ${ issue . message } ` ) ;
221+ }
222+ }
223+
224+ if ( byCategory . provenance . length > 0 || byCategory . quality . length > 0 ) {
225+ lines . push ( "" , "### Quality Follow-Up" ) ;
226+ for ( const issue of [ ...byCategory . provenance , ...byCategory . quality ] ) {
227+ lines . push ( `- \`${ issue . path } \`: ${ issue . message } ` ) ;
228+ }
229+ }
230+
154231 return lines . join ( "\n" ) ;
155232}
156233
@@ -183,6 +260,7 @@ export async function lintMemoryWikiVault(
183260) : Promise < LintMemoryWikiResult > {
184261 const compileResult = await compileMemoryWikiVault ( config ) ;
185262 const issues = collectPageIssues ( compileResult . pages ) ;
263+ const issuesByCategory = buildIssuesByCategory ( issues ) ;
186264 const reportPath = await writeLintReport ( config . vault . path , issues ) ;
187265
188266 await appendMemoryWikiLog ( config . vault . path , {
@@ -198,6 +276,7 @@ export async function lintMemoryWikiVault(
198276 vaultRoot : config . vault . path ,
199277 issueCount : issues . length ,
200278 issues,
279+ issuesByCategory,
201280 reportPath,
202281 } ;
203282}
0 commit comments