@sema-agent/wiki-gate 0.0.0-stage → 7.67.0
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/LICENSE +107 -0
- package/README.md +95 -2
- package/dist/acceptance.d.ts +45 -0
- package/dist/acceptance.js +79 -0
- package/dist/check.d.ts +57 -0
- package/dist/check.js +378 -0
- package/dist/corrections.d.ts +29 -0
- package/dist/corrections.js +44 -0
- package/dist/coverage.d.ts +91 -0
- package/dist/coverage.js +151 -0
- package/dist/generators.d.ts +12 -0
- package/dist/generators.js +45 -0
- package/dist/genre.d.ts +46 -0
- package/dist/genre.js +15 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.js +12 -0
- package/dist/knobs.d.ts +19 -0
- package/dist/knobs.js +58 -0
- package/dist/page.d.ts +128 -0
- package/dist/page.js +175 -0
- package/dist/run.d.ts +81 -0
- package/dist/run.js +231 -0
- package/dist/source.d.ts +82 -0
- package/dist/source.js +193 -0
- package/dist/tree.d.ts +234 -0
- package/dist/tree.js +240 -0
- package/dist/versions.d.ts +64 -0
- package/dist/versions.js +53 -0
- package/package.json +36 -4
package/dist/coverage.js
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { citationsOf, splitPage } from "./page.js";
|
|
4
|
+
import { generate, generatorOf } from "./check.js";
|
|
5
|
+
import { coverageSourcesOf, repoOf } from "./tree.js";
|
|
6
|
+
import { FEATURE_ROWS, genreRow } from "./genre.js";
|
|
7
|
+
export const COVERED_FEAT_STATUSES = Object.freeze(["shipped", "verified"]);
|
|
8
|
+
export function readFeatRows(repoWord, { commsRoot, candidates = [] } = {}) {
|
|
9
|
+
const tried = commsRoot ? [commsRoot] : [...candidates];
|
|
10
|
+
const root = tried.find((c) => existsSync(c));
|
|
11
|
+
if (root === undefined)
|
|
12
|
+
return { absent: tried };
|
|
13
|
+
const dir = join(root, "feats");
|
|
14
|
+
const files = existsSync(dir) ? readdirSync(dir).filter((f) => /^F-\d+\.md$/.test(f)).sort() : [];
|
|
15
|
+
if (files.length === 0)
|
|
16
|
+
return { error: `${dir} holds no F-<n>.md row` };
|
|
17
|
+
return {
|
|
18
|
+
rows: files.map((f) => {
|
|
19
|
+
const { fm } = splitPage(readFileSync(join(dir, f), "utf8"));
|
|
20
|
+
return { id: f.replace(/\.md$/, ""), status: String(fm?.[`status-${repoWord}`] ?? "").replace(/^"|"$/g, "").trim() };
|
|
21
|
+
}),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export function rootExportsSource() {
|
|
25
|
+
return { items: (tree) => Object.keys(tree.snapshot().exports).map((id) => ({ id })), refKind: "export", coversKey: "exports" };
|
|
26
|
+
}
|
|
27
|
+
export function stableRootExportsSource() {
|
|
28
|
+
return {
|
|
29
|
+
items: (tree) => {
|
|
30
|
+
const s = tree.snapshot();
|
|
31
|
+
return Object.keys(s.exports)
|
|
32
|
+
.filter((n) => s.tiers[n] === "stable")
|
|
33
|
+
.map((id) => ({ id }));
|
|
34
|
+
},
|
|
35
|
+
refKind: "export",
|
|
36
|
+
coversKey: "exports",
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export function liveClosedSetsSource() {
|
|
40
|
+
return { items: (tree) => tree.closedSetRows().filter((r) => r.status === "live").map((r) => ({ id: r.set, file: r.file })), refKind: "set", coversKey: "sets" };
|
|
41
|
+
}
|
|
42
|
+
export function contractSentencesSource() {
|
|
43
|
+
return { items: (tree) => tree.contractRows().map((r) => ({ id: r.id, file: r.file })), refKind: "contract", coversKey: "contracts" };
|
|
44
|
+
}
|
|
45
|
+
export function configurationKeysSource() {
|
|
46
|
+
return {
|
|
47
|
+
items: (tree) => {
|
|
48
|
+
const file = tree.config.readers?.catalog?.file;
|
|
49
|
+
return tree.configKnobs().map((k) => ({ id: k.key, ...(file === undefined ? {} : { file }), line: k.line }));
|
|
50
|
+
},
|
|
51
|
+
coversKey: "configKeys",
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
const NONE = new Set();
|
|
55
|
+
export function citedByPages(tree, pages) {
|
|
56
|
+
const feats = new Set();
|
|
57
|
+
const byRef = new Map();
|
|
58
|
+
const byCovers = new Map();
|
|
59
|
+
const add = (m, k, id) => {
|
|
60
|
+
if (!m.has(k))
|
|
61
|
+
m.set(k, new Set());
|
|
62
|
+
m.get(k).add(id);
|
|
63
|
+
};
|
|
64
|
+
const citeRef = (r) => {
|
|
65
|
+
const [kind, ...rest] = r.split(":");
|
|
66
|
+
add(byRef, kind, rest.join(":"));
|
|
67
|
+
};
|
|
68
|
+
for (const p of pages) {
|
|
69
|
+
const c = citationsOf(p.text);
|
|
70
|
+
for (const f of c.feats)
|
|
71
|
+
feats.add(f);
|
|
72
|
+
for (const r of c.refs)
|
|
73
|
+
citeRef(r);
|
|
74
|
+
for (const g of c.gens) {
|
|
75
|
+
const gen = generatorOf(tree, g.name);
|
|
76
|
+
if (!gen)
|
|
77
|
+
continue;
|
|
78
|
+
try {
|
|
79
|
+
for (const r of generate(gen, g.args).refs)
|
|
80
|
+
citeRef(r);
|
|
81
|
+
for (const [k, ids] of Object.entries(gen.covers?.(...g.args) ?? {}))
|
|
82
|
+
for (const id of ids)
|
|
83
|
+
add(byCovers, k, id);
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const refs = (kind) => byRef.get(kind) ?? NONE;
|
|
91
|
+
const covers = (key) => byCovers.get(key) ?? NONE;
|
|
92
|
+
return {
|
|
93
|
+
feats,
|
|
94
|
+
refs,
|
|
95
|
+
covers,
|
|
96
|
+
isCited: (source, id) => (source.refKind !== undefined && refs(source.refKind).has(id)) || (source.coversKey !== undefined && covers(source.coversKey).has(id)),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
export function coverageFindings(tree, pages, feats) {
|
|
100
|
+
const findings = [];
|
|
101
|
+
const repo = repoOf(tree);
|
|
102
|
+
const cited = citedByPages(tree, pages);
|
|
103
|
+
const coveredFeats = cited.feats;
|
|
104
|
+
const required = [];
|
|
105
|
+
if (genreRow(tree.genre).requiredFloor.includes(FEATURE_ROWS)) {
|
|
106
|
+
if ("absent" in feats) {
|
|
107
|
+
findings.push({ gate: "coverage", page: "(feature rows)", line: 0, message: `the feature-row half of the required set cannot be judged: no comms checkout at ${feats.absent.join(" or ")}` });
|
|
108
|
+
required.push({ source: FEATURE_ROWS, covered: 0, size: 0, unread: "no comms checkout" });
|
|
109
|
+
}
|
|
110
|
+
else if ("error" in feats) {
|
|
111
|
+
findings.push({ gate: "coverage", page: "(feature rows)", line: 0, message: `a comms checkout without its feature rows: ${feats.error}`, fixed: "fail" });
|
|
112
|
+
required.push({ source: FEATURE_ROWS, covered: 0, size: 0, unread: feats.error });
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
const due = feats.rows.filter((row) => COVERED_FEAT_STATUSES.includes(row.status));
|
|
116
|
+
for (const row of due)
|
|
117
|
+
if (!coveredFeats.has(row.id))
|
|
118
|
+
findings.push({ gate: "coverage", page: "(feature rows)", line: 0, message: `${row.id} (status-${repo.word}: ${row.status}) is on no page's \`feats:\`` });
|
|
119
|
+
required.push({ source: FEATURE_ROWS, covered: due.filter((row) => coveredFeats.has(row.id)).length, size: due.length });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
const report = [];
|
|
123
|
+
for (const { name, source, required: isRequired } of coverageSourcesOf(tree.config)) {
|
|
124
|
+
let items;
|
|
125
|
+
try {
|
|
126
|
+
items = source.items(tree);
|
|
127
|
+
}
|
|
128
|
+
catch (e) {
|
|
129
|
+
if (!isRequired)
|
|
130
|
+
throw e;
|
|
131
|
+
findings.push({ gate: "coverage", page: `(${name})`, line: 0, message: `the required source \`${name}\` cannot be read: ${e.message}`, fixed: "fail" });
|
|
132
|
+
required.push({ source: name, covered: 0, size: 0, unread: e.message });
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
const covered = items.filter((it) => cited.isCited(source, it.id));
|
|
136
|
+
if (!isRequired) {
|
|
137
|
+
report.push({ source: name, cited: covered.length, size: items.length });
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
for (const it of items)
|
|
141
|
+
if (!cited.isCited(source, it.id))
|
|
142
|
+
findings.push({ gate: "coverage", page: it.file ?? `(${name})`, line: it.line ?? 0, message: `${name}: \`${it.id}\` is covered by no page${source.refKind === undefined ? " (only a generated region covers one)" : ""}` });
|
|
143
|
+
required.push({ source: name, covered: covered.length, size: items.length });
|
|
144
|
+
}
|
|
145
|
+
return { findings, report, required };
|
|
146
|
+
}
|
|
147
|
+
export function requiredSummaryOf(rows) {
|
|
148
|
+
if (rows.length === 0)
|
|
149
|
+
return "required: none";
|
|
150
|
+
return `required: ${rows.map((r) => (r.unread !== undefined ? `${r.source} unread` : `${r.source} ${r.covered}/${r.size}`)).join(", ")}`;
|
|
151
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The package's own generators, held by every repository: the index of a genre's pages, grouped by the genre's
|
|
3
|
+
* readers, the genre table itself, and the table of knob fields. A repository's generators are added beside them,
|
|
4
|
+
* never under the same name.
|
|
5
|
+
*/
|
|
6
|
+
import { type GenreRow } from "./genre.js";
|
|
7
|
+
import type { Generator, GeneratorReaders } from "./tree.js";
|
|
8
|
+
/** The names of the package's own generators. */
|
|
9
|
+
export declare const BUILT_IN_GENERATORS: readonly string[];
|
|
10
|
+
/** The genre table rendered: one column per genre, one row per column of the table. */
|
|
11
|
+
export declare function genreTableText(rows?: Readonly<Record<string, GenreRow>>): string;
|
|
12
|
+
export declare function builtInGenerators(read: GeneratorReaders): Record<string, Generator>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { GENRES, behaviourEvidenceOf } from "./genre.js";
|
|
2
|
+
import { KNOB_FIELDS } from "./knobs.js";
|
|
3
|
+
export const BUILT_IN_GENERATORS = Object.freeze(["page-index", "genre-table", "knob-fields"]);
|
|
4
|
+
const list = (xs, wrap = (x) => x) => (xs.length === 0 ? "—" : xs.map(wrap).join(" · "));
|
|
5
|
+
const code = (x) => `\`${x}\``;
|
|
6
|
+
export function genreTableText(rows = GENRES) {
|
|
7
|
+
const names = Object.keys(rows);
|
|
8
|
+
const line = (label, cell) => `| ${label} | ${names.map((n) => cell(rows[n])).join(" | ")} |`;
|
|
9
|
+
return [
|
|
10
|
+
`| | ${names.join(" | ")} |`,
|
|
11
|
+
`|---|${names.map(() => "---").join("|")}|`,
|
|
12
|
+
line("directory", (r) => code(r.dir)),
|
|
13
|
+
line("readers", (r) => list(r.audiences)),
|
|
14
|
+
line("levels", (r) => list(r.levels)),
|
|
15
|
+
line("behaviour evidence", (r) => list(behaviourEvidenceOf(r), code)),
|
|
16
|
+
line("required coverage, besides what the repository declares", (r) => list(r.requiredFloor)),
|
|
17
|
+
].join("\n");
|
|
18
|
+
}
|
|
19
|
+
function knobFieldsText() {
|
|
20
|
+
return ["| field | what its column says | required |", "|---|---|---|", ...KNOB_FIELDS.map((f) => `| ${code(f.id)} | ${f.says} | ${f.required ? "yes" : "no"} |`)].join("\n");
|
|
21
|
+
}
|
|
22
|
+
export function builtInGenerators(read) {
|
|
23
|
+
return {
|
|
24
|
+
"page-index": {
|
|
25
|
+
render: () => {
|
|
26
|
+
const row = read.genre();
|
|
27
|
+
const pages = read.wikiPages().slice().sort((a, b) => a.fm["page-id"].localeCompare(b.fm["page-id"]));
|
|
28
|
+
const out = [`Pages: ${pages.length}; stable: ${pages.filter((p) => p.fm.stable === "true").length}.`];
|
|
29
|
+
for (const reader of row.audiences) {
|
|
30
|
+
const mine = pages.filter((p) => (p.fm._list.audience ?? []).includes(reader));
|
|
31
|
+
out.push("", `**${reader}** (${mine.length})`, "", "| page | id | kind | owner | stable |", "|---|---|---|---|---|");
|
|
32
|
+
for (const p of mine)
|
|
33
|
+
out.push(`| [${p.fm.title}](${p.rel}) | \`${p.fm["page-id"]}\` | ${p.fm.kind} | ${p.fm["owner-repo"]} | ${p.fm.stable} |`);
|
|
34
|
+
}
|
|
35
|
+
return { text: out.join("\n"), refs: [] };
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
"genre-table": {
|
|
39
|
+
render: () => ({ text: genreTableText(), refs: [] }),
|
|
40
|
+
},
|
|
41
|
+
"knob-fields": {
|
|
42
|
+
render: () => ({ text: knobFieldsText(), refs: [] }),
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
package/dist/genre.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The genres a gate run reads — one frozen table, one row per genre, chosen by the entry configuration's `genre`.
|
|
3
|
+
*
|
|
4
|
+
* Both rows are held to one rule set: every rule reads the row of the page's genre, so the two genres differ only
|
|
5
|
+
* where their rows differ — the page directory, the readers a page may name, the evidence levels a sentence may carry,
|
|
6
|
+
* and the coverage the genre itself requires besides what the repository declares.
|
|
7
|
+
*/
|
|
8
|
+
/** An evidence level: who re-checks a sentence. */
|
|
9
|
+
export type Level = "G" | "U" | "P" | "V" | "I";
|
|
10
|
+
/** Every evidence level a marker may spell, in the order a page lists them. */
|
|
11
|
+
export declare const LEVELS: readonly Level[];
|
|
12
|
+
/** The levels that vouch for what the code does (a `G` sentence restates a declaration and never vouches for behaviour). */
|
|
13
|
+
export declare const BEHAVIOUR_LEVELS: readonly Level[];
|
|
14
|
+
/** The required coverage source a genre names that no repository declares: its items are the shipped feature rows. */
|
|
15
|
+
export declare const FEATURE_ROWS = "feature rows";
|
|
16
|
+
/**
|
|
17
|
+
* One genre: the page directory under the repository root, the readers its pages may name in `audience:`, the evidence
|
|
18
|
+
* levels its sentences may carry, and the coverage sources it requires of every repository (besides what the
|
|
19
|
+
* repository declares).
|
|
20
|
+
*/
|
|
21
|
+
export interface GenreRow {
|
|
22
|
+
readonly dir: string;
|
|
23
|
+
readonly audiences: readonly string[];
|
|
24
|
+
readonly levels: readonly Level[];
|
|
25
|
+
readonly requiredFloor: readonly string[];
|
|
26
|
+
}
|
|
27
|
+
export declare const GENRES: Readonly<{
|
|
28
|
+
wiki: Readonly<{
|
|
29
|
+
dir: "docs/wiki";
|
|
30
|
+
audiences: readonly string[];
|
|
31
|
+
levels: readonly Level[];
|
|
32
|
+
requiredFloor: readonly string[];
|
|
33
|
+
}>;
|
|
34
|
+
manual: Readonly<{
|
|
35
|
+
dir: "docs/manual";
|
|
36
|
+
audiences: readonly string[];
|
|
37
|
+
levels: readonly Level[];
|
|
38
|
+
requiredFloor: readonly string[];
|
|
39
|
+
}>;
|
|
40
|
+
}>;
|
|
41
|
+
/** A word of the genre table. */
|
|
42
|
+
export type Genre = keyof typeof GENRES;
|
|
43
|
+
/** The row a configuration's genre names; a word the table does not hold is refused by name, never read as a default. */
|
|
44
|
+
export declare function genreRow(genre: unknown): GenreRow;
|
|
45
|
+
/** A row's levels that vouch for behaviour: the "behaviour evidence" of its genre. */
|
|
46
|
+
export declare function behaviourEvidenceOf(row: GenreRow): readonly Level[];
|
package/dist/genre.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const LEVELS = Object.freeze(["G", "U", "P", "V", "I"]);
|
|
2
|
+
export const BEHAVIOUR_LEVELS = Object.freeze(["U", "P"]);
|
|
3
|
+
export const FEATURE_ROWS = "feature rows";
|
|
4
|
+
export const GENRES = Object.freeze({
|
|
5
|
+
wiki: Object.freeze({ dir: "docs/wiki", audiences: Object.freeze(["embedder", "deployer", "caller"]), levels: Object.freeze(["G", "P", "V", "I"]), requiredFloor: Object.freeze([FEATURE_ROWS]) }),
|
|
6
|
+
manual: Object.freeze({ dir: "docs/manual", audiences: Object.freeze(["user", "scripter", "admin"]), levels: Object.freeze(["G", "U", "P", "I"]), requiredFloor: Object.freeze([]) }),
|
|
7
|
+
});
|
|
8
|
+
export function genreRow(genre) {
|
|
9
|
+
if (typeof genre === "string" && Object.hasOwn(GENRES, genre))
|
|
10
|
+
return GENRES[genre];
|
|
11
|
+
throw new Error(`unknown genre ${JSON.stringify(genre) ?? String(genre)}: the genres are ${Object.keys(GENRES).join(", ")}`);
|
|
12
|
+
}
|
|
13
|
+
export function behaviourEvidenceOf(row) {
|
|
14
|
+
return row.levels.filter((l) => BEHAVIOUR_LEVELS.includes(l));
|
|
15
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@sema-agent/wiki-gate` — the sema wiki gates: one reader module with three gate entries (anchors, freshness,
|
|
3
|
+
* coverage) and the stamp, over the repository a {@link WikiGateConfig} names.
|
|
4
|
+
*
|
|
5
|
+
* The gate holds every page directory it is pointed at — `docs/wiki` and `docs/manual` alike — to one rule set: the
|
|
6
|
+
* genre, and with it the directory, is chosen by the entry's configuration object, never by a page's own header, and
|
|
7
|
+
* the package opens no second interface for another genre ({@link GENRES} holds the genres, one row each).
|
|
8
|
+
*
|
|
9
|
+
* A repository hands in its profile: its identity, the version its findings fail from per genre, its own readers (its
|
|
10
|
+
* export surface, its closed-set and contract-sentence registries, its configuration catalog, its literal tables, its
|
|
11
|
+
* internal work-item name form, its pin reader when its tests are not vitest, the acceptance index and the criterion
|
|
12
|
+
* text a `U` sentence is vouched by), its generators, its required and reported sources, its own banned forms and its
|
|
13
|
+
* preflight. The rules — page shape, markers, refs, digests, severities, the stamp — are the package's, and read the
|
|
14
|
+
* same in every repository.
|
|
15
|
+
*/
|
|
16
|
+
export { V_AGE_MINORS, U_AGE_RELEASES, parseVersion, compareVersions, severityOf, parseRun, vAgeFinding, uAgeFinding, minorReleaseDistance } from "./versions.js";
|
|
17
|
+
export type { Severity, SeverityContext, RunRef, RepoIdentity, ReleaseDistance } from "./versions.js";
|
|
18
|
+
export { declarationOf, pinOf, literalTableReader, literalKeyCatalog } from "./source.js";
|
|
19
|
+
export type { TreeFiles, Resolved, Unresolvable, DeclarationReading, InitializerUnwrap, TableValuesReader, CatalogKnob, CatalogReader } from "./source.js";
|
|
20
|
+
export { splitPage, claimBlocks, citationsOf, correctionLineOf, sectionIdOf } from "./page.js";
|
|
21
|
+
export type { Frontmatter, FrontmatterLine, SplitPage, CutBody, ClaimBlock, GenRegion, Fence, PageUnit, Marker, CorrectionLine } from "./page.js";
|
|
22
|
+
export { GENRES, LEVELS, BEHAVIOUR_LEVELS, FEATURE_ROWS, genreRow, behaviourEvidenceOf } from "./genre.js";
|
|
23
|
+
export type { Genre, GenreRow, Level } from "./genre.js";
|
|
24
|
+
export { ACCEPTANCE_STATES, ACCEPTANCE_INDEX_FILE, parseCritKey, parseAcceptanceIndex, acceptanceIndexAt, cellRefusal } from "./acceptance.js";
|
|
25
|
+
export type { CritKey, AcceptanceCell, AcceptanceIndex, AcceptanceIndexReader } from "./acceptance.js";
|
|
26
|
+
export { CORRECTIONS_DIR, CORRECTION_RECORD, parseCorrectionRecord, correctionRecordAt } from "./corrections.js";
|
|
27
|
+
export type { CorrectionRecord, CorrectionRecordReader, CorrectionRecordSource } from "./corrections.js";
|
|
28
|
+
export { BUILT_IN_GENERATORS, genreTableText } from "./generators.js";
|
|
29
|
+
export { KNOB_FIELDS } from "./knobs.js";
|
|
30
|
+
export type { KnobField } from "./knobs.js";
|
|
31
|
+
export { REF_KINDS, createTree, repoOf, resolveRef, coverageSourcesOf, coversKeysOf } from "./tree.js";
|
|
32
|
+
export type { WikiGateConfig, WikiReaders, WikiTree, TreeOptions, ExportSurface, ClosedSetRow, ContractRow, ContractTag, Generator, Generated, GeneratorFactory, GeneratorReaders, PinReader, CoverageItem, CoverageSource, RequiredSource, RefRefusal } from "./tree.js";
|
|
33
|
+
export { PAGE_KINDS, OWNER_REPOS, FRONTMATTER_KEYS, publicnessBans, checkPage, regenerate, stampPage } from "./check.js";
|
|
34
|
+
export type { Finding, PageCheck } from "./check.js";
|
|
35
|
+
export { COVERED_FEAT_STATUSES, readFeatRows, citedByPages, coverageFindings, requiredSummaryOf, rootExportsSource, stableRootExportsSource, liveClosedSetsSource, contractSentencesSource, configurationKeysSource } from "./coverage.js";
|
|
36
|
+
export type { FeatRows, CitedByPages, CoverageReportRow, RequiredSummaryRow } from "./coverage.js";
|
|
37
|
+
export { verdictOf, wikiAnchors, wikiFreshness, wikiCoverage, runWikiGate } from "./run.js";
|
|
38
|
+
export type { Verdict, JudgedFinding, GateOptions, GateResult, WikiGateCommandLine } from "./run.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { V_AGE_MINORS, U_AGE_RELEASES, parseVersion, compareVersions, severityOf, parseRun, vAgeFinding, uAgeFinding, minorReleaseDistance } from "./versions.js";
|
|
2
|
+
export { declarationOf, pinOf, literalTableReader, literalKeyCatalog } from "./source.js";
|
|
3
|
+
export { splitPage, claimBlocks, citationsOf, correctionLineOf, sectionIdOf } from "./page.js";
|
|
4
|
+
export { GENRES, LEVELS, BEHAVIOUR_LEVELS, FEATURE_ROWS, genreRow, behaviourEvidenceOf } from "./genre.js";
|
|
5
|
+
export { ACCEPTANCE_STATES, ACCEPTANCE_INDEX_FILE, parseCritKey, parseAcceptanceIndex, acceptanceIndexAt, cellRefusal } from "./acceptance.js";
|
|
6
|
+
export { CORRECTIONS_DIR, CORRECTION_RECORD, parseCorrectionRecord, correctionRecordAt } from "./corrections.js";
|
|
7
|
+
export { BUILT_IN_GENERATORS, genreTableText } from "./generators.js";
|
|
8
|
+
export { KNOB_FIELDS } from "./knobs.js";
|
|
9
|
+
export { REF_KINDS, createTree, repoOf, resolveRef, coverageSourcesOf, coversKeysOf } from "./tree.js";
|
|
10
|
+
export { PAGE_KINDS, OWNER_REPOS, FRONTMATTER_KEYS, publicnessBans, checkPage, regenerate, stampPage } from "./check.js";
|
|
11
|
+
export { COVERED_FEAT_STATUSES, readFeatRows, citedByPages, coverageFindings, requiredSummaryOf, rootExportsSource, stableRootExportsSource, liveClosedSetsSource, contractSentencesSource, configurationKeysSource } from "./coverage.js";
|
|
12
|
+
export { verdictOf, wikiAnchors, wikiFreshness, wikiCoverage, runWikiGate } from "./run.js";
|
package/dist/knobs.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The knob table: the fields a generated table of configuration knobs may render, one column each, and what the gate
|
|
3
|
+
* holds such a table to. A generator whose region is a table of knobs names the fields it renders (`rowFields`); the
|
|
4
|
+
* gate reads that declaration against this table, never the table's prose.
|
|
5
|
+
*/
|
|
6
|
+
/** One field of a knob row: its id, what its column says, and whether every knob table renders it. */
|
|
7
|
+
export interface KnobField {
|
|
8
|
+
readonly id: string;
|
|
9
|
+
readonly says: string;
|
|
10
|
+
readonly required: boolean;
|
|
11
|
+
}
|
|
12
|
+
/** The fields of a knob row, in the order a table lists them. */
|
|
13
|
+
export declare const KNOB_FIELDS: readonly KnobField[];
|
|
14
|
+
/**
|
|
15
|
+
* What is wrong with the knob table a generator renders, given the fields it names and the text of its region; each
|
|
16
|
+
* problem is one sentence. A coordinate is read for its shape only: whether the file and the line exist is the anchors
|
|
17
|
+
* gate's own work and is not read again here.
|
|
18
|
+
*/
|
|
19
|
+
export declare function knobTableProblems(name: string, fields: readonly string[], text: string): string[];
|
package/dist/knobs.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
const field = (id, says, required = false) => Object.freeze({ id, says, required });
|
|
2
|
+
export const KNOB_FIELDS = Object.freeze([
|
|
3
|
+
field("name", "the knob's name", true),
|
|
4
|
+
field("domain", "the group of knobs it belongs to"),
|
|
5
|
+
field("type", "its value type, with the allowed words of an enumeration"),
|
|
6
|
+
field("default", "the value it takes when unset, as it stands at run time"),
|
|
7
|
+
field("timing", "when a change takes effect"),
|
|
8
|
+
field("writers", "who may set it"),
|
|
9
|
+
field("danger", "the risk axes the catalog names for it"),
|
|
10
|
+
field("embedderKey", "the option an embedder passes in place of the environment name"),
|
|
11
|
+
field("startupOnly", "whether it is read once, at start"),
|
|
12
|
+
field("meaning", "the catalog's own sentence, kept in a column of its own"),
|
|
13
|
+
field("badValue", "where a bad value surfaces, once the catalog declares it"),
|
|
14
|
+
field("readPoints", "where the knob is read: one or more `path:line` coordinates the generator computes from its source", true),
|
|
15
|
+
]);
|
|
16
|
+
const FIELD_IDS = KNOB_FIELDS.map((f) => f.id);
|
|
17
|
+
function cellsOf(line) {
|
|
18
|
+
const inner = line.trim().replace(/^\|/, "").replace(/(?<!\\)\|$/, "");
|
|
19
|
+
return inner.split(/(?<!\\)\|/).map((c) => c.trim());
|
|
20
|
+
}
|
|
21
|
+
const SEPARATOR = /^\|[\s:|-]+\|\s*$/;
|
|
22
|
+
const COORDINATE = /^\S+:[1-9]\d*$/;
|
|
23
|
+
export function knobTableProblems(name, fields, text) {
|
|
24
|
+
const out = [];
|
|
25
|
+
const seen = new Set();
|
|
26
|
+
for (const id of fields) {
|
|
27
|
+
if (!FIELD_IDS.includes(id))
|
|
28
|
+
out.push(`the generator \`${name}\` names the knob field \`${id}\`, which is none of ${FIELD_IDS.join(", ")}`);
|
|
29
|
+
else if (seen.has(id))
|
|
30
|
+
out.push(`the generator \`${name}\` names the knob field \`${id}\` twice`);
|
|
31
|
+
seen.add(id);
|
|
32
|
+
}
|
|
33
|
+
for (const f of KNOB_FIELDS)
|
|
34
|
+
if (f.required && !seen.has(f.id))
|
|
35
|
+
out.push(`the knob table of the generated region \`${name}\` lacks the required knob field \`${f.id}\` (${f.says})`);
|
|
36
|
+
const lines = text.split("\n");
|
|
37
|
+
const header = lines.findIndex((l, i) => /^\|/.test(l) && SEPARATOR.test(lines[i + 1] ?? ""));
|
|
38
|
+
if (header < 0)
|
|
39
|
+
return [...out, `the generated region \`${name}\` names knob fields and holds no table`];
|
|
40
|
+
const columns = cellsOf(lines[header]).length;
|
|
41
|
+
if (columns !== fields.length)
|
|
42
|
+
return [...out, `the generator \`${name}\` names ${fields.length} knob fields and the header row of its region has ${columns} columns`];
|
|
43
|
+
const at = fields.indexOf("readPoints");
|
|
44
|
+
if (at < 0)
|
|
45
|
+
return out;
|
|
46
|
+
const label = fields.indexOf("name");
|
|
47
|
+
const rows = lines.slice(header + 2);
|
|
48
|
+
const end = rows.findIndex((l) => /^\s*$/.test(l));
|
|
49
|
+
for (const row of end < 0 ? rows : rows.slice(0, end)) {
|
|
50
|
+
const cells = cellsOf(row);
|
|
51
|
+
const tokens = (cells[at] ?? "").replace(/`/g, "").split(/[\s,]+/).filter(Boolean);
|
|
52
|
+
if (!tokens.some((t) => COORDINATE.test(t))) {
|
|
53
|
+
const who = label >= 0 ? (cells[label] ?? "").replace(/`/g, "") : row.slice(0, 40);
|
|
54
|
+
out.push(`the \`readPoints\` cell of the knob row \`${who}\` holds no \`path:line\` coordinate: ${JSON.stringify((cells[at] ?? "").slice(0, 60))}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return out;
|
|
58
|
+
}
|
package/dist/page.d.ts
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Page text as the gates read it: the frontmatter, the claim blocks, the generated regions, the fences, the markers.
|
|
3
|
+
* One reading for every gate entry and for the stamp.
|
|
4
|
+
*/
|
|
5
|
+
import type { Level } from "./genre.js";
|
|
6
|
+
/**
|
|
7
|
+
* A page's frontmatter: the first value of each key, and under `_list` every value of each key in order. Keyed by page
|
|
8
|
+
* text, with no prototype, so a key spelled `constructor` or `__proto__` is a key like any other.
|
|
9
|
+
*/
|
|
10
|
+
export type Frontmatter = Record<string, string | undefined> & {
|
|
11
|
+
readonly _list: Record<string, string[] | undefined>;
|
|
12
|
+
};
|
|
13
|
+
/** One frontmatter line: its file line, and its key and value when it reads `key: value`. */
|
|
14
|
+
export interface FrontmatterLine {
|
|
15
|
+
readonly line: number;
|
|
16
|
+
readonly key: string | undefined;
|
|
17
|
+
readonly value: string | undefined;
|
|
18
|
+
readonly raw: string;
|
|
19
|
+
}
|
|
20
|
+
/** A page cut at its frontmatter fences. `bodyLine` is the file line the body starts on. */
|
|
21
|
+
export interface SplitPage {
|
|
22
|
+
readonly fm: Frontmatter | null;
|
|
23
|
+
readonly body: string;
|
|
24
|
+
readonly bodyLine: number;
|
|
25
|
+
readonly fmLines: readonly FrontmatterLine[];
|
|
26
|
+
}
|
|
27
|
+
/** A claim block: a paragraph, a list item or a table row, with its body-relative first line. */
|
|
28
|
+
export interface ClaimBlock {
|
|
29
|
+
readonly line: number;
|
|
30
|
+
readonly text: string;
|
|
31
|
+
}
|
|
32
|
+
/** A generated region: its generator, its arguments, its content, and its span in the body. */
|
|
33
|
+
export interface GenRegion {
|
|
34
|
+
readonly line: number;
|
|
35
|
+
readonly name: string;
|
|
36
|
+
readonly args: string[];
|
|
37
|
+
readonly content: string;
|
|
38
|
+
readonly inline: boolean;
|
|
39
|
+
readonly closed: boolean;
|
|
40
|
+
readonly span: readonly [number, number];
|
|
41
|
+
}
|
|
42
|
+
/** A code fence: its language, its info string, its lines, and whether it closes. */
|
|
43
|
+
export interface Fence {
|
|
44
|
+
readonly line: number;
|
|
45
|
+
readonly lang: string;
|
|
46
|
+
readonly info: string;
|
|
47
|
+
readonly body: string[];
|
|
48
|
+
readonly unclosed?: true;
|
|
49
|
+
}
|
|
50
|
+
/** The page's own text, unit by unit in document order: what the stamp binds. */
|
|
51
|
+
export interface PageUnit {
|
|
52
|
+
readonly kind: "block" | "fence" | "heading" | "header";
|
|
53
|
+
readonly text: string;
|
|
54
|
+
}
|
|
55
|
+
/** A marker: its evidence level, its refs, and where it stands in its text. */
|
|
56
|
+
export interface Marker {
|
|
57
|
+
readonly level: Level;
|
|
58
|
+
readonly refs: string[];
|
|
59
|
+
readonly index: number;
|
|
60
|
+
readonly length: number;
|
|
61
|
+
}
|
|
62
|
+
export declare const GEN_CLOSE = "<!-- /wiki:gen -->";
|
|
63
|
+
/**
|
|
64
|
+
* A page's frontmatter (`key: value` lines between `---` fences; a key may repeat, so every value is kept in order)
|
|
65
|
+
* and its body. `bodyLine` is the file line the body starts on, so findings point at file lines.
|
|
66
|
+
*/
|
|
67
|
+
export declare function splitPage(text: string): SplitPage;
|
|
68
|
+
/** What {@link claimBlocks} cuts a body into. */
|
|
69
|
+
export interface CutBody {
|
|
70
|
+
readonly blocks: ClaimBlock[];
|
|
71
|
+
readonly gens: GenRegion[];
|
|
72
|
+
readonly fences: Fence[];
|
|
73
|
+
readonly units: PageUnit[];
|
|
74
|
+
/** The headings, each with its body-relative line: the sections a block stands under. */
|
|
75
|
+
readonly headings: Array<{
|
|
76
|
+
line: number;
|
|
77
|
+
text: string;
|
|
78
|
+
}>;
|
|
79
|
+
readonly problems: Array<{
|
|
80
|
+
line: number;
|
|
81
|
+
message: string;
|
|
82
|
+
}>;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* The page body cut into what the gates read: claim blocks (a paragraph, a list item or a table row), generated regions,
|
|
86
|
+
* code fences, and the page's own text as units in document order (headings, table header rows, blocks with their
|
|
87
|
+
* generated regions folded to a placeholder, fences) — the text the stamp binds. Lines are body-relative (1 = first).
|
|
88
|
+
*/
|
|
89
|
+
export declare function claimBlocks(body: string): CutBody;
|
|
90
|
+
/** The inline generated regions of a text where they stand outside inline code and a correction's quote: each its offset, length and match. */
|
|
91
|
+
export declare function inlineGensOf(text: string): Array<{
|
|
92
|
+
index: number;
|
|
93
|
+
length: number;
|
|
94
|
+
m: RegExpExecArray;
|
|
95
|
+
}>;
|
|
96
|
+
/** A correction line: the quoted sentence it retires, the correction it names, its date and version. */
|
|
97
|
+
export interface CorrectionLine {
|
|
98
|
+
readonly correction: string;
|
|
99
|
+
readonly date: string;
|
|
100
|
+
readonly version: string;
|
|
101
|
+
/** The retired sentence as quoted: no longer a claim, its markers not judged. */
|
|
102
|
+
readonly quoted: string;
|
|
103
|
+
/** Where the quote stands in the block's text (its opening and closing quote marks included). */
|
|
104
|
+
readonly quoteSpan: readonly [number, number];
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* A block that is a correction line — `"<retired sentence>" — corrected by W-<n> on <YYYY-MM-DD> at <X.Y.Z>: <new
|
|
108
|
+
* sentence> {{…}}` — read; undefined for any other block. The quote ends at the first `" — corrected by `.
|
|
109
|
+
*/
|
|
110
|
+
export declare function correctionLineOf(text: string): CorrectionLine | undefined;
|
|
111
|
+
/** A heading's section id: its words, lowercased, every run of other characters one hyphen (`## The U level` → `the-u-level`). */
|
|
112
|
+
export declare function sectionIdOf(heading: string): string;
|
|
113
|
+
/**
|
|
114
|
+
* The markers of a text, found on its view with inline code, inline regions and a correction line's quote filled in,
|
|
115
|
+
* and read from the text itself: a marker inside backticks is an example, one inside a correction's quote belongs to
|
|
116
|
+
* the sentence it retired, and backticks inside a marker's quoted fragment stay as written. `stripped` is the view,
|
|
117
|
+
* for what follows the last marker.
|
|
118
|
+
*/
|
|
119
|
+
export declare function markersOf(text: string): {
|
|
120
|
+
stripped: string;
|
|
121
|
+
markers: Marker[];
|
|
122
|
+
};
|
|
123
|
+
/** Every ref and generated region a page cites, and its `feats:` — what the coverage gate counts (a region's refs are the generator's, read by the gate). */
|
|
124
|
+
export declare function citationsOf(text: string): {
|
|
125
|
+
feats: string[];
|
|
126
|
+
refs: string[];
|
|
127
|
+
gens: GenRegion[];
|
|
128
|
+
};
|