codealmanac 0.2.5 → 0.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -20
- package/dist/{agents-RVTQYE6A.js → agents-V2ZOIACP.js} +6 -5
- package/dist/{chunk-P5WGG4FJ.js → chunk-5BWUMAOX.js} +2 -2
- package/dist/chunk-5BWUMAOX.js.map +1 -0
- package/dist/{chunk-KQUVMF27.js → chunk-BFIG2CXM.js} +2 -516
- package/dist/chunk-BFIG2CXM.js.map +1 -0
- package/dist/{chunk-DL5BXZCX.js → chunk-BQY5L3DL.js} +3 -53
- package/dist/chunk-BQY5L3DL.js.map +1 -0
- package/dist/{chunk-F53U6JQG.js → chunk-CQJVM34R.js} +2 -2
- package/dist/chunk-FUBE6KCO.js +124 -0
- package/dist/chunk-FUBE6KCO.js.map +1 -0
- package/dist/chunk-IZBXXAVL.js +524 -0
- package/dist/chunk-IZBXXAVL.js.map +1 -0
- package/dist/{chunk-7JUX4ADQ.js → chunk-IZT6RBHS.js} +1 -1
- package/dist/{chunk-SMIK2YLU.js → chunk-JLQZELHQ.js} +82 -88
- package/dist/chunk-JLQZELHQ.js.map +1 -0
- package/dist/{chunk-TT6ZP4GS.js → chunk-KZXWPG4P.js} +2 -2
- package/dist/{chunk-6BJUYZ43.js → chunk-QIA22IAM.js} +8 -16
- package/dist/chunk-QIA22IAM.js.map +1 -0
- package/dist/{chunk-BGUID5BS.js → chunk-RALBM6HZ.js} +20 -139
- package/dist/chunk-RALBM6HZ.js.map +1 -0
- package/dist/{chunk-TILAKDN6.js → chunk-U5DLLWIC.js} +3 -3
- package/dist/chunk-WL4UE7Q6.js +1386 -0
- package/dist/chunk-WL4UE7Q6.js.map +1 -0
- package/dist/{chunk-GFUB57IT.js → chunk-ZUQN5Y3K.js} +48 -124
- package/dist/chunk-ZUQN5Y3K.js.map +1 -0
- package/dist/{chunk-MRRX4UQB.js → chunk-ZZLLOAI6.js} +3 -3
- package/dist/{cli-CL4ID7EO.js → cli-XWPNARA6.js} +35 -18
- package/dist/cli-XWPNARA6.js.map +1 -0
- package/dist/codealmanac.js +1 -1
- package/dist/{config-ML2RCR7J.js → config-KH3JUMG6.js} +4 -4
- package/dist/doctor-ENJT665Z.js +18 -0
- package/dist/paths-O5CZADP2.js +14 -0
- package/dist/process-KFSLENL3.js +61 -0
- package/dist/{register-commands-FBJ6XQ3L.js → register-commands-LULZUSPO.js} +993 -1015
- package/dist/register-commands-LULZUSPO.js.map +1 -0
- package/dist/uninstall-BD4MMQ7M.js +16 -0
- package/dist/uninstall-BD4MMQ7M.js.map +1 -0
- package/dist/update-XSKPDFMJ.js +11 -0
- package/dist/update-XSKPDFMJ.js.map +1 -0
- package/dist/{wiki-IGNRNLUZ.js → wiki-O4RWMAE6.js} +8 -6
- package/dist/wiki-O4RWMAE6.js.map +1 -0
- package/guides/mini.md +11 -9
- package/guides/reference.md +96 -39
- package/hooks/almanac-capture.sh +7 -8
- package/package.json +1 -1
- package/prompts/agents/.gitkeep +1 -0
- package/prompts/base/notability.md +139 -0
- package/prompts/base/purpose.md +85 -0
- package/prompts/base/syntax.md +114 -0
- package/prompts/operations/absorb.md +43 -0
- package/prompts/operations/build.md +49 -0
- package/prompts/operations/garden.md +51 -0
- package/dist/chunk-6BJUYZ43.js.map +0 -1
- package/dist/chunk-BGUID5BS.js.map +0 -1
- package/dist/chunk-DL5BXZCX.js.map +0 -1
- package/dist/chunk-GFUB57IT.js.map +0 -1
- package/dist/chunk-KQUVMF27.js.map +0 -1
- package/dist/chunk-P5WGG4FJ.js.map +0 -1
- package/dist/chunk-SMIK2YLU.js.map +0 -1
- package/dist/cli-CL4ID7EO.js.map +0 -1
- package/dist/doctor-DOLJRGS4.js +0 -17
- package/dist/register-commands-FBJ6XQ3L.js.map +0 -1
- package/dist/uninstall-DX6LFKMX.js +0 -15
- package/dist/update-P2IPG7RO.js +0 -11
- package/dist/wiki-IGNRNLUZ.js.map +0 -1
- package/prompts/bootstrap.md +0 -176
- package/prompts/reviewer.md +0 -129
- package/prompts/writer.md +0 -134
- /package/dist/{agents-RVTQYE6A.js.map → agents-V2ZOIACP.js.map} +0 -0
- /package/dist/{chunk-F53U6JQG.js.map → chunk-CQJVM34R.js.map} +0 -0
- /package/dist/{chunk-7JUX4ADQ.js.map → chunk-IZT6RBHS.js.map} +0 -0
- /package/dist/{chunk-TT6ZP4GS.js.map → chunk-KZXWPG4P.js.map} +0 -0
- /package/dist/{chunk-TILAKDN6.js.map → chunk-U5DLLWIC.js.map} +0 -0
- /package/dist/{chunk-MRRX4UQB.js.map → chunk-ZZLLOAI6.js.map} +0 -0
- /package/dist/{config-ML2RCR7J.js.map → config-KH3JUMG6.js.map} +0 -0
- /package/dist/{doctor-DOLJRGS4.js.map → doctor-ENJT665Z.js.map} +0 -0
- /package/dist/{uninstall-DX6LFKMX.js.map → paths-O5CZADP2.js.map} +0 -0
- /package/dist/{update-P2IPG7RO.js.map → process-KFSLENL3.js.map} +0 -0
|
@@ -1,17 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
BLUE,
|
|
4
|
-
BOLD,
|
|
5
|
-
DIM,
|
|
6
|
-
GREEN,
|
|
7
|
-
RED,
|
|
8
|
-
RST
|
|
9
|
-
} from "./chunk-FM3VRDK7.js";
|
|
10
|
-
import {
|
|
11
|
-
findNearestAlmanacDir,
|
|
12
|
-
getGlobalAlmanacDir,
|
|
13
|
-
getRegistryPath
|
|
14
|
-
} from "./chunk-7JUX4ADQ.js";
|
|
15
2
|
|
|
16
3
|
// src/indexer/schema.ts
|
|
17
4
|
import Database from "better-sqlite3";
|
|
@@ -724,497 +711,6 @@ function hashContent(raw) {
|
|
|
724
711
|
return createHash("sha256").update(raw).digest("hex");
|
|
725
712
|
}
|
|
726
713
|
|
|
727
|
-
// src/registry/index.ts
|
|
728
|
-
import { mkdir as mkdir2, readFile as readFile3, rename as rename2, writeFile as writeFile2 } from "fs/promises";
|
|
729
|
-
import { dirname as dirname2 } from "path";
|
|
730
|
-
async function readRegistry() {
|
|
731
|
-
const path = getRegistryPath();
|
|
732
|
-
let raw;
|
|
733
|
-
try {
|
|
734
|
-
raw = await readFile3(path, "utf8");
|
|
735
|
-
} catch (err) {
|
|
736
|
-
if (isNodeError2(err) && err.code === "ENOENT") {
|
|
737
|
-
return [];
|
|
738
|
-
}
|
|
739
|
-
throw err;
|
|
740
|
-
}
|
|
741
|
-
const trimmed = raw.trim();
|
|
742
|
-
if (trimmed.length === 0) {
|
|
743
|
-
return [];
|
|
744
|
-
}
|
|
745
|
-
let parsed;
|
|
746
|
-
try {
|
|
747
|
-
parsed = JSON.parse(trimmed);
|
|
748
|
-
} catch (err) {
|
|
749
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
750
|
-
throw new Error(`registry at ${path} is not valid JSON: ${message}`);
|
|
751
|
-
}
|
|
752
|
-
if (!Array.isArray(parsed)) {
|
|
753
|
-
throw new Error(`registry at ${path} must be a JSON array`);
|
|
754
|
-
}
|
|
755
|
-
return parsed.map((item, idx) => {
|
|
756
|
-
if (typeof item !== "object" || item === null) {
|
|
757
|
-
throw new Error(`registry entry ${idx} is not an object`);
|
|
758
|
-
}
|
|
759
|
-
const e = item;
|
|
760
|
-
const name = typeof e.name === "string" ? e.name : "";
|
|
761
|
-
const path2 = typeof e.path === "string" ? e.path : "";
|
|
762
|
-
if (name.length === 0) {
|
|
763
|
-
throw new Error(`registry entry ${idx} is missing a non-empty "name"`);
|
|
764
|
-
}
|
|
765
|
-
if (path2.length === 0) {
|
|
766
|
-
throw new Error(`registry entry ${idx} is missing a non-empty "path"`);
|
|
767
|
-
}
|
|
768
|
-
return {
|
|
769
|
-
name,
|
|
770
|
-
description: typeof e.description === "string" ? e.description : "",
|
|
771
|
-
path: path2,
|
|
772
|
-
registered_at: typeof e.registered_at === "string" ? e.registered_at : ""
|
|
773
|
-
};
|
|
774
|
-
});
|
|
775
|
-
}
|
|
776
|
-
async function writeRegistry(entries) {
|
|
777
|
-
const path = getRegistryPath();
|
|
778
|
-
await mkdir2(dirname2(path), { recursive: true });
|
|
779
|
-
const body = `${JSON.stringify(entries, null, 2)}
|
|
780
|
-
`;
|
|
781
|
-
const tmpPath = `${path}.tmp`;
|
|
782
|
-
await writeFile2(tmpPath, body, "utf8");
|
|
783
|
-
await rename2(tmpPath, path);
|
|
784
|
-
}
|
|
785
|
-
function pathsEqual(a, b) {
|
|
786
|
-
if (process.platform === "darwin" || process.platform === "win32") {
|
|
787
|
-
return a.toLowerCase() === b.toLowerCase();
|
|
788
|
-
}
|
|
789
|
-
return a === b;
|
|
790
|
-
}
|
|
791
|
-
async function addEntry(entry) {
|
|
792
|
-
const existing = await readRegistry();
|
|
793
|
-
const filtered = existing.filter(
|
|
794
|
-
(e) => e.name !== entry.name && !pathsEqual(e.path, entry.path)
|
|
795
|
-
);
|
|
796
|
-
filtered.push(entry);
|
|
797
|
-
await writeRegistry(filtered);
|
|
798
|
-
return filtered;
|
|
799
|
-
}
|
|
800
|
-
async function dropEntry(name) {
|
|
801
|
-
const existing = await readRegistry();
|
|
802
|
-
const idx = existing.findIndex((e) => e.name === name);
|
|
803
|
-
if (idx === -1) {
|
|
804
|
-
return null;
|
|
805
|
-
}
|
|
806
|
-
const [removed] = existing.splice(idx, 1);
|
|
807
|
-
await writeRegistry(existing);
|
|
808
|
-
return removed ?? null;
|
|
809
|
-
}
|
|
810
|
-
async function findEntry(params) {
|
|
811
|
-
const entries = await readRegistry();
|
|
812
|
-
for (const entry of entries) {
|
|
813
|
-
if (params.name !== void 0 && entry.name === params.name) return entry;
|
|
814
|
-
if (params.path !== void 0 && pathsEqual(entry.path, params.path)) {
|
|
815
|
-
return entry;
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
return null;
|
|
819
|
-
}
|
|
820
|
-
async function ensureGlobalDir() {
|
|
821
|
-
await mkdir2(getGlobalAlmanacDir(), { recursive: true });
|
|
822
|
-
}
|
|
823
|
-
function isNodeError2(err) {
|
|
824
|
-
return err instanceof Error && "code" in err;
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
// src/commands/health.ts
|
|
828
|
-
import { existsSync as existsSync6 } from "fs";
|
|
829
|
-
import { readFile as readFile4 } from "fs/promises";
|
|
830
|
-
import { basename as basename2, join as join4 } from "path";
|
|
831
|
-
import fg3 from "fast-glob";
|
|
832
|
-
|
|
833
|
-
// src/indexer/duration.ts
|
|
834
|
-
function parseDuration(input) {
|
|
835
|
-
const trimmed = input.trim();
|
|
836
|
-
const m = trimmed.match(/^(\d+)([mhdw])$/);
|
|
837
|
-
if (m === null) {
|
|
838
|
-
throw new Error(
|
|
839
|
-
`invalid duration "${input}" (expected Nw, Nd, Nh, or Nm \u2014 e.g. 2w, 30d)`
|
|
840
|
-
);
|
|
841
|
-
}
|
|
842
|
-
const n = Number.parseInt(m[1] ?? "0", 10);
|
|
843
|
-
const unit = m[2];
|
|
844
|
-
switch (unit) {
|
|
845
|
-
case "m":
|
|
846
|
-
return n * 60;
|
|
847
|
-
case "h":
|
|
848
|
-
return n * 60 * 60;
|
|
849
|
-
case "d":
|
|
850
|
-
return n * 60 * 60 * 24;
|
|
851
|
-
case "w":
|
|
852
|
-
return n * 60 * 60 * 24 * 7;
|
|
853
|
-
default:
|
|
854
|
-
throw new Error(`invalid duration unit "${unit ?? ""}"`);
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
|
-
|
|
858
|
-
// src/indexer/resolve-wiki.ts
|
|
859
|
-
import { existsSync as existsSync5 } from "fs";
|
|
860
|
-
import { join as join3 } from "path";
|
|
861
|
-
async function resolveWikiRoot(params) {
|
|
862
|
-
if (params.wiki !== void 0) {
|
|
863
|
-
const entry = await findEntry({ name: params.wiki });
|
|
864
|
-
if (entry === null) {
|
|
865
|
-
throw new Error(`no registered wiki named "${params.wiki}"`);
|
|
866
|
-
}
|
|
867
|
-
if (!existsSync5(join3(entry.path, ".almanac"))) {
|
|
868
|
-
throw new Error(
|
|
869
|
-
`wiki "${params.wiki}" path is unreachable (${entry.path})`
|
|
870
|
-
);
|
|
871
|
-
}
|
|
872
|
-
return entry.path;
|
|
873
|
-
}
|
|
874
|
-
const nearest = findNearestAlmanacDir(params.cwd);
|
|
875
|
-
if (nearest === null) {
|
|
876
|
-
throw new Error(
|
|
877
|
-
"no .almanac/ found in this directory or any parent; run `almanac bootstrap` first"
|
|
878
|
-
);
|
|
879
|
-
}
|
|
880
|
-
return nearest;
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
// src/topics/dag.ts
|
|
884
|
-
var DAG_DEPTH_CAP = 32;
|
|
885
|
-
function ancestorsInFile(file, slug) {
|
|
886
|
-
const parentsOf = /* @__PURE__ */ new Map();
|
|
887
|
-
for (const t of file.topics) {
|
|
888
|
-
parentsOf.set(t.slug, t.parents);
|
|
889
|
-
}
|
|
890
|
-
const ancestors = /* @__PURE__ */ new Set();
|
|
891
|
-
let frontier = parentsOf.get(slug) ?? [];
|
|
892
|
-
let depth = 0;
|
|
893
|
-
while (frontier.length > 0 && depth < DAG_DEPTH_CAP) {
|
|
894
|
-
const next = [];
|
|
895
|
-
for (const node of frontier) {
|
|
896
|
-
if (ancestors.has(node)) continue;
|
|
897
|
-
ancestors.add(node);
|
|
898
|
-
const ps = parentsOf.get(node);
|
|
899
|
-
if (ps !== void 0) next.push(...ps);
|
|
900
|
-
}
|
|
901
|
-
frontier = next;
|
|
902
|
-
depth += 1;
|
|
903
|
-
}
|
|
904
|
-
return ancestors;
|
|
905
|
-
}
|
|
906
|
-
function descendantsInDb(db, slug) {
|
|
907
|
-
const rows = db.prepare(
|
|
908
|
-
`WITH RECURSIVE desc(slug, depth) AS (
|
|
909
|
-
SELECT child_slug, 1 FROM topic_parents WHERE parent_slug = ?
|
|
910
|
-
UNION
|
|
911
|
-
SELECT tp.child_slug, d.depth + 1
|
|
912
|
-
FROM topic_parents tp
|
|
913
|
-
JOIN desc d ON tp.parent_slug = d.slug
|
|
914
|
-
WHERE d.depth < ?
|
|
915
|
-
)
|
|
916
|
-
SELECT DISTINCT slug FROM desc ORDER BY slug`
|
|
917
|
-
).all(slug, DAG_DEPTH_CAP).map((r) => r.slug);
|
|
918
|
-
return rows;
|
|
919
|
-
}
|
|
920
|
-
function subtreeInDb(db, slug) {
|
|
921
|
-
return [slug, ...descendantsInDb(db, slug)];
|
|
922
|
-
}
|
|
923
|
-
|
|
924
|
-
// src/commands/health.ts
|
|
925
|
-
var DEFAULT_STALE_SECONDS = 90 * 24 * 60 * 60;
|
|
926
|
-
async function runHealth(options) {
|
|
927
|
-
const repoRoot = await resolveWikiRoot({ cwd: options.cwd, wiki: options.wiki });
|
|
928
|
-
await ensureFreshIndex({ repoRoot });
|
|
929
|
-
const almanacDir = join4(repoRoot, ".almanac");
|
|
930
|
-
const pagesDir = join4(almanacDir, "pages");
|
|
931
|
-
const db = openIndex(join4(almanacDir, "index.db"));
|
|
932
|
-
try {
|
|
933
|
-
const staleSeconds = options.stale !== void 0 ? parseDuration(options.stale) : DEFAULT_STALE_SECONDS;
|
|
934
|
-
const scope = resolveScope(db, options);
|
|
935
|
-
const report = {
|
|
936
|
-
orphans: findOrphans(db, scope),
|
|
937
|
-
stale: findStale(db, scope, staleSeconds),
|
|
938
|
-
dead_refs: await findDeadRefs(db, scope, repoRoot),
|
|
939
|
-
broken_links: findBrokenLinks(db, scope),
|
|
940
|
-
broken_xwiki: await findBrokenXwiki(db, scope),
|
|
941
|
-
empty_topics: findEmptyTopics(db, scope),
|
|
942
|
-
empty_pages: await findEmptyPages(db, scope, pagesDir),
|
|
943
|
-
slug_collisions: await findSlugCollisions(pagesDir)
|
|
944
|
-
};
|
|
945
|
-
if (options.json === true) {
|
|
946
|
-
return {
|
|
947
|
-
stdout: `${JSON.stringify(report, null, 2)}
|
|
948
|
-
`,
|
|
949
|
-
stderr: "",
|
|
950
|
-
exitCode: 0
|
|
951
|
-
};
|
|
952
|
-
}
|
|
953
|
-
return {
|
|
954
|
-
stdout: formatReport(report),
|
|
955
|
-
stderr: "",
|
|
956
|
-
exitCode: 0
|
|
957
|
-
};
|
|
958
|
-
} finally {
|
|
959
|
-
db.close();
|
|
960
|
-
}
|
|
961
|
-
}
|
|
962
|
-
function resolveScope(db, options) {
|
|
963
|
-
let pages = null;
|
|
964
|
-
let topics = null;
|
|
965
|
-
if (options.topic !== void 0) {
|
|
966
|
-
const rootSlug = toKebabCase(options.topic);
|
|
967
|
-
if (rootSlug.length > 0) {
|
|
968
|
-
const subtree = subtreeInDb(db, rootSlug);
|
|
969
|
-
topics = new Set(subtree);
|
|
970
|
-
const placeholders = subtree.map(() => "?").join(", ");
|
|
971
|
-
const rows = db.prepare(
|
|
972
|
-
`SELECT DISTINCT page_slug FROM page_topics
|
|
973
|
-
WHERE topic_slug IN (${placeholders})`
|
|
974
|
-
).all(...subtree);
|
|
975
|
-
pages = new Set(rows.map((r) => r.page_slug));
|
|
976
|
-
}
|
|
977
|
-
}
|
|
978
|
-
if (options.stdin === true && options.stdinInput !== void 0) {
|
|
979
|
-
const stdinPages = /* @__PURE__ */ new Set();
|
|
980
|
-
for (const line of options.stdinInput.split(/\r?\n/)) {
|
|
981
|
-
const s = line.trim();
|
|
982
|
-
if (s.length > 0) stdinPages.add(s);
|
|
983
|
-
}
|
|
984
|
-
if (pages === null) pages = stdinPages;
|
|
985
|
-
else {
|
|
986
|
-
const out = /* @__PURE__ */ new Set();
|
|
987
|
-
for (const s of stdinPages) if (pages.has(s)) out.add(s);
|
|
988
|
-
pages = out;
|
|
989
|
-
}
|
|
990
|
-
}
|
|
991
|
-
return { pages, topics };
|
|
992
|
-
}
|
|
993
|
-
function inPageScope(scope, slug) {
|
|
994
|
-
if (scope.pages === null) return true;
|
|
995
|
-
return scope.pages.has(slug);
|
|
996
|
-
}
|
|
997
|
-
function findOrphans(db, scope) {
|
|
998
|
-
const rows = db.prepare(
|
|
999
|
-
`SELECT p.slug FROM pages p
|
|
1000
|
-
WHERE p.archived_at IS NULL
|
|
1001
|
-
AND NOT EXISTS (
|
|
1002
|
-
SELECT 1 FROM page_topics pt WHERE pt.page_slug = p.slug
|
|
1003
|
-
)
|
|
1004
|
-
ORDER BY p.slug`
|
|
1005
|
-
).all();
|
|
1006
|
-
return rows.filter((r) => inPageScope(scope, r.slug));
|
|
1007
|
-
}
|
|
1008
|
-
function findStale(db, scope, staleSeconds) {
|
|
1009
|
-
const now = Math.floor(Date.now() / 1e3);
|
|
1010
|
-
const threshold = now - staleSeconds;
|
|
1011
|
-
const rows = db.prepare(
|
|
1012
|
-
`SELECT slug, updated_at FROM pages
|
|
1013
|
-
WHERE archived_at IS NULL AND updated_at < ?
|
|
1014
|
-
ORDER BY updated_at ASC`
|
|
1015
|
-
).all(threshold);
|
|
1016
|
-
return rows.filter((r) => inPageScope(scope, r.slug)).map((r) => ({
|
|
1017
|
-
slug: r.slug,
|
|
1018
|
-
days_since_update: Math.floor((now - r.updated_at) / (60 * 60 * 24))
|
|
1019
|
-
}));
|
|
1020
|
-
}
|
|
1021
|
-
async function findDeadRefs(db, scope, repoRoot) {
|
|
1022
|
-
const rows = db.prepare(
|
|
1023
|
-
`SELECT p.slug, r.path, r.original_path, r.is_dir
|
|
1024
|
-
FROM file_refs r
|
|
1025
|
-
JOIN pages p ON p.slug = r.page_slug
|
|
1026
|
-
WHERE p.archived_at IS NULL
|
|
1027
|
-
ORDER BY p.slug, r.path`
|
|
1028
|
-
).all();
|
|
1029
|
-
const out = [];
|
|
1030
|
-
for (const r of rows) {
|
|
1031
|
-
if (!inPageScope(scope, r.slug)) continue;
|
|
1032
|
-
const abs = join4(repoRoot, r.original_path);
|
|
1033
|
-
if (!existsSync6(abs)) {
|
|
1034
|
-
out.push({ slug: r.slug, path: r.original_path });
|
|
1035
|
-
}
|
|
1036
|
-
}
|
|
1037
|
-
return out;
|
|
1038
|
-
}
|
|
1039
|
-
function findBrokenLinks(db, scope) {
|
|
1040
|
-
const rows = db.prepare(
|
|
1041
|
-
`SELECT w.source_slug, w.target_slug
|
|
1042
|
-
FROM wikilinks w
|
|
1043
|
-
JOIN pages src ON src.slug = w.source_slug
|
|
1044
|
-
LEFT JOIN pages tgt ON tgt.slug = w.target_slug
|
|
1045
|
-
WHERE tgt.slug IS NULL AND src.archived_at IS NULL
|
|
1046
|
-
ORDER BY w.source_slug, w.target_slug`
|
|
1047
|
-
).all();
|
|
1048
|
-
return rows.filter((r) => inPageScope(scope, r.source_slug));
|
|
1049
|
-
}
|
|
1050
|
-
async function findBrokenXwiki(db, scope) {
|
|
1051
|
-
const rows = db.prepare(
|
|
1052
|
-
// Same archived-source filter as `findBrokenLinks`. Retired pages
|
|
1053
|
-
// shouldn't spam the report with links to wikis that may have
|
|
1054
|
-
// been intentionally retired too.
|
|
1055
|
-
`SELECT x.source_slug, x.target_wiki, x.target_slug
|
|
1056
|
-
FROM cross_wiki_links x
|
|
1057
|
-
JOIN pages src ON src.slug = x.source_slug
|
|
1058
|
-
WHERE src.archived_at IS NULL
|
|
1059
|
-
ORDER BY x.source_slug, x.target_wiki, x.target_slug`
|
|
1060
|
-
).all();
|
|
1061
|
-
const out = [];
|
|
1062
|
-
const reachableCache = /* @__PURE__ */ new Map();
|
|
1063
|
-
for (const r of rows) {
|
|
1064
|
-
if (!inPageScope(scope, r.source_slug)) continue;
|
|
1065
|
-
let ok = reachableCache.get(r.target_wiki);
|
|
1066
|
-
if (ok === void 0) {
|
|
1067
|
-
const entry = await findEntry({ name: r.target_wiki });
|
|
1068
|
-
ok = entry !== null && existsSync6(join4(entry.path, ".almanac"));
|
|
1069
|
-
reachableCache.set(r.target_wiki, ok);
|
|
1070
|
-
}
|
|
1071
|
-
if (!ok) {
|
|
1072
|
-
out.push({
|
|
1073
|
-
source_slug: r.source_slug,
|
|
1074
|
-
target_wiki: r.target_wiki,
|
|
1075
|
-
target_slug: r.target_slug
|
|
1076
|
-
});
|
|
1077
|
-
}
|
|
1078
|
-
}
|
|
1079
|
-
return out;
|
|
1080
|
-
}
|
|
1081
|
-
function findEmptyTopics(db, scope) {
|
|
1082
|
-
const rows = db.prepare(
|
|
1083
|
-
`SELECT t.slug FROM topics t
|
|
1084
|
-
WHERE NOT EXISTS (
|
|
1085
|
-
SELECT 1 FROM page_topics pt WHERE pt.topic_slug = t.slug
|
|
1086
|
-
)
|
|
1087
|
-
ORDER BY t.slug`
|
|
1088
|
-
).all();
|
|
1089
|
-
if (scope.topics === null) return rows;
|
|
1090
|
-
return rows.filter((r) => scope.topics.has(r.slug));
|
|
1091
|
-
}
|
|
1092
|
-
async function findEmptyPages(db, scope, pagesDir) {
|
|
1093
|
-
const rows = db.prepare(
|
|
1094
|
-
`SELECT slug, file_path FROM pages
|
|
1095
|
-
WHERE archived_at IS NULL
|
|
1096
|
-
ORDER BY slug`
|
|
1097
|
-
).all();
|
|
1098
|
-
const out = [];
|
|
1099
|
-
for (const r of rows) {
|
|
1100
|
-
if (!inPageScope(scope, r.slug)) continue;
|
|
1101
|
-
let raw;
|
|
1102
|
-
try {
|
|
1103
|
-
raw = await readFile4(r.file_path, "utf8");
|
|
1104
|
-
} catch {
|
|
1105
|
-
continue;
|
|
1106
|
-
}
|
|
1107
|
-
const m = raw.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n?([\s\S]*)$/);
|
|
1108
|
-
const body = m !== null ? m[1] ?? "" : raw;
|
|
1109
|
-
void pagesDir;
|
|
1110
|
-
const hasSubstance = body.split(/\r?\n/).some((l) => {
|
|
1111
|
-
const t = l.trim();
|
|
1112
|
-
if (t.length === 0) return false;
|
|
1113
|
-
if (t.startsWith("#")) return false;
|
|
1114
|
-
return true;
|
|
1115
|
-
});
|
|
1116
|
-
if (!hasSubstance) {
|
|
1117
|
-
out.push({ slug: r.slug });
|
|
1118
|
-
}
|
|
1119
|
-
}
|
|
1120
|
-
return out;
|
|
1121
|
-
}
|
|
1122
|
-
async function findSlugCollisions(pagesDir) {
|
|
1123
|
-
if (!existsSync6(pagesDir)) return [];
|
|
1124
|
-
const files = await fg3("**/*.md", {
|
|
1125
|
-
cwd: pagesDir,
|
|
1126
|
-
absolute: false,
|
|
1127
|
-
onlyFiles: true,
|
|
1128
|
-
caseSensitiveMatch: true
|
|
1129
|
-
});
|
|
1130
|
-
const bySlug = /* @__PURE__ */ new Map();
|
|
1131
|
-
for (const rel of files) {
|
|
1132
|
-
const slug = toKebabCase(basename2(rel, ".md"));
|
|
1133
|
-
if (slug.length === 0) continue;
|
|
1134
|
-
const list = bySlug.get(slug) ?? [];
|
|
1135
|
-
list.push(rel);
|
|
1136
|
-
bySlug.set(slug, list);
|
|
1137
|
-
}
|
|
1138
|
-
const out = [];
|
|
1139
|
-
for (const [slug, paths] of bySlug.entries()) {
|
|
1140
|
-
if (paths.length > 1) {
|
|
1141
|
-
out.push({ slug, paths: paths.sort() });
|
|
1142
|
-
}
|
|
1143
|
-
}
|
|
1144
|
-
out.sort((a, b) => a.slug.localeCompare(b.slug));
|
|
1145
|
-
return out;
|
|
1146
|
-
}
|
|
1147
|
-
function formatReport(r) {
|
|
1148
|
-
const sections = [];
|
|
1149
|
-
sections.push(
|
|
1150
|
-
section(
|
|
1151
|
-
"orphans",
|
|
1152
|
-
r.orphans.length,
|
|
1153
|
-
r.orphans.map((o) => ` ${BLUE}${o.slug}${RST}`)
|
|
1154
|
-
)
|
|
1155
|
-
);
|
|
1156
|
-
sections.push(
|
|
1157
|
-
section(
|
|
1158
|
-
"stale",
|
|
1159
|
-
r.stale.length,
|
|
1160
|
-
r.stale.map((s) => ` ${BLUE}${s.slug}${RST} ${DIM}(${s.days_since_update} days)${RST}`)
|
|
1161
|
-
)
|
|
1162
|
-
);
|
|
1163
|
-
sections.push(
|
|
1164
|
-
section(
|
|
1165
|
-
"dead-refs",
|
|
1166
|
-
r.dead_refs.length,
|
|
1167
|
-
r.dead_refs.map((d) => ` ${BLUE}${d.slug}${RST} references ${d.path} ${DIM}(missing)${RST}`)
|
|
1168
|
-
)
|
|
1169
|
-
);
|
|
1170
|
-
sections.push(
|
|
1171
|
-
section(
|
|
1172
|
-
"broken-links",
|
|
1173
|
-
r.broken_links.length,
|
|
1174
|
-
r.broken_links.map(
|
|
1175
|
-
(b) => ` ${BLUE}${b.source_slug}${RST} \u2192 ${b.target_slug} ${DIM}(target does not exist)${RST}`
|
|
1176
|
-
)
|
|
1177
|
-
)
|
|
1178
|
-
);
|
|
1179
|
-
sections.push(
|
|
1180
|
-
section(
|
|
1181
|
-
"broken-xwiki",
|
|
1182
|
-
r.broken_xwiki.length,
|
|
1183
|
-
r.broken_xwiki.map(
|
|
1184
|
-
(b) => ` ${BLUE}${b.source_slug}${RST} \u2192 ${b.target_wiki}:${b.target_slug} ${DIM}(wiki unregistered or unreachable)${RST}`
|
|
1185
|
-
)
|
|
1186
|
-
)
|
|
1187
|
-
);
|
|
1188
|
-
sections.push(
|
|
1189
|
-
section(
|
|
1190
|
-
"empty-topics",
|
|
1191
|
-
r.empty_topics.length,
|
|
1192
|
-
r.empty_topics.map((e) => ` ${BLUE}${e.slug}${RST}`)
|
|
1193
|
-
)
|
|
1194
|
-
);
|
|
1195
|
-
sections.push(
|
|
1196
|
-
section(
|
|
1197
|
-
"empty-pages",
|
|
1198
|
-
r.empty_pages.length,
|
|
1199
|
-
r.empty_pages.map((e) => ` ${BLUE}${e.slug}${RST}`)
|
|
1200
|
-
)
|
|
1201
|
-
);
|
|
1202
|
-
sections.push(
|
|
1203
|
-
section(
|
|
1204
|
-
"slug-collisions",
|
|
1205
|
-
r.slug_collisions.length,
|
|
1206
|
-
r.slug_collisions.map((c) => ` ${BLUE}${c.slug}${RST}: ${c.paths.join(", ")}`)
|
|
1207
|
-
)
|
|
1208
|
-
);
|
|
1209
|
-
return `${sections.join("\n\n")}
|
|
1210
|
-
`;
|
|
1211
|
-
}
|
|
1212
|
-
function section(label, count, lines) {
|
|
1213
|
-
if (count === 0) return `${BOLD}${label}${RST} ${GREEN}(0): (ok)${RST}`;
|
|
1214
|
-
return `${BOLD}${label}${RST} ${RED}(${count})${RST}:
|
|
1215
|
-
${lines.join("\n")}`;
|
|
1216
|
-
}
|
|
1217
|
-
|
|
1218
714
|
export {
|
|
1219
715
|
toKebabCase,
|
|
1220
716
|
loadTopicsFile,
|
|
@@ -1227,16 +723,6 @@ export {
|
|
|
1227
723
|
looksLikeDir,
|
|
1228
724
|
openIndex,
|
|
1229
725
|
ensureFreshIndex,
|
|
1230
|
-
runIndexer
|
|
1231
|
-
readRegistry,
|
|
1232
|
-
addEntry,
|
|
1233
|
-
dropEntry,
|
|
1234
|
-
findEntry,
|
|
1235
|
-
ensureGlobalDir,
|
|
1236
|
-
resolveWikiRoot,
|
|
1237
|
-
ancestorsInFile,
|
|
1238
|
-
descendantsInDb,
|
|
1239
|
-
parseDuration,
|
|
1240
|
-
runHealth
|
|
726
|
+
runIndexer
|
|
1241
727
|
};
|
|
1242
|
-
//# sourceMappingURL=chunk-
|
|
728
|
+
//# sourceMappingURL=chunk-BFIG2CXM.js.map
|