@leji-org/leji 1.0.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/README.md +36 -0
- package/assets-manifest.json +25 -0
- package/cli.json +82 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +4 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/conformance.d.ts +24 -0
- package/dist/commands/conformance.js +111 -0
- package/dist/commands/conformance.js.map +1 -0
- package/dist/commands/docs.d.ts +32 -0
- package/dist/commands/docs.js +196 -0
- package/dist/commands/docs.js.map +1 -0
- package/dist/commands/freshness.d.ts +21 -0
- package/dist/commands/freshness.js +41 -0
- package/dist/commands/freshness.js.map +1 -0
- package/dist/commands/indexgen.d.ts +55 -0
- package/dist/commands/indexgen.js +256 -0
- package/dist/commands/indexgen.js.map +1 -0
- package/dist/commands/init.d.ts +28 -0
- package/dist/commands/init.js +378 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/validate.d.ts +25 -0
- package/dist/commands/validate.js +359 -0
- package/dist/commands/validate.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +324 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/findings.d.ts +17 -0
- package/dist/lib/findings.js +29 -0
- package/dist/lib/findings.js.map +1 -0
- package/dist/lib/frontmatter.d.ts +14 -0
- package/dist/lib/frontmatter.js +28 -0
- package/dist/lib/frontmatter.js.map +1 -0
- package/dist/lib/fsx.d.ts +21 -0
- package/dist/lib/fsx.js +100 -0
- package/dist/lib/fsx.js.map +1 -0
- package/dist/lib/git.d.ts +10 -0
- package/dist/lib/git.js +55 -0
- package/dist/lib/git.js.map +1 -0
- package/dist/lib/layer.d.ts +32 -0
- package/dist/lib/layer.js +138 -0
- package/dist/lib/layer.js.map +1 -0
- package/dist/lib/manifest.d.ts +62 -0
- package/dist/lib/manifest.js +54 -0
- package/dist/lib/manifest.js.map +1 -0
- package/dist/lib/schemas.d.ts +38 -0
- package/dist/lib/schemas.js +57 -0
- package/dist/lib/schemas.js.map +1 -0
- package/package.json +61 -0
- package/schemas/README.md +3 -0
- package/schemas/agent-profile.schema.json +129 -0
- package/schemas/context-changelog.schema.json +150 -0
- package/schemas/context-index.schema.json +137 -0
- package/schemas/context-manifest.schema.json +253 -0
- package/schemas/decision-record.schema.json +84 -0
- package/templates/README.md +5 -0
- package/templates/agent-profile.md +25 -0
- package/templates/agents/core.md +27 -0
- package/templates/boot-profile.md +39 -0
- package/templates/decision-record.md +28 -0
- package/templates/docs-viewer-assets/PROVENANCE.txt +18 -0
- package/templates/docs-viewer-assets/docsify-sidebar-collapse.min.css +24 -0
- package/templates/docs-viewer-assets/docsify-sidebar-collapse.min.js +149 -0
- package/templates/docs-viewer-assets/docsify.min.js +1 -0
- package/templates/docs-viewer-assets/search.min.js +314 -0
- package/templates/docs-viewer-assets/vue.css +1063 -0
- package/templates/docs-viewer.html +63 -0
- package/templates/leji.json +56 -0
package/README.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# leji
|
|
2
|
+
|
|
3
|
+
Reference SDK and CLI for the [Leji specification](https://leji.org): the open
|
|
4
|
+
specification for the shared context layer of AI-native teams.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
npm install -g @leji-org/leji # or: npx @leji-org/leji / npm create leji
|
|
8
|
+
leji init # bootstrap a context layer interactively
|
|
9
|
+
leji validate # manifest, artifacts, frontmatter, lint rules
|
|
10
|
+
leji index # generate the context index
|
|
11
|
+
leji index --check # fail when the index is stale
|
|
12
|
+
leji changelog check # append-only discipline
|
|
13
|
+
leji freshness # review-horizon report
|
|
14
|
+
leji conformance # score the layer against its claimed level
|
|
15
|
+
leji docs # project a browsable viewer (--serve for a localhost preview)
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Behaviorally identical to the `leji` package on PyPI: same commands, same
|
|
19
|
+
flags, same findings, same exit codes (0 clean, 1 findings, 2 usage error).
|
|
20
|
+
Both implementations are tested against one shared fixture suite. Install
|
|
21
|
+
whichever matches your toolchain; agents and CI see the same tool either way.
|
|
22
|
+
|
|
23
|
+
Supports spec line **1.0**. Schemas and templates for that line ship inside
|
|
24
|
+
the package; no network access is needed.
|
|
25
|
+
|
|
26
|
+
A programmatic API is exported alongside the CLI:
|
|
27
|
+
|
|
28
|
+
```js
|
|
29
|
+
import { validateLayer, writeIndex, conformanceReport } from '@leji-org/leji';
|
|
30
|
+
|
|
31
|
+
const { findings } = validateLayer('.');
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
- Specification: https://leji.org
|
|
35
|
+
- Source: https://github.com/leji-org/leji (`packages/sdk`)
|
|
36
|
+
- License: Apache-2.0
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"specLine": "1.0",
|
|
3
|
+
"sourceCommit": "53f0d9b6153af74ee35f42553ff36e7bce2dd66e",
|
|
4
|
+
"files": {
|
|
5
|
+
"schemas/README.md": "sha256:8dc011dbb3c5b33d4aac4e35a4f38264da3b582f0da83e04a5c8cefb4c1408cf",
|
|
6
|
+
"schemas/agent-profile.schema.json": "sha256:4502ddf71db0c0839a3a0d9b1b01a00f03b3871db4bfe399c464791640054077",
|
|
7
|
+
"schemas/context-changelog.schema.json": "sha256:b3dd88286f1900d17695422bf5b1b166a84d3c95370029004f34b70e61b015b1",
|
|
8
|
+
"schemas/context-index.schema.json": "sha256:41f254829e8116767ab1c72f5b041746ff3c66c1832d3d0d416c9344237a5c99",
|
|
9
|
+
"schemas/context-manifest.schema.json": "sha256:c19b627fdd7c02daa9db64244d75cbc3d928a86bf8f47c9bd02e8d8e87eb776e",
|
|
10
|
+
"schemas/decision-record.schema.json": "sha256:ad870d1943196e1a40303228b839a170b11e4de4ede324cdd103dfe87f317772",
|
|
11
|
+
"templates/README.md": "sha256:f5c19da485cecc87f8edf03ec5a28a8246b4d065154d2d61bbf2a82cc685448d",
|
|
12
|
+
"templates/agent-profile.md": "sha256:64922b74a79778c4b8736ebcdd2d6d11faf075dc3ed24413198e00f9ca1f0f9e",
|
|
13
|
+
"templates/agents/core.md": "sha256:6118a42d72712be08e10ba21843894b6afb895b350af6f5a308375f7d7281c5a",
|
|
14
|
+
"templates/boot-profile.md": "sha256:d6d07ac45116856057cf1ed856f3663d2ec8ed5f60271ed44133f6217740a5f4",
|
|
15
|
+
"templates/decision-record.md": "sha256:ea122e7ceb5984b1610c66df2503128d619b312b5b5fc57c29f4e017a300c91c",
|
|
16
|
+
"templates/docs-viewer-assets/PROVENANCE.txt": "sha256:0a1eca8f94ff27e4d1ad8661a1a4b454fc280ee514de75668b07927784107093",
|
|
17
|
+
"templates/docs-viewer-assets/docsify-sidebar-collapse.min.css": "sha256:ef27a5cc38b5fe5608afd766a9d3c181ab981131398a05fc2b37ddfa0b5abdc9",
|
|
18
|
+
"templates/docs-viewer-assets/docsify-sidebar-collapse.min.js": "sha256:78282f65a6dc77f098ad856b32f64b167e363cc4c8d8767879ab8c4577c5b363",
|
|
19
|
+
"templates/docs-viewer-assets/docsify.min.js": "sha256:9123f808d3f6ad736b4a8f99944a611f87c5d4f9328030080a5c029ed5f450a5",
|
|
20
|
+
"templates/docs-viewer-assets/search.min.js": "sha256:397f54df759c3093069b2d2acb3a2b19edd33e2d77af09c445d34d2c4853a015",
|
|
21
|
+
"templates/docs-viewer-assets/vue.css": "sha256:6c4015a7fa6e48e6c3994060ebb66a601bfc848a835700b522ce4bea456f123d",
|
|
22
|
+
"templates/docs-viewer.html": "sha256:c07e61583f56acadb44d82de36b682ef39236e2873e83af384283f196f037d9b",
|
|
23
|
+
"templates/leji.json": "sha256:4f6a33ab4152c0fa2ab5d1dfa35363bded25294ebb36118918d05396ee9a3a65"
|
|
24
|
+
}
|
|
25
|
+
}
|
package/cli.json
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "leji",
|
|
3
|
+
"summary": "Reference CLI for the Leji specification: validate, index, changelog, freshness, conformance, docs, and init for a shared context layer.",
|
|
4
|
+
"usage": "leji <command> [options]",
|
|
5
|
+
"globalOptions": [
|
|
6
|
+
{ "flags": "--root <dir>", "summary": "Repository root to operate on (default: the current directory)." },
|
|
7
|
+
{ "flags": "--json", "summary": "Machine-readable JSON output instead of human-readable text." },
|
|
8
|
+
{ "flags": "-V, --version", "summary": "Print the SDK version and exit." },
|
|
9
|
+
{ "flags": "-h, --help", "summary": "Show help and exit." }
|
|
10
|
+
],
|
|
11
|
+
"exitCodes": [
|
|
12
|
+
{ "code": 0, "meaning": "Clean. No errors; warnings are allowed." },
|
|
13
|
+
{ "code": 1, "meaning": "Findings. At least one error was reported." },
|
|
14
|
+
{ "code": 2, "meaning": "Usage error, or an internal failure (e.g. init refusing to overwrite)." }
|
|
15
|
+
],
|
|
16
|
+
"commands": [
|
|
17
|
+
{
|
|
18
|
+
"name": "validate",
|
|
19
|
+
"summary": "Validate the context layer: manifest, artifacts, frontmatter, and lint rules.",
|
|
20
|
+
"usage": "leji validate [--root <dir>] [--json]",
|
|
21
|
+
"description": "Loads leji.json and checks it against the schemas and the lint rules: declared files exist, categories are populated, vendor entrypoints redirect to the boot profile, frontmatter is valid, and (per the claimed conformance level) the index is current and the changelog is append-only. The single command CI runs to keep a context layer honest.",
|
|
22
|
+
"options": [],
|
|
23
|
+
"examples": ["leji validate", "leji validate --root . --json"]
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"name": "index",
|
|
27
|
+
"summary": "Generate the context index at the declared path, or verify it is current.",
|
|
28
|
+
"usage": "leji index [--check] [--root <dir>] [--json]",
|
|
29
|
+
"description": "Scans the mapped category paths and writes the context index to machine.indexPath. Ids are stable: a renamed or moved document keeps its id. With --check it writes nothing and instead fails when the stored index no longer matches the tree (a stale index is a hard failure).",
|
|
30
|
+
"options": [{ "flags": "--check", "summary": "Verify the stored index is current with the tree; write nothing." }],
|
|
31
|
+
"examples": ["leji index", "leji index --check"]
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"name": "changelog check",
|
|
35
|
+
"summary": "Verify the machine changelog: schema and append-only discipline.",
|
|
36
|
+
"usage": "leji changelog check [--strict] [--root <dir>] [--json]",
|
|
37
|
+
"description": "Validates the declared changelog against its schema and enforces append-only discipline against the git baseline: surviving entries are immutable, and entries may be removed only from the oldest end and only alongside a compaction entry. Without a git baseline the discipline is unverifiable and reported as a warning.",
|
|
38
|
+
"options": [{ "flags": "--strict", "summary": "Treat an unverifiable append-only check (no git baseline) as an error." }],
|
|
39
|
+
"examples": ["leji changelog check", "leji changelog check --strict"]
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"name": "freshness",
|
|
43
|
+
"summary": "Report review horizons across category documents and agent profiles.",
|
|
44
|
+
"usage": "leji freshness [--strict] [--root <dir>] [--json]",
|
|
45
|
+
"description": "Lists documents whose freshness.reviewAfter horizon has passed (expired) or falls within the next 30 days (upcoming). Report-only by default; expired horizons are warnings.",
|
|
46
|
+
"options": [{ "flags": "--strict", "summary": "Treat expired horizons as errors instead of warnings." }],
|
|
47
|
+
"examples": ["leji freshness", "leji freshness --strict --json"]
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"name": "conformance",
|
|
51
|
+
"summary": "Score the context layer against its claimed conformance level.",
|
|
52
|
+
"usage": "leji conformance [--root <dir>] [--json]",
|
|
53
|
+
"description": "Runs the core, indexed, governed, and federated checklists. Machine-checkable items pass or fail; process items (review gate, CI, external consumers) are reported as manual. A claim above the verified level is an error.",
|
|
54
|
+
"options": [],
|
|
55
|
+
"examples": ["leji conformance", "leji conformance --json"]
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"name": "docs",
|
|
59
|
+
"summary": "Generate the static docs viewer, and optionally serve it locally.",
|
|
60
|
+
"usage": "leji docs [--serve] [--port <n>] [--root <dir>] [--json]",
|
|
61
|
+
"description": "Projects the context index into a browsable Docsify viewer: writes index.html (with a frontmatter-stripping hook) and a deterministic _sidebar.md into the context root. Presentation is non-normative; this is the reference projection. With --serve it then serves the repository on 127.0.0.1 (a local preview, never hosting).",
|
|
62
|
+
"options": [
|
|
63
|
+
{ "flags": "--serve", "summary": "Serve the repository on 127.0.0.1 after generating." },
|
|
64
|
+
{ "flags": "--port <n>", "summary": "Port for --serve. Overrides the manifest docs.port; default 5354 (LEJI on a phone keypad); 0 picks a free port." }
|
|
65
|
+
],
|
|
66
|
+
"examples": ["leji docs", "leji docs --serve", "leji docs --serve --port 0"]
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"name": "init",
|
|
70
|
+
"summary": "Bootstrap a new context layer from the templates.",
|
|
71
|
+
"usage": "leji init [--dir <path>] [--yes] [--level <core|indexed>] [--name <name>]",
|
|
72
|
+
"description": "Interactively scaffolds a context layer: leji.json, a boot profile, seeded category documents, a first decision record, and (at the indexed level) a generated index and machine changelog. Refuses to overwrite an existing leji.json. The same entrypoint backs `npm create leji`.",
|
|
73
|
+
"options": [
|
|
74
|
+
{ "flags": "--dir <path>", "summary": "Target directory (default: the current directory)." },
|
|
75
|
+
{ "flags": "--yes, -y", "summary": "Accept all defaults; run non-interactively." },
|
|
76
|
+
{ "flags": "--level <level>", "summary": "Conformance level to claim: core or indexed (default: core)." },
|
|
77
|
+
{ "flags": "--name <name>", "summary": "Context layer name (default: derived from the directory)." }
|
|
78
|
+
],
|
|
79
|
+
"examples": ["leji init", "leji init --yes --level indexed --name acme-context"]
|
|
80
|
+
}
|
|
81
|
+
]
|
|
82
|
+
}
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AAEjC,OAAO,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type Finding } from '../lib/findings.js';
|
|
2
|
+
import { type ConformanceLevel } from '../lib/manifest.js';
|
|
3
|
+
export type ItemStatus = 'pass' | 'fail' | 'manual';
|
|
4
|
+
export interface ChecklistItem {
|
|
5
|
+
id: string;
|
|
6
|
+
level: ConformanceLevel;
|
|
7
|
+
description: string;
|
|
8
|
+
status: ItemStatus;
|
|
9
|
+
detail?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface ConformanceResult {
|
|
12
|
+
claimedLevel: ConformanceLevel | null;
|
|
13
|
+
/** Highest level whose machine-checkable items all pass. */
|
|
14
|
+
verifiedLevel: ConformanceLevel | null;
|
|
15
|
+
items: ChecklistItem[];
|
|
16
|
+
findings: Finding[];
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Score the layer against the conformance checklists. Machine-checkable items
|
|
20
|
+
* pass or fail; process items (review gate, CI, federation consumers) are
|
|
21
|
+
* reported as `manual` and never block a level. A claim above the verified
|
|
22
|
+
* level is an error.
|
|
23
|
+
*/
|
|
24
|
+
export declare function conformanceReport(root: string): ConformanceResult;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import * as path from 'node:path';
|
|
2
|
+
import { finding, sortFindings } from '../lib/findings.js';
|
|
3
|
+
import { gitToplevel } from '../lib/git.js';
|
|
4
|
+
import { exists, isFile } from '../lib/fsx.js';
|
|
5
|
+
import { scanAgentProfiles } from '../lib/layer.js';
|
|
6
|
+
import { CONFORMANCE_LEVELS, claimedLevel, loadManifest, } from '../lib/manifest.js';
|
|
7
|
+
import { checkIndex } from './indexgen.js';
|
|
8
|
+
import { checkChangelogAppendOnly, validateLayer } from './validate.js';
|
|
9
|
+
import { freshnessReport } from './freshness.js';
|
|
10
|
+
/**
|
|
11
|
+
* Score the layer against the conformance checklists. Machine-checkable items
|
|
12
|
+
* pass or fail; process items (review gate, CI, federation consumers) are
|
|
13
|
+
* reported as `manual` and never block a level. A claim above the verified
|
|
14
|
+
* level is an error.
|
|
15
|
+
*/
|
|
16
|
+
export function conformanceReport(root) {
|
|
17
|
+
const items = [];
|
|
18
|
+
const findings = [];
|
|
19
|
+
const { manifest } = loadManifest(root);
|
|
20
|
+
const validation = validateLayer(root);
|
|
21
|
+
const errorsBy = (rules) => validation.findings.filter((f) => f.severity === 'error' && rules.includes(f.rule));
|
|
22
|
+
const add = (id, level, description, status, detail) => {
|
|
23
|
+
items.push(detail ? { id, level, description, status, detail } : { id, level, description, status });
|
|
24
|
+
};
|
|
25
|
+
// --- core ---
|
|
26
|
+
const manifestErrors = errorsBy(['manifest-missing', 'manifest-parse', 'manifest-schema', 'manifest-line']);
|
|
27
|
+
add('manifest-valid', 'core', 'leji.json at the repository root, valid against the manifest schema', manifestErrors.length === 0 ? 'pass' : 'fail', manifestErrors[0]?.message);
|
|
28
|
+
// Git is a hard core MUST (context-layer.md). Deliberately reported as `manual`
|
|
29
|
+
// (not `fail`) when git can't be resolved, so the scorer stays usable on degraded
|
|
30
|
+
// copies and detached checkouts; the requirement is surfaced loudly by `validate`
|
|
31
|
+
// (the `git-required` finding), which is where enforcement lives. This mirrors the
|
|
32
|
+
// changelog item's "unverifiable without a git baseline" handling.
|
|
33
|
+
const inGit = gitToplevel(root) !== null;
|
|
34
|
+
add('git', 'core', 'the context layer lives in a git repository, versioned with the work it describes', inGit ? 'pass' : 'manual', inGit ? undefined : 'not resolvable to a git repository here; verify in the canonical repository');
|
|
35
|
+
if (!manifest) {
|
|
36
|
+
findings.push(...validation.findings.filter((f) => f.severity === 'error'));
|
|
37
|
+
return { claimedLevel: null, verifiedLevel: null, items, findings: sortFindings(findings) };
|
|
38
|
+
}
|
|
39
|
+
const bootErrors = errorsBy(['missing-declared-file']).filter((f) => f.path === manifest.bootProfilePath);
|
|
40
|
+
add('boot-profile', 'core', 'a boot profile at the declared path covering identity, loading, and posture', bootErrors.length === 0 ? 'pass' : 'fail', bootErrors[0]?.message);
|
|
41
|
+
const categoryErrors = errorsBy([
|
|
42
|
+
'categories-minimum',
|
|
43
|
+
'category-path-missing',
|
|
44
|
+
'category-empty',
|
|
45
|
+
'decisions-empty',
|
|
46
|
+
]);
|
|
47
|
+
add('categories', 'core', 'at least domain or system mapped and populated, plus decisions with a real record', categoryErrors.length === 0 ? 'pass' : 'fail', categoryErrors[0]?.message);
|
|
48
|
+
add('owner', 'core', 'a named primary owner', manifest.owners?.primary?.name ? 'pass' : 'fail');
|
|
49
|
+
const vendorErrors = errorsBy(['vendor-adapter-redirect']).concat(errorsBy(['missing-declared-file']).filter((f) => (manifest.vendorAdapters ?? []).includes(f.path ?? '')));
|
|
50
|
+
add('vendor-redirects', 'core', 'vendor entrypoint files, if present, redirect to the boot profile', vendorErrors.length === 0 ? 'pass' : 'fail', vendorErrors[0]?.message);
|
|
51
|
+
// --- indexed ---
|
|
52
|
+
const indexResult = checkIndex(root, manifest);
|
|
53
|
+
add('index-current', 'indexed', 'a generated context index, current with the tree', indexResult.stale === false ? 'pass' : 'fail', indexResult.findings[0]?.message);
|
|
54
|
+
const changelogRel = manifest.machine?.changelogPath;
|
|
55
|
+
if (changelogRel && isFile(path.join(root, changelogRel))) {
|
|
56
|
+
const changelog = checkChangelogAppendOnly(root, changelogRel);
|
|
57
|
+
const changelogErrors = changelog.findings.filter((f) => f.severity === 'error');
|
|
58
|
+
if (changelogErrors.length > 0) {
|
|
59
|
+
add('changelog', 'indexed', 'a machine-readable changelog; layer changes append entries', 'fail', changelogErrors[0].message);
|
|
60
|
+
}
|
|
61
|
+
else if (!changelog.verified) {
|
|
62
|
+
add('changelog', 'indexed', 'a machine-readable changelog; layer changes append entries', 'manual', 'append-only discipline unverifiable without a git baseline');
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
add('changelog', 'indexed', 'a machine-readable changelog; layer changes append entries', 'pass');
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
add('changelog', 'indexed', 'a machine-readable changelog; layer changes append entries', 'fail', changelogRel ? `declared changelog ${changelogRel} does not exist` : 'no machine.changelogPath declared');
|
|
70
|
+
}
|
|
71
|
+
// --- governed ---
|
|
72
|
+
add('review-gate', 'governed', "layer changes ride the repository's review gate; people approve", 'manual');
|
|
73
|
+
const validProfiles = scanAgentProfiles(root, manifest).filter((p) => p.findings.length === 0);
|
|
74
|
+
add('agent-profiles', 'governed', 'agent profiles (at least a core profile) valid against the profile schema', validProfiles.length > 0 ? 'pass' : 'fail', validProfiles.length === 0 ? 'no valid agent profile found' : undefined);
|
|
75
|
+
add('ci-validates', 'governed', 'CI validates the surface: manifest, index currency, changelog discipline, profiles', 'manual');
|
|
76
|
+
const freshness = freshnessReport(root, manifest);
|
|
77
|
+
add('freshness-declared', 'governed', 'freshness horizons are declared and checked (report-only is acceptable)', freshness.declared > 0 ? 'pass' : 'fail', freshness.declared === 0
|
|
78
|
+
? 'no freshness.reviewAfter declared anywhere'
|
|
79
|
+
: `${freshness.declared} horizon(s) declared, ${freshness.expired.length} expired`);
|
|
80
|
+
// --- federated ---
|
|
81
|
+
add('consumed-externally', 'federated', 'the context layer is consumed by at least one other repository as a pinned docs-only mount', 'manual');
|
|
82
|
+
add('stale-pin-reporting', 'federated', 'stale-pin reporting is in place', 'manual');
|
|
83
|
+
const mounts = manifest.federation?.mounts ?? [];
|
|
84
|
+
if (mounts.length > 0) {
|
|
85
|
+
const missing = mounts.filter((m) => !exists(path.join(root, m.path)));
|
|
86
|
+
add('sibling-mounts', 'federated', 'sibling layers are mounted with ownership intact', missing.length === 0 ? 'pass' : 'fail', missing.length > 0 ? `mount path ${missing[0].path} does not exist` : undefined);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
add('sibling-mounts', 'federated', 'sibling layers are mounted with ownership intact', 'manual', 'no federation.mounts declared');
|
|
90
|
+
}
|
|
91
|
+
// --- scoring ---
|
|
92
|
+
let verified = null;
|
|
93
|
+
for (const level of CONFORMANCE_LEVELS) {
|
|
94
|
+
const machineItems = items.filter((i) => i.level === level && i.status !== 'manual');
|
|
95
|
+
if (machineItems.some((i) => i.status === 'fail'))
|
|
96
|
+
break;
|
|
97
|
+
verified = level;
|
|
98
|
+
}
|
|
99
|
+
const claimed = claimedLevel(manifest);
|
|
100
|
+
// Verification answers "does the claim hold?", not "what could be claimed":
|
|
101
|
+
// never report a verified level above the claim (manual items make higher
|
|
102
|
+
// levels unknowable to tooling anyway).
|
|
103
|
+
if (verified !== null && CONFORMANCE_LEVELS.indexOf(verified) > CONFORMANCE_LEVELS.indexOf(claimed)) {
|
|
104
|
+
verified = claimed;
|
|
105
|
+
}
|
|
106
|
+
if (verified === null || CONFORMANCE_LEVELS.indexOf(claimed) > CONFORMANCE_LEVELS.indexOf(verified)) {
|
|
107
|
+
findings.push(finding('conformance-claim', 'error', `claimed level "${claimed}" exceeds the verified level "${verified ?? 'none'}"`, 'leji.json'));
|
|
108
|
+
}
|
|
109
|
+
return { claimedLevel: claimed, verifiedLevel: verified, items, findings: sortFindings(findings) };
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=conformance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conformance.js","sourceRoot":"","sources":["../../src/commands/conformance.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import * as http from 'node:http';
|
|
2
|
+
import { type Finding } from '../lib/findings.js';
|
|
3
|
+
import { type Manifest } from '../lib/manifest.js';
|
|
4
|
+
/** Preview-port precedence: explicit --port, then manifest docs.port, then 5354 (LEJI on a phone keypad). */
|
|
5
|
+
export declare function resolveDocsPort(manifest: Manifest, flagPort?: number): number;
|
|
6
|
+
export interface DocsResult {
|
|
7
|
+
written: string[];
|
|
8
|
+
findings: Finding[];
|
|
9
|
+
entries: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Project a deterministic Docsify sidebar from the live index: categories in
|
|
13
|
+
* their canonical order, entries sorted by path, paths relative to rootPath.
|
|
14
|
+
*/
|
|
15
|
+
export declare function buildSidebar(manifest: Manifest, entries: {
|
|
16
|
+
path: string;
|
|
17
|
+
title: string;
|
|
18
|
+
category: string;
|
|
19
|
+
}[]): string;
|
|
20
|
+
/**
|
|
21
|
+
* Generate the static docs viewer into the context root: a Docsify
|
|
22
|
+
* `index.html` (frontmatter-stripping hook included) and a `_sidebar.md`
|
|
23
|
+
* projected from the index. Presentation is non-normative; this is the
|
|
24
|
+
* reference projection of context-index.json into a browsable surface.
|
|
25
|
+
*/
|
|
26
|
+
export declare function generateDocs(root: string, manifest: Manifest): DocsResult;
|
|
27
|
+
/**
|
|
28
|
+
* Serve the repository root as a static site, bound to 127.0.0.1 (a local
|
|
29
|
+
* preview, never hosting). Mirrors Python's stdlib http.server, so both SDKs
|
|
30
|
+
* behave identically. Returns the listening server; port 0 picks a free port.
|
|
31
|
+
*/
|
|
32
|
+
export declare function serveDocs(root: string, port: number): Promise<http.Server>;
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as http from 'node:http';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import { stripSlash } from '../lib/fsx.js';
|
|
5
|
+
import { CATEGORY_IDS } from '../lib/manifest.js';
|
|
6
|
+
import { templatesDir } from '../lib/schemas.js';
|
|
7
|
+
import { generateIndex } from './indexgen.js';
|
|
8
|
+
/** Preview-port precedence: explicit --port, then manifest docs.port, then 5354 (LEJI on a phone keypad). */
|
|
9
|
+
export function resolveDocsPort(manifest, flagPort) {
|
|
10
|
+
return flagPort ?? manifest.docs?.port ?? 5354;
|
|
11
|
+
}
|
|
12
|
+
const CATEGORY_LABELS = {
|
|
13
|
+
domain: 'Domain',
|
|
14
|
+
system: 'System',
|
|
15
|
+
practice: 'Practice',
|
|
16
|
+
governance: 'Governance',
|
|
17
|
+
decisions: 'Decisions',
|
|
18
|
+
};
|
|
19
|
+
/** Escape text for safe interpolation into HTML element/attribute content. */
|
|
20
|
+
function htmlEscape(s) {
|
|
21
|
+
return s
|
|
22
|
+
.replaceAll('&', '&')
|
|
23
|
+
.replaceAll('<', '<')
|
|
24
|
+
.replaceAll('>', '>')
|
|
25
|
+
.replaceAll('"', '"')
|
|
26
|
+
.replaceAll("'", ''');
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Serialize a value to JSON safe to embed inside a `<script type="application/json">`
|
|
30
|
+
* block: neutralize a closing tag and the two JS line terminators U+2028/U+2029.
|
|
31
|
+
*/
|
|
32
|
+
function jsonForScript(value) {
|
|
33
|
+
return JSON.stringify(value)
|
|
34
|
+
.replaceAll('<', '\\u003c')
|
|
35
|
+
.replaceAll('>', '\\u003e')
|
|
36
|
+
.replaceAll('&', '\\u0026')
|
|
37
|
+
.replaceAll('
', '\\u2028')
|
|
38
|
+
.replaceAll('
', '\\u2029');
|
|
39
|
+
}
|
|
40
|
+
function relativeToRoot(relPath, rootPath) {
|
|
41
|
+
const base = stripSlash(rootPath);
|
|
42
|
+
if (base === '' || base === '.')
|
|
43
|
+
return relPath;
|
|
44
|
+
if (relPath.startsWith(base + '/'))
|
|
45
|
+
return relPath.slice(base.length + 1);
|
|
46
|
+
return null; // outside the context root: not servable from the viewer
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Project a deterministic Docsify sidebar from the live index: categories in
|
|
50
|
+
* their canonical order, entries sorted by path, paths relative to rootPath.
|
|
51
|
+
*/
|
|
52
|
+
export function buildSidebar(manifest, entries) {
|
|
53
|
+
// Ungrouped entries (the boot profile) sit above a divider; the category
|
|
54
|
+
// groups follow below it, giving the viewer a two-tier sidebar.
|
|
55
|
+
const topLines = [];
|
|
56
|
+
const boot = relativeToRoot(manifest.bootProfilePath, manifest.rootPath);
|
|
57
|
+
if (boot)
|
|
58
|
+
topLines.push(`- [Boot profile](${boot})`);
|
|
59
|
+
const groupLines = [];
|
|
60
|
+
for (const category of CATEGORY_IDS) {
|
|
61
|
+
const inCategory = entries
|
|
62
|
+
.filter((e) => e.category === category)
|
|
63
|
+
.map((e) => ({ ...e, rel: relativeToRoot(e.path, manifest.rootPath) }))
|
|
64
|
+
.filter((e) => e.rel !== null);
|
|
65
|
+
if (inCategory.length === 0)
|
|
66
|
+
continue;
|
|
67
|
+
groupLines.push(`- ${CATEGORY_LABELS[category]}`);
|
|
68
|
+
for (const entry of inCategory) {
|
|
69
|
+
groupLines.push(` - [${entry.title}](${entry.rel})`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const sections = [topLines, groupLines].filter((s) => s.length > 0).map((s) => s.join('\n'));
|
|
73
|
+
return sections.join('\n\n---\n\n') + '\n';
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Generate the static docs viewer into the context root: a Docsify
|
|
77
|
+
* `index.html` (frontmatter-stripping hook included) and a `_sidebar.md`
|
|
78
|
+
* projected from the index. Presentation is non-normative; this is the
|
|
79
|
+
* reference projection of context-index.json into a browsable surface.
|
|
80
|
+
*/
|
|
81
|
+
export function generateDocs(root, manifest) {
|
|
82
|
+
const result = generateIndex(root, manifest);
|
|
83
|
+
const entries = result.index?.entries ?? [];
|
|
84
|
+
const boot = relativeToRoot(manifest.bootProfilePath, manifest.rootPath) ?? 'boot-profile.md';
|
|
85
|
+
const html = fs
|
|
86
|
+
.readFileSync(path.join(templatesDir(), 'docs-viewer.html'), 'utf8')
|
|
87
|
+
.replaceAll('{{LEJI_NAME_HTML}}', htmlEscape(manifest.name))
|
|
88
|
+
.replaceAll('{{DOCSIFY_CONFIG}}', jsonForScript({ name: manifest.name, homepage: boot }));
|
|
89
|
+
const sidebar = buildSidebar(manifest, entries);
|
|
90
|
+
const rootDir = stripSlash(manifest.rootPath) || '.';
|
|
91
|
+
const written = [];
|
|
92
|
+
for (const [name, content] of [
|
|
93
|
+
['index.html', html],
|
|
94
|
+
['_sidebar.md', sidebar],
|
|
95
|
+
]) {
|
|
96
|
+
const rel = rootDir === '.' ? name : `${rootDir}/${name}`;
|
|
97
|
+
const abs = path.join(root, rel);
|
|
98
|
+
fs.mkdirSync(path.dirname(abs), { recursive: true });
|
|
99
|
+
fs.writeFileSync(abs, content);
|
|
100
|
+
written.push(rel);
|
|
101
|
+
}
|
|
102
|
+
// Copy every vendored viewer asset (Docsify core, theme, and the search +
|
|
103
|
+
// sidebar-collapse plugins) alongside the generated page so nothing loads
|
|
104
|
+
// from a remote CDN. The provenance note is documentation, never shipped.
|
|
105
|
+
const assetsSrc = path.join(templatesDir(), 'docs-viewer-assets');
|
|
106
|
+
const assetsRel = rootDir === '.' ? 'docs-viewer-assets' : `${rootDir}/docs-viewer-assets`;
|
|
107
|
+
const assetsAbs = path.join(root, assetsRel);
|
|
108
|
+
fs.mkdirSync(assetsAbs, { recursive: true });
|
|
109
|
+
for (const asset of fs.readdirSync(assetsSrc).sort()) {
|
|
110
|
+
if (asset === 'PROVENANCE.txt' || asset.startsWith('.'))
|
|
111
|
+
continue;
|
|
112
|
+
const bytes = fs.readFileSync(path.join(assetsSrc, asset));
|
|
113
|
+
fs.writeFileSync(path.join(assetsAbs, asset), bytes);
|
|
114
|
+
written.push(`${assetsRel}/${asset}`);
|
|
115
|
+
}
|
|
116
|
+
return { written, findings: result.findings, entries: entries.length };
|
|
117
|
+
}
|
|
118
|
+
const CONTENT_TYPES = {
|
|
119
|
+
'.html': 'text/html; charset=utf-8',
|
|
120
|
+
'.md': 'text/markdown; charset=utf-8',
|
|
121
|
+
'.js': 'text/javascript; charset=utf-8',
|
|
122
|
+
'.mjs': 'text/javascript; charset=utf-8',
|
|
123
|
+
'.css': 'text/css; charset=utf-8',
|
|
124
|
+
'.json': 'application/json; charset=utf-8',
|
|
125
|
+
'.svg': 'image/svg+xml',
|
|
126
|
+
'.png': 'image/png',
|
|
127
|
+
'.jpg': 'image/jpeg',
|
|
128
|
+
'.jpeg': 'image/jpeg',
|
|
129
|
+
'.gif': 'image/gif',
|
|
130
|
+
'.ico': 'image/x-icon',
|
|
131
|
+
'.txt': 'text/plain; charset=utf-8',
|
|
132
|
+
'.woff': 'font/woff',
|
|
133
|
+
'.woff2': 'font/woff2',
|
|
134
|
+
};
|
|
135
|
+
/**
|
|
136
|
+
* Serve the repository root as a static site, bound to 127.0.0.1 (a local
|
|
137
|
+
* preview, never hosting). Mirrors Python's stdlib http.server, so both SDKs
|
|
138
|
+
* behave identically. Returns the listening server; port 0 picks a free port.
|
|
139
|
+
*/
|
|
140
|
+
export function serveDocs(root, port) {
|
|
141
|
+
const rootAbs = fs.realpathSync(path.resolve(root));
|
|
142
|
+
const server = http.createServer((req, res) => {
|
|
143
|
+
let urlPath;
|
|
144
|
+
try {
|
|
145
|
+
// A malformed percent-encoding (e.g. GET /%E0%A4%A) throws URIError;
|
|
146
|
+
// answer 400 rather than letting it crash the server.
|
|
147
|
+
urlPath = decodeURIComponent(new URL(req.url ?? '/', 'http://localhost').pathname);
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
res.writeHead(400).end('bad request');
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
try {
|
|
154
|
+
const rel = path.normalize(urlPath).replace(/^([/\\])+/, '');
|
|
155
|
+
// Refuse any dotfile or VCS-internal segment outright.
|
|
156
|
+
if (rel.split(/[/\\]/).some((seg) => seg === '.git' || (seg.startsWith('.') && seg !== '.' && seg !== ''))) {
|
|
157
|
+
res.writeHead(404).end('not found');
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
let abs = path.resolve(rootAbs, rel);
|
|
161
|
+
// Lexical containment first (catches non-existent escaping paths).
|
|
162
|
+
if (abs !== rootAbs && !abs.startsWith(rootAbs + path.sep)) {
|
|
163
|
+
res.writeHead(403).end('forbidden');
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
if (fs.statSync(abs).isDirectory()) {
|
|
167
|
+
// Redirect a directory request without a trailing slash so the
|
|
168
|
+
// viewer's relative asset paths resolve (e.g. /docs -> /docs/).
|
|
169
|
+
if (!urlPath.endsWith('/')) {
|
|
170
|
+
res.writeHead(301, { Location: urlPath + '/' }).end();
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
abs = path.join(abs, 'index.html');
|
|
174
|
+
}
|
|
175
|
+
// Symlink containment: the resolved target must stay under the root.
|
|
176
|
+
const real = fs.realpathSync(abs);
|
|
177
|
+
if (real !== rootAbs && !real.startsWith(rootAbs + path.sep)) {
|
|
178
|
+
res.writeHead(403).end('forbidden');
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
const body = fs.readFileSync(abs);
|
|
182
|
+
res.writeHead(200, {
|
|
183
|
+
'content-type': CONTENT_TYPES[path.extname(abs).toLowerCase()] ?? 'application/octet-stream',
|
|
184
|
+
});
|
|
185
|
+
res.end(body);
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
res.writeHead(404).end('not found');
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
return new Promise((resolve, reject) => {
|
|
192
|
+
server.once('error', reject);
|
|
193
|
+
server.listen(port, '127.0.0.1', () => resolve(server));
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
//# sourceMappingURL=docs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"docs.js","sourceRoot":"","sources":["../../src/commands/docs.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type Finding } from '../lib/findings.js';
|
|
2
|
+
import { type Manifest } from '../lib/manifest.js';
|
|
3
|
+
export interface FreshnessItem {
|
|
4
|
+
path: string;
|
|
5
|
+
reviewAfter: string;
|
|
6
|
+
}
|
|
7
|
+
export interface FreshnessReport {
|
|
8
|
+
/** reviewAfter date in the past. */
|
|
9
|
+
expired: FreshnessItem[];
|
|
10
|
+
/** reviewAfter within the next 30 days. */
|
|
11
|
+
upcoming: FreshnessItem[];
|
|
12
|
+
/** Total documents carrying a freshness horizon. */
|
|
13
|
+
declared: number;
|
|
14
|
+
findings: Finding[];
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Report freshness horizons across category documents and agent profiles.
|
|
18
|
+
* Report-only by default (warnings); --strict raises expired horizons to
|
|
19
|
+
* errors. Scans documents directly so it works at any conformance level.
|
|
20
|
+
*/
|
|
21
|
+
export declare function freshnessReport(root: string, manifest: Manifest, strict?: boolean): FreshnessReport;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { finding, sortFindings } from '../lib/findings.js';
|
|
2
|
+
import { scanAgentProfiles, scanCategories } from '../lib/layer.js';
|
|
3
|
+
function reviewAfterOf(fm) {
|
|
4
|
+
const freshness = fm?.freshness;
|
|
5
|
+
const v = freshness?.reviewAfter;
|
|
6
|
+
return typeof v === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(v) ? v : null;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Report freshness horizons across category documents and agent profiles.
|
|
10
|
+
* Report-only by default (warnings); --strict raises expired horizons to
|
|
11
|
+
* errors. Scans documents directly so it works at any conformance level.
|
|
12
|
+
*/
|
|
13
|
+
export function freshnessReport(root, manifest, strict = false) {
|
|
14
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
15
|
+
const horizon = new Date(Date.now() + 30 * 24 * 3600 * 1000).toISOString().slice(0, 10);
|
|
16
|
+
const items = [];
|
|
17
|
+
for (const doc of scanCategories(root, manifest)) {
|
|
18
|
+
const reviewAfter = reviewAfterOf(doc.frontmatter);
|
|
19
|
+
if (reviewAfter)
|
|
20
|
+
items.push({ path: doc.relPath, reviewAfter });
|
|
21
|
+
}
|
|
22
|
+
for (const profile of scanAgentProfiles(root, manifest)) {
|
|
23
|
+
const reviewAfter = reviewAfterOf(profile.frontmatter);
|
|
24
|
+
if (reviewAfter)
|
|
25
|
+
items.push({ path: profile.relPath, reviewAfter });
|
|
26
|
+
}
|
|
27
|
+
items.sort((a, b) => a.reviewAfter !== b.reviewAfter
|
|
28
|
+
? a.reviewAfter < b.reviewAfter
|
|
29
|
+
? -1
|
|
30
|
+
: 1
|
|
31
|
+
: a.path < b.path
|
|
32
|
+
? -1
|
|
33
|
+
: a.path > b.path
|
|
34
|
+
? 1
|
|
35
|
+
: 0);
|
|
36
|
+
const expired = items.filter((i) => i.reviewAfter < today);
|
|
37
|
+
const upcoming = items.filter((i) => i.reviewAfter >= today && i.reviewAfter <= horizon);
|
|
38
|
+
const findings = expired.map((i) => finding('freshness-expired', strict ? 'error' : 'warning', `review horizon ${i.reviewAfter} has passed`, i.path));
|
|
39
|
+
return { expired, upcoming, declared: items.length, findings: sortFindings(findings) };
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=freshness.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"freshness.js","sourceRoot":"","sources":["../../src/commands/freshness.ts"],"names":[],"mappings":"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"}
|