@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.
- package/CHANGELOG.md +134 -0
- package/LICENSE +21 -0
- package/README.md +484 -0
- package/SKILL.md +252 -0
- package/guide/basics/concepts.md +74 -0
- package/guide/basics/index.md +45 -0
- package/guide/basics/schema.md +140 -0
- package/guide/cli.md +256 -0
- package/guide/correctness/index.md +45 -0
- package/guide/correctness/invariants.md +89 -0
- package/guide/correctness/safety.md +96 -0
- package/guide/history/diff.md +110 -0
- package/guide/history/hidden-git.md +130 -0
- package/guide/history/index.md +52 -0
- package/guide/history/remote-sync.md +113 -0
- package/guide/index.md +134 -0
- package/guide/isolation/coexistence.md +134 -0
- package/guide/isolation/index.md +44 -0
- package/guide/isolation/scale.md +251 -0
- package/guide/layout/in-place-mode.md +97 -0
- package/guide/layout/index.md +53 -0
- package/guide/layout/layout-contract.md +131 -0
- package/guide/layout/layout-modes.md +115 -0
- package/guide/operations/index.md +76 -0
- package/guide/operations/ingest/build.md +75 -0
- package/guide/operations/ingest/extend.md +61 -0
- package/guide/operations/ingest/index.md +54 -0
- package/guide/operations/ingest/join.md +65 -0
- package/guide/operations/maintain/fix.md +66 -0
- package/guide/operations/maintain/index.md +47 -0
- package/guide/operations/maintain/rebuild.md +86 -0
- package/guide/operations/validate.md +48 -0
- package/guide/substrate/index.md +47 -0
- package/guide/substrate/operators.md +96 -0
- package/guide/substrate/tiered-ai.md +363 -0
- package/guide/ux/index.md +44 -0
- package/guide/ux/preflight.md +150 -0
- package/guide/ux/user-intent.md +135 -0
- package/package.json +55 -0
- package/scripts/cli.mjs +893 -0
- package/scripts/commands/remote.mjs +93 -0
- package/scripts/commands/review.mjs +253 -0
- package/scripts/commands/sync.mjs +84 -0
- package/scripts/lib/chunk.mjs +421 -0
- package/scripts/lib/cluster-detect.mjs +516 -0
- package/scripts/lib/decision-log.mjs +343 -0
- package/scripts/lib/draft.mjs +158 -0
- package/scripts/lib/embeddings.mjs +366 -0
- package/scripts/lib/frontmatter.mjs +497 -0
- package/scripts/lib/git-commands.mjs +155 -0
- package/scripts/lib/git.mjs +486 -0
- package/scripts/lib/gitignore.mjs +62 -0
- package/scripts/lib/history.mjs +331 -0
- package/scripts/lib/indices.mjs +510 -0
- package/scripts/lib/ingest.mjs +258 -0
- package/scripts/lib/intent.mjs +713 -0
- package/scripts/lib/interactive.mjs +99 -0
- package/scripts/lib/migrate.mjs +126 -0
- package/scripts/lib/nest-applier.mjs +260 -0
- package/scripts/lib/operators.mjs +1365 -0
- package/scripts/lib/orchestrator.mjs +718 -0
- package/scripts/lib/paths.mjs +197 -0
- package/scripts/lib/preflight.mjs +213 -0
- package/scripts/lib/provenance.mjs +672 -0
- package/scripts/lib/quality-metric.mjs +269 -0
- package/scripts/lib/query-fixture.mjs +71 -0
- package/scripts/lib/rollback.mjs +95 -0
- package/scripts/lib/shape-check.mjs +172 -0
- package/scripts/lib/similarity-cache.mjs +126 -0
- package/scripts/lib/similarity.mjs +230 -0
- package/scripts/lib/snapshot.mjs +54 -0
- package/scripts/lib/source-frontmatter.mjs +85 -0
- package/scripts/lib/tier2-protocol.mjs +470 -0
- package/scripts/lib/tiered.mjs +453 -0
- 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
|
+
}
|