sdtk-wiki-kit 0.1.1 → 0.1.3
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 +110 -6
- package/package.json +1 -1
- package/src/commands/help.js +28 -0
- package/src/commands/lint.js +1 -0
- package/src/commands/operations.js +345 -0
- package/src/commands/search.js +1 -0
- package/src/commands/wiki.js +1 -1
- package/src/index.js +32 -1
- package/src/lib/wiki-compile.js +427 -42
- package/src/lib/wiki-extract.js +680 -4
- package/src/lib/wiki-lint.js +223 -2
- package/src/lib/wiki-search.js +1 -1
package/src/lib/wiki-lint.js
CHANGED
|
@@ -27,6 +27,21 @@ const REQUIRED_PAGE_FIELDS = [
|
|
|
27
27
|
"role",
|
|
28
28
|
];
|
|
29
29
|
|
|
30
|
+
const REQUIRED_PERSONAL_BRAIN_FIELDS = [
|
|
31
|
+
"id",
|
|
32
|
+
"title",
|
|
33
|
+
"type",
|
|
34
|
+
"status",
|
|
35
|
+
"created_at",
|
|
36
|
+
"updated_at",
|
|
37
|
+
"aliases",
|
|
38
|
+
"tags",
|
|
39
|
+
"related_pages",
|
|
40
|
+
"source_refs",
|
|
41
|
+
"confidence",
|
|
42
|
+
"review_status",
|
|
43
|
+
];
|
|
44
|
+
|
|
30
45
|
const CATEGORY_DEFS = [
|
|
31
46
|
["schema", "Frontmatter and schema"],
|
|
32
47
|
["duplicates", "Duplicate page IDs"],
|
|
@@ -40,8 +55,21 @@ const CATEGORY_DEFS = [
|
|
|
40
55
|
["markers", "TODO/Open Questions/Gaps"],
|
|
41
56
|
["contradictions", "Candidate contradictions"],
|
|
42
57
|
["sourceQuality", "Source quality"],
|
|
58
|
+
["personalBrainQuality", "Personal-brain quality gate"],
|
|
43
59
|
];
|
|
44
60
|
|
|
61
|
+
const REQUIRED_PERSONAL_BRAIN_SECTIONS = {
|
|
62
|
+
source: ["Summary", "Source Metadata", "Source Quality", "Provenance"],
|
|
63
|
+
tool_entity: ["Summary", "Key Facts", "Discovery Source", "Extracted Snippet", "Topic Labels", "Why It Matters", "When To Use", "Related Repos", "Overlaps / Differences", "Open Questions", "Provenance"],
|
|
64
|
+
concept: ["Summary", "Key Axes", "Implementations / Examples", "Patterns", "Recommendations / Caveats", "Open Questions", "Related Pages", "Source References"],
|
|
65
|
+
comparison: ["Summary", "Decision Axes", "Comparison Matrix", "Candidate Tools / Repos", "Recommendations", "Caveats", "Source Confidence", "Open Questions", "Source References"],
|
|
66
|
+
synthesis: ["Summary", "Landscape Snapshot", "Key Patterns", "Decision Axes", "Recommended Review Path", "Caveats", "Source Confidence", "Related Comparisons", "Open Questions", "Source References"],
|
|
67
|
+
maintenance: ["Source Quality Findings", "Unsupported Items"],
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const PERSONAL_BRAIN_STUB_CHAR_LIMIT = 600;
|
|
71
|
+
const PERSONAL_BRAIN_GIANT_PAGE_BYTES = 50000;
|
|
72
|
+
|
|
45
73
|
function toPosix(value) {
|
|
46
74
|
return String(value || "").replace(/\\/g, "/");
|
|
47
75
|
}
|
|
@@ -148,6 +176,20 @@ function extractMarkdownLinks(body) {
|
|
|
148
176
|
return links;
|
|
149
177
|
}
|
|
150
178
|
|
|
179
|
+
function hasHeading(body, heading) {
|
|
180
|
+
const escaped = String(heading).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
181
|
+
return new RegExp(`^##\\s+${escaped}\\s*$`, "im").test(String(body || ""));
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function hasSourceRefs(fields) {
|
|
185
|
+
const raw = String(fields.source_refs || "").trim();
|
|
186
|
+
return Boolean(raw && raw !== "[]" && raw !== "[\"\"]");
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function formatPercent(value) {
|
|
190
|
+
return `${Math.round(value * 100)}%`;
|
|
191
|
+
}
|
|
192
|
+
|
|
151
193
|
function createFindings() {
|
|
152
194
|
return Object.fromEntries(CATEGORY_DEFS.map(([key]) => [key, []]));
|
|
153
195
|
}
|
|
@@ -550,7 +592,154 @@ function analyzePages(projectPath) {
|
|
|
550
592
|
|
|
551
593
|
analyzeSourceQuality(projectPath, inputs, findings);
|
|
552
594
|
|
|
553
|
-
|
|
595
|
+
const personalBrainAnalysis = analyzePersonalBrainPages(projectPath, findings);
|
|
596
|
+
|
|
597
|
+
return {
|
|
598
|
+
findings,
|
|
599
|
+
pageCount: pages.length + personalBrainAnalysis.count,
|
|
600
|
+
personalBrainMetrics: personalBrainAnalysis.metrics,
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
function analyzePersonalBrainPages(projectPath, findings) {
|
|
605
|
+
const personalBrainRoot = path.join(getWikiWorkspacePath(projectPath), "personal-brain");
|
|
606
|
+
const pageFiles = listMarkdownPages(personalBrainRoot);
|
|
607
|
+
const pages = [];
|
|
608
|
+
const metrics = {
|
|
609
|
+
pageCount: pageFiles.length,
|
|
610
|
+
byType: {},
|
|
611
|
+
frontmatterCoverage: 0,
|
|
612
|
+
requiredSectionCoverage: 0,
|
|
613
|
+
sourceRefsCoverage: 0,
|
|
614
|
+
stubRatio: 0,
|
|
615
|
+
giantPageCount: 0,
|
|
616
|
+
brokenInternalLinks: 0,
|
|
617
|
+
conceptCount: 0,
|
|
618
|
+
entityCount: 0,
|
|
619
|
+
comparisonCount: 0,
|
|
620
|
+
synthesisCount: 0,
|
|
621
|
+
sourceEvidenceCoverage: 0,
|
|
622
|
+
};
|
|
623
|
+
let frontmatterCount = 0;
|
|
624
|
+
let sectionRequiredTotal = 0;
|
|
625
|
+
let sectionPresentTotal = 0;
|
|
626
|
+
let sourceRefsEligible = 0;
|
|
627
|
+
let sourceRefsPresent = 0;
|
|
628
|
+
let sourceEvidenceEligible = 0;
|
|
629
|
+
let sourceEvidencePresent = 0;
|
|
630
|
+
let stubCount = 0;
|
|
631
|
+
|
|
632
|
+
for (const filePath of pageFiles) {
|
|
633
|
+
const relPath = toPosix(path.relative(personalBrainRoot, filePath));
|
|
634
|
+
const parsed = parseFrontmatter(fs.readFileSync(filePath, "utf-8"));
|
|
635
|
+
const fields = parsed.fields;
|
|
636
|
+
const type = String(fields.type || "unknown");
|
|
637
|
+
pages.push({ filePath, relPath, fields, body: parsed.body, hasFrontmatter: parsed.hasFrontmatter, type });
|
|
638
|
+
metrics.byType[type] = (metrics.byType[type] || 0) + 1;
|
|
639
|
+
if (type === "concept") metrics.conceptCount += 1;
|
|
640
|
+
if (type === "tool_entity") metrics.entityCount += 1;
|
|
641
|
+
if (type === "comparison") metrics.comparisonCount += 1;
|
|
642
|
+
if (type === "synthesis") metrics.synthesisCount += 1;
|
|
643
|
+
|
|
644
|
+
if (!parsed.hasFrontmatter) {
|
|
645
|
+
appendFinding(findings, "schema", `personal-brain/${relPath} is missing parseable frontmatter.`);
|
|
646
|
+
continue;
|
|
647
|
+
}
|
|
648
|
+
frontmatterCount += 1;
|
|
649
|
+
|
|
650
|
+
const missingFields = REQUIRED_PERSONAL_BRAIN_FIELDS.filter((field) => !fields[field]);
|
|
651
|
+
if (missingFields.length > 0) {
|
|
652
|
+
appendFinding(
|
|
653
|
+
findings,
|
|
654
|
+
"schema",
|
|
655
|
+
`personal-brain/${relPath} is missing required personal-brain fields: ${missingFields.join(", ")}.`
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
const requiredSections = REQUIRED_PERSONAL_BRAIN_SECTIONS[type] || [];
|
|
660
|
+
sectionRequiredTotal += requiredSections.length;
|
|
661
|
+
const missingSections = requiredSections.filter((section) => !hasHeading(parsed.body, section));
|
|
662
|
+
sectionPresentTotal += requiredSections.length - missingSections.length;
|
|
663
|
+
if (missingSections.length > 0) {
|
|
664
|
+
appendFinding(
|
|
665
|
+
findings,
|
|
666
|
+
"personalBrainQuality",
|
|
667
|
+
`personal-brain/${relPath} is missing required sections for ${type}: ${missingSections.join(", ")}.`
|
|
668
|
+
);
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
if (!["root"].includes(type)) {
|
|
672
|
+
sourceRefsEligible += 1;
|
|
673
|
+
if (hasSourceRefs(fields)) sourceRefsPresent += 1;
|
|
674
|
+
else appendFinding(findings, "personalBrainQuality", `personal-brain/${relPath} has no source_refs coverage.`);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
if (["tool_entity", "concept", "comparison", "synthesis"].includes(type)) {
|
|
678
|
+
sourceEvidenceEligible += 1;
|
|
679
|
+
if (hasSourceRefs(fields) && /(?:source|provenance|evidence)/i.test(parsed.body)) {
|
|
680
|
+
sourceEvidencePresent += 1;
|
|
681
|
+
} else {
|
|
682
|
+
appendFinding(findings, "personalBrainQuality", `personal-brain/${relPath} lacks source evidence coverage in body/frontmatter.`);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
const compactBody = parsed.body.replace(/\s+/g, " ").trim();
|
|
687
|
+
if (compactBody.length < PERSONAL_BRAIN_STUB_CHAR_LIMIT && !["root"].includes(type)) {
|
|
688
|
+
stubCount += 1;
|
|
689
|
+
appendFinding(findings, "personalBrainQuality", `personal-brain/${relPath} appears stub-like (${compactBody.length} normalized characters).`);
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
const byteSize = fs.statSync(filePath).size;
|
|
693
|
+
if (byteSize > PERSONAL_BRAIN_GIANT_PAGE_BYTES) {
|
|
694
|
+
metrics.giantPageCount += 1;
|
|
695
|
+
appendFinding(findings, "personalBrainQuality", `personal-brain/${relPath} is very large (${byteSize} bytes) and may need splitting.`);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
for (const page of pages) {
|
|
700
|
+
for (const link of extractMarkdownLinks(page.body)) {
|
|
701
|
+
const rawPath = link.split("#")[0].trim();
|
|
702
|
+
if (!rawPath) continue;
|
|
703
|
+
const resolved = path.resolve(path.dirname(page.filePath), rawPath);
|
|
704
|
+
if (!isPathInsideOrEqual(resolved, personalBrainRoot) || !fs.existsSync(resolved)) {
|
|
705
|
+
appendFinding(findings, "brokenLinks", `personal-brain/${page.relPath} links to missing ${link}.`);
|
|
706
|
+
metrics.brokenInternalLinks += 1;
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
metrics.frontmatterCoverage = pageFiles.length > 0 ? frontmatterCount / pageFiles.length : 1;
|
|
712
|
+
metrics.requiredSectionCoverage = sectionRequiredTotal > 0 ? sectionPresentTotal / sectionRequiredTotal : 1;
|
|
713
|
+
metrics.sourceRefsCoverage = sourceRefsEligible > 0 ? sourceRefsPresent / sourceRefsEligible : 1;
|
|
714
|
+
metrics.stubRatio = pageFiles.length > 0 ? stubCount / pageFiles.length : 0;
|
|
715
|
+
metrics.sourceEvidenceCoverage = sourceEvidenceEligible > 0 ? sourceEvidencePresent / sourceEvidenceEligible : 1;
|
|
716
|
+
|
|
717
|
+
if (pageFiles.length === 0) {
|
|
718
|
+
appendFinding(findings, "personalBrainQuality", "No personal-brain pages were found under .sdtk/wiki/personal-brain.");
|
|
719
|
+
}
|
|
720
|
+
if (metrics.frontmatterCoverage < 1) {
|
|
721
|
+
appendFinding(findings, "personalBrainQuality", `Frontmatter coverage is ${formatPercent(metrics.frontmatterCoverage)}; expected 100%.`);
|
|
722
|
+
}
|
|
723
|
+
if (metrics.requiredSectionCoverage < 0.95) {
|
|
724
|
+
appendFinding(findings, "personalBrainQuality", `Required section coverage is ${formatPercent(metrics.requiredSectionCoverage)}; expected at least 95%.`);
|
|
725
|
+
}
|
|
726
|
+
if (metrics.sourceRefsCoverage < 0.9) {
|
|
727
|
+
appendFinding(findings, "personalBrainQuality", `Source refs coverage is ${formatPercent(metrics.sourceRefsCoverage)}; expected at least 90%.`);
|
|
728
|
+
}
|
|
729
|
+
if (metrics.stubRatio > 0.1) {
|
|
730
|
+
appendFinding(findings, "personalBrainQuality", `Stub ratio is ${formatPercent(metrics.stubRatio)}; expected at most 10%.`);
|
|
731
|
+
}
|
|
732
|
+
if (metrics.entityCount > 0 && metrics.conceptCount === 0) {
|
|
733
|
+
appendFinding(findings, "personalBrainQuality", "Tool/entity pages exist but no concept pages were generated.");
|
|
734
|
+
}
|
|
735
|
+
if (metrics.conceptCount > 0 && metrics.comparisonCount === 0) {
|
|
736
|
+
appendFinding(findings, "personalBrainQuality", "Concept pages exist but no comparison pages were generated.");
|
|
737
|
+
}
|
|
738
|
+
if (metrics.comparisonCount > 0 && metrics.synthesisCount === 0) {
|
|
739
|
+
appendFinding(findings, "personalBrainQuality", "Comparison pages exist but no synthesis pages were generated.");
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
return { count: pageFiles.length, metrics };
|
|
554
743
|
}
|
|
555
744
|
|
|
556
745
|
function todayStamp() {
|
|
@@ -561,7 +750,35 @@ function totalFindings(findings) {
|
|
|
561
750
|
return CATEGORY_DEFS.reduce((sum, [key]) => sum + findings[key].length, 0);
|
|
562
751
|
}
|
|
563
752
|
|
|
564
|
-
function
|
|
753
|
+
function renderPersonalBrainMetrics(metrics) {
|
|
754
|
+
if (!metrics) return ["## Personal-Brain Quality Metrics", "", "- No personal-brain metrics available.", ""];
|
|
755
|
+
const typeRows = Object.entries(metrics.byType || {})
|
|
756
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
757
|
+
.map(([type, count]) => `| ${type} | ${count} |`);
|
|
758
|
+
return [
|
|
759
|
+
"## Personal-Brain Quality Metrics",
|
|
760
|
+
"",
|
|
761
|
+
`- personal-brain pages: ${metrics.pageCount}`,
|
|
762
|
+
`- frontmatter coverage: ${formatPercent(metrics.frontmatterCoverage)}`,
|
|
763
|
+
`- required section coverage: ${formatPercent(metrics.requiredSectionCoverage)}`,
|
|
764
|
+
`- source refs coverage: ${formatPercent(metrics.sourceRefsCoverage)}`,
|
|
765
|
+
`- source evidence coverage: ${formatPercent(metrics.sourceEvidenceCoverage)}`,
|
|
766
|
+
`- stub ratio: ${formatPercent(metrics.stubRatio)}`,
|
|
767
|
+
`- giant page warnings: ${metrics.giantPageCount}`,
|
|
768
|
+
`- broken personal-brain internal links: ${metrics.brokenInternalLinks}`,
|
|
769
|
+
`- entity pages: ${metrics.entityCount}`,
|
|
770
|
+
`- concept pages: ${metrics.conceptCount}`,
|
|
771
|
+
`- comparison pages: ${metrics.comparisonCount}`,
|
|
772
|
+
`- synthesis pages: ${metrics.synthesisCount}`,
|
|
773
|
+
"",
|
|
774
|
+
"| Page type | Count |",
|
|
775
|
+
"|---|---:|",
|
|
776
|
+
...(typeRows.length > 0 ? typeRows : ["| none | 0 |"]),
|
|
777
|
+
"",
|
|
778
|
+
];
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
function renderReport({ projectPath, workspaceRoot, findings, pageCount, personalBrainMetrics }) {
|
|
565
782
|
const summaryRows = CATEGORY_DEFS.map(
|
|
566
783
|
([key, label]) => `| ${label} | ${findings[key].length} |`
|
|
567
784
|
);
|
|
@@ -593,6 +810,7 @@ function renderReport({ projectPath, workspaceRoot, findings, pageCount }) {
|
|
|
593
810
|
"|---|---:|",
|
|
594
811
|
...summaryRows,
|
|
595
812
|
"",
|
|
813
|
+
...renderPersonalBrainMetrics(personalBrainMetrics),
|
|
596
814
|
...detailSections,
|
|
597
815
|
].join("\n");
|
|
598
816
|
}
|
|
@@ -622,12 +840,14 @@ function runWikiLint(options = {}) {
|
|
|
622
840
|
workspaceRoot,
|
|
623
841
|
findings: analysis.findings,
|
|
624
842
|
pageCount: analysis.pageCount,
|
|
843
|
+
personalBrainMetrics: analysis.personalBrainMetrics,
|
|
625
844
|
});
|
|
626
845
|
fs.writeFileSync(reportPath, report + "\n", "utf-8");
|
|
627
846
|
return {
|
|
628
847
|
reportPath,
|
|
629
848
|
totalFindings: totalFindings(analysis.findings),
|
|
630
849
|
findings: analysis.findings,
|
|
850
|
+
personalBrainMetrics: analysis.personalBrainMetrics,
|
|
631
851
|
};
|
|
632
852
|
} catch (error) {
|
|
633
853
|
if (error instanceof CliError) throw error;
|
|
@@ -637,6 +857,7 @@ function runWikiLint(options = {}) {
|
|
|
637
857
|
|
|
638
858
|
module.exports = {
|
|
639
859
|
CATEGORY_DEFS,
|
|
860
|
+
REQUIRED_PERSONAL_BRAIN_FIELDS,
|
|
640
861
|
REQUIRED_PAGE_FIELDS,
|
|
641
862
|
analyzePages,
|
|
642
863
|
parseFrontmatter,
|
package/src/lib/wiki-search.js
CHANGED
|
@@ -126,7 +126,7 @@ function runWikiSearch({ projectPath, query, limit = DEFAULT_LIMIT }) {
|
|
|
126
126
|
}
|
|
127
127
|
if (!fs.existsSync(personalBrainPath) || !fs.statSync(personalBrainPath).isDirectory()) {
|
|
128
128
|
throw new ValidationError(
|
|
129
|
-
`No SDTK-WIKI personal brain found at ${personalBrainPath}. Run
|
|
129
|
+
`No SDTK-WIKI personal brain found at ${personalBrainPath}. Run "sdtk-wiki ingest <source-root>" and "sdtk-wiki compile --mode safe --apply" first. Advanced users can still use wiki extract/compile sidecar commands.`
|
|
130
130
|
);
|
|
131
131
|
}
|
|
132
132
|
|