@ctxr/skill-llm-wiki 1.0.1

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.
Files changed (75) hide show
  1. package/CHANGELOG.md +134 -0
  2. package/LICENSE +21 -0
  3. package/README.md +484 -0
  4. package/SKILL.md +252 -0
  5. package/guide/basics/concepts.md +74 -0
  6. package/guide/basics/index.md +45 -0
  7. package/guide/basics/schema.md +140 -0
  8. package/guide/cli.md +256 -0
  9. package/guide/correctness/index.md +45 -0
  10. package/guide/correctness/invariants.md +89 -0
  11. package/guide/correctness/safety.md +96 -0
  12. package/guide/history/diff.md +110 -0
  13. package/guide/history/hidden-git.md +130 -0
  14. package/guide/history/index.md +52 -0
  15. package/guide/history/remote-sync.md +113 -0
  16. package/guide/index.md +134 -0
  17. package/guide/isolation/coexistence.md +134 -0
  18. package/guide/isolation/index.md +44 -0
  19. package/guide/isolation/scale.md +251 -0
  20. package/guide/layout/in-place-mode.md +97 -0
  21. package/guide/layout/index.md +53 -0
  22. package/guide/layout/layout-contract.md +131 -0
  23. package/guide/layout/layout-modes.md +115 -0
  24. package/guide/operations/index.md +76 -0
  25. package/guide/operations/ingest/build.md +75 -0
  26. package/guide/operations/ingest/extend.md +61 -0
  27. package/guide/operations/ingest/index.md +54 -0
  28. package/guide/operations/ingest/join.md +65 -0
  29. package/guide/operations/maintain/fix.md +66 -0
  30. package/guide/operations/maintain/index.md +47 -0
  31. package/guide/operations/maintain/rebuild.md +86 -0
  32. package/guide/operations/validate.md +48 -0
  33. package/guide/substrate/index.md +47 -0
  34. package/guide/substrate/operators.md +96 -0
  35. package/guide/substrate/tiered-ai.md +363 -0
  36. package/guide/ux/index.md +44 -0
  37. package/guide/ux/preflight.md +150 -0
  38. package/guide/ux/user-intent.md +135 -0
  39. package/package.json +55 -0
  40. package/scripts/cli.mjs +893 -0
  41. package/scripts/commands/remote.mjs +93 -0
  42. package/scripts/commands/review.mjs +253 -0
  43. package/scripts/commands/sync.mjs +84 -0
  44. package/scripts/lib/chunk.mjs +421 -0
  45. package/scripts/lib/cluster-detect.mjs +516 -0
  46. package/scripts/lib/decision-log.mjs +343 -0
  47. package/scripts/lib/draft.mjs +158 -0
  48. package/scripts/lib/embeddings.mjs +366 -0
  49. package/scripts/lib/frontmatter.mjs +497 -0
  50. package/scripts/lib/git-commands.mjs +155 -0
  51. package/scripts/lib/git.mjs +486 -0
  52. package/scripts/lib/gitignore.mjs +62 -0
  53. package/scripts/lib/history.mjs +331 -0
  54. package/scripts/lib/indices.mjs +510 -0
  55. package/scripts/lib/ingest.mjs +258 -0
  56. package/scripts/lib/intent.mjs +713 -0
  57. package/scripts/lib/interactive.mjs +99 -0
  58. package/scripts/lib/migrate.mjs +126 -0
  59. package/scripts/lib/nest-applier.mjs +260 -0
  60. package/scripts/lib/operators.mjs +1365 -0
  61. package/scripts/lib/orchestrator.mjs +718 -0
  62. package/scripts/lib/paths.mjs +197 -0
  63. package/scripts/lib/preflight.mjs +213 -0
  64. package/scripts/lib/provenance.mjs +672 -0
  65. package/scripts/lib/quality-metric.mjs +269 -0
  66. package/scripts/lib/query-fixture.mjs +71 -0
  67. package/scripts/lib/rollback.mjs +95 -0
  68. package/scripts/lib/shape-check.mjs +172 -0
  69. package/scripts/lib/similarity-cache.mjs +126 -0
  70. package/scripts/lib/similarity.mjs +230 -0
  71. package/scripts/lib/snapshot.mjs +54 -0
  72. package/scripts/lib/source-frontmatter.mjs +85 -0
  73. package/scripts/lib/tier2-protocol.mjs +470 -0
  74. package/scripts/lib/tiered.mjs +453 -0
  75. package/scripts/lib/validate.mjs +362 -0
@@ -0,0 +1,258 @@
1
+ // Ingest phase: walk a source tree, compute content hashes, emit entry
2
+ // candidates. Deterministic ordering (paths sorted), deterministic id
3
+ // generation (kebab-case of filename with collision suffixes).
4
+ //
5
+ // Source-side frontmatter parsing is delegated to `gray-matter` (via
6
+ // `./source-frontmatter.mjs`), the de-facto-standard YAML frontmatter
7
+ // library. The skill's own output serialisation still flows through
8
+ // `frontmatter.mjs`, but any time we read a source file that may
9
+ // already carry frontmatter we use gray-matter so that:
10
+ // - every authored field (activation / covers / focus / tags / etc.)
11
+ // is parsed accurately and preserved through the pipeline;
12
+ // - the source's frontmatter block is stripped from the body exactly
13
+ // once, so the orchestrator does not double-stack a fresh fence on
14
+ // top of the authored one.
15
+
16
+ import { createHash } from "node:crypto";
17
+ import { readFileSync, readdirSync, statSync } from "node:fs";
18
+ import { basename, extname, join, relative, sep } from "node:path";
19
+ import { parseSourceFrontmatter } from "./source-frontmatter.mjs";
20
+
21
+ const SKIP_DIRS = new Set([
22
+ "node_modules",
23
+ ".git",
24
+ ".svn",
25
+ ".hg",
26
+ "dist",
27
+ "build",
28
+ "target",
29
+ "__pycache__",
30
+ ".venv",
31
+ "venv",
32
+ ".next",
33
+ ".turbo",
34
+ ".cache",
35
+ "coverage",
36
+ ]);
37
+
38
+ const TEXT_EXTS = new Set([
39
+ ".md",
40
+ ".mdx",
41
+ ".txt",
42
+ ".rst",
43
+ ".org",
44
+ ".adoc",
45
+ ".markdown",
46
+ ]);
47
+
48
+ const CODE_EXTS = new Set([
49
+ ".js",
50
+ ".mjs",
51
+ ".cjs",
52
+ ".ts",
53
+ ".tsx",
54
+ ".jsx",
55
+ ".py",
56
+ ".go",
57
+ ".rs",
58
+ ".java",
59
+ ".kt",
60
+ ".scala",
61
+ ".rb",
62
+ ".php",
63
+ ".cs",
64
+ ".swift",
65
+ ".c",
66
+ ".h",
67
+ ".cpp",
68
+ ".hpp",
69
+ ".sh",
70
+ ".zsh",
71
+ ".bash",
72
+ ]);
73
+
74
+ export function ingestSource(sourcePath, options = {}) {
75
+ const { includeCode = false } = options;
76
+ const files = walk(sourcePath, SKIP_DIRS);
77
+ files.sort();
78
+ const leaves = [];
79
+ const indexSources = [];
80
+ const usedIds = new Map();
81
+ for (const abs of files) {
82
+ const ext = extname(abs).toLowerCase();
83
+ const isText = TEXT_EXTS.has(ext);
84
+ const isCode = CODE_EXTS.has(ext);
85
+ if (!isText && !(includeCode && isCode)) continue;
86
+ const rel = relative(sourcePath, abs);
87
+ const raw = readFileSync(abs, "utf8");
88
+ const hash = sha256(raw);
89
+
90
+ // Parse the source's own frontmatter (if any) up-front. `body` is
91
+ // the source content MINUS its frontmatter fence — this is the
92
+ // content the orchestrator later concatenates a fresh drafted
93
+ // frontmatter on top of. Parsing once at ingest also lets the
94
+ // drafter pick up authored fields (activation / covers / tags /
95
+ // focus / etc.) and the index-source detector see `type: index`.
96
+ const parsed = parseSourceFrontmatter(raw);
97
+ const authored = parsed.data || {};
98
+ const body = parsed.body ?? raw;
99
+
100
+ // Index-source detection: a source file is an index input when
101
+ // either (a) it is literally named `index.md` OR (b) its
102
+ // frontmatter declares `type: index`. These feed into the
103
+ // synthesised target index (shared_covers / orientation /
104
+ // activation_defaults forwarding), not into the leaf write path.
105
+ const baseName = basename(abs).toLowerCase();
106
+ const isIndexSource =
107
+ baseName === "index.md" || authored.type === "index";
108
+
109
+ // `title` / `lead` / `headings` are only needed for the leaf draft
110
+ // heuristics (used when authored fields are absent). For index
111
+ // inputs we still compute them cheaply — the extra work is
112
+ // negligible and keeps the shape uniform for callers.
113
+ const candidate = {
114
+ source_path: rel,
115
+ absolute_path: abs,
116
+ ext,
117
+ size: raw.length,
118
+ hash,
119
+ kind: isText ? "prose" : "code",
120
+ title: extractTitle(body || raw, rel),
121
+ lead: extractLead(body || raw),
122
+ headings: extractHeadings(body || raw),
123
+ // Populated for downstream: authored frontmatter + stripped body.
124
+ authored_frontmatter: authored,
125
+ has_authored_frontmatter: parsed.hasFrontmatter === true,
126
+ body,
127
+ };
128
+
129
+ if (isIndexSource) {
130
+ // Directory this index governs, relative to the source root. For
131
+ // `index.md` at the root this is `""`. For `operations/index.md`
132
+ // it is `"operations"`. Used by `indices.mjs` to look up which
133
+ // synthesised target index should receive the authored hints.
134
+ candidate.dir = dirnameRel(rel);
135
+ indexSources.push(candidate);
136
+ continue;
137
+ }
138
+
139
+ const baseId = deriveId(rel);
140
+ const id = disambiguateId(baseId, usedIds);
141
+ usedIds.set(id, (usedIds.get(id) ?? 0) + 1);
142
+ candidate.id = id;
143
+ leaves.push(candidate);
144
+ }
145
+ return { leaves, indexSources, candidates: leaves };
146
+ }
147
+
148
+ function dirnameRel(relPath) {
149
+ const parts = relPath.split(/[\/\\]/);
150
+ parts.pop();
151
+ return parts.join("/");
152
+ }
153
+
154
+ export function sha256(buf) {
155
+ return "sha256:" + createHash("sha256").update(buf).digest("hex");
156
+ }
157
+
158
+ function walk(root, skipDirs) {
159
+ const out = [];
160
+ const stack = [root];
161
+ while (stack.length > 0) {
162
+ const dir = stack.pop();
163
+ let entries;
164
+ try {
165
+ entries = readdirSync(dir, { withFileTypes: true });
166
+ } catch {
167
+ continue;
168
+ }
169
+ for (const e of entries) {
170
+ if (e.name.startsWith(".") && e.name !== ".well-known") continue;
171
+ if (e.isDirectory()) {
172
+ if (skipDirs.has(e.name)) continue;
173
+ stack.push(join(dir, e.name));
174
+ } else if (e.isFile()) {
175
+ out.push(join(dir, e.name));
176
+ }
177
+ }
178
+ }
179
+ return out;
180
+ }
181
+
182
+ function deriveId(relPath) {
183
+ // The id is the plain filename (no directory prefixes). This keeps
184
+ // the leaf on disk at `operations/build.md` with id `build`, which
185
+ // is what the validator's ID-MISMATCH-FILE check enforces
186
+ // (`data.id === basename(file, ".md")`). Global uniqueness is still
187
+ // guaranteed because `disambiguateId` appends `-2`, `-3`, … on
188
+ // collision — the trade-off is deliberate: a flat id is worth a
189
+ // little awkwardness on the (rare) cross-directory collision case.
190
+ const base = basename(relPath, extname(relPath));
191
+ const slug = base
192
+ .toLowerCase()
193
+ .replace(/[^a-z0-9-]+/g, "-")
194
+ .replace(/-+/g, "-")
195
+ .replace(/^-|-$/g, "");
196
+ if (!slug) return "untitled";
197
+ // "index" is reserved for directory/category index files. A source
198
+ // leaf whose derived id is "index" would be written to
199
+ // `<category>/index.md` and collide with the bootstrap-generated
200
+ // category index stub. Rename to "overview" so the source content is
201
+ // preserved as a regular leaf under the category — disambiguateId
202
+ // handles any further collision with a sibling already named overview.
203
+ if (slug === "index") return "overview";
204
+ return slug;
205
+ }
206
+
207
+ function disambiguateId(baseId, usedIds) {
208
+ if (!usedIds.has(baseId)) return baseId;
209
+ let n = 2;
210
+ while (usedIds.has(`${baseId}-${n}`)) n++;
211
+ return `${baseId}-${n}`;
212
+ }
213
+
214
+ function extractTitle(raw, fallbackPath) {
215
+ const m = raw.match(/^#\s+(.+?)\s*$/m);
216
+ if (m) return m[1].trim();
217
+ return basename(fallbackPath, extname(fallbackPath));
218
+ }
219
+
220
+ function extractLead(raw) {
221
+ // First non-heading paragraph after the title.
222
+ const lines = raw.split("\n");
223
+ let inCode = false;
224
+ const paragraph = [];
225
+ let seenTitle = false;
226
+ for (const line of lines) {
227
+ if (line.startsWith("```")) {
228
+ inCode = !inCode;
229
+ continue;
230
+ }
231
+ if (inCode) continue;
232
+ if (/^#{1,6}\s+/.test(line)) {
233
+ if (!seenTitle) {
234
+ seenTitle = true;
235
+ continue;
236
+ }
237
+ if (paragraph.length > 0) break;
238
+ continue;
239
+ }
240
+ if (line.trim() === "") {
241
+ if (paragraph.length > 0) break;
242
+ continue;
243
+ }
244
+ paragraph.push(line.trim());
245
+ if (paragraph.length >= 6) break;
246
+ }
247
+ return paragraph.join(" ").slice(0, 400);
248
+ }
249
+
250
+ function extractHeadings(raw) {
251
+ const out = [];
252
+ const re = /^(#{1,6})\s+(.+?)\s*$/gm;
253
+ let m;
254
+ while ((m = re.exec(raw)) !== null) {
255
+ out.push({ level: m[1].length, text: m[2].trim() });
256
+ }
257
+ return out;
258
+ }