skillwiki 0.10.49 → 0.10.50
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/dist/{chunk-UHZBOOMK.js → chunk-74OSLCXE.js} +1 -1
- package/dist/{chunk-FXASPB3C.js → chunk-EUTNN4RN.js} +1949 -1296
- package/dist/{chunk-PLUHIOCQ.js → chunk-GAHMWLWU.js} +174 -175
- package/dist/chunk-HBQTTYXZ.js +1045 -0
- package/dist/{chunk-EVQASYII.js → chunk-IJ7DD7QZ.js} +59 -0
- package/dist/{chunk-F7LXKP4T.js → chunk-MKZKMSYM.js} +2309 -1398
- package/dist/{chunk-GAU4D25O.js → chunk-O3HCB7R2.js} +2 -2
- package/dist/cli.js +2391 -1789
- package/dist/{index-projection-BJNZUPH6.js → index-projection-EXBX32RR.js} +3 -3
- package/dist/{managed-write-preflight-H7KVK2KI.js → managed-write-preflight-SJDRNJSU.js} +4 -4
- package/dist/skillwiki-mcp.js +7 -6
- package/dist/sources-BFYEFTS4.js +9 -0
- package/package.json +1 -1
- package/skills/.claude-plugin/plugin.json +2 -2
- package/skills/.codex-plugin/plugin.json +3 -3
- package/skills/README.md +1 -1
- package/skills/agents/wiki-ingest.md +2 -0
- package/skills/package.json +1 -1
- package/skills/skills/using-skillwiki/SKILL.md +4 -1
- package/skills/skills/wiki-freshness-repair/SKILL.md +94 -0
- package/skills/skills/wiki-ingest/SKILL.md +8 -0
- package/skills/skills/wiki-query/SKILL.md +1 -1
- package/skills/using-skillwiki/SKILL.md +4 -1
- package/skills/wiki-freshness-repair/SKILL.md +94 -0
- package/skills/wiki-ingest/SKILL.md +8 -0
- package/skills/wiki-query/SKILL.md +1 -1
- package/dist/chunk-QNSD72FH.js +0 -466
- package/dist/sources-CMV5DKS5.js +0 -9
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
atomicWriteText,
|
|
4
4
|
buildRootIndexUniverse
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-74OSLCXE.js";
|
|
6
6
|
import {
|
|
7
7
|
authorizeRawOperation,
|
|
8
8
|
buildSourceReferenceIndex,
|
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
resolveExistingRegularFileInsideVault,
|
|
23
23
|
stripFencedBlocks,
|
|
24
24
|
writeLogEvent
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-GAHMWLWU.js";
|
|
26
26
|
import {
|
|
27
27
|
ExitCode,
|
|
28
28
|
FleetManifestSchema,
|
|
@@ -40,11 +40,11 @@ import {
|
|
|
40
40
|
scanVault,
|
|
41
41
|
splitFrontmatter,
|
|
42
42
|
vaultIoConcurrency
|
|
43
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-IJ7DD7QZ.js";
|
|
44
44
|
|
|
45
45
|
// src/utils/managed-write-preflight.ts
|
|
46
46
|
import { existsSync as existsSync11 } from "fs";
|
|
47
|
-
import { join as
|
|
47
|
+
import { join as join21, resolve as resolve6 } from "path";
|
|
48
48
|
|
|
49
49
|
// src/commands/fleet.ts
|
|
50
50
|
import { readFile as readFile2 } from "fs/promises";
|
|
@@ -606,13 +606,332 @@ function safeUserName() {
|
|
|
606
606
|
|
|
607
607
|
// src/commands/sync.ts
|
|
608
608
|
import { existsSync as existsSync8 } from "fs";
|
|
609
|
-
import { join as
|
|
609
|
+
import { join as join17 } from "path";
|
|
610
610
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
611
611
|
|
|
612
|
-
// src/
|
|
612
|
+
// src/utils/last-op.ts
|
|
613
|
+
import { readFileSync, writeFileSync, mkdirSync, unlinkSync, existsSync } from "fs";
|
|
614
|
+
import { join as join2 } from "path";
|
|
615
|
+
var LAST_OP_DIR = ".skillwiki";
|
|
616
|
+
var LAST_OP_FILE = "last-op.json";
|
|
617
|
+
function lastOpPath(vault) {
|
|
618
|
+
return join2(vault, LAST_OP_DIR, LAST_OP_FILE);
|
|
619
|
+
}
|
|
620
|
+
function readLastOp(vault) {
|
|
621
|
+
const p = lastOpPath(vault);
|
|
622
|
+
if (!existsSync(p)) return [];
|
|
623
|
+
try {
|
|
624
|
+
const raw = readFileSync(p, "utf8");
|
|
625
|
+
const parsed = JSON.parse(raw);
|
|
626
|
+
if (!Array.isArray(parsed)) {
|
|
627
|
+
unlinkSync(p);
|
|
628
|
+
return [];
|
|
629
|
+
}
|
|
630
|
+
return parsed;
|
|
631
|
+
} catch {
|
|
632
|
+
try {
|
|
633
|
+
unlinkSync(p);
|
|
634
|
+
} catch (_e) {
|
|
635
|
+
}
|
|
636
|
+
return [];
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
function appendLastOp(vault, entry) {
|
|
640
|
+
const existing = readLastOp(vault);
|
|
641
|
+
existing.push(entry);
|
|
642
|
+
const dir = join2(vault, LAST_OP_DIR);
|
|
643
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
644
|
+
writeFileSync(lastOpPath(vault), JSON.stringify(existing, null, 2), "utf8");
|
|
645
|
+
}
|
|
646
|
+
function clearLastOp(vault) {
|
|
647
|
+
const p = lastOpPath(vault);
|
|
648
|
+
try {
|
|
649
|
+
unlinkSync(p);
|
|
650
|
+
} catch (_e) {
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// src/lint/helpers.ts
|
|
655
|
+
import { readdir } from "fs/promises";
|
|
656
|
+
import { join as join3, relative, sep } from "path";
|
|
657
|
+
var ERROR_ORDER = [
|
|
658
|
+
"sensitive_content",
|
|
659
|
+
"conflict_markers",
|
|
660
|
+
"broken_wikilinks",
|
|
661
|
+
"invalid_frontmatter",
|
|
662
|
+
"raw_source_identity_conflict",
|
|
663
|
+
"raw_dedup",
|
|
664
|
+
"broken_sources",
|
|
665
|
+
"tag_not_in_taxonomy",
|
|
666
|
+
"path_too_long"
|
|
667
|
+
];
|
|
668
|
+
var WARNING_ORDER = [
|
|
669
|
+
"raw_body_duplicate",
|
|
670
|
+
"raw_subdirectory_duplicate",
|
|
671
|
+
"file_source_url",
|
|
672
|
+
"index_incomplete",
|
|
673
|
+
"index_link_format",
|
|
674
|
+
"stale_page",
|
|
675
|
+
"page_too_large",
|
|
676
|
+
"log_rotate_needed",
|
|
677
|
+
"orphans",
|
|
678
|
+
"compound_refs",
|
|
679
|
+
"legacy_citation_style",
|
|
680
|
+
"orphaned_citations",
|
|
681
|
+
"duplicate_frontmatter",
|
|
682
|
+
"frontmatter_yaml_invalid",
|
|
683
|
+
"work_item_health",
|
|
684
|
+
"orphaned_project_pages",
|
|
685
|
+
"missing_overview",
|
|
686
|
+
"missing_diagram",
|
|
687
|
+
"cycle_traps"
|
|
688
|
+
];
|
|
689
|
+
var INFO_ORDER = [
|
|
690
|
+
"bridges",
|
|
691
|
+
"sparse_community",
|
|
692
|
+
"page_structure",
|
|
693
|
+
"topic_map_recommended",
|
|
694
|
+
"frontmatter_wikilink",
|
|
695
|
+
"wikilink_citation",
|
|
696
|
+
"missing_tldr",
|
|
697
|
+
"stale_sections",
|
|
698
|
+
"cli_refs"
|
|
699
|
+
];
|
|
700
|
+
var KNOWN_BUCKETS = [...ERROR_ORDER, ...WARNING_ORDER, ...INFO_ORDER];
|
|
701
|
+
var CLI_REFS_TYPED_DIRS = ["entities", "concepts", "comparisons", "queries", "meta"];
|
|
702
|
+
var STRUCT_MIN_BODY_LINES = 60;
|
|
703
|
+
var STRUCT_MIN_SECTIONS = 3;
|
|
704
|
+
var CANONICAL_LOCAL_SOURCE_LABEL = /^\s*(?:>\s*)?(?:[-*+]\s*)?Source (?:file|inspected):/i;
|
|
705
|
+
var LOCAL_ABSOLUTE_SOURCE_REF = /(?:file:\/\/(?:\/)?(?:Users|home)\/|\/(?:Users|home)\/)/;
|
|
706
|
+
function hasDuplicateFrontmatter(body) {
|
|
707
|
+
if (/^---\r?\n/.test(body)) return true;
|
|
708
|
+
const lines = body.split(/\r?\n/);
|
|
709
|
+
const limit = Math.min(lines.length, 20);
|
|
710
|
+
let seenYamlKey = false;
|
|
711
|
+
for (let i = 0; i < limit; i++) {
|
|
712
|
+
if (/^\w[\w-]*:/.test(lines[i].trim())) seenYamlKey = true;
|
|
713
|
+
if (seenYamlKey && lines[i].trim() === "---") return true;
|
|
714
|
+
}
|
|
715
|
+
return false;
|
|
716
|
+
}
|
|
717
|
+
function hasCanonicalLocalSourceAssertion(body) {
|
|
718
|
+
const visibleBody = stripFencedBlocks(body);
|
|
719
|
+
return visibleBody.split(/\r?\n/).some(
|
|
720
|
+
(line) => CANONICAL_LOCAL_SOURCE_LABEL.test(line) && LOCAL_ABSOLUTE_SOURCE_REF.test(line)
|
|
721
|
+
);
|
|
722
|
+
}
|
|
723
|
+
function shouldCheckCanonicalLocalSourceAssertion(page) {
|
|
724
|
+
if (page.relPath.startsWith("raw/transcripts/")) return false;
|
|
725
|
+
if (/^projects\/[^/]+\/work\/[^/]+\/log\.md$/.test(page.relPath)) return false;
|
|
726
|
+
if (page.relPath.startsWith("raw/")) return true;
|
|
727
|
+
if (/^(entities|concepts|comparisons|queries|meta)\//.test(page.relPath)) return true;
|
|
728
|
+
if (/^projects\/[^/]+\/compound\//.test(page.relPath)) return true;
|
|
729
|
+
if (/^projects\/[^/]+\/work\/[^/]+\/(spec|plan)\.md$/.test(page.relPath)) return true;
|
|
730
|
+
return false;
|
|
731
|
+
}
|
|
732
|
+
function extractSourceEntries(rawFm) {
|
|
733
|
+
const lines = rawFm.split(/\r?\n/);
|
|
734
|
+
const sourcesLineIdx = lines.findIndex((l) => /^sources:/.test(l));
|
|
735
|
+
if (sourcesLineIdx === -1) return [];
|
|
736
|
+
const sourcesLine = lines[sourcesLineIdx].trim();
|
|
737
|
+
const inlineMatch = sourcesLine.match(/^sources:\s*\[(.+)]\s*$/);
|
|
738
|
+
if (inlineMatch) {
|
|
739
|
+
return [...inlineMatch[1].matchAll(/"[^"]*"|'[^']*'|[^,\s]\S*/g)].map(
|
|
740
|
+
(m) => m[0].replace(/,\s*$/, "")
|
|
741
|
+
);
|
|
742
|
+
}
|
|
743
|
+
const entries = [];
|
|
744
|
+
for (let i = sourcesLineIdx + 1; i < lines.length; i++) {
|
|
745
|
+
const line = lines[i];
|
|
746
|
+
if (!/^\s+- /.test(line)) break;
|
|
747
|
+
entries.push(line.replace(/^\s+- /, "").trim());
|
|
748
|
+
}
|
|
749
|
+
return entries;
|
|
750
|
+
}
|
|
751
|
+
function shellQuote(value) {
|
|
752
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
753
|
+
}
|
|
754
|
+
function formatExample(item) {
|
|
755
|
+
if (typeof item === "string") return item;
|
|
756
|
+
if (item === null || item === void 0) return String(item);
|
|
757
|
+
try {
|
|
758
|
+
return JSON.stringify(item);
|
|
759
|
+
} catch {
|
|
760
|
+
return String(item);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
function summarizeBucket(bucket, severity, vaultPath, examplesLimit) {
|
|
764
|
+
const safeLimit = Math.max(0, Math.min(examplesLimit, 10));
|
|
765
|
+
const examples = bucket.items.slice(0, safeLimit).map(formatExample);
|
|
766
|
+
return {
|
|
767
|
+
kind: bucket.kind,
|
|
768
|
+
severity,
|
|
769
|
+
count: bucket.items.length,
|
|
770
|
+
examples,
|
|
771
|
+
examples_limit: safeLimit,
|
|
772
|
+
sample_truncated: bucket.items.length > examples.length,
|
|
773
|
+
details_command: `skillwiki lint ${shellQuote(vaultPath)} --only ${bucket.kind}`
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
function severityForBucket(kind) {
|
|
777
|
+
if (ERROR_ORDER.includes(kind)) return "error";
|
|
778
|
+
if (WARNING_ORDER.includes(kind)) return "warning";
|
|
779
|
+
return "info";
|
|
780
|
+
}
|
|
781
|
+
function lintReadVault(input) {
|
|
782
|
+
if (input.fix) {
|
|
783
|
+
return { readPath: input.vault, readMirror: false };
|
|
784
|
+
}
|
|
785
|
+
const resolved = resolveReadOnlyVaultRoot(input.vault);
|
|
786
|
+
return { readPath: resolved.root, readMirror: resolved.mirrored };
|
|
787
|
+
}
|
|
788
|
+
function lintVaultOutput(input, readVault) {
|
|
789
|
+
return {
|
|
790
|
+
path: input.vault,
|
|
791
|
+
source: input.source ?? "resolved",
|
|
792
|
+
read_path: readVault.readPath,
|
|
793
|
+
read_mirror: readVault.readMirror
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
function readMirrorHintLines(vault) {
|
|
797
|
+
if (!vault.read_mirror) return [];
|
|
798
|
+
return [
|
|
799
|
+
`read mirror: ${vault.read_path}`,
|
|
800
|
+
`requested vault: ${vault.path}`,
|
|
801
|
+
"if results look stale, refresh the read mirror or rerun with SKILLWIKI_DISABLE_VAULT_READ_MIRROR=1 for a live scan; live scans may be slower"
|
|
802
|
+
];
|
|
803
|
+
}
|
|
804
|
+
function appendLintFixLastOp(vault, fixed) {
|
|
805
|
+
if (fixed.length === 0) return;
|
|
806
|
+
appendLastOp(vault, {
|
|
807
|
+
operation: "lint-fix",
|
|
808
|
+
summary: `fixed ${fixed.length} page(s)`,
|
|
809
|
+
files: fixed,
|
|
810
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
811
|
+
});
|
|
812
|
+
}
|
|
813
|
+
function summarizeLintOutput(output, examplesLimit = 3) {
|
|
814
|
+
const buckets = [
|
|
815
|
+
...output.by_severity.error.map(
|
|
816
|
+
(bucket) => summarizeBucket(bucket, "error", output.vault.path, examplesLimit)
|
|
817
|
+
),
|
|
818
|
+
...output.by_severity.warning.map(
|
|
819
|
+
(bucket) => summarizeBucket(bucket, "warning", output.vault.path, examplesLimit)
|
|
820
|
+
),
|
|
821
|
+
...output.by_severity.info.map(
|
|
822
|
+
(bucket) => summarizeBucket(bucket, "info", output.vault.path, examplesLimit)
|
|
823
|
+
)
|
|
824
|
+
];
|
|
825
|
+
const lines = [];
|
|
826
|
+
lines.push(...readMirrorHintLines(output.vault));
|
|
827
|
+
lines.push(`errors: ${output.summary.errors}`);
|
|
828
|
+
lines.push(`warnings: ${output.summary.warnings}`);
|
|
829
|
+
lines.push(`info: ${output.summary.info}`);
|
|
830
|
+
for (const bucket of buckets) {
|
|
831
|
+
lines.push(` ${bucket.kind}: ${bucket.count}`);
|
|
832
|
+
if (bucket.examples.length > 0) {
|
|
833
|
+
lines.push(` e.g. ${bucket.examples[0]}`);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
return {
|
|
837
|
+
vault: output.vault,
|
|
838
|
+
summary: output.summary,
|
|
839
|
+
buckets,
|
|
840
|
+
details_included: false,
|
|
841
|
+
truncated: false,
|
|
842
|
+
fixed: output.fixed,
|
|
843
|
+
unresolved: output.unresolved,
|
|
844
|
+
humanHint: lines.join("\n")
|
|
845
|
+
};
|
|
846
|
+
}
|
|
847
|
+
function outputForOnlyBucket(input, match, fixed, unresolved, readVault = lintReadVault(input)) {
|
|
848
|
+
const severity = severityForBucket(input.only);
|
|
849
|
+
const filtered = severity === "error" ? { error: match, warning: [], info: [] } : severity === "warning" ? { error: [], warning: match, info: [] } : { error: [], warning: [], info: match };
|
|
850
|
+
const summary = {
|
|
851
|
+
errors: filtered.error.reduce((n, b) => n + b.items.length, 0),
|
|
852
|
+
warnings: filtered.warning.reduce((n, b) => n + b.items.length, 0),
|
|
853
|
+
info: filtered.info.reduce((n, b) => n + b.items.length, 0)
|
|
854
|
+
};
|
|
855
|
+
let exitCode = ExitCode.OK;
|
|
856
|
+
if (summary.errors > 0) exitCode = ExitCode.LINT_HAS_ERRORS;
|
|
857
|
+
else if (summary.warnings > 0 || summary.info > 0) exitCode = ExitCode.LINT_HAS_WARNINGS;
|
|
858
|
+
const vault = lintVaultOutput(input, readVault);
|
|
859
|
+
const hintLines = [
|
|
860
|
+
...readMirrorHintLines(vault),
|
|
861
|
+
`--only ${input.only}`,
|
|
862
|
+
match.length === 0 ? "0 violations" : match.map((b) => ` ${b.kind}: ${b.items.length}`).join("\n")
|
|
863
|
+
];
|
|
864
|
+
const output = {
|
|
865
|
+
vault,
|
|
866
|
+
summary,
|
|
867
|
+
by_severity: filtered,
|
|
868
|
+
fixed,
|
|
869
|
+
unresolved,
|
|
870
|
+
humanHint: hintLines.join("\n")
|
|
871
|
+
};
|
|
872
|
+
return {
|
|
873
|
+
exitCode,
|
|
874
|
+
result: ok(input.summary ? summarizeLintOutput(output, input.examplesLimit) : output)
|
|
875
|
+
};
|
|
876
|
+
}
|
|
877
|
+
async function walkMarkdownFiles(absDir, vaultRoot) {
|
|
878
|
+
const entries = await readdir(absDir, { withFileTypes: true });
|
|
879
|
+
const pages = [];
|
|
880
|
+
for (const entry of entries) {
|
|
881
|
+
const absPath = join3(absDir, entry.name);
|
|
882
|
+
if (entry.isDirectory()) {
|
|
883
|
+
if (entry.name === ".git" || entry.name === "node_modules") continue;
|
|
884
|
+
pages.push(...await walkMarkdownFiles(absPath, vaultRoot));
|
|
885
|
+
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
886
|
+
pages.push({ absPath, relPath: relative(vaultRoot, absPath).split(sep).join("/") });
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
return pages;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
// src/lint/fingerprints.ts
|
|
893
|
+
function lintIssueFingerprint(bucket, item) {
|
|
894
|
+
const page = extractIssuePage(item);
|
|
895
|
+
const detail = normalizeIssueDetail(item);
|
|
896
|
+
return `${bucket}\0${page}\0${detail}`;
|
|
897
|
+
}
|
|
898
|
+
function extractIssuePage(item) {
|
|
899
|
+
if (typeof item === "string") {
|
|
900
|
+
const m = item.match(/^([^:]+?)(?::\s|$)/);
|
|
901
|
+
return (m?.[1] ?? item).trim();
|
|
902
|
+
}
|
|
903
|
+
if (item && typeof item === "object") {
|
|
904
|
+
const obj = item;
|
|
905
|
+
for (const key of ["path", "file", "page", "relPath"]) {
|
|
906
|
+
if (typeof obj[key] === "string") return obj[key];
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
return "";
|
|
910
|
+
}
|
|
911
|
+
function normalizeIssueDetail(item) {
|
|
912
|
+
if (typeof item === "string") {
|
|
913
|
+
return item.replace(/\s+/g, " ").trim();
|
|
914
|
+
}
|
|
915
|
+
try {
|
|
916
|
+
return JSON.stringify(item, Object.keys(item).sort());
|
|
917
|
+
} catch {
|
|
918
|
+
return String(item);
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
function collectLintErrorFingerprints(output) {
|
|
922
|
+
const fps = /* @__PURE__ */ new Set();
|
|
923
|
+
for (const bucket of output.by_severity.error) {
|
|
924
|
+
for (const item of bucket.items) {
|
|
925
|
+
fps.add(lintIssueFingerprint(bucket.kind, item));
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
return fps;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
// src/lint/rules.ts
|
|
613
932
|
import { existsSync as existsSync4 } from "fs";
|
|
614
|
-
import { readFile as readFile13
|
|
615
|
-
import { join as
|
|
933
|
+
import { readFile as readFile13 } from "fs/promises";
|
|
934
|
+
import { join as join13 } from "path";
|
|
616
935
|
|
|
617
936
|
// src/parsers/wikilinks.ts
|
|
618
937
|
var FENCE = /```[\s\S]*?```|`[^`\n]*`/g;
|
|
@@ -720,7 +1039,7 @@ ${broken.map((b) => ` ${b.page}:[[${b.slug}]] (line ${b.line})`).join("\n")}` }
|
|
|
720
1039
|
|
|
721
1040
|
// src/commands/tag-audit.ts
|
|
722
1041
|
import { readFile as readFile3 } from "fs/promises";
|
|
723
|
-
import { join as
|
|
1042
|
+
import { join as join4 } from "path";
|
|
724
1043
|
|
|
725
1044
|
// src/parsers/taxonomy.ts
|
|
726
1045
|
import yaml2 from "js-yaml";
|
|
@@ -873,7 +1192,7 @@ async function runTagAudit(input) {
|
|
|
873
1192
|
const scanResult = input.scan ? ok(input.scan) : await scanVault(input.vault);
|
|
874
1193
|
if (!scanResult.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scanResult };
|
|
875
1194
|
const scan = scanResult.data;
|
|
876
|
-
const schemaText = await readFile3(
|
|
1195
|
+
const schemaText = await readFile3(join4(input.vault, "SCHEMA.md"), "utf8");
|
|
877
1196
|
const tax = extractTaxonomy(schemaText);
|
|
878
1197
|
if (!tax.ok) return { exitCode: ExitCode.INVALID_FRONTMATTER, result: tax };
|
|
879
1198
|
const allowed = new Set(tax.data);
|
|
@@ -906,7 +1225,7 @@ async function runTagAudit(input) {
|
|
|
906
1225
|
|
|
907
1226
|
// src/commands/index-check.ts
|
|
908
1227
|
import { readFile as readFile4 } from "fs/promises";
|
|
909
|
-
import { join as
|
|
1228
|
+
import { join as join5 } from "path";
|
|
910
1229
|
function normalizeIndexTarget(raw) {
|
|
911
1230
|
return raw.replace(/\.md$/, "").replace(/^\.?\//, "");
|
|
912
1231
|
}
|
|
@@ -918,7 +1237,7 @@ async function runIndexCheck(input) {
|
|
|
918
1237
|
}
|
|
919
1238
|
let indexText = "";
|
|
920
1239
|
try {
|
|
921
|
-
indexText = await readFile4(
|
|
1240
|
+
indexText = await readFile4(join5(input.vault, "index.md"), "utf8");
|
|
922
1241
|
} catch {
|
|
923
1242
|
}
|
|
924
1243
|
const indexTargets = /* @__PURE__ */ new Set();
|
|
@@ -971,9 +1290,28 @@ async function runIndexCheck(input) {
|
|
|
971
1290
|
return { exitCode: ExitCode.OK, result: ok({ missing_from_index, ghost_entries, humanHint: hintLines.join("\n") }) };
|
|
972
1291
|
}
|
|
973
1292
|
|
|
1293
|
+
// src/commands/index-link-format.ts
|
|
1294
|
+
import { readFile as readFile5 } from "fs/promises";
|
|
1295
|
+
import { join as join6 } from "path";
|
|
1296
|
+
var MD_LINK_RE = /\[[^\[\]]+\]\([^)]+\.md\)/;
|
|
1297
|
+
async function runIndexLinkFormat(input) {
|
|
1298
|
+
let text = "";
|
|
1299
|
+
try {
|
|
1300
|
+
text = await readFile5(join6(input.vault, "index.md"), "utf8");
|
|
1301
|
+
} catch {
|
|
1302
|
+
}
|
|
1303
|
+
const markdown_links = [];
|
|
1304
|
+
for (const [i, line] of text.split("\n").entries()) {
|
|
1305
|
+
if (MD_LINK_RE.test(line)) markdown_links.push({ line: i + 1, text: line.trim() });
|
|
1306
|
+
}
|
|
1307
|
+
const humanHint = markdown_links.length === 0 ? "all index links use wikilink format" : `markdown links found: ${markdown_links.length}
|
|
1308
|
+
${markdown_links.map((l) => ` line ${l.line}: ${l.text}`).join("\n")}`;
|
|
1309
|
+
return { exitCode: ExitCode.OK, result: ok({ markdown_links, humanHint }) };
|
|
1310
|
+
}
|
|
1311
|
+
|
|
974
1312
|
// src/commands/stale.ts
|
|
975
|
-
import { readdir, rename, mkdir as mkdir3, readFile as
|
|
976
|
-
import { join as
|
|
1313
|
+
import { readdir as readdir2, rename, mkdir as mkdir3, readFile as readFile7 } from "fs/promises";
|
|
1314
|
+
import { join as join7 } from "path";
|
|
977
1315
|
|
|
978
1316
|
// src/parsers/expiry-annotations.ts
|
|
979
1317
|
var HEADING_RE = /^#{1,6}\s+(.+)$/;
|
|
@@ -1009,51 +1347,9 @@ function parseExpiryAnnotations(content, pagePath) {
|
|
|
1009
1347
|
return annotations;
|
|
1010
1348
|
}
|
|
1011
1349
|
|
|
1012
|
-
// src/utils/last-op.ts
|
|
1013
|
-
import { readFileSync, writeFileSync, mkdirSync, unlinkSync, existsSync } from "fs";
|
|
1014
|
-
import { join as join4 } from "path";
|
|
1015
|
-
var LAST_OP_DIR = ".skillwiki";
|
|
1016
|
-
var LAST_OP_FILE = "last-op.json";
|
|
1017
|
-
function lastOpPath(vault) {
|
|
1018
|
-
return join4(vault, LAST_OP_DIR, LAST_OP_FILE);
|
|
1019
|
-
}
|
|
1020
|
-
function readLastOp(vault) {
|
|
1021
|
-
const p = lastOpPath(vault);
|
|
1022
|
-
if (!existsSync(p)) return [];
|
|
1023
|
-
try {
|
|
1024
|
-
const raw = readFileSync(p, "utf8");
|
|
1025
|
-
const parsed = JSON.parse(raw);
|
|
1026
|
-
if (!Array.isArray(parsed)) {
|
|
1027
|
-
unlinkSync(p);
|
|
1028
|
-
return [];
|
|
1029
|
-
}
|
|
1030
|
-
return parsed;
|
|
1031
|
-
} catch {
|
|
1032
|
-
try {
|
|
1033
|
-
unlinkSync(p);
|
|
1034
|
-
} catch (_e) {
|
|
1035
|
-
}
|
|
1036
|
-
return [];
|
|
1037
|
-
}
|
|
1038
|
-
}
|
|
1039
|
-
function appendLastOp(vault, entry) {
|
|
1040
|
-
const existing = readLastOp(vault);
|
|
1041
|
-
existing.push(entry);
|
|
1042
|
-
const dir = join4(vault, LAST_OP_DIR);
|
|
1043
|
-
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
1044
|
-
writeFileSync(lastOpPath(vault), JSON.stringify(existing, null, 2), "utf8");
|
|
1045
|
-
}
|
|
1046
|
-
function clearLastOp(vault) {
|
|
1047
|
-
const p = lastOpPath(vault);
|
|
1048
|
-
try {
|
|
1049
|
-
unlinkSync(p);
|
|
1050
|
-
} catch (_e) {
|
|
1051
|
-
}
|
|
1052
|
-
}
|
|
1053
|
-
|
|
1054
1350
|
// src/utils/raw-structural-transaction.ts
|
|
1055
1351
|
import { createHash } from "crypto";
|
|
1056
|
-
import { copyFile, mkdir as mkdir2, readFile as
|
|
1352
|
+
import { copyFile, mkdir as mkdir2, readFile as readFile6, unlink } from "fs/promises";
|
|
1057
1353
|
import { constants } from "fs";
|
|
1058
1354
|
import { dirname as dirname2 } from "path";
|
|
1059
1355
|
function sha256(bytes) {
|
|
@@ -1109,7 +1405,7 @@ async function planRawStructuralMove(input) {
|
|
|
1109
1405
|
if (!destinationResolved.ok) {
|
|
1110
1406
|
return destinationResolved.error === "RAW_DESTINATION_EXISTS" ? destinationResolved : err("RAW_DESTINATION_UNSAFE", { path: input.destination, cause: destinationResolved });
|
|
1111
1407
|
}
|
|
1112
|
-
const sourceBytes = await
|
|
1408
|
+
const sourceBytes = await readFile6(sourceResolved.data);
|
|
1113
1409
|
const sourceSha = sha256(sourceBytes);
|
|
1114
1410
|
const operation_id = operationId("raw-structural", [input.operation, input.source, input.destination, sourceSha]);
|
|
1115
1411
|
const base = {
|
|
@@ -1142,7 +1438,7 @@ async function applyRawStructuralMove(input) {
|
|
|
1142
1438
|
} catch (error) {
|
|
1143
1439
|
return err("RAW_MOVE_COPY_FAILED", { source: input.source, destination: input.destination, message: String(error) });
|
|
1144
1440
|
}
|
|
1145
|
-
const destinationBytes = await
|
|
1441
|
+
const destinationBytes = await readFile6(destinationAbs);
|
|
1146
1442
|
const destinationSha = sha256(destinationBytes);
|
|
1147
1443
|
if (destinationSha !== planned.data.source_sha256) {
|
|
1148
1444
|
return err("RAW_MOVE_HASH_MISMATCH", { source: input.source, destination: input.destination, source_sha256: planned.data.source_sha256, destination_sha256: destinationSha });
|
|
@@ -1288,10 +1584,10 @@ async function runStale(input) {
|
|
|
1288
1584
|
const incompleteWorkItems = [];
|
|
1289
1585
|
const archived = [];
|
|
1290
1586
|
const workDirs = /* @__PURE__ */ new Map();
|
|
1291
|
-
const projectsDir =
|
|
1587
|
+
const projectsDir = join7(input.vault, "projects");
|
|
1292
1588
|
let projectSlugs = [];
|
|
1293
1589
|
try {
|
|
1294
|
-
projectSlugs = (await
|
|
1590
|
+
projectSlugs = (await readdir2(projectsDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
1295
1591
|
} catch {
|
|
1296
1592
|
}
|
|
1297
1593
|
if (input.project) {
|
|
@@ -1301,21 +1597,21 @@ async function runStale(input) {
|
|
|
1301
1597
|
projectSlugs = [input.project];
|
|
1302
1598
|
}
|
|
1303
1599
|
for (const slug of projectSlugs) {
|
|
1304
|
-
const workPath =
|
|
1600
|
+
const workPath = join7(projectsDir, slug, "work");
|
|
1305
1601
|
let entries;
|
|
1306
1602
|
try {
|
|
1307
|
-
entries = await
|
|
1603
|
+
entries = await readdir2(workPath, { withFileTypes: true });
|
|
1308
1604
|
} catch {
|
|
1309
1605
|
continue;
|
|
1310
1606
|
}
|
|
1311
1607
|
for (const e of entries) {
|
|
1312
1608
|
if (!e.isDirectory()) continue;
|
|
1313
1609
|
const relDir = `projects/${slug}/work/${e.name}`;
|
|
1314
|
-
const absDir =
|
|
1610
|
+
const absDir = join7(workPath, e.name);
|
|
1315
1611
|
let status = "";
|
|
1316
1612
|
let files;
|
|
1317
1613
|
try {
|
|
1318
|
-
files = await
|
|
1614
|
+
files = await readdir2(absDir);
|
|
1319
1615
|
} catch {
|
|
1320
1616
|
workDirs.set(relDir, "");
|
|
1321
1617
|
continue;
|
|
@@ -1323,7 +1619,7 @@ async function runStale(input) {
|
|
|
1323
1619
|
for (const f of files) {
|
|
1324
1620
|
if (!f.endsWith(".md")) continue;
|
|
1325
1621
|
try {
|
|
1326
|
-
const fm = extractFrontmatter(await
|
|
1622
|
+
const fm = extractFrontmatter(await readFile7(join7(absDir, f), "utf8"));
|
|
1327
1623
|
if (fm.ok && typeof fm.data.status === "string") {
|
|
1328
1624
|
status = fm.data.status;
|
|
1329
1625
|
break;
|
|
@@ -1383,9 +1679,9 @@ async function runStale(input) {
|
|
|
1383
1679
|
if (entry) transcriptMeta.set(entry[0], entry[1]);
|
|
1384
1680
|
}
|
|
1385
1681
|
const claimSources = await mapWithConcurrency([...workDirs.keys()], vaultIoConcurrency(), async (relDir) => {
|
|
1386
|
-
const specPath =
|
|
1682
|
+
const specPath = join7(input.vault, relDir, "spec.md");
|
|
1387
1683
|
try {
|
|
1388
|
-
const specContent = await
|
|
1684
|
+
const specContent = await readFile7(specPath, "utf8");
|
|
1389
1685
|
const specFm = extractFrontmatter(specContent);
|
|
1390
1686
|
if (!specFm.ok) return null;
|
|
1391
1687
|
return {
|
|
@@ -1427,7 +1723,7 @@ async function runStale(input) {
|
|
|
1427
1723
|
if (daysSince(dateStr) < input.days) continue;
|
|
1428
1724
|
let files;
|
|
1429
1725
|
try {
|
|
1430
|
-
files = await
|
|
1726
|
+
files = await readdir2(join7(input.vault, relDir));
|
|
1431
1727
|
} catch {
|
|
1432
1728
|
continue;
|
|
1433
1729
|
}
|
|
@@ -1497,7 +1793,7 @@ async function runStale(input) {
|
|
|
1497
1793
|
staleSections.push(...staleSectionResults.flat());
|
|
1498
1794
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
1499
1795
|
if (input.archive) {
|
|
1500
|
-
const archiveDir =
|
|
1796
|
+
const archiveDir = join7(input.vault, "_archive", today);
|
|
1501
1797
|
const relocations = await readSourceRelocations(input.vault);
|
|
1502
1798
|
if (!relocations.ok) return { exitCode: ExitCode.WRITE_FAILED, result: relocations };
|
|
1503
1799
|
const typedPaths = new Set(scan.typedKnowledge.map((page) => page.relPath));
|
|
@@ -1566,19 +1862,19 @@ async function runStale(input) {
|
|
|
1566
1862
|
const active = parseActiveWorkPath(w.path);
|
|
1567
1863
|
if (active) {
|
|
1568
1864
|
const { project: slug, item: itemName } = active;
|
|
1569
|
-
const histDir =
|
|
1865
|
+
const histDir = join7(input.vault, "projects", slug, "history", "archived-work");
|
|
1570
1866
|
await mkdir3(histDir, { recursive: true });
|
|
1571
|
-
const dest =
|
|
1867
|
+
const dest = join7(histDir, itemName);
|
|
1572
1868
|
try {
|
|
1573
|
-
await rename(
|
|
1869
|
+
await rename(join7(input.vault, w.path), dest);
|
|
1574
1870
|
archived.push(w.path);
|
|
1575
1871
|
} catch (error) {
|
|
1576
1872
|
return { exitCode: ExitCode.WRITE_FAILED, result: err("WRITE_FAILED", { path: w.path, destination: dest, message: String(error) }) };
|
|
1577
1873
|
}
|
|
1578
1874
|
} else {
|
|
1579
|
-
const dest =
|
|
1875
|
+
const dest = join7(archiveDir, w.path.replace(/\//g, "_"));
|
|
1580
1876
|
try {
|
|
1581
|
-
await rename(
|
|
1877
|
+
await rename(join7(input.vault, w.path), dest);
|
|
1582
1878
|
archived.push(w.path);
|
|
1583
1879
|
} catch (error) {
|
|
1584
1880
|
return { exitCode: ExitCode.WRITE_FAILED, result: err("WRITE_FAILED", { path: w.path, destination: dest, message: String(error) }) };
|
|
@@ -1634,8 +1930,8 @@ async function runPagesize(input) {
|
|
|
1634
1930
|
}
|
|
1635
1931
|
|
|
1636
1932
|
// src/commands/log-rotate.ts
|
|
1637
|
-
import { access, readFile as
|
|
1638
|
-
import { join as
|
|
1933
|
+
import { access, readFile as readFile8, rename as rename2, writeFile as writeFile2, stat } from "fs/promises";
|
|
1934
|
+
import { join as join8 } from "path";
|
|
1639
1935
|
var ENTRY_RE = /^## \[(\d{4})-\d{2}-\d{2}\]/gm;
|
|
1640
1936
|
var FULL_DATE_RE = /^## \[(\d{4}-\d{2}-\d{2})\]/gm;
|
|
1641
1937
|
async function pathExists(p) {
|
|
@@ -1658,27 +1954,27 @@ function archiveNameForExistingYearLog(existingText, fallbackYear) {
|
|
|
1658
1954
|
}
|
|
1659
1955
|
async function sidelineExistingYearLog(vault, rotatedPath, fallbackYear) {
|
|
1660
1956
|
if (!await pathExists(rotatedPath)) return void 0;
|
|
1661
|
-
const existingText = await
|
|
1957
|
+
const existingText = await readFile8(rotatedPath, "utf8");
|
|
1662
1958
|
let archiveName = archiveNameForExistingYearLog(existingText, fallbackYear);
|
|
1663
|
-
let archivePath =
|
|
1959
|
+
let archivePath = join8(vault, archiveName);
|
|
1664
1960
|
if (await pathExists(archivePath)) {
|
|
1665
1961
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
1666
1962
|
archiveName = archiveName.replace(/\.md$/, `-${stamp}.md`);
|
|
1667
|
-
archivePath =
|
|
1963
|
+
archivePath = join8(vault, archiveName);
|
|
1668
1964
|
}
|
|
1669
1965
|
await rename2(rotatedPath, archivePath);
|
|
1670
1966
|
return archiveName;
|
|
1671
1967
|
}
|
|
1672
1968
|
async function runLogRotate(input) {
|
|
1673
1969
|
try {
|
|
1674
|
-
await stat(
|
|
1970
|
+
await stat(join8(input.vault, "SCHEMA.md"));
|
|
1675
1971
|
} catch {
|
|
1676
1972
|
return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { vault: input.vault }) };
|
|
1677
1973
|
}
|
|
1678
|
-
const logPath =
|
|
1974
|
+
const logPath = join8(input.vault, "log.md");
|
|
1679
1975
|
let logText;
|
|
1680
1976
|
try {
|
|
1681
|
-
logText = await
|
|
1977
|
+
logText = await readFile8(logPath, "utf8");
|
|
1682
1978
|
} catch {
|
|
1683
1979
|
return { exitCode: ExitCode.FILE_NOT_FOUND, result: err("FILE_NOT_FOUND", { path: logPath }) };
|
|
1684
1980
|
}
|
|
@@ -1689,7 +1985,7 @@ async function runLogRotate(input) {
|
|
|
1689
1985
|
}
|
|
1690
1986
|
const newestYear = matches[matches.length - 1][1];
|
|
1691
1987
|
const rotatedName = `log-${newestYear}.md`;
|
|
1692
|
-
const rotatedPath =
|
|
1988
|
+
const rotatedPath = join8(input.vault, rotatedName);
|
|
1693
1989
|
const yearExists = await pathExists(rotatedPath);
|
|
1694
1990
|
if (!input.apply) {
|
|
1695
1991
|
const hint = yearExists ? `${entries}/${input.threshold} entries \u2014 rotation needed (use --apply); existing ${rotatedName} will be sidelined to log-archive-* first` : `${entries}/${input.threshold} entries \u2014 rotation needed (use --apply)`;
|
|
@@ -1740,7 +2036,7 @@ Chronological action log. Newest entries last. Skill writes append entries; lint
|
|
|
1740
2036
|
}
|
|
1741
2037
|
|
|
1742
2038
|
// src/utils/wiki-path.ts
|
|
1743
|
-
import { join as
|
|
2039
|
+
import { join as join9 } from "path";
|
|
1744
2040
|
async function resolveInitTimePath(input) {
|
|
1745
2041
|
const chain = [];
|
|
1746
2042
|
if (input.flag !== void 0 && input.flag.length > 0) {
|
|
@@ -1753,27 +2049,27 @@ async function resolveInitTimePath(input) {
|
|
|
1753
2049
|
return { path: input.envValue, source: "env", ...input.explain ? { chain } : {} };
|
|
1754
2050
|
}
|
|
1755
2051
|
if (input.explain) chain.push({ source: "env", matched: false });
|
|
1756
|
-
const sw = await parseDotenvFile(
|
|
2052
|
+
const sw = await parseDotenvFile(join9(input.home, ".skillwiki", ".env"));
|
|
1757
2053
|
if (sw.WIKI_PATH !== void 0) {
|
|
1758
2054
|
if (input.explain) chain.push({ source: "skillwiki-dotenv", matched: true, value: sw.WIKI_PATH });
|
|
1759
2055
|
return { path: sw.WIKI_PATH, source: "skillwiki-dotenv", ...input.explain ? { chain } : {} };
|
|
1760
2056
|
}
|
|
1761
2057
|
if (input.explain) chain.push({ source: "skillwiki-dotenv", matched: false });
|
|
1762
|
-
const hermes = await parseDotenvFile(
|
|
2058
|
+
const hermes = await parseDotenvFile(join9(input.home, ".hermes", ".env"));
|
|
1763
2059
|
if (hermes.WIKI_PATH !== void 0) {
|
|
1764
2060
|
if (input.explain) chain.push({ source: "hermes-dotenv", matched: true, value: hermes.WIKI_PATH });
|
|
1765
2061
|
return { path: hermes.WIKI_PATH, source: "hermes-dotenv", ...input.explain ? { chain } : {} };
|
|
1766
2062
|
}
|
|
1767
2063
|
if (input.explain) chain.push({ source: "hermes-dotenv", matched: false });
|
|
1768
2064
|
if (input.cwd) {
|
|
1769
|
-
const projCfg = await parseDotenvFile(
|
|
2065
|
+
const projCfg = await parseDotenvFile(join9(input.cwd, ".skillwiki", ".env"));
|
|
1770
2066
|
if (projCfg.WIKI_PATH !== void 0) {
|
|
1771
2067
|
if (input.explain) chain.push({ source: "project-dotenv", matched: true, value: projCfg.WIKI_PATH });
|
|
1772
2068
|
return { path: projCfg.WIKI_PATH, source: "project-dotenv", ...input.explain ? { chain } : {} };
|
|
1773
2069
|
}
|
|
1774
2070
|
}
|
|
1775
2071
|
if (input.explain) chain.push({ source: "project-dotenv", matched: false });
|
|
1776
|
-
const fallback =
|
|
2072
|
+
const fallback = join9(input.home, "wiki");
|
|
1777
2073
|
if (input.explain) chain.push({ source: "default", matched: true, value: fallback });
|
|
1778
2074
|
return { path: fallback, source: "default", ...input.explain ? { chain } : {} };
|
|
1779
2075
|
}
|
|
@@ -1784,7 +2080,7 @@ async function resolveRuntimePath(input) {
|
|
|
1784
2080
|
return ok({ path: input.flag, source: "flag", ...input.explain ? { chain } : {} });
|
|
1785
2081
|
}
|
|
1786
2082
|
if (input.explain) chain.push({ source: "flag", matched: false });
|
|
1787
|
-
const swGlobal = await parseDotenvFile(
|
|
2083
|
+
const swGlobal = await parseDotenvFile(join9(input.home, ".skillwiki", ".env"));
|
|
1788
2084
|
const wikiName = input.wiki;
|
|
1789
2085
|
if (wikiName !== void 0 && wikiName.length > 0) {
|
|
1790
2086
|
if (wikiName.toLowerCase() === "default") {
|
|
@@ -1828,7 +2124,7 @@ async function resolveRuntimePath(input) {
|
|
|
1828
2124
|
}
|
|
1829
2125
|
if (input.explain) chain.push({ source: "env", matched: false });
|
|
1830
2126
|
if (input.cwd) {
|
|
1831
|
-
const projCfg = await parseDotenvFile(
|
|
2127
|
+
const projCfg = await parseDotenvFile(join9(input.cwd, ".skillwiki", ".env"));
|
|
1832
2128
|
if (projCfg.WIKI_PATH !== void 0) {
|
|
1833
2129
|
if (input.explain) chain.push({ source: "project-dotenv", matched: true, value: projCfg.WIKI_PATH });
|
|
1834
2130
|
return ok({ path: projCfg.WIKI_PATH, source: "project-dotenv", ...input.explain ? { chain } : {} });
|
|
@@ -2107,29 +2403,10 @@ async function runTopicMapCheck(input) {
|
|
|
2107
2403
|
};
|
|
2108
2404
|
}
|
|
2109
2405
|
|
|
2110
|
-
// src/commands/index-link-format.ts
|
|
2111
|
-
import { readFile as readFile8 } from "fs/promises";
|
|
2112
|
-
import { join as join8 } from "path";
|
|
2113
|
-
var MD_LINK_RE = /\[[^\[\]]+\]\([^)]+\.md\)/;
|
|
2114
|
-
async function runIndexLinkFormat(input) {
|
|
2115
|
-
let text = "";
|
|
2116
|
-
try {
|
|
2117
|
-
text = await readFile8(join8(input.vault, "index.md"), "utf8");
|
|
2118
|
-
} catch {
|
|
2119
|
-
}
|
|
2120
|
-
const markdown_links = [];
|
|
2121
|
-
for (const [i, line] of text.split("\n").entries()) {
|
|
2122
|
-
if (MD_LINK_RE.test(line)) markdown_links.push({ line: i + 1, text: line.trim() });
|
|
2123
|
-
}
|
|
2124
|
-
const humanHint = markdown_links.length === 0 ? "all index links use wikilink format" : `markdown links found: ${markdown_links.length}
|
|
2125
|
-
${markdown_links.map((l) => ` line ${l.line}: ${l.text}`).join("\n")}`;
|
|
2126
|
-
return { exitCode: ExitCode.OK, result: ok({ markdown_links, humanHint }) };
|
|
2127
|
-
}
|
|
2128
|
-
|
|
2129
2406
|
// src/commands/dedup.ts
|
|
2130
2407
|
import { createHash as createHash3 } from "crypto";
|
|
2131
2408
|
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
2132
|
-
import { dirname as dirname3, join as
|
|
2409
|
+
import { dirname as dirname3, join as join10, posix, resolve } from "path";
|
|
2133
2410
|
|
|
2134
2411
|
// src/utils/rclone.ts
|
|
2135
2412
|
import { execFile } from "child_process";
|
|
@@ -2374,7 +2651,7 @@ async function runDedup(input) {
|
|
|
2374
2651
|
const structuralPlans = [];
|
|
2375
2652
|
for (const entry of safeEntries) {
|
|
2376
2653
|
for (const duplicate of entry.duplicates) {
|
|
2377
|
-
if (!existsSync2(
|
|
2654
|
+
if (!existsSync2(join10(input.vault, duplicate))) continue;
|
|
2378
2655
|
const destination = dedupDestination(input.vault, duplicate);
|
|
2379
2656
|
const plan = await planRawStructuralMove({ vault: input.vault, operation: "deduplicate", source: duplicate, destination });
|
|
2380
2657
|
if (!plan.ok) return { exitCode: ExitCode.WRITE_FAILED, result: plan };
|
|
@@ -2403,7 +2680,7 @@ async function runDedup(input) {
|
|
|
2403
2680
|
for (const plan of structuralPlans) replacements.set(plan.source, plan.canonical);
|
|
2404
2681
|
const pendingWrites = [];
|
|
2405
2682
|
for (const page of scan.allMarkdown.filter((p) => !p.relPath.startsWith("raw/"))) {
|
|
2406
|
-
const text = readFileSync2(
|
|
2683
|
+
const text = readFileSync2(join10(input.vault, page.relPath), "utf-8");
|
|
2407
2684
|
let updated = text;
|
|
2408
2685
|
let changed = false;
|
|
2409
2686
|
for (const [oldPath, newPath] of replacements) {
|
|
@@ -2430,7 +2707,7 @@ async function runDedup(input) {
|
|
|
2430
2707
|
relocated.push({ from: plan.source, to: plan.destination });
|
|
2431
2708
|
}
|
|
2432
2709
|
for (const pending of pendingWrites) {
|
|
2433
|
-
const write = await safeWritePage(
|
|
2710
|
+
const write = await safeWritePage(join10(input.vault, pending.page), pending.text);
|
|
2434
2711
|
if (!write.ok) return { exitCode: ExitCode.WRITE_FAILED, result: write };
|
|
2435
2712
|
}
|
|
2436
2713
|
}
|
|
@@ -2485,8 +2762,8 @@ async function runDedup(input) {
|
|
|
2485
2762
|
function dedupDestination(vault, source) {
|
|
2486
2763
|
const base = lifecycleDestination(source, "deduplicate");
|
|
2487
2764
|
if (!base.ok) throw new Error(`unsupported raw dedup path: ${source}`);
|
|
2488
|
-
if (!existsSync2(
|
|
2489
|
-
const contentHash = createHash3("sha256").update(readFileSync2(
|
|
2765
|
+
if (!existsSync2(join10(vault, base.data))) return base.data;
|
|
2766
|
+
const contentHash = createHash3("sha256").update(readFileSync2(join10(vault, source))).digest("hex").slice(0, 8);
|
|
2490
2767
|
const ext = posix.extname(base.data);
|
|
2491
2768
|
const stem = ext ? base.data.slice(0, -ext.length) : base.data;
|
|
2492
2769
|
return `${stem}--${contentHash}${ext}`;
|
|
@@ -2538,7 +2815,7 @@ function buildSafeEntries(vault, duplicates, unsafe) {
|
|
|
2538
2815
|
return entries;
|
|
2539
2816
|
}
|
|
2540
2817
|
function hashRawBody(vault, relPath) {
|
|
2541
|
-
const text = readFileSync2(
|
|
2818
|
+
const text = readFileSync2(join10(vault, relPath), "utf-8");
|
|
2542
2819
|
return hashRawBodyText(text);
|
|
2543
2820
|
}
|
|
2544
2821
|
function hashRawBodyText(text) {
|
|
@@ -2579,13 +2856,13 @@ function validateManifestLocalState(vault, manifest) {
|
|
|
2579
2856
|
return err("APPROVAL_INVALID", { message: "dedup manifest contains an invalid active raw source path", path });
|
|
2580
2857
|
}
|
|
2581
2858
|
}
|
|
2582
|
-
const canonicalExists = existsSync2(
|
|
2583
|
-
const liveDuplicates = entry.duplicates.filter((path) => existsSync2(
|
|
2859
|
+
const canonicalExists = existsSync2(join10(vault, entry.canonical));
|
|
2860
|
+
const liveDuplicates = entry.duplicates.filter((path) => existsSync2(join10(vault, path)));
|
|
2584
2861
|
if (!canonicalExists && liveDuplicates.length > 0) {
|
|
2585
2862
|
return err("APPROVAL_INVALID", { message: "dedup manifest canonical is missing while a local duplicate exists", canonical: entry.canonical });
|
|
2586
2863
|
}
|
|
2587
2864
|
for (const path of canonicalExists ? [entry.canonical, ...liveDuplicates] : liveDuplicates) {
|
|
2588
|
-
const text = readFileSync2(
|
|
2865
|
+
const text = readFileSync2(join10(vault, path), "utf8");
|
|
2589
2866
|
const fm = extractFrontmatter(text);
|
|
2590
2867
|
const declaredSha = fm.ok && typeof fm.data.sha256 === "string" ? fm.data.sha256 : null;
|
|
2591
2868
|
if (declaredSha !== entry.sha256 || hashRawBodyText(text) !== entry.bodyHash) {
|
|
@@ -2644,7 +2921,7 @@ async function runRawBodyDedup(vault, scan, pageTextCache) {
|
|
|
2644
2921
|
|
|
2645
2922
|
// src/commands/audit.ts
|
|
2646
2923
|
import { readFile as readFile11, stat as stat2 } from "fs/promises";
|
|
2647
|
-
import { dirname as dirname4, resolve as resolve2, join as
|
|
2924
|
+
import { dirname as dirname4, resolve as resolve2, join as join11 } from "path";
|
|
2648
2925
|
async function runAudit(input) {
|
|
2649
2926
|
let text;
|
|
2650
2927
|
try {
|
|
@@ -2701,7 +2978,7 @@ async function findVaultRoot(start) {
|
|
|
2701
2978
|
let cur = start;
|
|
2702
2979
|
for (let i = 0; i < 20; i++) {
|
|
2703
2980
|
try {
|
|
2704
|
-
await stat2(
|
|
2981
|
+
await stat2(join11(cur, "SCHEMA.md"));
|
|
2705
2982
|
return cur;
|
|
2706
2983
|
} catch {
|
|
2707
2984
|
}
|
|
@@ -2752,86 +3029,10 @@ async function validateCompoundReferences(vault, existingScan, pageTextCache) {
|
|
|
2752
3029
|
return ok(findings);
|
|
2753
3030
|
}
|
|
2754
3031
|
|
|
2755
|
-
// src/commands/frontmatter-fix.ts
|
|
2756
|
-
function isoToday() {
|
|
2757
|
-
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
2758
|
-
}
|
|
2759
|
-
function fixFrontmatter(rawFm) {
|
|
2760
|
-
const additions = [];
|
|
2761
|
-
if (!/^created:/m.test(rawFm)) additions.push(`created: ${isoToday()}`);
|
|
2762
|
-
if (!/^updated:/m.test(rawFm)) additions.push(`updated: ${isoToday()}`);
|
|
2763
|
-
if (!/^tags:/m.test(rawFm)) additions.push("tags: []");
|
|
2764
|
-
if (!/^sources:/m.test(rawFm)) additions.push("sources: []");
|
|
2765
|
-
if (!/^provenance:/m.test(rawFm)) additions.push("provenance: research");
|
|
2766
|
-
if (additions.length === 0) return rawFm;
|
|
2767
|
-
return rawFm.trimEnd() + "\n" + additions.join("\n") + "\n";
|
|
2768
|
-
}
|
|
2769
|
-
function removeOrphanTagsLines(body) {
|
|
2770
|
-
return body.split("\n").filter((line) => !/^tags:\s*\[/.test(line.trim())).join("\n");
|
|
2771
|
-
}
|
|
2772
|
-
async function runFrontmatterFix(input) {
|
|
2773
|
-
const scan = await scanVault(input.vault);
|
|
2774
|
-
if (!scan.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scan };
|
|
2775
|
-
const fixed = [];
|
|
2776
|
-
const skipped = [];
|
|
2777
|
-
let unchanged = 0;
|
|
2778
|
-
for (const page of scan.data.typedKnowledge) {
|
|
2779
|
-
const text = await readPage(page);
|
|
2780
|
-
const split = splitFrontmatter(text);
|
|
2781
|
-
if (!split.ok) {
|
|
2782
|
-
skipped.push(page.relPath);
|
|
2783
|
-
continue;
|
|
2784
|
-
}
|
|
2785
|
-
const { rawFrontmatter, body } = split.data;
|
|
2786
|
-
const newFm = fixFrontmatter(rawFrontmatter);
|
|
2787
|
-
const newBody = removeOrphanTagsLines(body);
|
|
2788
|
-
const newText = `---
|
|
2789
|
-
${newFm}
|
|
2790
|
-
---
|
|
2791
|
-
${newBody}`;
|
|
2792
|
-
if (newText === text) {
|
|
2793
|
-
unchanged++;
|
|
2794
|
-
continue;
|
|
2795
|
-
}
|
|
2796
|
-
if (!input.dryRun) {
|
|
2797
|
-
const w = await safeWritePage(page.absPath, newText);
|
|
2798
|
-
if (!w.ok) {
|
|
2799
|
-
skipped.push(page.relPath);
|
|
2800
|
-
continue;
|
|
2801
|
-
}
|
|
2802
|
-
}
|
|
2803
|
-
fixed.push(page.relPath);
|
|
2804
|
-
}
|
|
2805
|
-
const exitCode = fixed.length > 0 ? ExitCode.MIGRATION_APPLIED : ExitCode.OK;
|
|
2806
|
-
const hintLines = [`scanned: ${fixed.length + skipped.length + unchanged}`];
|
|
2807
|
-
if (fixed.length > 0) hintLines.push(`fixed: ${fixed.length}`);
|
|
2808
|
-
if (skipped.length > 0) hintLines.push(`skipped (parse error): ${skipped.length}`);
|
|
2809
|
-
if (unchanged > 0) hintLines.push(`unchanged: ${unchanged}`);
|
|
2810
|
-
if (input.dryRun && fixed.length > 0) hintLines.push("(dry run \u2014 no files written)");
|
|
2811
|
-
if (!input.dryRun && fixed.length > 0) {
|
|
2812
|
-
appendLastOp(input.vault, {
|
|
2813
|
-
operation: "frontmatter-fix",
|
|
2814
|
-
summary: `normalized frontmatter on ${fixed.length} page(s)`,
|
|
2815
|
-
files: fixed,
|
|
2816
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2817
|
-
});
|
|
2818
|
-
}
|
|
2819
|
-
return {
|
|
2820
|
-
exitCode,
|
|
2821
|
-
result: ok({
|
|
2822
|
-
scanned: fixed.length + skipped.length + unchanged,
|
|
2823
|
-
fixed,
|
|
2824
|
-
skipped,
|
|
2825
|
-
unchanged,
|
|
2826
|
-
humanHint: hintLines.join("\n")
|
|
2827
|
-
})
|
|
2828
|
-
};
|
|
2829
|
-
}
|
|
2830
|
-
|
|
2831
3032
|
// src/commands/path-too-long.ts
|
|
2832
3033
|
import { existsSync as existsSync3 } from "fs";
|
|
2833
3034
|
import { mkdir as mkdir4, readFile as readFile12, rename as rename3, unlink as unlink2 } from "fs/promises";
|
|
2834
|
-
import { dirname as dirname5, join as
|
|
3035
|
+
import { dirname as dirname5, join as join12, posix as posix2, resolve as resolve3 } from "path";
|
|
2835
3036
|
var MAX_PATH_LENGTH = 240;
|
|
2836
3037
|
var WINDOWS_ABSOLUTE_PATH_LIMIT = 259;
|
|
2837
3038
|
async function runPathTooLong(input) {
|
|
@@ -2868,10 +3069,10 @@ async function fixPathTooLong(input) {
|
|
|
2868
3069
|
}
|
|
2869
3070
|
try {
|
|
2870
3071
|
if (target.mode === "dedupe") {
|
|
2871
|
-
await unlink2(
|
|
3072
|
+
await unlink2(join12(input.vault, violation.relPath));
|
|
2872
3073
|
} else {
|
|
2873
|
-
await mkdir4(dirname5(
|
|
2874
|
-
await rename3(
|
|
3074
|
+
await mkdir4(dirname5(join12(input.vault, target.relPath)), { recursive: true });
|
|
3075
|
+
await rename3(join12(input.vault, violation.relPath), join12(input.vault, target.relPath));
|
|
2875
3076
|
}
|
|
2876
3077
|
fixed.push({ from: violation.relPath, to: target.relPath });
|
|
2877
3078
|
} catch {
|
|
@@ -2950,9 +3151,9 @@ function truncateFilename(relPath, maxLength = MAX_PATH_LENGTH) {
|
|
|
2950
3151
|
async function resolveFixTarget(vault, original, preferred, maxLength) {
|
|
2951
3152
|
for (const candidate of candidateRelPaths(preferred, maxLength)) {
|
|
2952
3153
|
if (candidate === original || candidate.length > maxLength) continue;
|
|
2953
|
-
const candidatePath =
|
|
3154
|
+
const candidatePath = join12(vault, candidate);
|
|
2954
3155
|
if (!existsSync3(candidatePath)) return { relPath: candidate, mode: "rename" };
|
|
2955
|
-
if (await hasSameContent(
|
|
3156
|
+
if (await hasSameContent(join12(vault, original), candidatePath)) {
|
|
2956
3157
|
return { relPath: candidate, mode: "dedupe" };
|
|
2957
3158
|
}
|
|
2958
3159
|
}
|
|
@@ -2996,15 +3197,91 @@ function replacePathReferences(content, oldRelPath, newRelPath) {
|
|
|
2996
3197
|
const stemWikilinkRe = new RegExp(`\\[\\[${oldStemEscaped}(\\|[^\\]]*)?\\]\\]`, "g");
|
|
2997
3198
|
updated = updated.replace(stemWikilinkRe, (_match, alias) => `[[${newStem}${alias ?? ""}]]`);
|
|
2998
3199
|
}
|
|
2999
|
-
return updated;
|
|
3000
|
-
}
|
|
3001
|
-
function computeShortHash(input) {
|
|
3002
|
-
let hash = 2166136261;
|
|
3003
|
-
for (let i = 0; i < input.length; i++) {
|
|
3004
|
-
hash ^= input.charCodeAt(i);
|
|
3005
|
-
hash = Math.imul(hash, 16777619);
|
|
3200
|
+
return updated;
|
|
3201
|
+
}
|
|
3202
|
+
function computeShortHash(input) {
|
|
3203
|
+
let hash = 2166136261;
|
|
3204
|
+
for (let i = 0; i < input.length; i++) {
|
|
3205
|
+
hash ^= input.charCodeAt(i);
|
|
3206
|
+
hash = Math.imul(hash, 16777619);
|
|
3207
|
+
}
|
|
3208
|
+
return (hash >>> 0).toString(16).padStart(8, "0").slice(0, 8);
|
|
3209
|
+
}
|
|
3210
|
+
|
|
3211
|
+
// src/commands/frontmatter-fix.ts
|
|
3212
|
+
function isoToday() {
|
|
3213
|
+
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
3214
|
+
}
|
|
3215
|
+
function fixFrontmatter(rawFm) {
|
|
3216
|
+
const additions = [];
|
|
3217
|
+
if (!/^created:/m.test(rawFm)) additions.push(`created: ${isoToday()}`);
|
|
3218
|
+
if (!/^updated:/m.test(rawFm)) additions.push(`updated: ${isoToday()}`);
|
|
3219
|
+
if (!/^tags:/m.test(rawFm)) additions.push("tags: []");
|
|
3220
|
+
if (!/^sources:/m.test(rawFm)) additions.push("sources: []");
|
|
3221
|
+
if (!/^provenance:/m.test(rawFm)) additions.push("provenance: research");
|
|
3222
|
+
if (additions.length === 0) return rawFm;
|
|
3223
|
+
return rawFm.trimEnd() + "\n" + additions.join("\n") + "\n";
|
|
3224
|
+
}
|
|
3225
|
+
function removeOrphanTagsLines(body) {
|
|
3226
|
+
return body.split("\n").filter((line) => !/^tags:\s*\[/.test(line.trim())).join("\n");
|
|
3227
|
+
}
|
|
3228
|
+
async function runFrontmatterFix(input) {
|
|
3229
|
+
const scan = await scanVault(input.vault);
|
|
3230
|
+
if (!scan.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scan };
|
|
3231
|
+
const fixed = [];
|
|
3232
|
+
const skipped = [];
|
|
3233
|
+
let unchanged = 0;
|
|
3234
|
+
for (const page of scan.data.typedKnowledge) {
|
|
3235
|
+
const text = await readPage(page);
|
|
3236
|
+
const split = splitFrontmatter(text);
|
|
3237
|
+
if (!split.ok) {
|
|
3238
|
+
skipped.push(page.relPath);
|
|
3239
|
+
continue;
|
|
3240
|
+
}
|
|
3241
|
+
const { rawFrontmatter, body } = split.data;
|
|
3242
|
+
const newFm = fixFrontmatter(rawFrontmatter);
|
|
3243
|
+
const newBody = removeOrphanTagsLines(body);
|
|
3244
|
+
const newText = `---
|
|
3245
|
+
${newFm}
|
|
3246
|
+
---
|
|
3247
|
+
${newBody}`;
|
|
3248
|
+
if (newText === text) {
|
|
3249
|
+
unchanged++;
|
|
3250
|
+
continue;
|
|
3251
|
+
}
|
|
3252
|
+
if (!input.dryRun) {
|
|
3253
|
+
const w = await safeWritePage(page.absPath, newText);
|
|
3254
|
+
if (!w.ok) {
|
|
3255
|
+
skipped.push(page.relPath);
|
|
3256
|
+
continue;
|
|
3257
|
+
}
|
|
3258
|
+
}
|
|
3259
|
+
fixed.push(page.relPath);
|
|
3260
|
+
}
|
|
3261
|
+
const exitCode = fixed.length > 0 ? ExitCode.MIGRATION_APPLIED : ExitCode.OK;
|
|
3262
|
+
const hintLines = [`scanned: ${fixed.length + skipped.length + unchanged}`];
|
|
3263
|
+
if (fixed.length > 0) hintLines.push(`fixed: ${fixed.length}`);
|
|
3264
|
+
if (skipped.length > 0) hintLines.push(`skipped (parse error): ${skipped.length}`);
|
|
3265
|
+
if (unchanged > 0) hintLines.push(`unchanged: ${unchanged}`);
|
|
3266
|
+
if (input.dryRun && fixed.length > 0) hintLines.push("(dry run \u2014 no files written)");
|
|
3267
|
+
if (!input.dryRun && fixed.length > 0) {
|
|
3268
|
+
appendLastOp(input.vault, {
|
|
3269
|
+
operation: "frontmatter-fix",
|
|
3270
|
+
summary: `normalized frontmatter on ${fixed.length} page(s)`,
|
|
3271
|
+
files: fixed,
|
|
3272
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3273
|
+
});
|
|
3006
3274
|
}
|
|
3007
|
-
return
|
|
3275
|
+
return {
|
|
3276
|
+
exitCode,
|
|
3277
|
+
result: ok({
|
|
3278
|
+
scanned: fixed.length + skipped.length + unchanged,
|
|
3279
|
+
fixed,
|
|
3280
|
+
skipped,
|
|
3281
|
+
unchanged,
|
|
3282
|
+
humanHint: hintLines.join("\n")
|
|
3283
|
+
})
|
|
3284
|
+
};
|
|
3008
3285
|
}
|
|
3009
3286
|
|
|
3010
3287
|
// src/utils/cli-surface.ts
|
|
@@ -3018,8 +3295,10 @@ function buildCliSurface() {
|
|
|
3018
3295
|
program.command("validate").option("--apply").option("--vault <dir>").option("--wiki <name>");
|
|
3019
3296
|
program.command("graph");
|
|
3020
3297
|
program.command("canvas");
|
|
3298
|
+
program.command("eval").option("--base <git-ref>").option("--top <n>").option("--wiki <name>");
|
|
3021
3299
|
program.command("overlap").option("--wiki <name>");
|
|
3022
|
-
program.command("query").option("--limit <n>").option("--include-pending").option("--wiki <name>");
|
|
3300
|
+
program.command("query").option("--limit <n>").option("--include-pending").option("--hybrid").option("--wiki <name>");
|
|
3301
|
+
program.command("vectors");
|
|
3023
3302
|
program.command("sources");
|
|
3024
3303
|
program.command("orphans").option("--wiki <name>");
|
|
3025
3304
|
program.command("audit");
|
|
@@ -3049,7 +3328,7 @@ function buildCliSurface() {
|
|
|
3049
3328
|
program.command("status").option("--wiki <name>");
|
|
3050
3329
|
program.command("archive").option("--wiki <name>").option("--cascade").option("--apply").option("--approve <token>").option("--remote <remote>").option("--remote-delete").option("--max-remote-deletes <n>");
|
|
3051
3330
|
program.command("remove").option("--wiki <name>").option("--remote <remote>").option("--remote-delete").option("--max-remote-deletes <n>").option("--reason <text>");
|
|
3052
|
-
program.command("drift").option("--apply").option("--new <date>").option("--wiki <name>");
|
|
3331
|
+
program.command("drift").option("--apply").option("--affected-pages").option("--new <date>").option("--wiki <name>");
|
|
3053
3332
|
program.command("dedup").option("--apply").option("--approve <token>").option("--canonical-policy <policy>").option("--manifest-out <path>").option("--manifest-in <path>").option("--remote <remote>").option("--remote-delete").option("--max-remote-deletes <n>").option("--wiki <name>");
|
|
3054
3333
|
program.command("migrate-citations").option("--dry-run").option("--wiki <name>");
|
|
3055
3334
|
program.command("frontmatter-fix").option("--dry-run").option("--wiki <name>");
|
|
@@ -3076,9 +3355,22 @@ function buildCliSurface() {
|
|
|
3076
3355
|
program.command("mcp");
|
|
3077
3356
|
const graphCmd = program.commands.find((c) => c.name() === "graph");
|
|
3078
3357
|
graphCmd.command("build").option("--out <path>").option("--wiki <name>");
|
|
3358
|
+
const vectorsCmd = program.commands.find((c) => c.name() === "vectors");
|
|
3359
|
+
vectorsCmd.command("rebuild").option("--wiki <name>");
|
|
3360
|
+
vectorsCmd.command("status").option("--wiki <name>");
|
|
3361
|
+
vectorsCmd.command("reindex-page").option("--wiki <name>");
|
|
3362
|
+
vectorsCmd.command("prune-page").option("--dry-run").option("--wiki <name>");
|
|
3079
3363
|
const sourcesCmd = program.commands.find((c) => c.name() === "sources");
|
|
3080
3364
|
sourcesCmd.command("pending").option("--since <date>").option("--older-than <days>").option("--match <text>").option("--ingested-by <channel>").option("--scope <scope>").option("--sort <order>").option("--limit <n>").option("--all").option("--include-integrated").option("--include-archived").option("--include-duplicates").option("--include-legacy-archived").option("--wiki <name>");
|
|
3365
|
+
sourcesCmd.command("skipped").option("--wiki <name>");
|
|
3081
3366
|
sourcesCmd.command("disposition").requiredOption("--status <status>").requiredOption("--reason <text>").option("--review-after <date>").option("--duplicate-of <raw-path>").option("--write").option("--approve <token>").option("--wiki <name>");
|
|
3367
|
+
const sourcesCompileCmd = sourcesCmd.command("compile");
|
|
3368
|
+
sourcesCompileCmd.command("claim").requiredOption("--reason <text>").option("--write").option("--approve <token>").option("--wiki <name>");
|
|
3369
|
+
sourcesCompileCmd.command("release").requiredOption("--reason <text>").option("--write").option("--approve <token>").option("--wiki <name>");
|
|
3370
|
+
sourcesCompileCmd.command("published").requiredOption("--pages <paths>").requiredOption("--reason <text>").option("--write").option("--approve <token>").option("--wiki <name>");
|
|
3371
|
+
sourcesCompileCmd.command("status").option("--wiki <name>");
|
|
3372
|
+
sourcesCmd.command("review").requiredOption("--status <status>").requiredOption("--reason <text>").option("--write").option("--approve <token>").option("--wiki <name>");
|
|
3373
|
+
sourcesCmd.command("reviews").option("--wiki <name>");
|
|
3082
3374
|
sourcesCmd.command("dispose").requiredOption("--reason <text>").option("--write").option("--approve <token>").option("--wiki <name>");
|
|
3083
3375
|
const canvasCmd = program.commands.find((c) => c.name() === "canvas");
|
|
3084
3376
|
canvasCmd.command("generate").option("--graph-path <path>").option("--wiki <name>");
|
|
@@ -3254,275 +3546,50 @@ function compatible(left, right) {
|
|
|
3254
3546
|
}
|
|
3255
3547
|
function hasAnyIncompatibleSignals(leftSignals, rightSignals) {
|
|
3256
3548
|
if (leftSignals.length === 0 || rightSignals.length === 0) return false;
|
|
3257
|
-
return leftSignals.some((left) => rightSignals.some((right) => !compatible(left, right)));
|
|
3258
|
-
}
|
|
3259
|
-
function hasAnyCompatibleSignals(leftSignals, rightSignals) {
|
|
3260
|
-
return leftSignals.some((left) => rightSignals.some((right) => compatible(left, right)));
|
|
3261
|
-
}
|
|
3262
|
-
function assessSourceIdentity(input) {
|
|
3263
|
-
const pathSignals = collectSignals(input.rawPath);
|
|
3264
|
-
const sourceSignals = collectSignals(input.sourceUrl ?? "");
|
|
3265
|
-
const bodySignals = collectSignals(firstBodyWindow(input.body));
|
|
3266
|
-
const reasons = [];
|
|
3267
|
-
if (hasAnyIncompatibleSignals(pathSignals, sourceSignals)) {
|
|
3268
|
-
reasons.push(`filename/path signals [${pathSignals.join(", ")}] but source_url signals [${sourceSignals.join(", ")}]`);
|
|
3269
|
-
}
|
|
3270
|
-
if (pathSignals.length > 0 && bodySignals.length > 0 && !hasAnyCompatibleSignals(pathSignals, bodySignals)) {
|
|
3271
|
-
reasons.push(`filename/path signals [${pathSignals.join(", ")}] but body signals [${bodySignals.join(", ")}]`);
|
|
3272
|
-
}
|
|
3273
|
-
if (reasons.length > 0) {
|
|
3274
|
-
return { status: "conflict", pathSignals, sourceSignals, bodySignals, reasons };
|
|
3275
|
-
}
|
|
3276
|
-
if (pathSignals.length === 0 && sourceSignals.length > 0 && bodySignals.length > 0 && !hasAnyCompatibleSignals(sourceSignals, bodySignals)) {
|
|
3277
|
-
return {
|
|
3278
|
-
status: "suspicious",
|
|
3279
|
-
pathSignals,
|
|
3280
|
-
sourceSignals,
|
|
3281
|
-
bodySignals,
|
|
3282
|
-
reasons: [`source_url signals [${sourceSignals.join(", ")}] but body signals [${bodySignals.join(", ")}]`]
|
|
3283
|
-
};
|
|
3284
|
-
}
|
|
3285
|
-
return { status: "ok", pathSignals, sourceSignals, bodySignals, reasons };
|
|
3286
|
-
}
|
|
3287
|
-
|
|
3288
|
-
// src/commands/lint.ts
|
|
3289
|
-
var STRUCT_MIN_BODY_LINES = 60;
|
|
3290
|
-
var STRUCT_MIN_SECTIONS = 3;
|
|
3291
|
-
function hasDuplicateFrontmatter(body) {
|
|
3292
|
-
if (/^---\r?\n/.test(body)) return true;
|
|
3293
|
-
const lines = body.split(/\r?\n/);
|
|
3294
|
-
const limit = Math.min(lines.length, 20);
|
|
3295
|
-
let seenYamlKey = false;
|
|
3296
|
-
for (let i = 0; i < limit; i++) {
|
|
3297
|
-
if (/^\w[\w-]*:/.test(lines[i].trim())) seenYamlKey = true;
|
|
3298
|
-
if (seenYamlKey && lines[i].trim() === "---") return true;
|
|
3299
|
-
}
|
|
3300
|
-
return false;
|
|
3301
|
-
}
|
|
3302
|
-
var CANONICAL_LOCAL_SOURCE_LABEL = /^\s*(?:>\s*)?(?:[-*+]\s*)?Source (?:file|inspected):/i;
|
|
3303
|
-
var LOCAL_ABSOLUTE_SOURCE_REF = /(?:file:\/\/(?:\/)?(?:Users|home)\/|\/(?:Users|home)\/)/;
|
|
3304
|
-
function hasCanonicalLocalSourceAssertion(body) {
|
|
3305
|
-
const visibleBody = stripFencedBlocks(body);
|
|
3306
|
-
return visibleBody.split(/\r?\n/).some(
|
|
3307
|
-
(line) => CANONICAL_LOCAL_SOURCE_LABEL.test(line) && LOCAL_ABSOLUTE_SOURCE_REF.test(line)
|
|
3308
|
-
);
|
|
3309
|
-
}
|
|
3310
|
-
function shouldCheckCanonicalLocalSourceAssertion(page) {
|
|
3311
|
-
if (page.relPath.startsWith("raw/transcripts/")) return false;
|
|
3312
|
-
if (/^projects\/[^/]+\/work\/[^/]+\/log\.md$/.test(page.relPath)) return false;
|
|
3313
|
-
if (page.relPath.startsWith("raw/")) return true;
|
|
3314
|
-
if (/^(entities|concepts|comparisons|queries|meta)\//.test(page.relPath)) return true;
|
|
3315
|
-
if (/^projects\/[^/]+\/compound\//.test(page.relPath)) return true;
|
|
3316
|
-
if (/^projects\/[^/]+\/work\/[^/]+\/(spec|plan)\.md$/.test(page.relPath)) return true;
|
|
3317
|
-
return false;
|
|
3318
|
-
}
|
|
3319
|
-
function extractSourceEntries(rawFm) {
|
|
3320
|
-
const lines = rawFm.split(/\r?\n/);
|
|
3321
|
-
const sourcesLineIdx = lines.findIndex((l) => /^sources:/.test(l));
|
|
3322
|
-
if (sourcesLineIdx === -1) return [];
|
|
3323
|
-
const sourcesLine = lines[sourcesLineIdx].trim();
|
|
3324
|
-
const inlineMatch = sourcesLine.match(/^sources:\s*\[(.+)]\s*$/);
|
|
3325
|
-
if (inlineMatch) {
|
|
3326
|
-
return [...inlineMatch[1].matchAll(/"[^"]*"|'[^']*'|[^,\s]\S*/g)].map((m) => m[0].replace(/,\s*$/, ""));
|
|
3327
|
-
}
|
|
3328
|
-
const entries = [];
|
|
3329
|
-
for (let i = sourcesLineIdx + 1; i < lines.length; i++) {
|
|
3330
|
-
const line = lines[i];
|
|
3331
|
-
if (!/^\s+- /.test(line)) break;
|
|
3332
|
-
entries.push(line.replace(/^\s+- /, "").trim());
|
|
3333
|
-
}
|
|
3334
|
-
return entries;
|
|
3335
|
-
}
|
|
3336
|
-
var ERROR_ORDER = ["sensitive_content", "conflict_markers", "broken_wikilinks", "invalid_frontmatter", "raw_source_identity_conflict", "raw_dedup", "broken_sources", "tag_not_in_taxonomy", "path_too_long"];
|
|
3337
|
-
var WARNING_ORDER = ["raw_body_duplicate", "raw_subdirectory_duplicate", "file_source_url", "index_incomplete", "index_link_format", "stale_page", "page_too_large", "log_rotate_needed", "orphans", "compound_refs", "legacy_citation_style", "orphaned_citations", "duplicate_frontmatter", "frontmatter_yaml_invalid", "work_item_health", "orphaned_project_pages", "missing_overview", "missing_diagram"];
|
|
3338
|
-
var INFO_ORDER = ["bridges", "sparse_community", "page_structure", "topic_map_recommended", "frontmatter_wikilink", "wikilink_citation", "missing_tldr", "stale_sections", "cli_refs"];
|
|
3339
|
-
var KNOWN_BUCKETS = [...ERROR_ORDER, ...WARNING_ORDER, ...INFO_ORDER];
|
|
3340
|
-
var CLI_REFS_TYPED_DIRS = ["entities", "concepts", "comparisons", "queries", "meta"];
|
|
3341
|
-
function shellQuote(value) {
|
|
3342
|
-
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
3343
|
-
}
|
|
3344
|
-
function formatExample(item) {
|
|
3345
|
-
if (typeof item === "string") return item;
|
|
3346
|
-
if (item === null || item === void 0) return String(item);
|
|
3347
|
-
try {
|
|
3348
|
-
return JSON.stringify(item);
|
|
3349
|
-
} catch {
|
|
3350
|
-
return String(item);
|
|
3351
|
-
}
|
|
3352
|
-
}
|
|
3353
|
-
function summarizeBucket(bucket, severity, vaultPath, examplesLimit) {
|
|
3354
|
-
const safeLimit = Math.max(0, Math.min(examplesLimit, 10));
|
|
3355
|
-
const examples = bucket.items.slice(0, safeLimit).map(formatExample);
|
|
3356
|
-
return {
|
|
3357
|
-
kind: bucket.kind,
|
|
3358
|
-
severity,
|
|
3359
|
-
count: bucket.items.length,
|
|
3360
|
-
examples,
|
|
3361
|
-
examples_limit: safeLimit,
|
|
3362
|
-
sample_truncated: bucket.items.length > examples.length,
|
|
3363
|
-
details_command: `skillwiki lint ${shellQuote(vaultPath)} --only ${bucket.kind}`
|
|
3364
|
-
};
|
|
3365
|
-
}
|
|
3366
|
-
function severityForBucket(kind) {
|
|
3367
|
-
if (ERROR_ORDER.includes(kind)) return "error";
|
|
3368
|
-
if (WARNING_ORDER.includes(kind)) return "warning";
|
|
3369
|
-
return "info";
|
|
3370
|
-
}
|
|
3371
|
-
function outputForOnlyBucket(input, match, fixed, unresolved, readVault = lintReadVault(input)) {
|
|
3372
|
-
const severity = severityForBucket(input.only);
|
|
3373
|
-
const filtered = severity === "error" ? { error: match, warning: [], info: [] } : severity === "warning" ? { error: [], warning: match, info: [] } : { error: [], warning: [], info: match };
|
|
3374
|
-
const summary = {
|
|
3375
|
-
errors: filtered.error.reduce((n, b) => n + b.items.length, 0),
|
|
3376
|
-
warnings: filtered.warning.reduce((n, b) => n + b.items.length, 0),
|
|
3377
|
-
info: filtered.info.reduce((n, b) => n + b.items.length, 0)
|
|
3378
|
-
};
|
|
3379
|
-
let exitCode = ExitCode.OK;
|
|
3380
|
-
if (summary.errors > 0) exitCode = ExitCode.LINT_HAS_ERRORS;
|
|
3381
|
-
else if (summary.warnings > 0 || summary.info > 0) exitCode = ExitCode.LINT_HAS_WARNINGS;
|
|
3382
|
-
const vault = lintVaultOutput(input, readVault);
|
|
3383
|
-
const hintLines = [
|
|
3384
|
-
...readMirrorHintLines(vault),
|
|
3385
|
-
`--only ${input.only}`,
|
|
3386
|
-
match.length === 0 ? "0 violations" : match.map((b) => ` ${b.kind}: ${b.items.length}`).join("\n")
|
|
3387
|
-
];
|
|
3388
|
-
const output = {
|
|
3389
|
-
vault,
|
|
3390
|
-
summary,
|
|
3391
|
-
by_severity: filtered,
|
|
3392
|
-
fixed,
|
|
3393
|
-
unresolved,
|
|
3394
|
-
humanHint: hintLines.join("\n")
|
|
3395
|
-
};
|
|
3396
|
-
if (input.fix) appendLintFixLastOp(input.vault, fixed);
|
|
3397
|
-
return {
|
|
3398
|
-
exitCode,
|
|
3399
|
-
result: ok(input.summary ? summarizeLintOutput(output, input.examplesLimit) : output)
|
|
3400
|
-
};
|
|
3401
|
-
}
|
|
3402
|
-
function lintReadVault(input) {
|
|
3403
|
-
if (input.fix) {
|
|
3404
|
-
return { readPath: input.vault, readMirror: false };
|
|
3405
|
-
}
|
|
3406
|
-
const resolved = resolveReadOnlyVaultRoot(input.vault);
|
|
3407
|
-
return { readPath: resolved.root, readMirror: resolved.mirrored };
|
|
3408
|
-
}
|
|
3409
|
-
function lintVaultOutput(input, readVault) {
|
|
3410
|
-
return {
|
|
3411
|
-
path: input.vault,
|
|
3412
|
-
source: input.source ?? "resolved",
|
|
3413
|
-
read_path: readVault.readPath,
|
|
3414
|
-
read_mirror: readVault.readMirror
|
|
3415
|
-
};
|
|
3416
|
-
}
|
|
3417
|
-
function readMirrorHintLines(vault) {
|
|
3418
|
-
if (!vault.read_mirror) return [];
|
|
3419
|
-
return [
|
|
3420
|
-
`read mirror: ${vault.read_path}`,
|
|
3421
|
-
`requested vault: ${vault.path}`,
|
|
3422
|
-
"if results look stale, refresh the read mirror or rerun with SKILLWIKI_DISABLE_VAULT_READ_MIRROR=1 for a live scan; live scans may be slower"
|
|
3423
|
-
];
|
|
3424
|
-
}
|
|
3425
|
-
function appendLintFixLastOp(vault, fixed) {
|
|
3426
|
-
if (fixed.length === 0) return;
|
|
3427
|
-
appendLastOp(vault, {
|
|
3428
|
-
operation: "lint-fix",
|
|
3429
|
-
summary: `fixed ${fixed.length} page(s)`,
|
|
3430
|
-
files: fixed,
|
|
3431
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3432
|
-
});
|
|
3433
|
-
}
|
|
3434
|
-
function summarizeLintOutput(output, examplesLimit = 3) {
|
|
3435
|
-
const buckets = [
|
|
3436
|
-
...output.by_severity.error.map((bucket) => summarizeBucket(bucket, "error", output.vault.path, examplesLimit)),
|
|
3437
|
-
...output.by_severity.warning.map((bucket) => summarizeBucket(bucket, "warning", output.vault.path, examplesLimit)),
|
|
3438
|
-
...output.by_severity.info.map((bucket) => summarizeBucket(bucket, "info", output.vault.path, examplesLimit))
|
|
3439
|
-
];
|
|
3440
|
-
const lines = [];
|
|
3441
|
-
lines.push(...readMirrorHintLines(output.vault));
|
|
3442
|
-
lines.push(`errors: ${output.summary.errors}`);
|
|
3443
|
-
lines.push(`warnings: ${output.summary.warnings}`);
|
|
3444
|
-
lines.push(`info: ${output.summary.info}`);
|
|
3445
|
-
for (const bucket of buckets) {
|
|
3446
|
-
lines.push(` ${bucket.kind}: ${bucket.count}`);
|
|
3447
|
-
if (bucket.examples.length > 0) {
|
|
3448
|
-
lines.push(` e.g. ${bucket.examples[0]}`);
|
|
3449
|
-
}
|
|
3450
|
-
}
|
|
3451
|
-
return {
|
|
3452
|
-
vault: output.vault,
|
|
3453
|
-
summary: output.summary,
|
|
3454
|
-
buckets,
|
|
3455
|
-
details_included: false,
|
|
3456
|
-
truncated: false,
|
|
3457
|
-
fixed: output.fixed,
|
|
3458
|
-
unresolved: output.unresolved,
|
|
3459
|
-
humanHint: lines.join("\n")
|
|
3460
|
-
};
|
|
3549
|
+
return leftSignals.some((left) => rightSignals.some((right) => !compatible(left, right)));
|
|
3461
3550
|
}
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
}
|
|
3551
|
+
function hasAnyCompatibleSignals(leftSignals, rightSignals) {
|
|
3552
|
+
return leftSignals.some((left) => rightSignals.some((right) => compatible(left, right)));
|
|
3553
|
+
}
|
|
3554
|
+
function assessSourceIdentity(input) {
|
|
3555
|
+
const pathSignals = collectSignals(input.rawPath);
|
|
3556
|
+
const sourceSignals = collectSignals(input.sourceUrl ?? "");
|
|
3557
|
+
const bodySignals = collectSignals(firstBodyWindow(input.body));
|
|
3558
|
+
const reasons = [];
|
|
3559
|
+
if (hasAnyIncompatibleSignals(pathSignals, sourceSignals)) {
|
|
3560
|
+
reasons.push(`filename/path signals [${pathSignals.join(", ")}] but source_url signals [${sourceSignals.join(", ")}]`);
|
|
3473
3561
|
}
|
|
3474
|
-
|
|
3562
|
+
if (pathSignals.length > 0 && bodySignals.length > 0 && !hasAnyCompatibleSignals(pathSignals, bodySignals)) {
|
|
3563
|
+
reasons.push(`filename/path signals [${pathSignals.join(", ")}] but body signals [${bodySignals.join(", ")}]`);
|
|
3564
|
+
}
|
|
3565
|
+
if (reasons.length > 0) {
|
|
3566
|
+
return { status: "conflict", pathSignals, sourceSignals, bodySignals, reasons };
|
|
3567
|
+
}
|
|
3568
|
+
if (pathSignals.length === 0 && sourceSignals.length > 0 && bodySignals.length > 0 && !hasAnyCompatibleSignals(sourceSignals, bodySignals)) {
|
|
3569
|
+
return {
|
|
3570
|
+
status: "suspicious",
|
|
3571
|
+
pathSignals,
|
|
3572
|
+
sourceSignals,
|
|
3573
|
+
bodySignals,
|
|
3574
|
+
reasons: [`source_url signals [${sourceSignals.join(", ")}] but body signals [${bodySignals.join(", ")}]`]
|
|
3575
|
+
};
|
|
3576
|
+
}
|
|
3577
|
+
return { status: "ok", pathSignals, sourceSignals, bodySignals, reasons };
|
|
3475
3578
|
}
|
|
3579
|
+
|
|
3580
|
+
// src/lint/rules.ts
|
|
3476
3581
|
async function collectCliRefsPages(vault) {
|
|
3477
|
-
if (!existsSync4(
|
|
3582
|
+
if (!existsSync4(join13(vault, "SCHEMA.md"))) {
|
|
3478
3583
|
return err("VAULT_PATH_INVALID", { root: vault, reason: "SCHEMA.md missing" });
|
|
3479
3584
|
}
|
|
3480
3585
|
const pages = [];
|
|
3481
3586
|
for (const dir of CLI_REFS_TYPED_DIRS) {
|
|
3482
|
-
const absDir =
|
|
3587
|
+
const absDir = join13(vault, dir);
|
|
3483
3588
|
if (!existsSync4(absDir)) continue;
|
|
3484
3589
|
pages.push(...await walkMarkdownFiles(absDir, vault));
|
|
3485
3590
|
}
|
|
3486
3591
|
return ok(pages);
|
|
3487
3592
|
}
|
|
3488
|
-
async function runCliRefsOnly(input) {
|
|
3489
|
-
const readVault = lintReadVault(input);
|
|
3490
|
-
const lintVault = readVault.readPath;
|
|
3491
|
-
const pages = await collectCliRefsPages(lintVault);
|
|
3492
|
-
if (!pages.ok) {
|
|
3493
|
-
return { exitCode: ExitCode.VAULT_PATH_INVALID, result: pages };
|
|
3494
|
-
}
|
|
3495
|
-
const cliRefFlags = [];
|
|
3496
|
-
const cliSurface = buildCliSurface();
|
|
3497
|
-
for (const page of pages.data) {
|
|
3498
|
-
const text = await readPageCached(page);
|
|
3499
|
-
const violations = validateCliRefs(text, page.relPath, cliSurface);
|
|
3500
|
-
for (const v of violations) {
|
|
3501
|
-
cliRefFlags.push(`${v.page}: ${v.ref} (${v.reason})`);
|
|
3502
|
-
}
|
|
3503
|
-
}
|
|
3504
|
-
const infoOut = cliRefFlags.length > 0 ? [{ kind: "cli_refs", items: cliRefFlags }] : [];
|
|
3505
|
-
const summary = { errors: 0, warnings: 0, info: cliRefFlags.length };
|
|
3506
|
-
const exitCode = cliRefFlags.length > 0 ? ExitCode.LINT_HAS_WARNINGS : ExitCode.OK;
|
|
3507
|
-
const vault = lintVaultOutput(input, readVault);
|
|
3508
|
-
const hintLines = [
|
|
3509
|
-
...readMirrorHintLines(vault),
|
|
3510
|
-
`--only cli_refs`,
|
|
3511
|
-
cliRefFlags.length === 0 ? "0 violations" : ` cli_refs: ${cliRefFlags.length}`
|
|
3512
|
-
];
|
|
3513
|
-
const output = {
|
|
3514
|
-
vault,
|
|
3515
|
-
summary,
|
|
3516
|
-
by_severity: { error: [], warning: [], info: infoOut },
|
|
3517
|
-
fixed: [],
|
|
3518
|
-
unresolved: [],
|
|
3519
|
-
humanHint: hintLines.join("\n")
|
|
3520
|
-
};
|
|
3521
|
-
return {
|
|
3522
|
-
exitCode,
|
|
3523
|
-
result: ok(input.summary ? summarizeLintOutput(output, input.examplesLimit) : output)
|
|
3524
|
-
};
|
|
3525
|
-
}
|
|
3526
3593
|
async function collectFileSourceUrlFindings(scan, pageTextCache, options) {
|
|
3527
3594
|
const fileSourceUrlFlags = /* @__PURE__ */ new Set();
|
|
3528
3595
|
const fileSourceUrlFrontmatterFlags = /* @__PURE__ */ new Set();
|
|
@@ -3654,884 +3721,1466 @@ async function applyFileSourceUrlFix(input, scan, fileSourceUrlFlags, fileSource
|
|
|
3654
3721
|
}
|
|
3655
3722
|
return remaining;
|
|
3656
3723
|
}
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
const remaining = await applyFileSourceUrlFix(
|
|
3667
|
-
input,
|
|
3668
|
-
scanResult.data,
|
|
3669
|
-
findings.fileSourceUrlFlags,
|
|
3670
|
-
findings.fileSourceUrlFrontmatterFlags,
|
|
3671
|
-
fixed,
|
|
3672
|
-
unresolved
|
|
3673
|
-
);
|
|
3674
|
-
const match = remaining.size > 0 ? [{ kind: "file_source_url", items: [...remaining] }] : [];
|
|
3675
|
-
return outputForOnlyBucket(input, match, fixed, unresolved, readVault);
|
|
3676
|
-
}
|
|
3677
|
-
function scanConflictMarkerBlocks(path, text) {
|
|
3678
|
-
const findings = [];
|
|
3679
|
-
const lines = text.split(/\r?\n/);
|
|
3680
|
-
let inFence = false;
|
|
3681
|
-
let openLine = 0;
|
|
3682
|
-
let sawSeparator = false;
|
|
3683
|
-
for (let i = 0; i < lines.length; i += 1) {
|
|
3684
|
-
const line = lines[i];
|
|
3685
|
-
if (line.startsWith("```") || line.startsWith("~~~")) {
|
|
3686
|
-
inFence = !inFence;
|
|
3687
|
-
continue;
|
|
3724
|
+
var brokenWikilinksRule = {
|
|
3725
|
+
id: "broken_wikilinks",
|
|
3726
|
+
severity: "error",
|
|
3727
|
+
producedBuckets: ["broken_wikilinks", "invalid_frontmatter"],
|
|
3728
|
+
async run(ctx) {
|
|
3729
|
+
const buckets = {};
|
|
3730
|
+
const links = await runLinks({ vault: ctx.vault, scan: ctx.scan, pageTextCache: ctx.pageTextCache });
|
|
3731
|
+
if (links.result.ok && links.result.data.broken.length > 0) {
|
|
3732
|
+
buckets.broken_wikilinks = links.result.data.broken;
|
|
3688
3733
|
}
|
|
3689
|
-
if (
|
|
3690
|
-
|
|
3691
|
-
openLine = i + 1;
|
|
3692
|
-
sawSeparator = false;
|
|
3693
|
-
continue;
|
|
3734
|
+
if (!links.result.ok && links.result.error === "INVALID_FRONTMATTER") {
|
|
3735
|
+
buckets.invalid_frontmatter = [links.result.detail ?? {}];
|
|
3694
3736
|
}
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3737
|
+
return { buckets };
|
|
3738
|
+
}
|
|
3739
|
+
};
|
|
3740
|
+
var tagNotInTaxonomyRule = {
|
|
3741
|
+
id: "tag_not_in_taxonomy",
|
|
3742
|
+
severity: "error",
|
|
3743
|
+
producedBuckets: ["tag_not_in_taxonomy", "invalid_frontmatter"],
|
|
3744
|
+
async run(ctx) {
|
|
3745
|
+
const buckets = {};
|
|
3746
|
+
const tags = await runTagAudit({ vault: ctx.vault, scan: ctx.scan, pageTextCache: ctx.pageTextCache });
|
|
3747
|
+
if (tags.result.ok && tags.result.data.violations.length > 0) {
|
|
3748
|
+
buckets.tag_not_in_taxonomy = tags.result.data.violations;
|
|
3749
|
+
}
|
|
3750
|
+
if (!tags.result.ok && tags.result.error === "INVALID_FRONTMATTER") {
|
|
3751
|
+
buckets.invalid_frontmatter = [tags.result.detail ?? {}];
|
|
3752
|
+
}
|
|
3753
|
+
return { buckets };
|
|
3754
|
+
}
|
|
3755
|
+
};
|
|
3756
|
+
var indexIncompleteRule = {
|
|
3757
|
+
id: "index_incomplete",
|
|
3758
|
+
severity: "warning",
|
|
3759
|
+
async run(ctx) {
|
|
3760
|
+
const buckets = {};
|
|
3761
|
+
const idx = await runIndexCheck({ vault: ctx.vault, scan: ctx.scan });
|
|
3762
|
+
if (idx.result.ok && (idx.result.data.missing_from_index.length > 0 || idx.result.data.ghost_entries.length > 0)) {
|
|
3763
|
+
buckets.index_incomplete = [
|
|
3764
|
+
{
|
|
3765
|
+
missing_from_index: idx.result.data.missing_from_index,
|
|
3766
|
+
ghost_entries: idx.result.data.ghost_entries
|
|
3767
|
+
}
|
|
3768
|
+
];
|
|
3769
|
+
}
|
|
3770
|
+
return { buckets };
|
|
3771
|
+
}
|
|
3772
|
+
};
|
|
3773
|
+
var indexLinkFormatRule = {
|
|
3774
|
+
id: "index_link_format",
|
|
3775
|
+
severity: "warning",
|
|
3776
|
+
async run(ctx) {
|
|
3777
|
+
const buckets = {};
|
|
3778
|
+
const linkFmt = await runIndexLinkFormat({ vault: ctx.vault });
|
|
3779
|
+
if (linkFmt.result.ok && linkFmt.result.data.markdown_links.length > 0) {
|
|
3780
|
+
buckets.index_link_format = linkFmt.result.data.markdown_links;
|
|
3781
|
+
}
|
|
3782
|
+
return { buckets };
|
|
3783
|
+
}
|
|
3784
|
+
};
|
|
3785
|
+
var stalePageRule = {
|
|
3786
|
+
id: "stale_page",
|
|
3787
|
+
severity: "warning",
|
|
3788
|
+
async run(ctx) {
|
|
3789
|
+
const buckets = {};
|
|
3790
|
+
const staleResult = await runStale({
|
|
3791
|
+
vault: ctx.vault,
|
|
3792
|
+
days: ctx.days,
|
|
3793
|
+
scan: ctx.scan,
|
|
3794
|
+
pageTextCache: ctx.pageTextCache
|
|
3795
|
+
});
|
|
3796
|
+
if (staleResult.result.ok) {
|
|
3797
|
+
const st = staleResult.result.data;
|
|
3798
|
+
const staleList = [
|
|
3799
|
+
...st.stale_transcripts.map((t) => t.path),
|
|
3800
|
+
...(st.unclaimed_transcripts ?? []).map((t) => t.path),
|
|
3801
|
+
...st.incomplete_work_items.map((w) => w.path),
|
|
3802
|
+
...(st.done_work_items ?? []).map((w) => w.path)
|
|
3803
|
+
];
|
|
3804
|
+
if (staleList.length > 0) buckets.stale_page = staleList;
|
|
3805
|
+
}
|
|
3806
|
+
return { buckets };
|
|
3807
|
+
}
|
|
3808
|
+
};
|
|
3809
|
+
var pageTooLargeRule = {
|
|
3810
|
+
id: "page_too_large",
|
|
3811
|
+
severity: "warning",
|
|
3812
|
+
async run(ctx) {
|
|
3813
|
+
const buckets = {};
|
|
3814
|
+
const pagesize = await runPagesize({
|
|
3815
|
+
vault: ctx.vault,
|
|
3816
|
+
lines: ctx.lines,
|
|
3817
|
+
scan: ctx.scan,
|
|
3818
|
+
pageTextCache: ctx.pageTextCache
|
|
3819
|
+
});
|
|
3820
|
+
if (pagesize.result.ok && pagesize.result.data.oversized.length > 0) {
|
|
3821
|
+
buckets.page_too_large = pagesize.result.data.oversized;
|
|
3822
|
+
}
|
|
3823
|
+
return { buckets };
|
|
3824
|
+
}
|
|
3825
|
+
};
|
|
3826
|
+
var logRotateNeededRule = {
|
|
3827
|
+
id: "log_rotate_needed",
|
|
3828
|
+
severity: "warning",
|
|
3829
|
+
async run(ctx) {
|
|
3830
|
+
const buckets = {};
|
|
3831
|
+
const rotate = await runLogRotate({ vault: ctx.vault, threshold: ctx.logThreshold, apply: false });
|
|
3832
|
+
if (rotate.result.ok && rotate.exitCode === ExitCode.LOG_ROTATE_NEEDED) {
|
|
3833
|
+
buckets.log_rotate_needed = [{ entries: rotate.result.data.entries, threshold: rotate.result.data.threshold }];
|
|
3834
|
+
}
|
|
3835
|
+
return { buckets };
|
|
3836
|
+
}
|
|
3837
|
+
};
|
|
3838
|
+
var orphansRule = {
|
|
3839
|
+
id: "orphans",
|
|
3840
|
+
severity: "warning",
|
|
3841
|
+
producedBuckets: ["orphans", "bridges"],
|
|
3842
|
+
async run(ctx) {
|
|
3843
|
+
const buckets = {};
|
|
3844
|
+
const orphans = await runOrphans({ vault: ctx.vault, scan: ctx.scan, pageTextCache: ctx.pageTextCache });
|
|
3845
|
+
if (orphans.result.ok) {
|
|
3846
|
+
if (orphans.result.data.orphans.length > 0) buckets.orphans = orphans.result.data.orphans;
|
|
3847
|
+
if (orphans.result.data.bridges.length > 0) buckets.bridges = orphans.result.data.bridges;
|
|
3848
|
+
}
|
|
3849
|
+
return { buckets };
|
|
3850
|
+
}
|
|
3851
|
+
};
|
|
3852
|
+
var sparseCommunityRule = {
|
|
3853
|
+
id: "sparse_community",
|
|
3854
|
+
severity: "info",
|
|
3855
|
+
async run(ctx) {
|
|
3856
|
+
const buckets = {};
|
|
3857
|
+
const sparse = await runSparseCommunity({ vault: ctx.vault, scan: ctx.scan, pageTextCache: ctx.pageTextCache });
|
|
3858
|
+
if (sparse.result.ok && sparse.result.data.communities.length > 0) {
|
|
3859
|
+
buckets.sparse_community = sparse.result.data.communities;
|
|
3860
|
+
}
|
|
3861
|
+
return { buckets };
|
|
3862
|
+
}
|
|
3863
|
+
};
|
|
3864
|
+
var topicMapRecommendedRule = {
|
|
3865
|
+
id: "topic_map_recommended",
|
|
3866
|
+
severity: "info",
|
|
3867
|
+
async run(ctx) {
|
|
3868
|
+
const buckets = {};
|
|
3869
|
+
const topicMap = await runTopicMapCheck({ vault: ctx.vault, scan: ctx.scan });
|
|
3870
|
+
if (topicMap.result.ok && topicMap.result.data.recommended) {
|
|
3871
|
+
buckets.topic_map_recommended = [
|
|
3872
|
+
{ page_count: topicMap.result.data.page_count, threshold: topicMap.result.data.threshold }
|
|
3873
|
+
];
|
|
3874
|
+
}
|
|
3875
|
+
return { buckets };
|
|
3876
|
+
}
|
|
3877
|
+
};
|
|
3878
|
+
var rawDedupRule = {
|
|
3879
|
+
id: "raw_dedup",
|
|
3880
|
+
severity: "error",
|
|
3881
|
+
async run(ctx) {
|
|
3882
|
+
const buckets = {};
|
|
3883
|
+
const dedup = await runDedup({ vault: ctx.vault, scan: ctx.scan, pageTextCache: ctx.pageTextCache });
|
|
3884
|
+
if (dedup.result.ok && dedup.result.data.duplicates.length > 0) {
|
|
3885
|
+
buckets.raw_dedup = dedup.result.data.duplicates;
|
|
3886
|
+
}
|
|
3887
|
+
return { buckets };
|
|
3888
|
+
}
|
|
3889
|
+
};
|
|
3890
|
+
var rawBodyDuplicateRule = {
|
|
3891
|
+
id: "raw_body_duplicate",
|
|
3892
|
+
severity: "warning",
|
|
3893
|
+
async run(ctx) {
|
|
3894
|
+
const buckets = {};
|
|
3895
|
+
const bodyDedup = await runRawBodyDedup(ctx.vault, ctx.scan, ctx.pageTextCache);
|
|
3896
|
+
if (bodyDedup.result.ok && bodyDedup.result.data.duplicates.length > 0) {
|
|
3897
|
+
buckets.raw_body_duplicate = bodyDedup.result.data.duplicates.map((d) => ({
|
|
3898
|
+
body_hash: d.bodyHash.slice(0, 12),
|
|
3899
|
+
files: d.files.map((f) => `${f.relPath} (sha256: ${f.sha256 ?? "none"})`)
|
|
3900
|
+
}));
|
|
3901
|
+
}
|
|
3902
|
+
return { buckets };
|
|
3903
|
+
}
|
|
3904
|
+
};
|
|
3905
|
+
var compoundRefsRule = {
|
|
3906
|
+
id: "compound_refs",
|
|
3907
|
+
severity: "warning",
|
|
3908
|
+
async run(ctx) {
|
|
3909
|
+
const buckets = {};
|
|
3910
|
+
const compoundRefs = await validateCompoundReferences(ctx.vault, ctx.scan, ctx.pageTextCache);
|
|
3911
|
+
if (compoundRefs.ok && compoundRefs.data.length > 0) {
|
|
3912
|
+
buckets.compound_refs = compoundRefs.data;
|
|
3913
|
+
}
|
|
3914
|
+
return { buckets };
|
|
3915
|
+
}
|
|
3916
|
+
};
|
|
3917
|
+
var pathTooLongRule = {
|
|
3918
|
+
id: "path_too_long",
|
|
3919
|
+
severity: "error",
|
|
3920
|
+
async run(ctx) {
|
|
3921
|
+
const buckets = {};
|
|
3922
|
+
const pathCheck = await runPathTooLong({ vault: ctx.vault, scan: ctx.scan });
|
|
3923
|
+
if (pathCheck.result.ok && pathCheck.result.data.violations.length > 0) {
|
|
3924
|
+
buckets.path_too_long = pathCheck.result.data.violations;
|
|
3925
|
+
}
|
|
3926
|
+
return { buckets };
|
|
3927
|
+
},
|
|
3928
|
+
async fix(ctx, currentBucketItems) {
|
|
3929
|
+
const pathViolations = currentBucketItems;
|
|
3930
|
+
if (!pathViolations || pathViolations.length === 0) return currentBucketItems;
|
|
3931
|
+
const pathFix = await fixPathTooLong({ vault: ctx.input.vault });
|
|
3932
|
+
const pathFixed = pathFix.result.ok ? pathFix.result.data.fixed.map((f) => f.from) : [];
|
|
3933
|
+
if (pathFix.result.ok) ctx.unresolved.push(...pathFix.result.data.unresolved);
|
|
3934
|
+
else ctx.unresolved.push(...pathViolations.map((v) => v.relPath));
|
|
3935
|
+
ctx.fixed.push(...pathFixed);
|
|
3936
|
+
let remaining = pathViolations;
|
|
3937
|
+
if (pathFixed.length > 0) {
|
|
3938
|
+
const fixedSet = new Set(pathFixed);
|
|
3939
|
+
remaining = pathViolations.filter((v) => !fixedSet.has(v.relPath));
|
|
3940
|
+
}
|
|
3941
|
+
if (remaining && remaining.length > 0) {
|
|
3942
|
+
const rawRemaining = remaining.filter((v) => v.relPath.startsWith("raw/"));
|
|
3943
|
+
if (rawRemaining.length > 0) {
|
|
3944
|
+
ctx.unresolved.push(...rawRemaining.map((v) => v.relPath));
|
|
3945
|
+
const nonRaw = remaining.filter((v) => !v.relPath.startsWith("raw/"));
|
|
3946
|
+
return nonRaw.length > 0 ? nonRaw : void 0;
|
|
3947
|
+
}
|
|
3948
|
+
return remaining;
|
|
3949
|
+
}
|
|
3950
|
+
return void 0;
|
|
3951
|
+
}
|
|
3952
|
+
};
|
|
3953
|
+
var cliRefsRule = {
|
|
3954
|
+
id: "cli_refs",
|
|
3955
|
+
severity: "info",
|
|
3956
|
+
async run(ctx) {
|
|
3957
|
+
const cliRefFlags = [];
|
|
3958
|
+
const allScanPages = [...ctx.scan.typedKnowledge];
|
|
3959
|
+
const cliRefResults = await mapWithConcurrency(allScanPages, vaultIoConcurrency(), async (page) => {
|
|
3960
|
+
const flags = [];
|
|
3961
|
+
try {
|
|
3962
|
+
const text = await readPageCached(page, ctx.pageTextCache);
|
|
3963
|
+
const violations = validateCliRefs(text, page.relPath, ctx.cliSurface);
|
|
3964
|
+
for (const v of violations) {
|
|
3965
|
+
flags.push(`${v.page}: ${v.ref} (${v.reason})`);
|
|
3966
|
+
}
|
|
3967
|
+
} catch {
|
|
3968
|
+
}
|
|
3969
|
+
return flags;
|
|
3970
|
+
});
|
|
3971
|
+
cliRefFlags.push(...cliRefResults.flat());
|
|
3972
|
+
const buckets = {};
|
|
3973
|
+
if (cliRefFlags.length > 0) buckets.cli_refs = cliRefFlags;
|
|
3974
|
+
return { buckets };
|
|
3975
|
+
},
|
|
3976
|
+
async runFastPath(input) {
|
|
3977
|
+
const readVault = lintReadVault(input);
|
|
3978
|
+
const lintVault = readVault.readPath;
|
|
3979
|
+
const pages = await collectCliRefsPages(lintVault);
|
|
3980
|
+
if (!pages.ok) {
|
|
3981
|
+
return { exitCode: ExitCode.VAULT_PATH_INVALID, result: pages };
|
|
3982
|
+
}
|
|
3983
|
+
const cliRefFlags = [];
|
|
3984
|
+
const cliSurface = buildCliSurface();
|
|
3985
|
+
for (const page of pages.data) {
|
|
3986
|
+
const text = await readPageCached(page);
|
|
3987
|
+
const violations = validateCliRefs(text, page.relPath, cliSurface);
|
|
3988
|
+
for (const v of violations) {
|
|
3989
|
+
cliRefFlags.push(`${v.page}: ${v.ref} (${v.reason})`);
|
|
3990
|
+
}
|
|
3991
|
+
}
|
|
3992
|
+
const infoOut = cliRefFlags.length > 0 ? [{ kind: "cli_refs", items: cliRefFlags }] : [];
|
|
3993
|
+
const summary = { errors: 0, warnings: 0, info: cliRefFlags.length };
|
|
3994
|
+
const exitCode = cliRefFlags.length > 0 ? ExitCode.LINT_HAS_WARNINGS : ExitCode.OK;
|
|
3995
|
+
const vault = lintVaultOutput(input, readVault);
|
|
3996
|
+
const hintLines = [
|
|
3997
|
+
...readMirrorHintLines(vault),
|
|
3998
|
+
`--only cli_refs`,
|
|
3999
|
+
cliRefFlags.length === 0 ? "0 violations" : ` cli_refs: ${cliRefFlags.length}`
|
|
4000
|
+
];
|
|
4001
|
+
const output = {
|
|
4002
|
+
vault,
|
|
4003
|
+
summary,
|
|
4004
|
+
by_severity: { error: [], warning: [], info: infoOut },
|
|
4005
|
+
fixed: [],
|
|
4006
|
+
unresolved: [],
|
|
4007
|
+
humanHint: hintLines.join("\n")
|
|
4008
|
+
};
|
|
4009
|
+
return {
|
|
4010
|
+
exitCode,
|
|
4011
|
+
result: ok(input.summary ? summarizeLintOutput(output, input.examplesLimit) : output)
|
|
4012
|
+
};
|
|
4013
|
+
}
|
|
4014
|
+
};
|
|
4015
|
+
var fileSourceUrlRule = {
|
|
4016
|
+
id: "file_source_url",
|
|
4017
|
+
severity: "warning",
|
|
4018
|
+
producedBuckets: ["file_source_url", "raw_source_identity_conflict"],
|
|
4019
|
+
async run(ctx) {
|
|
4020
|
+
if (!ctx.parsedPagesCache) ctx.parsedPagesCache = {};
|
|
4021
|
+
if (!ctx.parsedPagesCache.fileSourceUrlFindings) {
|
|
4022
|
+
ctx.parsedPagesCache.fileSourceUrlFindings = await collectFileSourceUrlFindings(ctx.scan, ctx.pageTextCache, {
|
|
4023
|
+
includeRawIdentityConflicts: true
|
|
4024
|
+
});
|
|
4025
|
+
}
|
|
4026
|
+
const findings = ctx.parsedPagesCache.fileSourceUrlFindings;
|
|
4027
|
+
const buckets = {};
|
|
4028
|
+
if (findings.fileSourceUrlFlags.size > 0) {
|
|
4029
|
+
buckets.file_source_url = [...findings.fileSourceUrlFlags];
|
|
4030
|
+
}
|
|
4031
|
+
if (findings.rawIdentityConflicts.length > 0) {
|
|
4032
|
+
buckets.raw_source_identity_conflict = findings.rawIdentityConflicts;
|
|
4033
|
+
}
|
|
4034
|
+
return { buckets };
|
|
4035
|
+
},
|
|
4036
|
+
async runFastPath(input) {
|
|
4037
|
+
const readVault = lintReadVault(input);
|
|
4038
|
+
const lintVault = readVault.readPath;
|
|
4039
|
+
const scanResult = await scanVault(lintVault);
|
|
4040
|
+
if (!scanResult.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scanResult };
|
|
4041
|
+
const pageTextCache = /* @__PURE__ */ new Map();
|
|
4042
|
+
const fixed = [];
|
|
4043
|
+
const unresolved = [];
|
|
4044
|
+
const findings = await collectFileSourceUrlFindings(scanResult.data, pageTextCache, {
|
|
4045
|
+
includeRawIdentityConflicts: false
|
|
4046
|
+
});
|
|
4047
|
+
const remaining = await applyFileSourceUrlFix(
|
|
4048
|
+
input,
|
|
4049
|
+
scanResult.data,
|
|
4050
|
+
findings.fileSourceUrlFlags,
|
|
4051
|
+
findings.fileSourceUrlFrontmatterFlags,
|
|
4052
|
+
fixed,
|
|
4053
|
+
unresolved
|
|
4054
|
+
);
|
|
4055
|
+
const match = remaining.size > 0 ? [{ kind: "file_source_url", items: [...remaining] }] : [];
|
|
4056
|
+
return outputForOnlyBucket(input, match, fixed, unresolved, readVault);
|
|
4057
|
+
},
|
|
4058
|
+
async fix(ctx, currentBucketItems) {
|
|
4059
|
+
const findings = ctx.pageTextCache ? await collectFileSourceUrlFindings(ctx.scan, ctx.pageTextCache, { includeRawIdentityConflicts: false }) : { fileSourceUrlFlags: /* @__PURE__ */ new Set(), fileSourceUrlFrontmatterFlags: /* @__PURE__ */ new Set(), rawIdentityConflicts: [] };
|
|
4060
|
+
const remaining = await applyFileSourceUrlFix(
|
|
4061
|
+
ctx.input,
|
|
4062
|
+
ctx.scan,
|
|
4063
|
+
findings.fileSourceUrlFlags,
|
|
4064
|
+
findings.fileSourceUrlFrontmatterFlags,
|
|
4065
|
+
ctx.fixed,
|
|
4066
|
+
ctx.unresolved
|
|
4067
|
+
);
|
|
4068
|
+
return remaining.size > 0 ? [...remaining] : void 0;
|
|
4069
|
+
}
|
|
4070
|
+
};
|
|
4071
|
+
var sensitiveContentRule = {
|
|
4072
|
+
id: "sensitive_content",
|
|
4073
|
+
severity: "error",
|
|
4074
|
+
async run(ctx) {
|
|
4075
|
+
const sensitiveFlags = [];
|
|
4076
|
+
await mapWithConcurrency(ctx.scan.allMarkdown, vaultIoConcurrency(), async (page) => {
|
|
4077
|
+
try {
|
|
4078
|
+
const text = await readPageCached(page, ctx.pageTextCache);
|
|
4079
|
+
sensitiveFlags.push(...scanSensitiveContent(text, { file: page.relPath }));
|
|
4080
|
+
} catch {
|
|
4081
|
+
}
|
|
4082
|
+
});
|
|
4083
|
+
const buckets = {};
|
|
4084
|
+
if (sensitiveFlags.length > 0) buckets.sensitive_content = sensitiveFlags;
|
|
4085
|
+
return { buckets };
|
|
4086
|
+
},
|
|
4087
|
+
async fix(ctx, currentBucketItems) {
|
|
4088
|
+
const sensitiveFixed = [];
|
|
4089
|
+
for (const page of ctx.scan.allMarkdown) {
|
|
4090
|
+
try {
|
|
4091
|
+
const raw = await readPage(page);
|
|
4092
|
+
const redacted = redactSensitiveContent(raw, { file: page.relPath });
|
|
4093
|
+
if (!redacted.changed) continue;
|
|
4094
|
+
if (page.relPath.startsWith("raw/")) {
|
|
4095
|
+
ctx.unresolved.push(page.relPath);
|
|
4096
|
+
continue;
|
|
4097
|
+
}
|
|
4098
|
+
const w = await safeWritePage(page.absPath, redacted.text, { minBodyRatio: null });
|
|
4099
|
+
if (!w.ok) {
|
|
4100
|
+
ctx.unresolved.push(page.relPath);
|
|
4101
|
+
continue;
|
|
4102
|
+
}
|
|
4103
|
+
sensitiveFixed.push(page.relPath);
|
|
4104
|
+
} catch {
|
|
4105
|
+
ctx.unresolved.push(page.relPath);
|
|
4106
|
+
}
|
|
4107
|
+
}
|
|
4108
|
+
ctx.fixed.push(...sensitiveFixed);
|
|
4109
|
+
const remainingSensitiveFlags = [];
|
|
4110
|
+
for (const page of ctx.scan.allMarkdown) {
|
|
4111
|
+
try {
|
|
4112
|
+
const text = await readPage(page);
|
|
4113
|
+
remainingSensitiveFlags.push(...scanSensitiveContent(text, { file: page.relPath }));
|
|
4114
|
+
} catch {
|
|
4115
|
+
}
|
|
4116
|
+
}
|
|
4117
|
+
return remainingSensitiveFlags.length > 0 ? remainingSensitiveFlags : void 0;
|
|
4118
|
+
}
|
|
4119
|
+
};
|
|
4120
|
+
var conflictMarkersRule = {
|
|
4121
|
+
id: "conflict_markers",
|
|
4122
|
+
severity: "error",
|
|
4123
|
+
async run(ctx) {
|
|
4124
|
+
const conflictMarkers = [];
|
|
4125
|
+
await mapWithConcurrency(ctx.scan.allMarkdown, vaultIoConcurrency(), async (page) => {
|
|
4126
|
+
try {
|
|
4127
|
+
const text = await readPageCached(page, ctx.pageTextCache);
|
|
4128
|
+
const lines = text.split(/\r?\n/);
|
|
4129
|
+
let inFence = false;
|
|
4130
|
+
let openLine = 0;
|
|
4131
|
+
let sawSeparator = false;
|
|
4132
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
4133
|
+
const line = lines[i];
|
|
4134
|
+
if (line.startsWith("```") || line.startsWith("~~~")) {
|
|
4135
|
+
inFence = !inFence;
|
|
4136
|
+
continue;
|
|
4137
|
+
}
|
|
4138
|
+
if (inFence) continue;
|
|
4139
|
+
if (line.startsWith("<<<<<<< ")) {
|
|
4140
|
+
openLine = i + 1;
|
|
4141
|
+
sawSeparator = false;
|
|
4142
|
+
continue;
|
|
4143
|
+
}
|
|
4144
|
+
if (line === "=======" && openLine > 0) {
|
|
4145
|
+
sawSeparator = true;
|
|
4146
|
+
continue;
|
|
4147
|
+
}
|
|
4148
|
+
if (line.startsWith(">>>>>>> ")) {
|
|
4149
|
+
if (openLine > 0 && sawSeparator) {
|
|
4150
|
+
conflictMarkers.push({ path: page.relPath, line: openLine, message: "complete Git conflict-marker block" });
|
|
4151
|
+
}
|
|
4152
|
+
openLine = 0;
|
|
4153
|
+
sawSeparator = false;
|
|
4154
|
+
}
|
|
4155
|
+
}
|
|
4156
|
+
} catch {
|
|
4157
|
+
}
|
|
4158
|
+
});
|
|
4159
|
+
const buckets = {};
|
|
4160
|
+
if (conflictMarkers.length > 0) buckets.conflict_markers = conflictMarkers;
|
|
4161
|
+
return { buckets };
|
|
4162
|
+
}
|
|
4163
|
+
};
|
|
4164
|
+
var frontmatterYamlInvalidRule = {
|
|
4165
|
+
id: "frontmatter_yaml_invalid",
|
|
4166
|
+
severity: "warning",
|
|
4167
|
+
async run(ctx) {
|
|
4168
|
+
const fmYamlInvalid = [];
|
|
4169
|
+
await mapWithConcurrency(ctx.scan.allMarkdown, vaultIoConcurrency(), async (page) => {
|
|
4170
|
+
try {
|
|
4171
|
+
const text = await readPageCached(page, ctx.pageTextCache);
|
|
4172
|
+
const fm = extractFrontmatter(text);
|
|
4173
|
+
if (!fm.ok && fm.error === "INVALID_FRONTMATTER") {
|
|
4174
|
+
const detail = fm.detail;
|
|
4175
|
+
const message = detail?.message ?? "invalid YAML";
|
|
4176
|
+
fmYamlInvalid.push({ path: page.relPath, message });
|
|
4177
|
+
}
|
|
4178
|
+
} catch {
|
|
4179
|
+
}
|
|
4180
|
+
});
|
|
4181
|
+
const buckets = {};
|
|
4182
|
+
if (fmYamlInvalid.length > 0) buckets.frontmatter_yaml_invalid = fmYamlInvalid;
|
|
4183
|
+
return { buckets };
|
|
4184
|
+
},
|
|
4185
|
+
async fix(ctx, currentBucketItems) {
|
|
4186
|
+
const invalidItems = currentBucketItems;
|
|
4187
|
+
if (!invalidItems) return currentBucketItems;
|
|
4188
|
+
const remaining = [];
|
|
4189
|
+
for (const item of invalidItems) {
|
|
4190
|
+
if (item.path.startsWith("raw/")) {
|
|
4191
|
+
ctx.unresolved.push(item.path);
|
|
4192
|
+
remaining.push(item);
|
|
4193
|
+
continue;
|
|
4194
|
+
}
|
|
4195
|
+
const page = ctx.scan.allMarkdown.find((p) => p.relPath === item.path);
|
|
4196
|
+
if (!page) {
|
|
4197
|
+
ctx.unresolved.push(item.path);
|
|
4198
|
+
remaining.push(item);
|
|
4199
|
+
continue;
|
|
4200
|
+
}
|
|
4201
|
+
try {
|
|
4202
|
+
const text = await readPage(page);
|
|
4203
|
+
const split = splitFrontmatter(text);
|
|
4204
|
+
if (!split.ok) {
|
|
4205
|
+
ctx.unresolved.push(item.path);
|
|
4206
|
+
remaining.push(item);
|
|
4207
|
+
continue;
|
|
4208
|
+
}
|
|
4209
|
+
const newFm = fixFrontmatter(split.data.rawFrontmatter);
|
|
4210
|
+
const newText = `---
|
|
4211
|
+
${newFm}
|
|
4212
|
+
---
|
|
4213
|
+
${split.data.body}`;
|
|
4214
|
+
const recheck = extractFrontmatter(newText);
|
|
4215
|
+
if (!recheck.ok) {
|
|
4216
|
+
ctx.unresolved.push(item.path);
|
|
4217
|
+
remaining.push(item);
|
|
4218
|
+
continue;
|
|
4219
|
+
}
|
|
4220
|
+
const w = await safeWritePage(page.absPath, newText, { minBodyRatio: null });
|
|
4221
|
+
if (!w.ok) {
|
|
4222
|
+
ctx.unresolved.push(item.path);
|
|
4223
|
+
remaining.push(item);
|
|
4224
|
+
continue;
|
|
4225
|
+
}
|
|
4226
|
+
ctx.fixed.push(item.path);
|
|
4227
|
+
} catch {
|
|
4228
|
+
ctx.unresolved.push(item.path);
|
|
4229
|
+
remaining.push(item);
|
|
4230
|
+
}
|
|
4231
|
+
}
|
|
4232
|
+
return remaining.length > 0 ? remaining : void 0;
|
|
4233
|
+
}
|
|
4234
|
+
};
|
|
4235
|
+
var rawSubdirectoryDuplicateRule = {
|
|
4236
|
+
id: "raw_subdirectory_duplicate",
|
|
4237
|
+
severity: "warning",
|
|
4238
|
+
async run(ctx) {
|
|
4239
|
+
const subDirDupes = [];
|
|
4240
|
+
const flatStems = /* @__PURE__ */ new Map();
|
|
4241
|
+
const deepFiles = [];
|
|
4242
|
+
for (const raw of ctx.scan.raw) {
|
|
4243
|
+
const parts = raw.relPath.split("/");
|
|
4244
|
+
if (parts.length === 3) {
|
|
4245
|
+
const stem = parts[2].replace(/\.md$/, "");
|
|
4246
|
+
flatStems.set(`${parts[1]}/${stem}`, raw.relPath);
|
|
4247
|
+
} else if (parts.length > 3) {
|
|
4248
|
+
const stem = parts[parts.length - 1].replace(/\.md$/, "");
|
|
4249
|
+
deepFiles.push({ relPath: raw.relPath, stem, parentType: parts[1] });
|
|
4250
|
+
}
|
|
4251
|
+
}
|
|
4252
|
+
for (const df of deepFiles) {
|
|
4253
|
+
const flatPath = flatStems.get(`${df.parentType}/${df.stem}`);
|
|
4254
|
+
if (flatPath) {
|
|
4255
|
+
subDirDupes.push(`${df.relPath} -> duplicate of ${flatPath}`);
|
|
4256
|
+
}
|
|
3698
4257
|
}
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
4258
|
+
const buckets = {};
|
|
4259
|
+
if (subDirDupes.length > 0) buckets.raw_subdirectory_duplicate = subDirDupes;
|
|
4260
|
+
return { buckets };
|
|
4261
|
+
}
|
|
4262
|
+
};
|
|
4263
|
+
var legacyCitationStyleRule = {
|
|
4264
|
+
id: "legacy_citation_style",
|
|
4265
|
+
severity: "warning",
|
|
4266
|
+
async run(ctx) {
|
|
4267
|
+
const legacyPages = [];
|
|
4268
|
+
await mapWithConcurrency(ctx.scan.typedKnowledge, vaultIoConcurrency(), async (page) => {
|
|
4269
|
+
try {
|
|
4270
|
+
const text = await readPageCached(page, ctx.pageTextCache);
|
|
4271
|
+
const split = splitFrontmatter(text);
|
|
4272
|
+
if (!split.ok) return;
|
|
4273
|
+
if (isLegacyCitationStyle(split.data.body)) legacyPages.push(page.relPath);
|
|
4274
|
+
} catch {
|
|
4275
|
+
}
|
|
4276
|
+
});
|
|
4277
|
+
const buckets = {};
|
|
4278
|
+
if (legacyPages.length > 0) buckets.legacy_citation_style = legacyPages;
|
|
4279
|
+
return { buckets };
|
|
4280
|
+
},
|
|
4281
|
+
async fix(ctx, currentBucketItems) {
|
|
4282
|
+
const legacyPages = currentBucketItems;
|
|
4283
|
+
if (!legacyPages || legacyPages.length === 0) return currentBucketItems;
|
|
4284
|
+
const FENCE_RE = /```[\s\S]*?```/g;
|
|
4285
|
+
const INLINE_MARKER = /\^\[raw\/[^\]]+\]/g;
|
|
4286
|
+
const fixedHere = [];
|
|
4287
|
+
for (const relPath of legacyPages) {
|
|
4288
|
+
try {
|
|
4289
|
+
const absPath = `${ctx.input.vault}/${relPath}`;
|
|
4290
|
+
const raw = await readFile13(absPath, "utf8");
|
|
4291
|
+
const split = splitFrontmatter(raw);
|
|
4292
|
+
if (!split.ok) {
|
|
4293
|
+
ctx.unresolved.push(relPath);
|
|
4294
|
+
continue;
|
|
4295
|
+
}
|
|
4296
|
+
const body = split.data.body;
|
|
4297
|
+
const rawFm = split.data.rawFrontmatter;
|
|
4298
|
+
const stripped = body.replace(FENCE_RE, "");
|
|
4299
|
+
const lines = stripped.split("\n");
|
|
4300
|
+
const inlineMarkers = [];
|
|
4301
|
+
let inSources = false;
|
|
4302
|
+
for (const line of lines) {
|
|
4303
|
+
if (/^## Sources\b/.test(line.trim())) {
|
|
4304
|
+
inSources = true;
|
|
4305
|
+
continue;
|
|
4306
|
+
}
|
|
4307
|
+
if (inSources) continue;
|
|
4308
|
+
for (const m of line.matchAll(INLINE_MARKER)) {
|
|
4309
|
+
inlineMarkers.push(m[0]);
|
|
4310
|
+
}
|
|
4311
|
+
}
|
|
4312
|
+
if (inlineMarkers.length === 0) {
|
|
4313
|
+
ctx.unresolved.push(relPath);
|
|
4314
|
+
continue;
|
|
4315
|
+
}
|
|
4316
|
+
const bodyLines = body.split("\n");
|
|
4317
|
+
let inSrc = false;
|
|
4318
|
+
const newBodyLines = [];
|
|
4319
|
+
for (const line of bodyLines) {
|
|
4320
|
+
if (/^## Sources\b/.test(line.trim())) {
|
|
4321
|
+
inSrc = true;
|
|
4322
|
+
newBodyLines.push(line);
|
|
4323
|
+
continue;
|
|
4324
|
+
}
|
|
4325
|
+
if (inSrc) {
|
|
4326
|
+
newBodyLines.push(line);
|
|
4327
|
+
continue;
|
|
4328
|
+
}
|
|
4329
|
+
INLINE_MARKER.lastIndex = 0;
|
|
4330
|
+
const lineWithoutMarkers = line.replace(INLINE_MARKER, "").trim();
|
|
4331
|
+
INLINE_MARKER.lastIndex = 0;
|
|
4332
|
+
if (lineWithoutMarkers.length === 0 && INLINE_MARKER.test(line)) {
|
|
4333
|
+
continue;
|
|
4334
|
+
}
|
|
4335
|
+
let cleaned = line;
|
|
4336
|
+
for (const marker of inlineMarkers) {
|
|
4337
|
+
const escapedMarker = marker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4338
|
+
const trailingRe = new RegExp(`([.!?]\\s*)${escapedMarker}`);
|
|
4339
|
+
if (trailingRe.test(cleaned)) {
|
|
4340
|
+
cleaned = cleaned.replace(trailingRe, "$1");
|
|
4341
|
+
}
|
|
4342
|
+
const midRe = new RegExp(`${escapedMarker}\\s*`);
|
|
4343
|
+
if (midRe.test(cleaned)) {
|
|
4344
|
+
cleaned = cleaned.replace(midRe, "");
|
|
4345
|
+
}
|
|
4346
|
+
}
|
|
4347
|
+
newBodyLines.push(cleaned);
|
|
4348
|
+
}
|
|
4349
|
+
let newBody = newBodyLines.join("\n");
|
|
4350
|
+
const dedupedMarkers = [...new Set(inlineMarkers)];
|
|
4351
|
+
if (inSrc) {
|
|
4352
|
+
const existingSources = new Set(
|
|
4353
|
+
body.split("\n").filter((l) => /^- \^\[raw\//.test(l.trim())).map((l) => l.trim().replace(/^- /, ""))
|
|
4354
|
+
);
|
|
4355
|
+
const newMarkers = dedupedMarkers.filter((m) => !existingSources.has(m));
|
|
4356
|
+
const sourceLines = newMarkers.map((m) => `- ${m}`);
|
|
4357
|
+
if (sourceLines.length > 0) {
|
|
4358
|
+
newBody = newBody.trimEnd() + "\n" + sourceLines.join("\n") + "\n";
|
|
4359
|
+
}
|
|
4360
|
+
} else {
|
|
4361
|
+
const sourceLines = dedupedMarkers.map((m) => `- ${m}`);
|
|
4362
|
+
newBody = newBody.trimEnd() + "\n\n## Sources\n\n" + sourceLines.join("\n") + "\n";
|
|
4363
|
+
}
|
|
4364
|
+
const newContent = `---
|
|
4365
|
+
${rawFm}
|
|
4366
|
+
---
|
|
4367
|
+
${newBody}`;
|
|
4368
|
+
const w = await safeWritePage(absPath, newContent);
|
|
4369
|
+
if (!w.ok) {
|
|
4370
|
+
ctx.unresolved.push(relPath);
|
|
4371
|
+
continue;
|
|
4372
|
+
}
|
|
4373
|
+
fixedHere.push(relPath);
|
|
4374
|
+
} catch {
|
|
4375
|
+
ctx.unresolved.push(relPath);
|
|
3702
4376
|
}
|
|
3703
|
-
openLine = 0;
|
|
3704
|
-
sawSeparator = false;
|
|
3705
4377
|
}
|
|
4378
|
+
ctx.fixed.push(...fixedHere);
|
|
4379
|
+
if (fixedHere.length > 0) {
|
|
4380
|
+
const fixedSet = new Set(fixedHere);
|
|
4381
|
+
const remaining = legacyPages.filter((p) => !fixedSet.has(p));
|
|
4382
|
+
return remaining.length > 0 ? remaining : void 0;
|
|
4383
|
+
}
|
|
4384
|
+
return currentBucketItems;
|
|
3706
4385
|
}
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
4386
|
+
};
|
|
4387
|
+
var orphanedCitationsRule = {
|
|
4388
|
+
id: "orphaned_citations",
|
|
4389
|
+
severity: "warning",
|
|
4390
|
+
async run(ctx) {
|
|
4391
|
+
const orphanedPages = [];
|
|
4392
|
+
await mapWithConcurrency(ctx.scan.typedKnowledge, vaultIoConcurrency(), async (page) => {
|
|
4393
|
+
try {
|
|
4394
|
+
const text = await readPageCached(page, ctx.pageTextCache);
|
|
4395
|
+
const split = splitFrontmatter(text);
|
|
4396
|
+
if (split.ok && hasOrphanedCitations(split.data.body)) orphanedPages.push(page.relPath);
|
|
4397
|
+
} catch {
|
|
4398
|
+
}
|
|
4399
|
+
});
|
|
4400
|
+
const buckets = {};
|
|
4401
|
+
if (orphanedPages.length > 0) buckets.orphaned_citations = orphanedPages;
|
|
4402
|
+
return { buckets };
|
|
3718
4403
|
}
|
|
3719
|
-
|
|
3720
|
-
|
|
4404
|
+
};
|
|
4405
|
+
var pageStructureRule = {
|
|
4406
|
+
id: "page_structure",
|
|
4407
|
+
severity: "info",
|
|
4408
|
+
async run(ctx) {
|
|
4409
|
+
const structFlags = [];
|
|
4410
|
+
await mapWithConcurrency(ctx.scan.typedKnowledge, vaultIoConcurrency(), async (page) => {
|
|
4411
|
+
try {
|
|
4412
|
+
const text = await readPageCached(page, ctx.pageTextCache);
|
|
4413
|
+
const split = splitFrontmatter(text);
|
|
4414
|
+
if (!split.ok) return;
|
|
4415
|
+
const body = split.data.body;
|
|
4416
|
+
const bodyLines = body.split("\n").filter((l) => l.trim().length > 0).length;
|
|
4417
|
+
if (bodyLines < STRUCT_MIN_BODY_LINES) {
|
|
4418
|
+
const hasRelated = /^## (Related|Relationships)/m.test(body);
|
|
4419
|
+
const sectionCount = (body.match(/^## /gm) ?? []).length;
|
|
4420
|
+
if (!hasRelated || sectionCount < STRUCT_MIN_SECTIONS) {
|
|
4421
|
+
const reasons = [];
|
|
4422
|
+
if (!hasRelated) reasons.push("no Related or Relationships");
|
|
4423
|
+
if (sectionCount < STRUCT_MIN_SECTIONS) reasons.push(`only ${sectionCount} sections`);
|
|
4424
|
+
structFlags.push(`${page.relPath}: ${bodyLines} lines, ${reasons.join(", ")}`);
|
|
4425
|
+
}
|
|
4426
|
+
}
|
|
4427
|
+
} catch {
|
|
4428
|
+
}
|
|
4429
|
+
});
|
|
4430
|
+
const buckets = {};
|
|
4431
|
+
if (structFlags.length > 0) buckets.page_structure = structFlags;
|
|
4432
|
+
return { buckets };
|
|
3721
4433
|
}
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
4434
|
+
};
|
|
4435
|
+
var duplicateFrontmatterRule = {
|
|
4436
|
+
id: "duplicate_frontmatter",
|
|
4437
|
+
severity: "warning",
|
|
4438
|
+
async run(ctx) {
|
|
4439
|
+
const dupFrontmatter = [];
|
|
4440
|
+
await mapWithConcurrency(ctx.scan.typedKnowledge, vaultIoConcurrency(), async (page) => {
|
|
4441
|
+
try {
|
|
4442
|
+
const text = await readPageCached(page, ctx.pageTextCache);
|
|
4443
|
+
const split = splitFrontmatter(text);
|
|
4444
|
+
if (split.ok && hasDuplicateFrontmatter(split.data.body)) dupFrontmatter.push(page.relPath);
|
|
4445
|
+
} catch {
|
|
4446
|
+
}
|
|
4447
|
+
});
|
|
4448
|
+
const buckets = {};
|
|
4449
|
+
if (dupFrontmatter.length > 0) buckets.duplicate_frontmatter = dupFrontmatter;
|
|
4450
|
+
return { buckets };
|
|
3731
4451
|
}
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
4452
|
+
};
|
|
4453
|
+
var missingOverviewRule = {
|
|
4454
|
+
id: "missing_overview",
|
|
4455
|
+
severity: "warning",
|
|
4456
|
+
async run(ctx) {
|
|
4457
|
+
const noOverview = [];
|
|
4458
|
+
await mapWithConcurrency(ctx.scan.typedKnowledge, vaultIoConcurrency(), async (page) => {
|
|
3736
4459
|
try {
|
|
3737
|
-
await readPageCached(page, pageTextCache);
|
|
4460
|
+
const text = await readPageCached(page, ctx.pageTextCache);
|
|
4461
|
+
const split = splitFrontmatter(text);
|
|
4462
|
+
if (!split.ok) return;
|
|
4463
|
+
if (!/^## Overview/m.test(split.data.body)) noOverview.push(page.relPath);
|
|
3738
4464
|
} catch {
|
|
3739
4465
|
}
|
|
3740
4466
|
});
|
|
4467
|
+
const buckets = {};
|
|
4468
|
+
if (noOverview.length > 0) buckets.missing_overview = noOverview;
|
|
4469
|
+
return { buckets };
|
|
4470
|
+
},
|
|
4471
|
+
async fix(ctx, currentBucketItems) {
|
|
4472
|
+
const noOverview = currentBucketItems;
|
|
4473
|
+
if (!noOverview || noOverview.length === 0) return currentBucketItems;
|
|
4474
|
+
const fixedHere = [];
|
|
4475
|
+
for (const relPath of noOverview) {
|
|
4476
|
+
try {
|
|
4477
|
+
const absPath = `${ctx.input.vault}/${relPath}`;
|
|
4478
|
+
const raw = await readFile13(absPath, "utf8");
|
|
4479
|
+
const split = splitFrontmatter(raw);
|
|
4480
|
+
if (!split.ok) {
|
|
4481
|
+
ctx.unresolved.push(relPath);
|
|
4482
|
+
continue;
|
|
4483
|
+
}
|
|
4484
|
+
const body = split.data.body;
|
|
4485
|
+
const rawFm = split.data.rawFrontmatter;
|
|
4486
|
+
const fm = extractFrontmatter(raw);
|
|
4487
|
+
const title = fm.ok && typeof fm.data.title === "string" ? fm.data.title : "";
|
|
4488
|
+
const overviewSection = `## Overview
|
|
4489
|
+
|
|
4490
|
+
${title}`;
|
|
4491
|
+
const trimmedBody = body.replace(/^\n+/, "");
|
|
4492
|
+
const newContent = `---
|
|
4493
|
+
${rawFm}
|
|
4494
|
+
---
|
|
4495
|
+
|
|
4496
|
+
${overviewSection}
|
|
4497
|
+
|
|
4498
|
+
${trimmedBody}`;
|
|
4499
|
+
const w = await safeWritePage(absPath, newContent);
|
|
4500
|
+
if (!w.ok) {
|
|
4501
|
+
ctx.unresolved.push(relPath);
|
|
4502
|
+
continue;
|
|
4503
|
+
}
|
|
4504
|
+
fixedHere.push(relPath);
|
|
4505
|
+
} catch {
|
|
4506
|
+
ctx.unresolved.push(relPath);
|
|
4507
|
+
}
|
|
4508
|
+
}
|
|
4509
|
+
ctx.fixed.push(...fixedHere);
|
|
4510
|
+
if (fixedHere.length > 0) {
|
|
4511
|
+
const fixedSet = new Set(fixedHere);
|
|
4512
|
+
const remaining = noOverview.filter((p) => !fixedSet.has(p));
|
|
4513
|
+
return remaining.length > 0 ? remaining : void 0;
|
|
4514
|
+
}
|
|
4515
|
+
return currentBucketItems;
|
|
3741
4516
|
}
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
if (!tags.result.ok && tags.result.error === "INVALID_FRONTMATTER") {
|
|
3750
|
-
buckets.invalid_frontmatter = [...buckets.invalid_frontmatter ?? [], tags.result.detail ?? {}];
|
|
3751
|
-
}
|
|
3752
|
-
const idx = await runIndexCheck({ vault: lintVault, scan });
|
|
3753
|
-
if (idx.result.ok && (idx.result.data.missing_from_index.length > 0 || idx.result.data.ghost_entries.length > 0)) {
|
|
3754
|
-
buckets.index_incomplete = [{
|
|
3755
|
-
missing_from_index: idx.result.data.missing_from_index,
|
|
3756
|
-
ghost_entries: idx.result.data.ghost_entries
|
|
3757
|
-
}];
|
|
3758
|
-
}
|
|
3759
|
-
const linkFmt = await runIndexLinkFormat({ vault: lintVault });
|
|
3760
|
-
if (linkFmt.result.ok && linkFmt.result.data.markdown_links.length > 0) {
|
|
3761
|
-
buckets.index_link_format = linkFmt.result.data.markdown_links;
|
|
3762
|
-
}
|
|
3763
|
-
const staleResult = await runStale({ vault: lintVault, days: input.days, scan, pageTextCache });
|
|
3764
|
-
if (staleResult.result.ok) {
|
|
3765
|
-
const st = staleResult.result.data;
|
|
3766
|
-
const staleList = [...st.stale_transcripts.map((t) => t.path), ...(st.unclaimed_transcripts ?? []).map((t) => t.path), ...st.incomplete_work_items.map((w) => w.path), ...(st.done_work_items ?? []).map((w) => w.path)];
|
|
3767
|
-
if (staleList.length > 0) buckets.stale_page = staleList;
|
|
3768
|
-
}
|
|
3769
|
-
const pagesize = await runPagesize({ vault: lintVault, lines: input.lines, scan, pageTextCache });
|
|
3770
|
-
if (pagesize.result.ok && pagesize.result.data.oversized.length > 0) buckets.page_too_large = pagesize.result.data.oversized;
|
|
3771
|
-
const rotate = await runLogRotate({ vault: lintVault, threshold: input.logThreshold, apply: false });
|
|
3772
|
-
if (rotate.result.ok && rotate.exitCode === ExitCode.LOG_ROTATE_NEEDED) {
|
|
3773
|
-
buckets.log_rotate_needed = [{ entries: rotate.result.data.entries, threshold: rotate.result.data.threshold }];
|
|
3774
|
-
}
|
|
3775
|
-
const orphans = await runOrphans({ vault: lintVault, scan, pageTextCache });
|
|
3776
|
-
if (orphans.result.ok) {
|
|
3777
|
-
if (orphans.result.data.orphans.length > 0) buckets.orphans = orphans.result.data.orphans;
|
|
3778
|
-
if (orphans.result.data.bridges.length > 0) buckets.bridges = orphans.result.data.bridges;
|
|
3779
|
-
}
|
|
3780
|
-
const sparse = await runSparseCommunity({ vault: lintVault, scan, pageTextCache });
|
|
3781
|
-
if (sparse.result.ok && sparse.result.data.communities.length > 0) {
|
|
3782
|
-
buckets.sparse_community = sparse.result.data.communities;
|
|
3783
|
-
}
|
|
3784
|
-
const topicMap = await runTopicMapCheck({ vault: lintVault, scan });
|
|
3785
|
-
if (topicMap.result.ok && topicMap.result.data.recommended) {
|
|
3786
|
-
buckets.topic_map_recommended = [{ page_count: topicMap.result.data.page_count, threshold: topicMap.result.data.threshold }];
|
|
3787
|
-
}
|
|
3788
|
-
const dedup = await runDedup({ vault: lintVault, scan, pageTextCache });
|
|
3789
|
-
if (dedup.result.ok && dedup.result.data.duplicates.length > 0) buckets.raw_dedup = dedup.result.data.duplicates;
|
|
3790
|
-
const bodyDedup = await runRawBodyDedup(lintVault, scan, pageTextCache);
|
|
3791
|
-
if (bodyDedup.result.ok && bodyDedup.result.data.duplicates.length > 0) {
|
|
3792
|
-
buckets.raw_body_duplicate = bodyDedup.result.data.duplicates.map((d) => ({
|
|
3793
|
-
body_hash: d.bodyHash.slice(0, 12),
|
|
3794
|
-
files: d.files.map((f) => `${f.relPath} (sha256: ${f.sha256 ?? "none"})`)
|
|
3795
|
-
}));
|
|
3796
|
-
}
|
|
3797
|
-
const compoundRefs = await validateCompoundReferences(lintVault, scan, pageTextCache);
|
|
3798
|
-
if (compoundRefs.ok && compoundRefs.data.length > 0) buckets.compound_refs = compoundRefs.data;
|
|
3799
|
-
const pathCheck = await runPathTooLong({ vault: lintVault, scan });
|
|
3800
|
-
if (pathCheck.result.ok && pathCheck.result.data.violations.length > 0) buckets.path_too_long = pathCheck.result.data.violations;
|
|
3801
|
-
const wikilinkResolver = buildWikilinkResolver(scan.allMarkdown);
|
|
3802
|
-
{
|
|
3803
|
-
const allPageResults = await mapWithConcurrency(scan.allMarkdown, vaultIoConcurrency(), async (page) => {
|
|
3804
|
-
const sensitiveFlags2 = [];
|
|
3805
|
-
const conflictMarkers2 = [];
|
|
3806
|
-
let fmYamlInvalid2 = null;
|
|
4517
|
+
};
|
|
4518
|
+
var frontmatterWikilinkRule = {
|
|
4519
|
+
id: "frontmatter_wikilink",
|
|
4520
|
+
severity: "info",
|
|
4521
|
+
async run(ctx) {
|
|
4522
|
+
const fmWikilinkFlags = [];
|
|
4523
|
+
await mapWithConcurrency(ctx.scan.typedKnowledge, vaultIoConcurrency(), async (page) => {
|
|
3807
4524
|
try {
|
|
3808
|
-
const text = await readPageCached(page, pageTextCache);
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
const
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
const
|
|
3815
|
-
|
|
4525
|
+
const text = await readPageCached(page, ctx.pageTextCache);
|
|
4526
|
+
const split = splitFrontmatter(text);
|
|
4527
|
+
if (!split.ok) return;
|
|
4528
|
+
const rawFm = split.data.rawFrontmatter;
|
|
4529
|
+
const fmLinks = rawFm.match(/\[\[([^\[\]|]+)(?:\|[^\[\]]*)?\]\]/g) ?? [];
|
|
4530
|
+
for (const link of fmLinks) {
|
|
4531
|
+
const target = link.replace(/^\[\[/, "").replace(/(?:\|[^\[\]]*)?\]\]$/, "").trim();
|
|
4532
|
+
if (!ctx.wikilinkResolver.resolve(target).path) {
|
|
4533
|
+
fmWikilinkFlags.push(`${page.relPath}: [[${target}]] does not resolve`);
|
|
4534
|
+
}
|
|
3816
4535
|
}
|
|
3817
4536
|
} catch {
|
|
3818
4537
|
}
|
|
3819
|
-
return { sensitiveFlags: sensitiveFlags2, conflictMarkers: conflictMarkers2, fmYamlInvalid: fmYamlInvalid2 };
|
|
3820
4538
|
});
|
|
3821
|
-
const
|
|
3822
|
-
if (
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
const
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
deepFiles.push({ relPath: raw.relPath, stem, parentType: parts[1] });
|
|
4539
|
+
const buckets = {};
|
|
4540
|
+
if (fmWikilinkFlags.length > 0) buckets.frontmatter_wikilink = fmWikilinkFlags;
|
|
4541
|
+
return { buckets };
|
|
4542
|
+
}
|
|
4543
|
+
};
|
|
4544
|
+
var wikilinkCitationRule = {
|
|
4545
|
+
id: "wikilink_citation",
|
|
4546
|
+
severity: "info",
|
|
4547
|
+
async run(ctx) {
|
|
4548
|
+
const wikilinkCitationFlags = [];
|
|
4549
|
+
await mapWithConcurrency(ctx.scan.typedKnowledge, vaultIoConcurrency(), async (page) => {
|
|
4550
|
+
try {
|
|
4551
|
+
const text = await readPageCached(page, ctx.pageTextCache);
|
|
4552
|
+
const split = splitFrontmatter(text);
|
|
4553
|
+
if (split.ok && hasWikilinkCitations(split.data.body)) wikilinkCitationFlags.push(page.relPath);
|
|
4554
|
+
} catch {
|
|
3838
4555
|
}
|
|
3839
|
-
}
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
4556
|
+
});
|
|
4557
|
+
const buckets = {};
|
|
4558
|
+
if (wikilinkCitationFlags.length > 0) buckets.wikilink_citation = wikilinkCitationFlags;
|
|
4559
|
+
return { buckets };
|
|
4560
|
+
},
|
|
4561
|
+
async fix(ctx, currentBucketItems) {
|
|
4562
|
+
const wikilinkCitationFlags = currentBucketItems;
|
|
4563
|
+
if (!wikilinkCitationFlags || wikilinkCitationFlags.length === 0) return currentBucketItems;
|
|
4564
|
+
const WIKILINK_RE = /\[\[raw\/([^\]|]+)(?:\|[^\]]*)?\]\]/g;
|
|
4565
|
+
const FENCE_RE = /```[\s\S]*?```/g;
|
|
4566
|
+
const wikilinkFixed = [];
|
|
4567
|
+
for (const relPath of wikilinkCitationFlags) {
|
|
4568
|
+
try {
|
|
4569
|
+
const absPath = `${ctx.input.vault}/${relPath}`;
|
|
4570
|
+
const raw = await readFile13(absPath, "utf8");
|
|
4571
|
+
const split = splitFrontmatter(raw);
|
|
4572
|
+
if (!split.ok) {
|
|
4573
|
+
ctx.unresolved.push(relPath);
|
|
4574
|
+
continue;
|
|
4575
|
+
}
|
|
4576
|
+
const body = split.data.body;
|
|
4577
|
+
const rawFm = split.data.rawFrontmatter;
|
|
4578
|
+
const stripped = body.replace(FENCE_RE, "");
|
|
4579
|
+
const wikilinkMatches = [...stripped.matchAll(WIKILINK_RE)];
|
|
4580
|
+
if (wikilinkMatches.length === 0) {
|
|
4581
|
+
ctx.unresolved.push(relPath);
|
|
4582
|
+
continue;
|
|
4583
|
+
}
|
|
4584
|
+
const wikilinkPaths = [...new Set(wikilinkMatches.map((m) => m[1]))];
|
|
4585
|
+
const bodyLines = body.split("\n");
|
|
4586
|
+
let inSrc = false;
|
|
4587
|
+
const newBodyLines = [];
|
|
4588
|
+
for (const line of bodyLines) {
|
|
4589
|
+
if (/^## Sources\b/.test(line.trim())) {
|
|
4590
|
+
inSrc = true;
|
|
4591
|
+
newBodyLines.push(line);
|
|
4592
|
+
continue;
|
|
4593
|
+
}
|
|
4594
|
+
if (inSrc) {
|
|
4595
|
+
newBodyLines.push(line);
|
|
4596
|
+
continue;
|
|
4597
|
+
}
|
|
4598
|
+
let cleaned = line.replace(/\[\[raw\/[^\]|]+(?:\|[^\]]*)?\]\]/g, "");
|
|
4599
|
+
cleaned = cleaned.replace(/\s+\./g, ".").replace(/\s{2,}/g, " ").replace(/\s+$/, "");
|
|
4600
|
+
if (cleaned.length > 0 || line.trim().length === 0) {
|
|
4601
|
+
newBodyLines.push(cleaned);
|
|
4602
|
+
}
|
|
4603
|
+
}
|
|
4604
|
+
let newBody = newBodyLines.join("\n");
|
|
4605
|
+
const citationMarkers = wikilinkPaths.map((p) => `^[raw/${p}]`);
|
|
4606
|
+
const sourceEntries = extractSourceEntries(rawFm);
|
|
4607
|
+
const fmMarkers = [];
|
|
4608
|
+
for (const entry of sourceEntries) {
|
|
4609
|
+
let rawPath = entry.replace(/^"/, "").replace(/"$/, "").replace(/^'/, "").replace(/'$/, "");
|
|
4610
|
+
rawPath = rawPath.replace(/^\^\[/, "").replace(/\]$/, "");
|
|
4611
|
+
if (rawPath.startsWith("raw/")) {
|
|
4612
|
+
fmMarkers.push(`^[${rawPath}]`);
|
|
4613
|
+
}
|
|
4614
|
+
}
|
|
4615
|
+
const allMarkers = [.../* @__PURE__ */ new Set([...citationMarkers, ...fmMarkers])];
|
|
4616
|
+
const hasSourcesSection = /^## Sources\b/m.test(newBody);
|
|
4617
|
+
if (hasSourcesSection) {
|
|
4618
|
+
const existingSources = new Set(
|
|
4619
|
+
newBody.split("\n").filter((l) => /^- \^\[raw\//.test(l.trim())).map((l) => l.trim().replace(/^- /, ""))
|
|
4620
|
+
);
|
|
4621
|
+
const newMarkers = allMarkers.filter((m) => !existingSources.has(m));
|
|
4622
|
+
const sourceLines = newMarkers.map((m) => `- ${m}`);
|
|
4623
|
+
if (sourceLines.length > 0) {
|
|
4624
|
+
newBody = newBody.trimEnd() + "\n" + sourceLines.join("\n") + "\n";
|
|
4625
|
+
}
|
|
4626
|
+
} else {
|
|
4627
|
+
const sourceLines = allMarkers.map((m) => `- ${m}`);
|
|
4628
|
+
newBody = newBody.trimEnd() + "\n\n## Sources\n\n" + sourceLines.join("\n") + "\n";
|
|
4629
|
+
}
|
|
4630
|
+
const newContent = `---
|
|
4631
|
+
${rawFm}
|
|
4632
|
+
---
|
|
4633
|
+
${newBody}`;
|
|
4634
|
+
const w = await safeWritePage(absPath, newContent);
|
|
4635
|
+
if (!w.ok) {
|
|
4636
|
+
ctx.unresolved.push(relPath);
|
|
4637
|
+
continue;
|
|
4638
|
+
}
|
|
4639
|
+
wikilinkFixed.push(relPath);
|
|
4640
|
+
} catch {
|
|
4641
|
+
ctx.unresolved.push(relPath);
|
|
3844
4642
|
}
|
|
3845
4643
|
}
|
|
3846
|
-
|
|
3847
|
-
|
|
4644
|
+
ctx.fixed.push(...wikilinkFixed);
|
|
4645
|
+
if (wikilinkFixed.length > 0) {
|
|
4646
|
+
const fixedSet = new Set(wikilinkFixed);
|
|
4647
|
+
const remaining = wikilinkCitationFlags.filter((p) => !fixedSet.has(p));
|
|
4648
|
+
return remaining.length > 0 ? remaining : void 0;
|
|
3848
4649
|
}
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
const orphanedPages = [];
|
|
3857
|
-
const structFlags = [];
|
|
3858
|
-
const dupFrontmatter = [];
|
|
3859
|
-
const noOverview = [];
|
|
3860
|
-
const fmWikilinkFlags = [];
|
|
3861
|
-
const wikilinkCitationFlags = [];
|
|
4650
|
+
return currentBucketItems;
|
|
4651
|
+
}
|
|
4652
|
+
};
|
|
4653
|
+
var brokenSourcesRule = {
|
|
4654
|
+
id: "broken_sources",
|
|
4655
|
+
severity: "error",
|
|
4656
|
+
async run(ctx) {
|
|
3862
4657
|
const brokenSourceFlags = /* @__PURE__ */ new Set();
|
|
3863
|
-
|
|
3864
|
-
const missingDiagramFlags = [];
|
|
3865
|
-
const typedPageResults = await mapWithConcurrency(scan.typedKnowledge, vaultIoConcurrency(), async (page) => {
|
|
3866
|
-
const result = {
|
|
3867
|
-
legacyPages: [],
|
|
3868
|
-
orphanedPages: [],
|
|
3869
|
-
structFlags: [],
|
|
3870
|
-
dupFrontmatter: [],
|
|
3871
|
-
noOverview: [],
|
|
3872
|
-
fmWikilinkFlags: [],
|
|
3873
|
-
wikilinkCitationFlags: [],
|
|
3874
|
-
brokenSourceFlags: [],
|
|
3875
|
-
missingTldrFlags: [],
|
|
3876
|
-
missingDiagramFlags: []
|
|
3877
|
-
};
|
|
4658
|
+
await mapWithConcurrency(ctx.scan.typedKnowledge, vaultIoConcurrency(), async (page) => {
|
|
3878
4659
|
try {
|
|
3879
|
-
const text = await readPageCached(page, pageTextCache);
|
|
4660
|
+
const text = await readPageCached(page, ctx.pageTextCache);
|
|
3880
4661
|
const split = splitFrontmatter(text);
|
|
3881
|
-
if (!split.ok) return
|
|
4662
|
+
if (!split.ok) return;
|
|
3882
4663
|
const body = split.data.body;
|
|
3883
4664
|
const rawFm = split.data.rawFrontmatter;
|
|
3884
|
-
if (hasDuplicateFrontmatter(body)) result.dupFrontmatter.push(page.relPath);
|
|
3885
|
-
if (isLegacyCitationStyle(body)) result.legacyPages.push(page.relPath);
|
|
3886
|
-
if (hasOrphanedCitations(body)) result.orphanedPages.push(page.relPath);
|
|
3887
|
-
if (hasWikilinkCitations(body)) result.wikilinkCitationFlags.push(page.relPath);
|
|
3888
4665
|
const sourcesEntries = extractSourceEntries(rawFm);
|
|
3889
4666
|
for (const entry of sourcesEntries) {
|
|
3890
4667
|
const rawPath = normalizeRawSourceTarget(entry);
|
|
3891
4668
|
if (!rawPath) continue;
|
|
3892
|
-
if (!rawSourceTargetExistsSync(
|
|
3893
|
-
|
|
4669
|
+
if (!rawSourceTargetExistsSync(ctx.vault, rawPath)) {
|
|
4670
|
+
brokenSourceFlags.add(`${page.relPath}: ${rawPath}`);
|
|
3894
4671
|
}
|
|
3895
4672
|
}
|
|
3896
4673
|
for (const marker of extractCitationMarkers(body)) {
|
|
3897
|
-
if (!rawSourceTargetExistsSync(
|
|
3898
|
-
|
|
4674
|
+
if (!rawSourceTargetExistsSync(ctx.vault, marker.target)) {
|
|
4675
|
+
brokenSourceFlags.add(`${page.relPath}: ${marker.target}`);
|
|
3899
4676
|
}
|
|
3900
4677
|
}
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
4678
|
+
} catch {
|
|
4679
|
+
}
|
|
4680
|
+
});
|
|
4681
|
+
const buckets = {};
|
|
4682
|
+
if (brokenSourceFlags.size > 0) buckets.broken_sources = [...brokenSourceFlags];
|
|
4683
|
+
return { buckets };
|
|
4684
|
+
}
|
|
4685
|
+
};
|
|
4686
|
+
var missingTldrRule = {
|
|
4687
|
+
id: "missing_tldr",
|
|
4688
|
+
severity: "info",
|
|
4689
|
+
async run(ctx) {
|
|
4690
|
+
const missingTldrFlags = [];
|
|
4691
|
+
await mapWithConcurrency(ctx.scan.typedKnowledge, vaultIoConcurrency(), async (page) => {
|
|
4692
|
+
try {
|
|
4693
|
+
const text = await readPageCached(page, ctx.pageTextCache);
|
|
4694
|
+
const split = splitFrontmatter(text);
|
|
4695
|
+
if (!split.ok) return;
|
|
4696
|
+
const body = split.data.body;
|
|
4697
|
+
const bodyFirst15 = body.split("\n").slice(0, 15).join("\n");
|
|
4698
|
+
if (!/^>\s*\*\*TL;DR:?\*\*/m.test(bodyFirst15) && !/^##\s+TL;\s*DR/m.test(bodyFirst15)) {
|
|
4699
|
+
missingTldrFlags.push(page.relPath);
|
|
4700
|
+
}
|
|
4701
|
+
} catch {
|
|
4702
|
+
}
|
|
4703
|
+
});
|
|
4704
|
+
const buckets = {};
|
|
4705
|
+
if (missingTldrFlags.length > 0) buckets.missing_tldr = missingTldrFlags;
|
|
4706
|
+
return { buckets };
|
|
4707
|
+
},
|
|
4708
|
+
async fix(ctx, currentBucketItems) {
|
|
4709
|
+
const missingTldrFlags = currentBucketItems;
|
|
4710
|
+
if (!missingTldrFlags || missingTldrFlags.length === 0) return currentBucketItems;
|
|
4711
|
+
const fixedHere = [];
|
|
4712
|
+
for (const relPath of missingTldrFlags) {
|
|
4713
|
+
try {
|
|
4714
|
+
const absPath = `${ctx.input.vault}/${relPath}`;
|
|
4715
|
+
const raw = await readFile13(absPath, "utf8");
|
|
4716
|
+
const split = splitFrontmatter(raw);
|
|
4717
|
+
if (!split.ok) {
|
|
4718
|
+
ctx.unresolved.push(relPath);
|
|
4719
|
+
continue;
|
|
4720
|
+
}
|
|
4721
|
+
const body = split.data.body;
|
|
4722
|
+
const rawFm = split.data.rawFrontmatter;
|
|
4723
|
+
const lines = body.split("\n");
|
|
4724
|
+
let insertIndex = 0;
|
|
4725
|
+
for (let i = 0; i < lines.length; i++) {
|
|
4726
|
+
if (/^# /.test(lines[i])) {
|
|
4727
|
+
insertIndex = i + 1;
|
|
4728
|
+
while (insertIndex < lines.length && lines[insertIndex].trim() === "") {
|
|
4729
|
+
insertIndex++;
|
|
4730
|
+
}
|
|
4731
|
+
break;
|
|
3906
4732
|
}
|
|
3907
4733
|
}
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
4734
|
+
if (insertIndex === 0) {
|
|
4735
|
+
lines.splice(0, 0, "", "> **TL;DR:** ");
|
|
4736
|
+
} else {
|
|
4737
|
+
lines.splice(insertIndex, 0, "> **TL;DR:** ");
|
|
4738
|
+
}
|
|
4739
|
+
const trimmedFm = rawFm.endsWith("\n") ? rawFm : rawFm + "\n";
|
|
4740
|
+
const newContent = `---
|
|
4741
|
+
${trimmedFm}---
|
|
4742
|
+
${lines.join("\n")}`;
|
|
4743
|
+
const w = await safeWritePage(absPath, newContent);
|
|
4744
|
+
if (!w.ok) {
|
|
4745
|
+
ctx.unresolved.push(relPath);
|
|
4746
|
+
continue;
|
|
4747
|
+
}
|
|
4748
|
+
fixedHere.push(relPath);
|
|
4749
|
+
} catch {
|
|
4750
|
+
ctx.unresolved.push(relPath);
|
|
4751
|
+
}
|
|
4752
|
+
}
|
|
4753
|
+
ctx.fixed.push(...fixedHere);
|
|
4754
|
+
if (fixedHere.length > 0) {
|
|
4755
|
+
const fixedSet = new Set(fixedHere);
|
|
4756
|
+
const remaining = missingTldrFlags.filter((p) => !fixedSet.has(p));
|
|
4757
|
+
return remaining.length > 0 ? remaining : void 0;
|
|
4758
|
+
}
|
|
4759
|
+
return currentBucketItems;
|
|
4760
|
+
}
|
|
4761
|
+
};
|
|
4762
|
+
var missingDiagramRule = {
|
|
4763
|
+
id: "missing_diagram",
|
|
4764
|
+
severity: "warning",
|
|
4765
|
+
async run(ctx) {
|
|
4766
|
+
const missingDiagramFlags = [];
|
|
4767
|
+
await mapWithConcurrency(ctx.scan.typedKnowledge, vaultIoConcurrency(), async (page) => {
|
|
4768
|
+
try {
|
|
4769
|
+
const text = await readPageCached(page, ctx.pageTextCache);
|
|
3913
4770
|
const fmData = extractFrontmatter(text);
|
|
3914
4771
|
const pageTags = fmData.ok && Array.isArray(fmData.data.tags) ? fmData.data.tags : [];
|
|
3915
|
-
if (pageTags.includes("architecture") && !
|
|
3916
|
-
|
|
3917
|
-
}
|
|
3918
|
-
if (bodyLines < STRUCT_MIN_BODY_LINES) {
|
|
3919
|
-
const hasRelated = /^## (Related|Relationships)/m.test(body);
|
|
3920
|
-
const sectionCount = (body.match(/^## /gm) ?? []).length;
|
|
3921
|
-
if (!hasRelated || sectionCount < STRUCT_MIN_SECTIONS) {
|
|
3922
|
-
const reasons = [];
|
|
3923
|
-
if (!hasRelated) reasons.push("no Related or Relationships");
|
|
3924
|
-
if (sectionCount < STRUCT_MIN_SECTIONS) reasons.push(`only ${sectionCount} sections`);
|
|
3925
|
-
result.structFlags.push(`${page.relPath}: ${bodyLines} lines, ${reasons.join(", ")}`);
|
|
3926
|
-
}
|
|
4772
|
+
if (pageTags.includes("architecture") && !text.includes("```mermaid")) {
|
|
4773
|
+
missingDiagramFlags.push(page.relPath);
|
|
3927
4774
|
}
|
|
3928
4775
|
} catch {
|
|
3929
4776
|
}
|
|
3930
|
-
return result;
|
|
3931
4777
|
});
|
|
3932
|
-
|
|
3933
|
-
legacyPages.push(...result.legacyPages);
|
|
3934
|
-
orphanedPages.push(...result.orphanedPages);
|
|
3935
|
-
structFlags.push(...result.structFlags);
|
|
3936
|
-
dupFrontmatter.push(...result.dupFrontmatter);
|
|
3937
|
-
noOverview.push(...result.noOverview);
|
|
3938
|
-
fmWikilinkFlags.push(...result.fmWikilinkFlags);
|
|
3939
|
-
wikilinkCitationFlags.push(...result.wikilinkCitationFlags);
|
|
3940
|
-
for (const flag of result.brokenSourceFlags) brokenSourceFlags.add(flag);
|
|
3941
|
-
missingTldrFlags.push(...result.missingTldrFlags);
|
|
3942
|
-
missingDiagramFlags.push(...result.missingDiagramFlags);
|
|
3943
|
-
}
|
|
3944
|
-
if (legacyPages.length > 0) buckets.legacy_citation_style = legacyPages;
|
|
3945
|
-
if (orphanedPages.length > 0) buckets.orphaned_citations = orphanedPages;
|
|
3946
|
-
if (structFlags.length > 0) buckets.page_structure = structFlags;
|
|
3947
|
-
if (dupFrontmatter.length > 0) buckets.duplicate_frontmatter = dupFrontmatter;
|
|
3948
|
-
if (noOverview.length > 0) buckets.missing_overview = noOverview;
|
|
3949
|
-
if (fmWikilinkFlags.length > 0) buckets.frontmatter_wikilink = fmWikilinkFlags;
|
|
3950
|
-
if (wikilinkCitationFlags.length > 0) buckets.wikilink_citation = wikilinkCitationFlags;
|
|
3951
|
-
if (brokenSourceFlags.size > 0) buckets.broken_sources = [...brokenSourceFlags];
|
|
3952
|
-
if (missingTldrFlags.length > 0) buckets.missing_tldr = missingTldrFlags;
|
|
4778
|
+
const buckets = {};
|
|
3953
4779
|
if (missingDiagramFlags.length > 0) buckets.missing_diagram = missingDiagramFlags;
|
|
4780
|
+
return { buckets };
|
|
4781
|
+
}
|
|
4782
|
+
};
|
|
4783
|
+
var workItemHealthRule = {
|
|
4784
|
+
id: "work_item_health",
|
|
4785
|
+
severity: "warning",
|
|
4786
|
+
async run(ctx) {
|
|
3954
4787
|
const workItemHealth = [];
|
|
3955
4788
|
const workItemDirs = /* @__PURE__ */ new Map();
|
|
3956
|
-
for (const page of scan.workItems) {
|
|
4789
|
+
for (const page of ctx.scan.workItems) {
|
|
3957
4790
|
const dir = page.relPath.replace(/\/(spec|plan|log)\.md$/, "");
|
|
3958
4791
|
const pages = workItemDirs.get(dir) ?? [];
|
|
3959
4792
|
pages.push(page);
|
|
3960
4793
|
workItemDirs.set(dir, pages);
|
|
3961
4794
|
}
|
|
3962
|
-
const workItemHealthResults = await mapWithConcurrency(
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
if (
|
|
3972
|
-
|
|
3973
|
-
|
|
4795
|
+
const workItemHealthResults = await mapWithConcurrency(
|
|
4796
|
+
[...workItemDirs.entries()],
|
|
4797
|
+
vaultIoConcurrency(),
|
|
4798
|
+
async ([dir, pages]) => {
|
|
4799
|
+
const flags = [];
|
|
4800
|
+
const specPage = pages.find((p) => p.relPath.endsWith("/spec.md"));
|
|
4801
|
+
const hasPlan = pages.some((p) => p.relPath.endsWith("/plan.md"));
|
|
4802
|
+
let specStatus;
|
|
4803
|
+
let specStarted;
|
|
4804
|
+
if (specPage) {
|
|
4805
|
+
const text = await readPageCached(specPage, ctx.pageTextCache);
|
|
4806
|
+
const fm = extractFrontmatter(text);
|
|
4807
|
+
if (fm.ok) {
|
|
4808
|
+
specStatus = typeof fm.data.status === "string" ? fm.data.status : void 0;
|
|
4809
|
+
specStarted = fm.data.started;
|
|
4810
|
+
}
|
|
3974
4811
|
}
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
4812
|
+
const isClosed = specStatus === "completed" || specStatus === "abandoned";
|
|
4813
|
+
if (specPage && !hasPlan && !isClosed) {
|
|
4814
|
+
const lastSegment = dir.split("/").pop();
|
|
4815
|
+
const dateMatch = lastSegment.match(/^(\d{4}-\d{2}-\d{2})/);
|
|
4816
|
+
if (dateMatch) {
|
|
4817
|
+
const dirDate = Date.parse(dateMatch[1]);
|
|
4818
|
+
if (!isNaN(dirDate) && Date.now() - dirDate > 24 * 60 * 60 * 1e3) {
|
|
4819
|
+
flags.push(`${dir}/spec.md: has spec but no plan after 24h`);
|
|
4820
|
+
}
|
|
3984
4821
|
}
|
|
3985
4822
|
}
|
|
4823
|
+
if (specPage && specStatus === "in-progress" && !specStarted) {
|
|
4824
|
+
flags.push(`${specPage.relPath}: in-progress without started date`);
|
|
4825
|
+
}
|
|
4826
|
+
return flags;
|
|
3986
4827
|
}
|
|
3987
|
-
|
|
3988
|
-
flags.push(`${specPage.relPath}: in-progress without started date`);
|
|
3989
|
-
}
|
|
3990
|
-
return flags;
|
|
3991
|
-
});
|
|
4828
|
+
);
|
|
3992
4829
|
workItemHealth.push(...workItemHealthResults.flat());
|
|
4830
|
+
const buckets = {};
|
|
3993
4831
|
if (workItemHealth.length > 0) buckets.work_item_health = workItemHealth;
|
|
4832
|
+
return { buckets };
|
|
4833
|
+
}
|
|
4834
|
+
};
|
|
4835
|
+
var orphanedProjectPagesRule = {
|
|
4836
|
+
id: "orphaned_project_pages",
|
|
4837
|
+
severity: "warning",
|
|
4838
|
+
async run(ctx) {
|
|
3994
4839
|
const orphanedProjectPages = [];
|
|
3995
4840
|
const knowledgeContentCache = /* @__PURE__ */ new Map();
|
|
3996
4841
|
const readKnowledgeContent = (slug) => {
|
|
3997
4842
|
const existing = knowledgeContentCache.get(slug);
|
|
3998
4843
|
if (existing) return existing;
|
|
3999
|
-
const knowledgePath =
|
|
4844
|
+
const knowledgePath = join13(ctx.vault, "projects", slug, "knowledge.md");
|
|
4000
4845
|
const pending = existsSync4(knowledgePath) ? readFile13(knowledgePath, "utf8").catch(() => null) : Promise.resolve(null);
|
|
4001
4846
|
knowledgeContentCache.set(slug, pending);
|
|
4002
4847
|
return pending;
|
|
4003
4848
|
};
|
|
4004
|
-
const orphanedProjectPageResults = await mapWithConcurrency(
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
const
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
const
|
|
4014
|
-
if (!
|
|
4015
|
-
const
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
orphanedProjectPages.push(...orphanedProjectPageResults.flat());
|
|
4028
|
-
if (orphanedProjectPages.length > 0) buckets.orphaned_project_pages = orphanedProjectPages;
|
|
4029
|
-
const cliRefFlags = [];
|
|
4030
|
-
const cliSurface = buildCliSurface();
|
|
4031
|
-
const allScanPages = [...scan.typedKnowledge];
|
|
4032
|
-
const cliRefResults = await mapWithConcurrency(allScanPages, vaultIoConcurrency(), async (page) => {
|
|
4033
|
-
const flags = [];
|
|
4034
|
-
try {
|
|
4035
|
-
const text = await readPageCached(page, pageTextCache);
|
|
4036
|
-
const violations = validateCliRefs(text, page.relPath, cliSurface);
|
|
4037
|
-
for (const v of violations) {
|
|
4038
|
-
flags.push(`${v.page}: ${v.ref} (${v.reason})`);
|
|
4849
|
+
const orphanedProjectPageResults = await mapWithConcurrency(
|
|
4850
|
+
ctx.scan.typedKnowledge,
|
|
4851
|
+
vaultIoConcurrency(),
|
|
4852
|
+
async (page) => {
|
|
4853
|
+
const flags = [];
|
|
4854
|
+
try {
|
|
4855
|
+
const text = await readPageCached(page, ctx.pageTextCache);
|
|
4856
|
+
const fm = extractFrontmatter(text);
|
|
4857
|
+
if (!fm.ok) return flags;
|
|
4858
|
+
const pp = fm.data.provenance_projects;
|
|
4859
|
+
if (!Array.isArray(pp)) return flags;
|
|
4860
|
+
for (const entry of pp) {
|
|
4861
|
+
const slugMatch = String(entry).match(/\[\[([^\]]+)\]\]/);
|
|
4862
|
+
if (!slugMatch) continue;
|
|
4863
|
+
const slug = slugMatch[1];
|
|
4864
|
+
const knowledgeContent = await readKnowledgeContent(slug);
|
|
4865
|
+
if (knowledgeContent === null) continue;
|
|
4866
|
+
const pageRef = page.relPath.replace(/\.md$/, "");
|
|
4867
|
+
if (!knowledgeContent.includes(`[[${pageRef}]]`)) {
|
|
4868
|
+
flags.push(`${page.relPath}: not in projects/${slug}/knowledge.md`);
|
|
4869
|
+
}
|
|
4870
|
+
}
|
|
4871
|
+
} catch {
|
|
4039
4872
|
}
|
|
4040
|
-
|
|
4873
|
+
return flags;
|
|
4041
4874
|
}
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
if (
|
|
4875
|
+
);
|
|
4876
|
+
orphanedProjectPages.push(...orphanedProjectPageResults.flat());
|
|
4877
|
+
const buckets = {};
|
|
4878
|
+
if (orphanedProjectPages.length > 0) buckets.orphaned_project_pages = orphanedProjectPages;
|
|
4879
|
+
return { buckets };
|
|
4880
|
+
}
|
|
4881
|
+
};
|
|
4882
|
+
var staleSectionsRule = {
|
|
4883
|
+
id: "stale_sections",
|
|
4884
|
+
severity: "info",
|
|
4885
|
+
async run(ctx) {
|
|
4046
4886
|
const staleSectionFlags = [];
|
|
4047
4887
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
4048
4888
|
const approachingThreshold = 7;
|
|
4049
|
-
const staleSectionResults = await mapWithConcurrency(
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
const
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4889
|
+
const staleSectionResults = await mapWithConcurrency(
|
|
4890
|
+
ctx.scan.typedKnowledge,
|
|
4891
|
+
vaultIoConcurrency(),
|
|
4892
|
+
async (page) => {
|
|
4893
|
+
const flags = [];
|
|
4894
|
+
try {
|
|
4895
|
+
const text = await readPageCached(page, ctx.pageTextCache);
|
|
4896
|
+
const annotations = parseExpiryAnnotations(text, page.relPath);
|
|
4897
|
+
for (const ann of annotations) {
|
|
4898
|
+
if (ann.expires < today) {
|
|
4899
|
+
flags.push(`${page.relPath}: section "${ann.heading}" expired on ${ann.expires}`);
|
|
4900
|
+
} else {
|
|
4901
|
+
const daysUntilExpiry = Math.floor((Date.parse(ann.expires) - Date.now()) / 864e5);
|
|
4902
|
+
if (daysUntilExpiry <= approachingThreshold) {
|
|
4903
|
+
flags.push(
|
|
4904
|
+
`${page.relPath}: section "${ann.heading}" expires in ${daysUntilExpiry} day(s) (${ann.expires})`
|
|
4905
|
+
);
|
|
4906
|
+
}
|
|
4061
4907
|
}
|
|
4062
4908
|
}
|
|
4909
|
+
} catch {
|
|
4063
4910
|
}
|
|
4064
|
-
|
|
4911
|
+
return flags;
|
|
4065
4912
|
}
|
|
4066
|
-
|
|
4067
|
-
});
|
|
4913
|
+
);
|
|
4068
4914
|
staleSectionFlags.push(...staleSectionResults.flat());
|
|
4915
|
+
const buckets = {};
|
|
4069
4916
|
if (staleSectionFlags.length > 0) buckets.stale_sections = staleSectionFlags;
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4917
|
+
return { buckets };
|
|
4918
|
+
}
|
|
4919
|
+
};
|
|
4920
|
+
var cycleTrapsRule = {
|
|
4921
|
+
id: "cycle_traps",
|
|
4922
|
+
severity: "warning",
|
|
4923
|
+
async run(ctx) {
|
|
4924
|
+
const pages = ctx.scan.typedKnowledge;
|
|
4925
|
+
const pagePaths = new Set(pages.map((p) => p.relPath));
|
|
4926
|
+
const adj = /* @__PURE__ */ new Map();
|
|
4927
|
+
for (const p of pages) {
|
|
4928
|
+
adj.set(p.relPath, []);
|
|
4929
|
+
}
|
|
4930
|
+
const perPageTargets = await mapWithConcurrency(pages, vaultIoConcurrency(), async (p) => {
|
|
4931
|
+
try {
|
|
4932
|
+
const text = await readPageCached(p, ctx.pageTextCache);
|
|
4933
|
+
const split = splitFrontmatter(text);
|
|
4934
|
+
const body = split.ok ? split.data.body : text;
|
|
4935
|
+
const targets = [];
|
|
4936
|
+
for (const slug of extractBodyWikilinks(body)) {
|
|
4937
|
+
const resolution = ctx.wikilinkResolver.resolve(slug);
|
|
4938
|
+
if (resolution.path && pagePaths.has(resolution.path)) {
|
|
4939
|
+
targets.push(resolution.path);
|
|
4085
4940
|
}
|
|
4086
|
-
sensitiveFixed.push(page.relPath);
|
|
4087
|
-
} catch {
|
|
4088
|
-
unresolved.push(page.relPath);
|
|
4089
4941
|
}
|
|
4942
|
+
return { relPath: p.relPath, targets };
|
|
4943
|
+
} catch {
|
|
4944
|
+
return { relPath: p.relPath, targets: [] };
|
|
4090
4945
|
}
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4946
|
+
});
|
|
4947
|
+
for (const item of perPageTargets) {
|
|
4948
|
+
adj.set(item.relPath, item.targets);
|
|
4949
|
+
}
|
|
4950
|
+
let index = 0;
|
|
4951
|
+
const indices = /* @__PURE__ */ new Map();
|
|
4952
|
+
const lowlink = /* @__PURE__ */ new Map();
|
|
4953
|
+
const onStack = /* @__PURE__ */ new Set();
|
|
4954
|
+
const stack = [];
|
|
4955
|
+
const cyclePages = /* @__PURE__ */ new Set();
|
|
4956
|
+
function strongConnect(v) {
|
|
4957
|
+
indices.set(v, index);
|
|
4958
|
+
lowlink.set(v, index);
|
|
4959
|
+
index++;
|
|
4960
|
+
stack.push(v);
|
|
4961
|
+
onStack.add(v);
|
|
4962
|
+
const neighbors = adj.get(v) ?? [];
|
|
4963
|
+
for (const w of neighbors) {
|
|
4964
|
+
if (!indices.has(w)) {
|
|
4965
|
+
strongConnect(w);
|
|
4966
|
+
lowlink.set(v, Math.min(lowlink.get(v), lowlink.get(w)));
|
|
4967
|
+
} else if (onStack.has(w)) {
|
|
4968
|
+
lowlink.set(v, Math.min(lowlink.get(v), indices.get(w)));
|
|
4098
4969
|
}
|
|
4099
4970
|
}
|
|
4100
|
-
if (
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
const
|
|
4110
|
-
|
|
4111
|
-
if (!split.ok) {
|
|
4112
|
-
unresolved.push(relPath);
|
|
4113
|
-
continue;
|
|
4114
|
-
}
|
|
4115
|
-
const body = split.data.body;
|
|
4116
|
-
const rawFm = split.data.rawFrontmatter;
|
|
4117
|
-
const stripped = body.replace(FENCE_RE, "");
|
|
4118
|
-
const lines = stripped.split("\n");
|
|
4119
|
-
const inlineMarkers = [];
|
|
4120
|
-
let inSources = false;
|
|
4121
|
-
for (const line of lines) {
|
|
4122
|
-
if (/^## Sources\b/.test(line.trim())) {
|
|
4123
|
-
inSources = true;
|
|
4124
|
-
continue;
|
|
4125
|
-
}
|
|
4126
|
-
if (inSources) continue;
|
|
4127
|
-
for (const m of line.matchAll(INLINE_MARKER)) {
|
|
4128
|
-
inlineMarkers.push(m[0]);
|
|
4129
|
-
}
|
|
4130
|
-
}
|
|
4131
|
-
if (inlineMarkers.length === 0) {
|
|
4132
|
-
unresolved.push(relPath);
|
|
4133
|
-
continue;
|
|
4134
|
-
}
|
|
4135
|
-
const bodyLines = body.split("\n");
|
|
4136
|
-
let inSrc = false;
|
|
4137
|
-
const newBodyLines = [];
|
|
4138
|
-
for (const line of bodyLines) {
|
|
4139
|
-
if (/^## Sources\b/.test(line.trim())) {
|
|
4140
|
-
inSrc = true;
|
|
4141
|
-
newBodyLines.push(line);
|
|
4142
|
-
continue;
|
|
4143
|
-
}
|
|
4144
|
-
if (inSrc) {
|
|
4145
|
-
newBodyLines.push(line);
|
|
4146
|
-
continue;
|
|
4147
|
-
}
|
|
4148
|
-
INLINE_MARKER.lastIndex = 0;
|
|
4149
|
-
const lineWithoutMarkers = line.replace(INLINE_MARKER, "").trim();
|
|
4150
|
-
INLINE_MARKER.lastIndex = 0;
|
|
4151
|
-
if (lineWithoutMarkers.length === 0 && INLINE_MARKER.test(line)) {
|
|
4152
|
-
continue;
|
|
4153
|
-
}
|
|
4154
|
-
let cleaned = line;
|
|
4155
|
-
for (const marker of inlineMarkers) {
|
|
4156
|
-
const escapedMarker = marker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4157
|
-
const trailingRe = new RegExp(`([.!?]\\s*)${escapedMarker}`);
|
|
4158
|
-
if (trailingRe.test(cleaned)) {
|
|
4159
|
-
cleaned = cleaned.replace(trailingRe, "$1");
|
|
4160
|
-
}
|
|
4161
|
-
const midRe = new RegExp(`${escapedMarker}\\s*`);
|
|
4162
|
-
if (midRe.test(cleaned)) {
|
|
4163
|
-
cleaned = cleaned.replace(midRe, "");
|
|
4164
|
-
}
|
|
4165
|
-
}
|
|
4166
|
-
newBodyLines.push(cleaned);
|
|
4167
|
-
}
|
|
4168
|
-
let newBody = newBodyLines.join("\n");
|
|
4169
|
-
const dedupedMarkers = [...new Set(inlineMarkers)];
|
|
4170
|
-
if (inSrc) {
|
|
4171
|
-
const existingSources = new Set(
|
|
4172
|
-
body.split("\n").filter((l) => /^- \^\[raw\//.test(l.trim())).map((l) => l.trim().replace(/^- /, ""))
|
|
4173
|
-
);
|
|
4174
|
-
const newMarkers = dedupedMarkers.filter((m) => !existingSources.has(m));
|
|
4175
|
-
const sourceLines = newMarkers.map((m) => `- ${m}`);
|
|
4176
|
-
if (sourceLines.length > 0) {
|
|
4177
|
-
newBody = newBody.trimEnd() + "\n" + sourceLines.join("\n") + "\n";
|
|
4178
|
-
}
|
|
4179
|
-
} else {
|
|
4180
|
-
const sourceLines = dedupedMarkers.map((m) => `- ${m}`);
|
|
4181
|
-
newBody = newBody.trimEnd() + "\n\n## Sources\n\n" + sourceLines.join("\n") + "\n";
|
|
4971
|
+
if (lowlink.get(v) === indices.get(v)) {
|
|
4972
|
+
const scc = [];
|
|
4973
|
+
let node;
|
|
4974
|
+
do {
|
|
4975
|
+
node = stack.pop();
|
|
4976
|
+
onStack.delete(node);
|
|
4977
|
+
scc.push(node);
|
|
4978
|
+
} while (node !== v);
|
|
4979
|
+
if (scc.length > 1) {
|
|
4980
|
+
for (const member of scc) {
|
|
4981
|
+
cyclePages.add(member);
|
|
4182
4982
|
}
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
const w = await safeWritePage(absPath, newContent);
|
|
4188
|
-
if (!w.ok) {
|
|
4189
|
-
unresolved.push(relPath);
|
|
4190
|
-
continue;
|
|
4983
|
+
} else if (scc.length === 1) {
|
|
4984
|
+
const single = scc[0];
|
|
4985
|
+
if ((adj.get(single) ?? []).includes(single)) {
|
|
4986
|
+
cyclePages.add(single);
|
|
4191
4987
|
}
|
|
4192
|
-
fixed.push(relPath);
|
|
4193
|
-
} catch {
|
|
4194
|
-
unresolved.push(relPath);
|
|
4195
4988
|
}
|
|
4196
4989
|
}
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
else delete buckets.legacy_citation_style;
|
|
4990
|
+
}
|
|
4991
|
+
for (const p of pages) {
|
|
4992
|
+
if (!indices.has(p.relPath)) {
|
|
4993
|
+
strongConnect(p.relPath);
|
|
4202
4994
|
}
|
|
4203
4995
|
}
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4996
|
+
const buckets = {};
|
|
4997
|
+
if (cyclePages.size > 0) {
|
|
4998
|
+
buckets.cycle_traps = [...cyclePages].sort();
|
|
4999
|
+
}
|
|
5000
|
+
return { buckets };
|
|
5001
|
+
}
|
|
5002
|
+
};
|
|
5003
|
+
var LINT_RULES = [
|
|
5004
|
+
brokenWikilinksRule,
|
|
5005
|
+
tagNotInTaxonomyRule,
|
|
5006
|
+
indexIncompleteRule,
|
|
5007
|
+
indexLinkFormatRule,
|
|
5008
|
+
stalePageRule,
|
|
5009
|
+
pageTooLargeRule,
|
|
5010
|
+
logRotateNeededRule,
|
|
5011
|
+
orphansRule,
|
|
5012
|
+
sparseCommunityRule,
|
|
5013
|
+
topicMapRecommendedRule,
|
|
5014
|
+
rawDedupRule,
|
|
5015
|
+
rawBodyDuplicateRule,
|
|
5016
|
+
compoundRefsRule,
|
|
5017
|
+
pathTooLongRule,
|
|
5018
|
+
sensitiveContentRule,
|
|
5019
|
+
conflictMarkersRule,
|
|
5020
|
+
frontmatterYamlInvalidRule,
|
|
5021
|
+
rawSubdirectoryDuplicateRule,
|
|
5022
|
+
fileSourceUrlRule,
|
|
5023
|
+
legacyCitationStyleRule,
|
|
5024
|
+
orphanedCitationsRule,
|
|
5025
|
+
pageStructureRule,
|
|
5026
|
+
duplicateFrontmatterRule,
|
|
5027
|
+
missingOverviewRule,
|
|
5028
|
+
frontmatterWikilinkRule,
|
|
5029
|
+
wikilinkCitationRule,
|
|
5030
|
+
brokenSourcesRule,
|
|
5031
|
+
missingTldrRule,
|
|
5032
|
+
missingDiagramRule,
|
|
5033
|
+
workItemHealthRule,
|
|
5034
|
+
orphanedProjectPagesRule,
|
|
5035
|
+
cliRefsRule,
|
|
5036
|
+
staleSectionsRule,
|
|
5037
|
+
cycleTrapsRule
|
|
5038
|
+
];
|
|
4227
5039
|
|
|
4228
|
-
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
|
|
5040
|
+
// src/lint/runner.ts
|
|
5041
|
+
var LintRunner = class {
|
|
5042
|
+
rules;
|
|
5043
|
+
constructor(rules = LINT_RULES) {
|
|
5044
|
+
this.rules = rules;
|
|
5045
|
+
}
|
|
5046
|
+
getRegisteredRules() {
|
|
5047
|
+
return this.rules;
|
|
5048
|
+
}
|
|
5049
|
+
findRuleForBucket(bucketName) {
|
|
5050
|
+
return this.rules.find((r) => r.id === bucketName || r.producedBuckets?.includes(bucketName));
|
|
5051
|
+
}
|
|
5052
|
+
async run(input) {
|
|
5053
|
+
if (input.only && !KNOWN_BUCKETS.includes(input.only)) {
|
|
5054
|
+
return {
|
|
5055
|
+
exitCode: ExitCode.USAGE,
|
|
5056
|
+
result: {
|
|
5057
|
+
ok: false,
|
|
5058
|
+
error: "UNKNOWN_BUCKET",
|
|
5059
|
+
detail: `Unknown bucket "${input.only}". Valid: ${KNOWN_BUCKETS.join(", ")}`
|
|
4237
5060
|
}
|
|
5061
|
+
};
|
|
5062
|
+
}
|
|
5063
|
+
if (input.only) {
|
|
5064
|
+
const rule = this.findRuleForBucket(input.only);
|
|
5065
|
+
if (rule && rule.id === input.only && rule.runFastPath) {
|
|
5066
|
+
return rule.runFastPath(input);
|
|
4238
5067
|
}
|
|
4239
|
-
const fixedBeforeOverview = fixed.length;
|
|
4240
|
-
const fixedSet = new Set(fixed);
|
|
4241
|
-
const remaining = noOverview.filter((p) => !fixedSet.has(p));
|
|
4242
|
-
if (remaining.length > 0) buckets.missing_overview = remaining;
|
|
4243
|
-
else delete buckets.missing_overview;
|
|
4244
5068
|
}
|
|
4245
|
-
|
|
4246
|
-
|
|
5069
|
+
const shouldFix = (bucket) => !!input.fix && (!input.only || input.only === bucket);
|
|
5070
|
+
const readVault = lintReadVault(input);
|
|
5071
|
+
const lintVault = readVault.readPath;
|
|
5072
|
+
const buckets = {};
|
|
5073
|
+
const fixed = [];
|
|
5074
|
+
const unresolved = [];
|
|
5075
|
+
const scanResult = await scanVault(lintVault);
|
|
5076
|
+
if (!scanResult.ok) {
|
|
5077
|
+
return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scanResult };
|
|
5078
|
+
}
|
|
5079
|
+
const scan = scanResult.data;
|
|
5080
|
+
const pageTextCache = /* @__PURE__ */ new Map();
|
|
5081
|
+
if (!input.fix) {
|
|
5082
|
+
await mapWithConcurrency(scan.allMarkdown, vaultIoConcurrency(), async (page) => {
|
|
4247
5083
|
try {
|
|
4248
|
-
|
|
4249
|
-
const raw = await readFile13(absPath, "utf8");
|
|
4250
|
-
const split = splitFrontmatter(raw);
|
|
4251
|
-
if (!split.ok) {
|
|
4252
|
-
unresolved.push(relPath);
|
|
4253
|
-
continue;
|
|
4254
|
-
}
|
|
4255
|
-
const body = split.data.body;
|
|
4256
|
-
const rawFm = split.data.rawFrontmatter;
|
|
4257
|
-
const lines = body.split("\n");
|
|
4258
|
-
let insertIndex = 0;
|
|
4259
|
-
for (let i = 0; i < lines.length; i++) {
|
|
4260
|
-
if (/^# /.test(lines[i])) {
|
|
4261
|
-
insertIndex = i + 1;
|
|
4262
|
-
while (insertIndex < lines.length && lines[insertIndex].trim() === "") {
|
|
4263
|
-
insertIndex++;
|
|
4264
|
-
}
|
|
4265
|
-
break;
|
|
4266
|
-
}
|
|
4267
|
-
}
|
|
4268
|
-
if (insertIndex === 0) {
|
|
4269
|
-
lines.splice(0, 0, "", "> **TL;DR:** ");
|
|
4270
|
-
} else {
|
|
4271
|
-
lines.splice(insertIndex, 0, "> **TL;DR:** ");
|
|
4272
|
-
}
|
|
4273
|
-
const trimmedFm = rawFm.endsWith("\n") ? rawFm : rawFm + "\n";
|
|
4274
|
-
const newContent = `---
|
|
4275
|
-
${trimmedFm}---
|
|
4276
|
-
${lines.join("\n")}`;
|
|
4277
|
-
const w = await safeWritePage(absPath, newContent);
|
|
4278
|
-
if (!w.ok) {
|
|
4279
|
-
unresolved.push(relPath);
|
|
4280
|
-
continue;
|
|
4281
|
-
}
|
|
4282
|
-
fixed.push(relPath);
|
|
5084
|
+
await readPageCached(page, pageTextCache);
|
|
4283
5085
|
} catch {
|
|
4284
|
-
unresolved.push(relPath);
|
|
4285
5086
|
}
|
|
4286
|
-
}
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
const rawFm = split.data.rawFrontmatter;
|
|
4307
|
-
const stripped = body.replace(FENCE_RE, "");
|
|
4308
|
-
const wikilinkMatches = [...stripped.matchAll(WIKILINK_RE)];
|
|
4309
|
-
if (wikilinkMatches.length === 0) {
|
|
4310
|
-
unresolved.push(relPath);
|
|
4311
|
-
continue;
|
|
4312
|
-
}
|
|
4313
|
-
const wikilinkPaths = [...new Set(wikilinkMatches.map((m) => m[1]))];
|
|
4314
|
-
const bodyLines = body.split("\n");
|
|
4315
|
-
let inSrc = false;
|
|
4316
|
-
const newBodyLines = [];
|
|
4317
|
-
for (const line of bodyLines) {
|
|
4318
|
-
if (/^## Sources\b/.test(line.trim())) {
|
|
4319
|
-
inSrc = true;
|
|
4320
|
-
newBodyLines.push(line);
|
|
4321
|
-
continue;
|
|
4322
|
-
}
|
|
4323
|
-
if (inSrc) {
|
|
4324
|
-
newBodyLines.push(line);
|
|
4325
|
-
continue;
|
|
4326
|
-
}
|
|
4327
|
-
let cleaned = line.replace(/\[\[raw\/[^\]|]+(?:\|[^\]]*)?\]\]/g, "");
|
|
4328
|
-
cleaned = cleaned.replace(/\s+\./g, ".").replace(/\s{2,}/g, " ").replace(/\s+$/, "");
|
|
4329
|
-
if (cleaned.length > 0 || line.trim().length === 0) {
|
|
4330
|
-
newBodyLines.push(cleaned);
|
|
4331
|
-
}
|
|
4332
|
-
}
|
|
4333
|
-
let newBody = newBodyLines.join("\n");
|
|
4334
|
-
const citationMarkers = wikilinkPaths.map((p) => `^[raw/${p}]`);
|
|
4335
|
-
const sourceEntries = extractSourceEntries(rawFm);
|
|
4336
|
-
const fmMarkers = [];
|
|
4337
|
-
for (const entry of sourceEntries) {
|
|
4338
|
-
let rawPath = entry.replace(/^"/, "").replace(/"$/, "").replace(/^'/, "").replace(/'$/, "");
|
|
4339
|
-
rawPath = rawPath.replace(/^\^\[/, "").replace(/\]$/, "");
|
|
4340
|
-
if (rawPath.startsWith("raw/")) {
|
|
4341
|
-
fmMarkers.push(`^[${rawPath}]`);
|
|
4342
|
-
}
|
|
4343
|
-
}
|
|
4344
|
-
const allMarkers = [.../* @__PURE__ */ new Set([...citationMarkers, ...fmMarkers])];
|
|
4345
|
-
const hasSourcesSection = /^## Sources\b/m.test(newBody);
|
|
4346
|
-
if (hasSourcesSection) {
|
|
4347
|
-
const existingSources = new Set(
|
|
4348
|
-
newBody.split("\n").filter((l) => /^- \^\[raw\//.test(l.trim())).map((l) => l.trim().replace(/^- /, ""))
|
|
4349
|
-
);
|
|
4350
|
-
const newMarkers = allMarkers.filter((m) => !existingSources.has(m));
|
|
4351
|
-
const sourceLines = newMarkers.map((m) => `- ${m}`);
|
|
4352
|
-
if (sourceLines.length > 0) {
|
|
4353
|
-
newBody = newBody.trimEnd() + "\n" + sourceLines.join("\n") + "\n";
|
|
4354
|
-
}
|
|
5087
|
+
});
|
|
5088
|
+
}
|
|
5089
|
+
const wikilinkResolver = buildWikilinkResolver(scan.allMarkdown);
|
|
5090
|
+
const cliSurface = buildCliSurface();
|
|
5091
|
+
const ctx = {
|
|
5092
|
+
vault: lintVault,
|
|
5093
|
+
scan,
|
|
5094
|
+
pageTextCache,
|
|
5095
|
+
days: input.days,
|
|
5096
|
+
lines: input.lines,
|
|
5097
|
+
logThreshold: input.logThreshold,
|
|
5098
|
+
wikilinkResolver,
|
|
5099
|
+
cliSurface
|
|
5100
|
+
};
|
|
5101
|
+
for (const rule of this.rules) {
|
|
5102
|
+
const ruleRes = await rule.run(ctx);
|
|
5103
|
+
for (const [kind, items] of Object.entries(ruleRes.buckets)) {
|
|
5104
|
+
if (items && items.length > 0) {
|
|
5105
|
+
if (buckets[kind]) {
|
|
5106
|
+
buckets[kind] = [...buckets[kind], ...items];
|
|
4355
5107
|
} else {
|
|
4356
|
-
|
|
4357
|
-
newBody = newBody.trimEnd() + "\n\n## Sources\n\n" + sourceLines.join("\n") + "\n";
|
|
4358
|
-
}
|
|
4359
|
-
const newContent = `---
|
|
4360
|
-
${rawFm}
|
|
4361
|
-
---
|
|
4362
|
-
${newBody}`;
|
|
4363
|
-
const w = await safeWritePage(absPath, newContent);
|
|
4364
|
-
if (!w.ok) {
|
|
4365
|
-
unresolved.push(relPath);
|
|
4366
|
-
continue;
|
|
5108
|
+
buckets[kind] = items;
|
|
4367
5109
|
}
|
|
4368
|
-
wikilinkFixed.push(relPath);
|
|
4369
|
-
} catch {
|
|
4370
|
-
unresolved.push(relPath);
|
|
4371
|
-
}
|
|
4372
|
-
}
|
|
4373
|
-
fixed.push(...wikilinkFixed);
|
|
4374
|
-
if (wikilinkFixed.length > 0) {
|
|
4375
|
-
const fixedSet = new Set(wikilinkFixed);
|
|
4376
|
-
const remaining = wikilinkCitationFlags.filter((p) => !fixedSet.has(p));
|
|
4377
|
-
if (remaining.length > 0) buckets.wikilink_citation = remaining;
|
|
4378
|
-
else delete buckets.wikilink_citation;
|
|
4379
|
-
}
|
|
4380
|
-
}
|
|
4381
|
-
if (shouldFix("file_source_url") && fileSourceUrlFrontmatterFlags.size > 0) {
|
|
4382
|
-
fileSourceUrlFlags = await applyFileSourceUrlFix(input, scan, fileSourceUrlFlags, fileSourceUrlFrontmatterFlags, fixed, unresolved);
|
|
4383
|
-
if (fileSourceUrlFlags.size > 0) buckets.file_source_url = [...fileSourceUrlFlags];
|
|
4384
|
-
else delete buckets.file_source_url;
|
|
4385
|
-
}
|
|
4386
|
-
const pathViolations = buckets.path_too_long;
|
|
4387
|
-
if (shouldFix("path_too_long") && pathViolations && pathViolations.length > 0) {
|
|
4388
|
-
const pathFix = await fixPathTooLong({ vault: input.vault });
|
|
4389
|
-
const pathFixed = pathFix.result.ok ? pathFix.result.data.fixed.map((f) => f.from) : [];
|
|
4390
|
-
if (pathFix.result.ok) unresolved.push(...pathFix.result.data.unresolved);
|
|
4391
|
-
else unresolved.push(...pathViolations.map((v) => v.relPath));
|
|
4392
|
-
fixed.push(...pathFixed);
|
|
4393
|
-
if (pathFixed.length > 0) {
|
|
4394
|
-
const fixedSet = new Set(pathFixed);
|
|
4395
|
-
const remaining = pathViolations.filter((v) => !fixedSet.has(v.relPath));
|
|
4396
|
-
if (remaining.length > 0) buckets.path_too_long = remaining;
|
|
4397
|
-
else delete buckets.path_too_long;
|
|
4398
|
-
}
|
|
4399
|
-
if (buckets.path_too_long) {
|
|
4400
|
-
const rawRemaining = buckets.path_too_long.filter((v) => v.relPath.startsWith("raw/"));
|
|
4401
|
-
if (rawRemaining.length > 0) {
|
|
4402
|
-
unresolved.push(...rawRemaining.map((v) => v.relPath));
|
|
4403
|
-
const nonRaw = buckets.path_too_long.filter((v) => !v.relPath.startsWith("raw/"));
|
|
4404
|
-
if (nonRaw.length > 0) buckets.path_too_long = nonRaw;
|
|
4405
|
-
else delete buckets.path_too_long;
|
|
4406
5110
|
}
|
|
4407
5111
|
}
|
|
4408
5112
|
}
|
|
4409
|
-
if (
|
|
4410
|
-
const
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
unresolved.push(item.path);
|
|
4429
|
-
remaining.push(item);
|
|
4430
|
-
continue;
|
|
4431
|
-
}
|
|
4432
|
-
const newFm = fixFrontmatter(split.data.rawFrontmatter);
|
|
4433
|
-
const newText = `---
|
|
4434
|
-
${newFm}
|
|
4435
|
-
---
|
|
4436
|
-
${split.data.body}`;
|
|
4437
|
-
const recheck = extractFrontmatter(newText);
|
|
4438
|
-
if (!recheck.ok) {
|
|
4439
|
-
unresolved.push(item.path);
|
|
4440
|
-
remaining.push(item);
|
|
4441
|
-
continue;
|
|
4442
|
-
}
|
|
4443
|
-
const w = await safeWritePage(page.absPath, newText, { minBodyRatio: null });
|
|
4444
|
-
if (!w.ok) {
|
|
4445
|
-
unresolved.push(item.path);
|
|
4446
|
-
remaining.push(item);
|
|
4447
|
-
continue;
|
|
5113
|
+
if (input.fix) {
|
|
5114
|
+
const fixCtx = {
|
|
5115
|
+
vault: lintVault,
|
|
5116
|
+
scan,
|
|
5117
|
+
pageTextCache,
|
|
5118
|
+
input,
|
|
5119
|
+
fixed,
|
|
5120
|
+
unresolved
|
|
5121
|
+
};
|
|
5122
|
+
for (const rule of this.rules) {
|
|
5123
|
+
const produced = rule.producedBuckets ?? [rule.id];
|
|
5124
|
+
for (const bucketName of produced) {
|
|
5125
|
+
if (shouldFix(bucketName) && buckets[bucketName] && rule.fix) {
|
|
5126
|
+
const remaining = await rule.fix(fixCtx, buckets[bucketName]);
|
|
5127
|
+
if (remaining && remaining.length > 0) {
|
|
5128
|
+
buckets[bucketName] = remaining;
|
|
5129
|
+
} else {
|
|
5130
|
+
delete buckets[bucketName];
|
|
5131
|
+
}
|
|
4448
5132
|
}
|
|
4449
|
-
fixed.push(item.path);
|
|
4450
|
-
} catch {
|
|
4451
|
-
unresolved.push(item.path);
|
|
4452
|
-
remaining.push(item);
|
|
4453
5133
|
}
|
|
4454
5134
|
}
|
|
4455
|
-
if (remaining.length > 0) buckets.frontmatter_yaml_invalid = remaining;
|
|
4456
|
-
else delete buckets.frontmatter_yaml_invalid;
|
|
4457
|
-
}
|
|
4458
|
-
}
|
|
4459
|
-
const errorOut = ERROR_ORDER.flatMap((k) => buckets[k] ? [{ kind: k, items: buckets[k] }] : []);
|
|
4460
|
-
const warningOut = WARNING_ORDER.flatMap((k) => buckets[k] ? [{ kind: k, items: buckets[k] }] : []);
|
|
4461
|
-
const infoOut = INFO_ORDER.flatMap((k) => buckets[k] ? [{ kind: k, items: buckets[k] }] : []);
|
|
4462
|
-
if (input.only) {
|
|
4463
|
-
const match = [...errorOut, ...warningOut, ...infoOut].filter((b) => b.kind === input.only);
|
|
4464
|
-
return outputForOnlyBucket(input, match, fixed, unresolved, readVault);
|
|
4465
|
-
}
|
|
4466
|
-
const summary = {
|
|
4467
|
-
errors: errorOut.reduce((n, b) => n + b.items.length, 0),
|
|
4468
|
-
warnings: warningOut.reduce((n, b) => n + b.items.length, 0),
|
|
4469
|
-
info: infoOut.reduce((n, b) => n + b.items.length, 0)
|
|
4470
|
-
};
|
|
4471
|
-
let exitCode = ExitCode.OK;
|
|
4472
|
-
if (summary.errors > 0) exitCode = ExitCode.LINT_HAS_ERRORS;
|
|
4473
|
-
else if (summary.warnings > 0 || summary.info > 0) exitCode = ExitCode.LINT_HAS_WARNINGS;
|
|
4474
|
-
const vault = lintVaultOutput(input, readVault);
|
|
4475
|
-
const hintLines = [];
|
|
4476
|
-
hintLines.push(...readMirrorHintLines(vault));
|
|
4477
|
-
if (summary.errors > 0) hintLines.push(`errors: ${summary.errors}`);
|
|
4478
|
-
if (summary.warnings > 0) hintLines.push(`warnings: ${summary.warnings}`);
|
|
4479
|
-
if (summary.info > 0) hintLines.push(`info: ${summary.info}`);
|
|
4480
|
-
const allBuckets = [...errorOut, ...warningOut, ...infoOut];
|
|
4481
|
-
for (const b of allBuckets) {
|
|
4482
|
-
hintLines.push(` ${b.kind}: ${b.items.length}`);
|
|
4483
|
-
}
|
|
4484
|
-
if (hintLines.length === 0) hintLines.push("0 errors, 0 warnings, 0 info");
|
|
4485
|
-
if (input.fix) appendLintFixLastOp(input.vault, fixed);
|
|
4486
|
-
const output = {
|
|
4487
|
-
vault,
|
|
4488
|
-
summary,
|
|
4489
|
-
by_severity: { error: errorOut, warning: warningOut, info: infoOut },
|
|
4490
|
-
fixed,
|
|
4491
|
-
unresolved,
|
|
4492
|
-
humanHint: hintLines.join("\n")
|
|
4493
|
-
};
|
|
4494
|
-
return {
|
|
4495
|
-
exitCode,
|
|
4496
|
-
result: ok(input.summary ? summarizeLintOutput(output, input.examplesLimit) : output)
|
|
4497
|
-
};
|
|
4498
|
-
}
|
|
4499
|
-
function lintIssueFingerprint(bucket, item) {
|
|
4500
|
-
const page = extractIssuePage(item);
|
|
4501
|
-
const detail = normalizeIssueDetail(item);
|
|
4502
|
-
return `${bucket}\0${page}\0${detail}`;
|
|
4503
|
-
}
|
|
4504
|
-
function extractIssuePage(item) {
|
|
4505
|
-
if (typeof item === "string") {
|
|
4506
|
-
const m = item.match(/^([^:]+?)(?::\s|$)/);
|
|
4507
|
-
return (m?.[1] ?? item).trim();
|
|
4508
|
-
}
|
|
4509
|
-
if (item && typeof item === "object") {
|
|
4510
|
-
const obj = item;
|
|
4511
|
-
for (const key of ["path", "file", "page", "relPath"]) {
|
|
4512
|
-
if (typeof obj[key] === "string") return obj[key];
|
|
4513
|
-
}
|
|
4514
|
-
}
|
|
4515
|
-
return "";
|
|
4516
|
-
}
|
|
4517
|
-
function normalizeIssueDetail(item) {
|
|
4518
|
-
if (typeof item === "string") {
|
|
4519
|
-
return item.replace(/\s+/g, " ").trim();
|
|
4520
|
-
}
|
|
4521
|
-
try {
|
|
4522
|
-
return JSON.stringify(item, Object.keys(item).sort());
|
|
4523
|
-
} catch {
|
|
4524
|
-
return String(item);
|
|
4525
|
-
}
|
|
4526
|
-
}
|
|
4527
|
-
function collectLintErrorFingerprints(output) {
|
|
4528
|
-
const fps = /* @__PURE__ */ new Set();
|
|
4529
|
-
for (const bucket of output.by_severity.error) {
|
|
4530
|
-
for (const item of bucket.items) {
|
|
4531
|
-
fps.add(lintIssueFingerprint(bucket.kind, item));
|
|
4532
5135
|
}
|
|
5136
|
+
const errorOut = ERROR_ORDER.flatMap((k) => buckets[k] ? [{ kind: k, items: buckets[k] }] : []);
|
|
5137
|
+
const warningOut = WARNING_ORDER.flatMap(
|
|
5138
|
+
(k) => buckets[k] ? [{ kind: k, items: buckets[k] }] : []
|
|
5139
|
+
);
|
|
5140
|
+
const infoOut = INFO_ORDER.flatMap((k) => buckets[k] ? [{ kind: k, items: buckets[k] }] : []);
|
|
5141
|
+
if (input.only) {
|
|
5142
|
+
const match = [...errorOut, ...warningOut, ...infoOut].filter((b) => b.kind === input.only);
|
|
5143
|
+
const out = outputForOnlyBucket(input, match, fixed, unresolved, readVault);
|
|
5144
|
+
if (input.fix) appendLintFixLastOp(input.vault, fixed);
|
|
5145
|
+
return out;
|
|
5146
|
+
}
|
|
5147
|
+
const summary = {
|
|
5148
|
+
errors: errorOut.reduce((n, b) => n + b.items.length, 0),
|
|
5149
|
+
warnings: warningOut.reduce((n, b) => n + b.items.length, 0),
|
|
5150
|
+
info: infoOut.reduce((n, b) => n + b.items.length, 0)
|
|
5151
|
+
};
|
|
5152
|
+
let exitCode = ExitCode.OK;
|
|
5153
|
+
if (summary.errors > 0) exitCode = ExitCode.LINT_HAS_ERRORS;
|
|
5154
|
+
else if (summary.warnings > 0 || summary.info > 0) exitCode = ExitCode.LINT_HAS_WARNINGS;
|
|
5155
|
+
const vault = lintVaultOutput(input, readVault);
|
|
5156
|
+
const hintLines = [];
|
|
5157
|
+
hintLines.push(...readMirrorHintLines(vault));
|
|
5158
|
+
if (summary.errors > 0) hintLines.push(`errors: ${summary.errors}`);
|
|
5159
|
+
if (summary.warnings > 0) hintLines.push(`warnings: ${summary.warnings}`);
|
|
5160
|
+
if (summary.info > 0) hintLines.push(`info: ${summary.info}`);
|
|
5161
|
+
const allBuckets = [...errorOut, ...warningOut, ...infoOut];
|
|
5162
|
+
for (const b of allBuckets) {
|
|
5163
|
+
hintLines.push(` ${b.kind}: ${b.items.length}`);
|
|
5164
|
+
}
|
|
5165
|
+
if (hintLines.length === 0) hintLines.push("0 errors, 0 warnings, 0 info");
|
|
5166
|
+
if (input.fix) appendLintFixLastOp(input.vault, fixed);
|
|
5167
|
+
const output = {
|
|
5168
|
+
vault,
|
|
5169
|
+
summary,
|
|
5170
|
+
by_severity: { error: errorOut, warning: warningOut, info: infoOut },
|
|
5171
|
+
fixed,
|
|
5172
|
+
unresolved,
|
|
5173
|
+
humanHint: hintLines.join("\n")
|
|
5174
|
+
};
|
|
5175
|
+
return {
|
|
5176
|
+
exitCode,
|
|
5177
|
+
result: ok(input.summary ? summarizeLintOutput(output, input.examplesLimit) : output)
|
|
5178
|
+
};
|
|
4533
5179
|
}
|
|
4534
|
-
|
|
5180
|
+
};
|
|
5181
|
+
var defaultLintRunner = new LintRunner();
|
|
5182
|
+
function runLint(input) {
|
|
5183
|
+
return defaultLintRunner.run(input);
|
|
4535
5184
|
}
|
|
4536
5185
|
async function runSyncLintDelta(input) {
|
|
4537
5186
|
const { mkdtempSync, rmSync, existsSync: fsExists } = await import("fs");
|
|
@@ -4665,7 +5314,7 @@ function gitStrict(cwd, args) {
|
|
|
4665
5314
|
|
|
4666
5315
|
// src/utils/sync-lock.ts
|
|
4667
5316
|
import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync3, renameSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
4668
|
-
import { join as
|
|
5317
|
+
import { join as join14 } from "path";
|
|
4669
5318
|
import { createHash as createHash5, randomBytes } from "crypto";
|
|
4670
5319
|
function getEnvSessionId() {
|
|
4671
5320
|
if (process.env.CLAUDE_SESSION_ID) return process.env.CLAUDE_SESSION_ID;
|
|
@@ -4688,7 +5337,7 @@ function getCliSessionId(cwd) {
|
|
|
4688
5337
|
return `cli-${getCwdHash(cwd)}`;
|
|
4689
5338
|
}
|
|
4690
5339
|
function lockPath(vault) {
|
|
4691
|
-
return
|
|
5340
|
+
return join14(vault, ".skillwiki", "sync.lock");
|
|
4692
5341
|
}
|
|
4693
5342
|
function readLock(vault) {
|
|
4694
5343
|
const path = lockPath(vault);
|
|
@@ -4707,7 +5356,7 @@ function isStale(lock, now) {
|
|
|
4707
5356
|
}
|
|
4708
5357
|
function acquireLock(vault, opts = {}) {
|
|
4709
5358
|
const path = lockPath(vault);
|
|
4710
|
-
const dir =
|
|
5359
|
+
const dir = join14(vault, ".skillwiki");
|
|
4711
5360
|
if (!existsSync5(dir)) {
|
|
4712
5361
|
mkdirSync3(dir, { recursive: true });
|
|
4713
5362
|
}
|
|
@@ -4792,7 +5441,7 @@ function acquireOwnedSyncLock(vault, opts) {
|
|
|
4792
5441
|
};
|
|
4793
5442
|
const path = lockPath(vault);
|
|
4794
5443
|
try {
|
|
4795
|
-
mkdirSync3(
|
|
5444
|
+
mkdirSync3(join14(vault, ".skillwiki"), { recursive: true });
|
|
4796
5445
|
} catch (error) {
|
|
4797
5446
|
return err("WRITE_FAILED", { path, message: String(error) });
|
|
4798
5447
|
}
|
|
@@ -4844,7 +5493,7 @@ function stageVaultContentChanges(vault) {
|
|
|
4844
5493
|
|
|
4845
5494
|
// src/utils/remote-health.ts
|
|
4846
5495
|
import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
|
|
4847
|
-
import { join as
|
|
5496
|
+
import { join as join15 } from "path";
|
|
4848
5497
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
4849
5498
|
var REMOTE_PROBE_TIMEOUT_MS = 3e3;
|
|
4850
5499
|
var defaultExec = (file, args, cwd) => execFileSync2(file, args, {
|
|
@@ -4855,7 +5504,7 @@ var defaultExec = (file, args, cwd) => execFileSync2(file, args, {
|
|
|
4855
5504
|
}).trim();
|
|
4856
5505
|
function readWikiS3RemoteConfigured(home) {
|
|
4857
5506
|
try {
|
|
4858
|
-
const content = readFileSync4(
|
|
5507
|
+
const content = readFileSync4(join15(home, ".skillwiki", ".env"), "utf8");
|
|
4859
5508
|
for (const line of content.split(/\r?\n/)) {
|
|
4860
5509
|
const trimmed = line.trim();
|
|
4861
5510
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
@@ -4880,7 +5529,7 @@ function resolveWikiS3Remote(input) {
|
|
|
4880
5529
|
return readWikiS3RemoteConfigured(input.home);
|
|
4881
5530
|
}
|
|
4882
5531
|
function probeGithubReachability(vaultPath, exec = defaultExec) {
|
|
4883
|
-
if (!existsSync6(
|
|
5532
|
+
if (!existsSync6(join15(vaultPath, ".git"))) return "unknown";
|
|
4884
5533
|
try {
|
|
4885
5534
|
exec("git", ["remote", "get-url", "origin"], vaultPath);
|
|
4886
5535
|
} catch {
|
|
@@ -4948,7 +5597,7 @@ function probeRemoteHealth(input) {
|
|
|
4948
5597
|
import { spawnSync } from "child_process";
|
|
4949
5598
|
import { existsSync as existsSync7 } from "fs";
|
|
4950
5599
|
import { homedir, platform } from "os";
|
|
4951
|
-
import { dirname as dirname6, join as
|
|
5600
|
+
import { dirname as dirname6, join as join16 } from "path";
|
|
4952
5601
|
import { fileURLToPath } from "url";
|
|
4953
5602
|
var HELPER_NAME = "wiki-pull-with-auto-resolve.sh";
|
|
4954
5603
|
function candidateHelperPaths(input = { vault: "" }) {
|
|
@@ -4965,10 +5614,10 @@ function candidateHelperPaths(input = { vault: "" }) {
|
|
|
4965
5614
|
}
|
|
4966
5615
|
}
|
|
4967
5616
|
if (here) {
|
|
4968
|
-
paths.push(
|
|
4969
|
-
paths.push(
|
|
4970
|
-
paths.push(
|
|
4971
|
-
paths.push(
|
|
5617
|
+
paths.push(join16(here, "vault-sync", "scripts", HELPER_NAME));
|
|
5618
|
+
paths.push(join16(here, "..", "vault-sync", "scripts", HELPER_NAME));
|
|
5619
|
+
paths.push(join16(here, "..", "..", "vault-sync", "scripts", HELPER_NAME));
|
|
5620
|
+
paths.push(join16(here, "..", "..", "..", "vault-sync", "scripts", HELPER_NAME));
|
|
4972
5621
|
}
|
|
4973
5622
|
const home = input.home ?? env.HOME ?? env.USERPROFILE ?? (() => {
|
|
4974
5623
|
try {
|
|
@@ -4981,11 +5630,11 @@ function candidateHelperPaths(input = { vault: "" }) {
|
|
|
4981
5630
|
const xdg = env.XDG_DATA_HOME;
|
|
4982
5631
|
const isDarwin = platform() === "darwin";
|
|
4983
5632
|
if (isDarwin) {
|
|
4984
|
-
paths.push(
|
|
5633
|
+
paths.push(join16(home, "Library", "Application Support", "vault-sync", "bin", HELPER_NAME));
|
|
4985
5634
|
}
|
|
4986
|
-
paths.push(
|
|
5635
|
+
paths.push(join16(xdg || join16(home, ".local", "share"), "vault-sync", "bin", HELPER_NAME));
|
|
4987
5636
|
if (!isDarwin) {
|
|
4988
|
-
paths.push(
|
|
5637
|
+
paths.push(join16(home, "Library", "Application Support", "vault-sync", "bin", HELPER_NAME));
|
|
4989
5638
|
}
|
|
4990
5639
|
}
|
|
4991
5640
|
return paths;
|
|
@@ -5079,7 +5728,7 @@ function refHasPath(vault, ref, path) {
|
|
|
5079
5728
|
function runSyncStatus(input) {
|
|
5080
5729
|
const vault = input.vault;
|
|
5081
5730
|
const includeStashes = input.includeStashes ?? false;
|
|
5082
|
-
if (!existsSync8(
|
|
5731
|
+
if (!existsSync8(join17(vault, ".git"))) {
|
|
5083
5732
|
return {
|
|
5084
5733
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
5085
5734
|
result: ok({
|
|
@@ -5186,7 +5835,7 @@ function runSyncStatus(input) {
|
|
|
5186
5835
|
}
|
|
5187
5836
|
async function runSyncPush(input) {
|
|
5188
5837
|
const vault = input.vault;
|
|
5189
|
-
if (!existsSync8(
|
|
5838
|
+
if (!existsSync8(join17(vault, ".git"))) {
|
|
5190
5839
|
return {
|
|
5191
5840
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
5192
5841
|
result: err("NOT_A_GIT_REPO", { path: vault })
|
|
@@ -5346,7 +5995,7 @@ function enableGitLongPathsOnWindows(vault) {
|
|
|
5346
5995
|
}
|
|
5347
5996
|
async function runSyncPull(input) {
|
|
5348
5997
|
const vault = input.vault;
|
|
5349
|
-
if (!existsSync8(
|
|
5998
|
+
if (!existsSync8(join17(vault, ".git"))) {
|
|
5350
5999
|
return {
|
|
5351
6000
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
5352
6001
|
result: err("NOT_A_GIT_REPO", { path: vault })
|
|
@@ -5643,11 +6292,11 @@ import {
|
|
|
5643
6292
|
writeFileSync as writeFileSync4
|
|
5644
6293
|
} from "fs";
|
|
5645
6294
|
import { hostname } from "os";
|
|
5646
|
-
import { dirname as dirname7, join as
|
|
6295
|
+
import { dirname as dirname7, join as join18, resolve as resolve4 } from "path";
|
|
5647
6296
|
function managedWriteLockPath(vault) {
|
|
5648
6297
|
const gitPath = git(vault, ["rev-parse", "--git-path", "vault-sync/managed-write.lock"]);
|
|
5649
|
-
if (gitPath) return gitPath.startsWith("/") ? gitPath :
|
|
5650
|
-
return
|
|
6298
|
+
if (gitPath) return gitPath.startsWith("/") ? gitPath : join18(vault, gitPath);
|
|
6299
|
+
return join18(vault, ".skillwiki", "managed-write.lock");
|
|
5651
6300
|
}
|
|
5652
6301
|
function readLockRecord(path) {
|
|
5653
6302
|
try {
|
|
@@ -5670,12 +6319,12 @@ function hasUnsafeGitState(vault) {
|
|
|
5670
6319
|
if (!isGitBackedVault(vault)) return false;
|
|
5671
6320
|
const gitDirRaw = git(vault, ["rev-parse", "--git-dir"]);
|
|
5672
6321
|
if (!gitDirRaw) return true;
|
|
5673
|
-
const gitDir = gitDirRaw.startsWith("/") ? gitDirRaw :
|
|
6322
|
+
const gitDir = gitDirRaw.startsWith("/") ? gitDirRaw : join18(vault, gitDirRaw);
|
|
5674
6323
|
for (const rel of ["rebase-merge", "rebase-apply"]) {
|
|
5675
|
-
if (existsSync9(
|
|
6324
|
+
if (existsSync9(join18(gitDir, rel))) return true;
|
|
5676
6325
|
}
|
|
5677
6326
|
for (const rel of ["MERGE_HEAD", "CHERRY_PICK_HEAD", "REVERT_HEAD"]) {
|
|
5678
|
-
if (existsSync9(
|
|
6327
|
+
if (existsSync9(join18(gitDir, rel))) return true;
|
|
5679
6328
|
}
|
|
5680
6329
|
const unmerged = git(vault, ["ls-files", "-u"]);
|
|
5681
6330
|
return Boolean(unmerged && unmerged.trim().length > 0);
|
|
@@ -5713,10 +6362,10 @@ function reclaimDeadManagedWriteLockOwner(vault, options = {}) {
|
|
|
5713
6362
|
});
|
|
5714
6363
|
}
|
|
5715
6364
|
try {
|
|
5716
|
-
const recoveryDir =
|
|
6365
|
+
const recoveryDir = join18(dirname7(path), "recovery");
|
|
5717
6366
|
mkdirSync4(recoveryDir, { recursive: true });
|
|
5718
6367
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:]/g, "").replace(/\.\d+Z$/, "Z");
|
|
5719
|
-
const recoveryPath =
|
|
6368
|
+
const recoveryPath = join18(recoveryDir, `stale-managed-write-lock-${stamp}-${process.pid}.json`);
|
|
5720
6369
|
const meta = {
|
|
5721
6370
|
recovered_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5722
6371
|
recovery_reason: "owner_pid_dead",
|
|
@@ -5795,11 +6444,11 @@ import {
|
|
|
5795
6444
|
renameSync as renameSync2,
|
|
5796
6445
|
writeFileSync as writeFileSync5
|
|
5797
6446
|
} from "fs";
|
|
5798
|
-
import { join as
|
|
6447
|
+
import { join as join19 } from "path";
|
|
5799
6448
|
function journalDir(vault) {
|
|
5800
6449
|
const gitPath = git(vault, ["rev-parse", "--git-path", "vault-sync/operations"]);
|
|
5801
6450
|
if (!gitPath) return null;
|
|
5802
|
-
return gitPath.startsWith("/") ? gitPath :
|
|
6451
|
+
return gitPath.startsWith("/") ? gitPath : join19(vault, gitPath);
|
|
5803
6452
|
}
|
|
5804
6453
|
function parseJournalEnv(text) {
|
|
5805
6454
|
return Object.fromEntries(
|
|
@@ -5823,7 +6472,7 @@ function serializeJournalEnv(fields, preferredOrder = []) {
|
|
|
5823
6472
|
function readJournal(vault, opId) {
|
|
5824
6473
|
const dir = journalDir(vault);
|
|
5825
6474
|
if (!dir) return null;
|
|
5826
|
-
const path =
|
|
6475
|
+
const path = join19(dir, `${opId}.env`);
|
|
5827
6476
|
if (!existsSync10(path)) return null;
|
|
5828
6477
|
try {
|
|
5829
6478
|
return parseJournalEnv(readFileSync6(path, "utf8"));
|
|
@@ -5836,7 +6485,7 @@ function writeJournal(vault, opId, fields) {
|
|
|
5836
6485
|
if (!dir) return false;
|
|
5837
6486
|
try {
|
|
5838
6487
|
mkdirSync5(dir, { recursive: true });
|
|
5839
|
-
const path =
|
|
6488
|
+
const path = join19(dir, `${opId}.env`);
|
|
5840
6489
|
const tmp = `${path}.tmp.${process.pid}`;
|
|
5841
6490
|
const order = [
|
|
5842
6491
|
"operation_id",
|
|
@@ -5902,9 +6551,9 @@ function hasActiveGitSequencer(vault) {
|
|
|
5902
6551
|
const gitDir = git(vault, ["rev-parse", "--absolute-git-dir"]);
|
|
5903
6552
|
if (!gitDir) return false;
|
|
5904
6553
|
for (const m of ["MERGE_HEAD", "CHERRY_PICK_HEAD", "REVERT_HEAD"]) {
|
|
5905
|
-
if (existsSync10(
|
|
6554
|
+
if (existsSync10(join19(gitDir, m))) return true;
|
|
5906
6555
|
}
|
|
5907
|
-
if (existsSync10(
|
|
6556
|
+
if (existsSync10(join19(gitDir, "rebase-merge")) || existsSync10(join19(gitDir, "rebase-apply"))) {
|
|
5908
6557
|
return true;
|
|
5909
6558
|
}
|
|
5910
6559
|
return false;
|
|
@@ -5975,7 +6624,7 @@ function supersedeStaleReviewRequiredJournals(vault, opts = {}) {
|
|
|
5975
6624
|
|
|
5976
6625
|
// src/utils/snapshot-worktree.ts
|
|
5977
6626
|
import { readFileSync as readFileSync7 } from "fs";
|
|
5978
|
-
import { join as
|
|
6627
|
+
import { join as join20, resolve as resolve5 } from "path";
|
|
5979
6628
|
function readSkillWikiConfig(path) {
|
|
5980
6629
|
try {
|
|
5981
6630
|
return parseDotenvText(readFileSync7(path, "utf8"));
|
|
@@ -6002,7 +6651,7 @@ function readSnapshotProfile(path) {
|
|
|
6002
6651
|
}
|
|
6003
6652
|
function resolveConfiguredSnapshotWorktree(home) {
|
|
6004
6653
|
if (!home) return void 0;
|
|
6005
|
-
const config = readSkillWikiConfig(
|
|
6654
|
+
const config = readSkillWikiConfig(join20(home, ".skillwiki", ".env"));
|
|
6006
6655
|
const explicit = config["vault_sync.snapshot_worktree"];
|
|
6007
6656
|
if (explicit) return resolve5(explicit);
|
|
6008
6657
|
const snapshotProfile = config["vault_sync.snapshot_profile"];
|
|
@@ -6154,7 +6803,7 @@ function preflightBlocker(vault) {
|
|
|
6154
6803
|
return null;
|
|
6155
6804
|
}
|
|
6156
6805
|
function hasFleetManifest(vault) {
|
|
6157
|
-
return existsSync11(
|
|
6806
|
+
return existsSync11(join21(vault, FLEET_REL_PATH));
|
|
6158
6807
|
}
|
|
6159
6808
|
function isGitVault(vault) {
|
|
6160
6809
|
return Boolean(git(vault, ["rev-parse", "--absolute-git-dir"]));
|
|
@@ -6421,35 +7070,21 @@ export {
|
|
|
6421
7070
|
getCliSessionId,
|
|
6422
7071
|
acquireOwnedSyncLock,
|
|
6423
7072
|
releaseOwnedSyncLock,
|
|
7073
|
+
extractBodyWikilinks,
|
|
7074
|
+
buildWikilinkResolver,
|
|
6424
7075
|
buildWikilinkAdjacency,
|
|
6425
7076
|
toUndirectedWeighted,
|
|
6426
7077
|
louvain,
|
|
6427
7078
|
communityCohesion,
|
|
6428
|
-
|
|
6429
|
-
|
|
6430
|
-
profileKey,
|
|
6431
|
-
parseDotenvText,
|
|
6432
|
-
parseDotenvFile,
|
|
6433
|
-
writeDotenv,
|
|
6434
|
-
resolveInitTimePath,
|
|
6435
|
-
resolveRuntimePath,
|
|
6436
|
-
runOrphans,
|
|
6437
|
-
runAudit,
|
|
7079
|
+
extractIssuePage,
|
|
7080
|
+
runLinks,
|
|
6438
7081
|
extractTaxonomy,
|
|
6439
7082
|
taxonomyCommentForPage,
|
|
6440
7083
|
reconcileTaxonomyDocument,
|
|
6441
7084
|
mergeTaxonomyConflict,
|
|
6442
|
-
runLinks,
|
|
6443
7085
|
runTagAudit,
|
|
6444
7086
|
runIndexCheck,
|
|
6445
|
-
|
|
6446
|
-
runFleetValidate,
|
|
6447
|
-
runFleetContext,
|
|
6448
|
-
loadFleetManifestAndHost,
|
|
6449
|
-
snapshotterAliasForLocalHost,
|
|
6450
|
-
satelliteGateFromFleetLoad,
|
|
6451
|
-
loadFleetManifest,
|
|
6452
|
-
resolveFleetHostId,
|
|
7087
|
+
runIndexLinkFormat,
|
|
6453
7088
|
planRawStructuralMove,
|
|
6454
7089
|
applyRawStructuralMove,
|
|
6455
7090
|
REDACTED_MALFORMED_REFERENCE,
|
|
@@ -6460,8 +7095,16 @@ export {
|
|
|
6460
7095
|
runStale,
|
|
6461
7096
|
runPagesize,
|
|
6462
7097
|
runLogRotate,
|
|
7098
|
+
CONFIG_KEYS,
|
|
7099
|
+
isValidWikiProfileKey,
|
|
7100
|
+
profileKey,
|
|
7101
|
+
parseDotenvText,
|
|
7102
|
+
parseDotenvFile,
|
|
7103
|
+
writeDotenv,
|
|
7104
|
+
resolveInitTimePath,
|
|
7105
|
+
resolveRuntimePath,
|
|
7106
|
+
runOrphans,
|
|
6463
7107
|
runTopicMapCheck,
|
|
6464
|
-
runIndexLinkFormat,
|
|
6465
7108
|
normalizeRemoteRoot,
|
|
6466
7109
|
buildRemoteObjectPath,
|
|
6467
7110
|
isValidRemoteDeleteCap,
|
|
@@ -6470,10 +7113,20 @@ export {
|
|
|
6470
7113
|
rewriteRawSourceReferences,
|
|
6471
7114
|
snapshotMaintainedPageState,
|
|
6472
7115
|
runDedup,
|
|
7116
|
+
runAudit,
|
|
6473
7117
|
runFrontmatterFix,
|
|
6474
7118
|
assessSourceIdentity,
|
|
7119
|
+
defaultLintRunner,
|
|
6475
7120
|
runLint,
|
|
6476
7121
|
runSyncLintDelta,
|
|
7122
|
+
FLEET_REL_PATH,
|
|
7123
|
+
runFleetValidate,
|
|
7124
|
+
runFleetContext,
|
|
7125
|
+
loadFleetManifestAndHost,
|
|
7126
|
+
snapshotterAliasForLocalHost,
|
|
7127
|
+
satelliteGateFromFleetLoad,
|
|
7128
|
+
loadFleetManifest,
|
|
7129
|
+
resolveFleetHostId,
|
|
6477
7130
|
git,
|
|
6478
7131
|
gitStrict,
|
|
6479
7132
|
VAULT_COMMIT_PATHSPEC,
|