@sema-agent/wiki-gate 0.0.0-stage → 7.67.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/LICENSE ADDED
@@ -0,0 +1,107 @@
1
+ Business Source License 1.1
2
+
3
+ Parameters
4
+
5
+ Licensor: clay (github.com/clayboby)
6
+ Licensed Work: @sema-agent/core (sema-core) and the satellite packages
7
+ published from this repository (currently
8
+ @sema-agent/mods-kernel, @sema-agent/primitives,
9
+ @sema-agent/core-stores-sql and @sema-agent/wiki-gate)
10
+ (formerly published as @sema-ai/core)
11
+ The Licensed Work is (c) 2026 clay.
12
+ Additional Use Grant: You may make production use of the Licensed Work for
13
+ personal, educational, research, or other
14
+ non-commercial purposes. Commercial production use
15
+ requires a commercial license from the Licensor.
16
+ Change Date: 2030-07-13
17
+ Change License: Apache License, Version 2.0
18
+
19
+ For information about alternative licensing arrangements for the Licensed
20
+ Work, please contact the Licensor.
21
+
22
+ Notice
23
+
24
+ The Business Source License (this document, or the "License") is not an Open
25
+ Source license. However, the Licensed Work will eventually be made available
26
+ under an Open Source License, as stated in this License.
27
+
28
+ License text copyright (c) 2017 MariaDB Corporation Ab, All Rights Reserved.
29
+ "Business Source License" is a trademark of MariaDB Corporation Ab.
30
+
31
+ -----------------------------------------------------------------------------
32
+
33
+ Terms
34
+
35
+ The Licensor hereby grants you the right to copy, modify, create derivative
36
+ works, redistribute, and make non-production use of the Licensed Work. The
37
+ Licensor may make an Additional Use Grant, above, permitting limited
38
+ production use.
39
+
40
+ Effective on the Change Date, or the fourth anniversary of the first publicly
41
+ available distribution of a specific version of the Licensed Work under this
42
+ License, whichever comes first, the Licensor hereby grants you rights under
43
+ the terms of the Change License, and the rights granted in the paragraph
44
+ above terminate.
45
+
46
+ If your use of the Licensed Work does not comply with the requirements
47
+ currently in effect as described in this License, you must purchase a
48
+ commercial license from the Licensor, its affiliated entities, or authorized
49
+ resellers, or you must refrain from using the Licensed Work.
50
+
51
+ All copies of the original and modified Licensed Work, and derivative works
52
+ of the Licensed Work, are subject to this License. This License applies
53
+ separately for each version of the Licensed Work and the Change Date may vary
54
+ for each version of the Licensed Work released by Licensor.
55
+
56
+ You must conspicuously display this License on each original or modified copy
57
+ of the Licensed Work. If you receive the Licensed Work in original or
58
+ modified form from a third party, the terms and conditions set forth in this
59
+ License apply to your use of that work.
60
+
61
+ Any use of the Licensed Work in violation of this License will automatically
62
+ terminate your rights under this License for the current and all other
63
+ versions of the Licensed Work.
64
+
65
+ This License does not grant you any right in any trademark or logo of
66
+ Licensor or its affiliates (provided that you may use a trademark or logo of
67
+ Licensor as expressly required by this License).
68
+
69
+ TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
70
+ AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
71
+ EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
72
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
73
+ TITLE.
74
+
75
+ MariaDB hereby grants you permission to use this License's text to license
76
+ your works, and to refer to it using the trademark "Business Source License",
77
+ as long as you comply with the Covenants of Licensor below.
78
+
79
+ Covenants of Licensor
80
+
81
+ In consideration of the right to use this License's text and the "Business
82
+ Source License" name and trademark, Licensor covenants to MariaDB, and to all
83
+ other recipients of the licensed work to be provided by Licensor:
84
+
85
+ 1. To specify as the Change License the GPL Version 2.0 or any later version,
86
+ or a license that is compatible with GPL Version 2.0 or a later version,
87
+ where "compatible" means that software provided under the Change License can
88
+ be included in a program with software provided under GPL Version 2.0 or a
89
+ later version. Licensor may specify additional Change Licenses without
90
+ limitation.
91
+
92
+ 2. To either: (a) specify an additional grant of rights to use that does not
93
+ impose any additional restriction on the right granted in this License, as
94
+ the Additional Use Grant; or (b) insert the text "None".
95
+
96
+ 3. To specify a Change Date.
97
+
98
+ 4. Not to modify this License in any other way.
99
+
100
+ -----------------------------------------------------------------------------
101
+
102
+ Third-party notices
103
+
104
+ Versions of this package up to and including 1.277.1 were published under the
105
+ MIT License; those published copies remain governed by the license they were
106
+ distributed under. This package incorporates design and (where noted) prompt
107
+ content adapted from MIT-licensed projects — see NOTICE.md for attributions.
package/README.md CHANGED
@@ -1,3 +1,96 @@
1
- # Temporary Holding Version
1
+ # @sema-agent/wiki-gate
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The sema wiki gates: one reader module with three gate entries — anchors, freshness, coverage — and the stamp tool,
4
+ over the repository a configuration object names. A wiki page carries sentences marked with their evidence level and
5
+ the refs they rest on; the gates answer whether every claim is marked, whether every ref still resolves to what was
6
+ stamped, and whether every source the repository requires (for the wiki genre, also the shipped feature rows) is on
7
+ some page.
8
+
9
+ ## One rule set
10
+
11
+ The gate holds every page directory it is pointed at — `docs/wiki` and `docs/manual` alike — to one rule set: the
12
+ genre, and with it the directory, is chosen by the entry's configuration object, never by a page's own header, and the
13
+ package opens no second interface for another genre. The genre is a word of the package's frozen genre table, `GENRES`
14
+ (`wiki` → `docs/wiki`, `manual` → `docs/manual`); a row names its directory, its readers, the evidence levels it admits
15
+ and the coverage it requires of its own. The wiki is read by embedders, deployers and callers, admits `G`, `P`, `V` and
16
+ `I`, and requires the shipped feature rows; the manual is read by users, scripters and admins, admits `G`, `U`, `P` and
17
+ `I`, and requires nothing of its own. A `U` sentence cites one `crit:<repo>@<X.Y.Z>:<cell>` key, and it is evidence only
18
+ while the acceptance index judges that cell `pass` and no later cell supersedes or retires it. A word the table does not
19
+ hold is every entry's refusal (exit 2), by name — there is no default genre and no directory member to name instead.
20
+
21
+ The rules — page shape, markers, refs, digests, severities, corrections, the stamp — are the package's and read the same
22
+ in every repository. A repository hands in its own readers (its export surface, its closed-set and contract-sentence
23
+ registries, its configuration catalog, its literal tables, its internal work-item name form, its test reader when its
24
+ tests are not vitest, its acceptance index and its criteria as it publishes them) and its own generators. A ref whose
25
+ kind needs a reader the repository does not configure is an anchors finding that says so. A source the repository names
26
+ among its `required` or `reported` sources comes with its reader: one named without a reader is every entry's refusal
27
+ (exit 2), and the coverage verdict line names each required source with its coverage. The page index (`page-index`),
28
+ the genre table (`genre-table`) and the table of knob fields (`knob-fields`) are the package's own generators, held by
29
+ every repository; a repository generator spelled like any of them is every entry's refusal.
30
+
31
+ A generator whose region is a table of configuration knobs names the fields its columns render with `rowFields`, out of
32
+ `KNOB_FIELDS`. The anchors gate then holds the region to that table: an unknown field, a field named twice, a missing
33
+ required field (`name`, `readPoints`) and a header row of another width are findings, and so is a knob row whose
34
+ `readPoints` cell holds no `path:line` coordinate. The gate reads the shape of a coordinate, never whether the file
35
+ and the line exist, and it judges a generator only by what the generator declares: a table rendered without a
36
+ `rowFields` declaration is not read as a table of knobs. The package scans no source for read points; the generator of
37
+ the repository that owns the catalog computes them.
38
+
39
+ ## Install
40
+
41
+ ```sh
42
+ npm install --save-dev @sema-agent/wiki-gate
43
+ ```
44
+
45
+ ES modules only (`"type": "module"`), Node >= 20.18.1. One entry, `.`; there are no deep paths. Its one dependency is
46
+ `typescript` at an exact version: the digests a stamp writes are read through that compiler's printer, so another
47
+ version could read the same declaration to another digest.
48
+
49
+ ## Use
50
+
51
+ ```js
52
+ import { wikiAnchors, wikiFreshness, wikiCoverage, runWikiGate } from "@sema-agent/wiki-gate";
53
+
54
+ const config = {
55
+ root: "/path/to/repo", // every path is relative to it
56
+ genre: "wiki", // its row of GENRES names the page directory ("manual" = docs/manual, the same rules)
57
+ redFrom: { wiki: "7.69.0" }, // per genre: below it a finding warns, from it on a finding fails
58
+ readers: { /* snapshot, closedSets, contracts, catalog, tableValues, workItemName, pin, acceptanceIndex, criterionText */ },
59
+ generators: (read) => ({ /* name → { render: (...args) => ({ text, refs }), covers?, rowFields? } */ }),
60
+ required: { /* source name → its reader; one named without a reader cannot be judged */ },
61
+ reported: { /* source name → its reader; counted, never failed */ },
62
+ // optional: identity, releaseDistance, bans, preflight
63
+ };
64
+
65
+ const r = wikiAnchors(config, { strict: true });
66
+ // r.exit: 0 = no failure (warnings counted), 1 = at least one failure, 2 = cannot judge (r.couldNotJudge says why)
67
+ // r.line: the verdict line every run speaks — a gate that judged nothing never looks like a green one
68
+ ```
69
+
70
+ `runWikiGate(config, { argv })` is the command line over the same entries and the stamp:
71
+ `anchors|freshness [--strict] [<page.md> …]`, `coverage [--strict] [--comms <dir>] [--report]`, `stamp <page.md> …`; each
72
+ also takes `--test <dir>` (the acceptance checkout) and `--corrections <dir>` (the correction-record checkout).
73
+ `--report` prints the coverage report as a table (source, cited, size, cited %). An option a command does not take is
74
+ refused by name (exit 2).
75
+
76
+ `citedByPages(tree, pages)` is the one reading of "what the pages cite" (feature rows on `feats:`, and for each source the
77
+ ids cited through a ref of its kind or covered by a generated region); the coverage gate counts from it, and a
78
+ repository's release tooling can ask it whether a name a release moved is on a page.
79
+
80
+ A finding is a warning while the repository's own `package.json` version is below its `redFrom` version for the page's
81
+ genre and a failure from that version on; `strict` takes the version out. A severity a finding carries of its own holds
82
+ under every reading: a `crit:` cell that is not a passing, current one, and an open correction, fail under every
83
+ reading; an old `V` run, or an old `U` cell, is a warning.
84
+
85
+ ## Version
86
+
87
+ `@sema-agent/wiki-gate@X.Y.Z` is cut with `@sema-agent/core@X.Y.Z`: the same version number, built from the same
88
+ commit. A package version moves with every core release whether or not the gates changed. It does not take core.
89
+
90
+ ## Source and package
91
+
92
+ In the sema-core repository the gates run through that repository's own binding, which loads this package's source
93
+ (tsx's `tsImport`) with the repository's readers, so a gate run needs no build. The package published from the same
94
+ source is its `tsc` output, and the two do not differ: the package's frozen export surface
95
+ (`test/export-surface.snapshot.json`), and the tarball test that imports the three entries from a packed tarball and
96
+ judges the same sample pages green and broken pages red, are the proof.
@@ -0,0 +1,45 @@
1
+ /** The verdicts an acceptance cell may carry. Only `pass` vouches for a sentence. */
2
+ export declare const ACCEPTANCE_STATES: readonly string[];
3
+ /** Where an acceptance-index checkout holds its index. */
4
+ export declare const ACCEPTANCE_INDEX_FILE = "criteria-index/INDEX.json";
5
+ /** An acceptance cell's immutable key read: the repository word, the release, and the cell id within that release. */
6
+ export interface CritKey {
7
+ readonly repo: string;
8
+ readonly version: string;
9
+ readonly cell: string;
10
+ }
11
+ /**
12
+ * `<repo>@<X.Y.Z>:<cell>`: `core@7.66.0:G6`, `core@7.66.0:G6.a`, `cli@1.0.161:G-EN1`. Undefined for anything else.
13
+ */
14
+ export declare function parseCritKey(key: string): CritKey | undefined;
15
+ /** One cell as the index judges it now: its verdict, the digest of the text it judged, and the cells it supersedes or retires. */
16
+ export interface AcceptanceCell {
17
+ readonly key: string;
18
+ readonly state: string;
19
+ readonly textSha: string;
20
+ readonly supersedes: readonly string[];
21
+ readonly retires: readonly string[];
22
+ }
23
+ /** An index read at one commit: every key's current cell, and which later cell supersedes or retires a key. */
24
+ export interface AcceptanceIndex {
25
+ readonly commit: string;
26
+ readonly cells: ReadonlyMap<string, AcceptanceCell>;
27
+ readonly supersededBy: ReadonlyMap<string, string>;
28
+ readonly retiredBy: ReadonlyMap<string, string>;
29
+ }
30
+ /**
31
+ * The index text read: a JSON array of rows, each `{ key, state, textSha, supersedes?, retires? }` (other members are
32
+ * the owner's and are not read). The index only grows: a key written again is judged by its last row. A row that does
33
+ * not read this way makes the whole index unreadable — a gate never judges a cell from half an index.
34
+ */
35
+ export declare function parseAcceptanceIndex(text: string, commit: string): AcceptanceIndex;
36
+ /** How an index is read from a checkout named on the command line: undefined when none is named or the file is not there. */
37
+ export type AcceptanceIndexReader = (testRoot: string | undefined) => AcceptanceIndex | undefined;
38
+ /** The reader over `<checkout>/criteria-index/INDEX.json`, at the commit `commitOf` reads for the checkout. */
39
+ export declare function acceptanceIndexAt({ commitOf }: {
40
+ readonly commitOf: (dir: string) => string;
41
+ }): AcceptanceIndexReader;
42
+ /** Why a cell is no evidence, or undefined when it vouches (`pass`, not superseded, not retired). */
43
+ export declare function cellRefusal(index: AcceptanceIndex, key: string): string | undefined;
44
+ /** Whether a key's version is one this repository's tree has reached. */
45
+ export declare function keyReached(key: CritKey, version: string): boolean;
@@ -0,0 +1,79 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { compareVersions, parseVersion } from "./versions.js";
4
+ export const ACCEPTANCE_STATES = Object.freeze(["pass", "fail", "obs", "info", "not_constructed", "exempt"]);
5
+ export const ACCEPTANCE_INDEX_FILE = "criteria-index/INDEX.json";
6
+ const CRIT_KEY = /^([a-z][a-z0-9-]*)@(\d+\.\d+\.\d+):(G(?:\d+|-[A-Za-z0-9][A-Za-z0-9-]*)(?:\.[A-Za-z0-9]+)*)$/;
7
+ export function parseCritKey(key) {
8
+ const m = CRIT_KEY.exec(key);
9
+ return m && parseVersion(m[2]) ? { repo: m[1], version: m[2], cell: m[3] } : undefined;
10
+ }
11
+ export function parseAcceptanceIndex(text, commit) {
12
+ let rows;
13
+ try {
14
+ rows = JSON.parse(text);
15
+ }
16
+ catch (e) {
17
+ throw new Error(`the acceptance index is not JSON: ${e.message}`);
18
+ }
19
+ if (!Array.isArray(rows))
20
+ throw new Error("the acceptance index is not an array of rows");
21
+ const keys = (v, at) => {
22
+ if (v === undefined)
23
+ return [];
24
+ if (!Array.isArray(v) || v.some((k) => typeof k !== "string" || parseCritKey(k) === undefined))
25
+ throw new Error(`${at} is not an array of acceptance keys`);
26
+ return v;
27
+ };
28
+ const cells = new Map();
29
+ const supersededBy = new Map();
30
+ const retiredBy = new Map();
31
+ rows.forEach((row, i) => {
32
+ const at = `acceptance index row ${i + 1}`;
33
+ if (row === null || typeof row !== "object" || Array.isArray(row))
34
+ throw new Error(`${at} is not an object`);
35
+ const r = row;
36
+ if (typeof r.key !== "string" || parseCritKey(r.key) === undefined)
37
+ throw new Error(`${at}: key ${JSON.stringify(r.key)} is not <repo>@<X.Y.Z>:<cell>`);
38
+ if (typeof r.state !== "string" || !ACCEPTANCE_STATES.includes(r.state))
39
+ throw new Error(`${at}: state ${JSON.stringify(r.state)} is not one of ${ACCEPTANCE_STATES.join(", ")}`);
40
+ if (typeof r.textSha !== "string" || r.textSha.length === 0)
41
+ throw new Error(`${at}: textSha is not a non-empty string`);
42
+ const cell = { key: r.key, state: r.state, textSha: r.textSha, supersedes: keys(r.supersedes, `${at}: supersedes`), retires: keys(r.retires, `${at}: retires`) };
43
+ cells.set(cell.key, cell);
44
+ });
45
+ for (const cell of cells.values()) {
46
+ for (const k of cell.supersedes)
47
+ supersededBy.set(k, cell.key);
48
+ for (const k of cell.retires)
49
+ retiredBy.set(k, cell.key);
50
+ }
51
+ return { commit, cells, supersededBy, retiredBy };
52
+ }
53
+ export function acceptanceIndexAt({ commitOf }) {
54
+ return (testRoot) => {
55
+ if (testRoot === undefined)
56
+ return undefined;
57
+ const file = join(testRoot, ACCEPTANCE_INDEX_FILE);
58
+ if (!existsSync(file))
59
+ return undefined;
60
+ return parseAcceptanceIndex(readFileSync(file, "utf8"), commitOf(testRoot));
61
+ };
62
+ }
63
+ export function cellRefusal(index, key) {
64
+ const cell = index.cells.get(key);
65
+ if (cell === undefined)
66
+ return `the acceptance index (at ${index.commit}) holds no cell ${key}`;
67
+ if (cell.state !== "pass")
68
+ return `the acceptance index (at ${index.commit}) judges ${key} \`${cell.state}\`; only \`pass\` vouches for a sentence`;
69
+ const by = index.supersededBy.get(key);
70
+ if (by !== undefined)
71
+ return `${key} is superseded by ${by}`;
72
+ const retired = index.retiredBy.get(key);
73
+ if (retired !== undefined)
74
+ return `${key} is retired by ${retired}`;
75
+ return undefined;
76
+ }
77
+ export function keyReached(key, version) {
78
+ return compareVersions(key.version, version) <= 0;
79
+ }
@@ -0,0 +1,57 @@
1
+ import { type Generated, type Generator, type WikiTree } from "./tree.js";
2
+ import { type Severity } from "./versions.js";
3
+ export declare const PAGE_KINDS: readonly string[];
4
+ /** The repositories that own pages; a page-id is prefixed by one of them, or by a genre's own word for a page about the genre. */
5
+ export declare const OWNER_REPOS: readonly string[];
6
+ /** A frontmatter key and whether it may repeat. A key not in this table is a finding (a typo is not a new key). */
7
+ export declare const FRONTMATTER_KEYS: Readonly<Record<string, "one" | "many">>;
8
+ /** The gate a finding belongs to, the page and file line it points at, its sentence, and a severity of its own. */
9
+ export interface Finding {
10
+ readonly gate: "anchors" | "freshness" | "coverage";
11
+ readonly page: string;
12
+ readonly line: number;
13
+ readonly message: string;
14
+ readonly fixed?: Severity;
15
+ }
16
+ /**
17
+ * What page text, marker text and generated text never carry: identifiers of the repositories' internal record. A test
18
+ * file path inside a `pin:` ref is a coordinate and is exempt (it is removed before these run). The work-item name form
19
+ * is the internal record's own reading of one, its flags kept (a global or sticky flag is dropped: each line is matched
20
+ * on its own); a repository that configures none has no such row. A repository's own forms (`extra`) follow the shared ones.
21
+ */
22
+ export declare function publicnessBans(workItemName?: RegExp, extra?: ReadonlyArray<readonly [string, RegExp]>): ReadonlyArray<readonly [string, RegExp]>;
23
+ /** A generator by name; a name the generators object only inherits (`constructor`) is no generator. */
24
+ export declare const generatorOf: (tree: Pick<WikiTree, "generators">, name: string) => Generator | undefined;
25
+ /** A region's generation, or why it cannot be had: a generator's answer that is not `{ text, refs }` is unreadable. */
26
+ export declare function generate(gen: Generator, args: readonly string[]): Generated;
27
+ /** The findings of one page, the digests the stamp writes, the coordinates of its refs, and its marker levels. */
28
+ export interface PageCheck {
29
+ readonly findings: Finding[];
30
+ readonly digests: Record<string, string>;
31
+ readonly coords: Record<string, string>;
32
+ readonly levels: Record<string, number>;
33
+ }
34
+ /**
35
+ * Check one page. Findings carry the gate that owns them (`anchors` | `freshness`), a file line and a sentence.
36
+ * `stamp: true` is the stamp tool's reading: the stamped digests are not compared (they are about to be rewritten).
37
+ */
38
+ export declare function checkPage(tree: WikiTree, rel: string, text: string, { stamp }?: {
39
+ stamp?: boolean;
40
+ }): PageCheck;
41
+ /**
42
+ * The page with every generated region rewritten from a fresh generation — the regions `claimBlocks` reads, so an
43
+ * example inside inline code or a code fence is never rewritten; a region whose generator fails is left as it is.
44
+ */
45
+ export declare function regenerate(tree: WikiTree, text: string): string;
46
+ /**
47
+ * The stamp: regenerate every region, then rewrite `verified:` and every `anchor:` line from the tree. Refuses (returns
48
+ * the anchors findings, no text) while the page has any — a stamp vouches for a page that resolves, never for one that does not.
49
+ */
50
+ export declare function stampPage(tree: WikiTree, rel: string, text: string, { commit }: {
51
+ commit: string;
52
+ }): {
53
+ text: string;
54
+ anchors: number;
55
+ } | {
56
+ refused: Finding[];
57
+ };