@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.
Files changed (68) hide show
  1. package/README.md +36 -0
  2. package/assets-manifest.json +25 -0
  3. package/cli.json +82 -0
  4. package/dist/cli.d.ts +2 -0
  5. package/dist/cli.js +4 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/commands/conformance.d.ts +24 -0
  8. package/dist/commands/conformance.js +111 -0
  9. package/dist/commands/conformance.js.map +1 -0
  10. package/dist/commands/docs.d.ts +32 -0
  11. package/dist/commands/docs.js +196 -0
  12. package/dist/commands/docs.js.map +1 -0
  13. package/dist/commands/freshness.d.ts +21 -0
  14. package/dist/commands/freshness.js +41 -0
  15. package/dist/commands/freshness.js.map +1 -0
  16. package/dist/commands/indexgen.d.ts +55 -0
  17. package/dist/commands/indexgen.js +256 -0
  18. package/dist/commands/indexgen.js.map +1 -0
  19. package/dist/commands/init.d.ts +28 -0
  20. package/dist/commands/init.js +378 -0
  21. package/dist/commands/init.js.map +1 -0
  22. package/dist/commands/validate.d.ts +25 -0
  23. package/dist/commands/validate.js +359 -0
  24. package/dist/commands/validate.js.map +1 -0
  25. package/dist/index.d.ts +17 -0
  26. package/dist/index.js +324 -0
  27. package/dist/index.js.map +1 -0
  28. package/dist/lib/findings.d.ts +17 -0
  29. package/dist/lib/findings.js +29 -0
  30. package/dist/lib/findings.js.map +1 -0
  31. package/dist/lib/frontmatter.d.ts +14 -0
  32. package/dist/lib/frontmatter.js +28 -0
  33. package/dist/lib/frontmatter.js.map +1 -0
  34. package/dist/lib/fsx.d.ts +21 -0
  35. package/dist/lib/fsx.js +100 -0
  36. package/dist/lib/fsx.js.map +1 -0
  37. package/dist/lib/git.d.ts +10 -0
  38. package/dist/lib/git.js +55 -0
  39. package/dist/lib/git.js.map +1 -0
  40. package/dist/lib/layer.d.ts +32 -0
  41. package/dist/lib/layer.js +138 -0
  42. package/dist/lib/layer.js.map +1 -0
  43. package/dist/lib/manifest.d.ts +62 -0
  44. package/dist/lib/manifest.js +54 -0
  45. package/dist/lib/manifest.js.map +1 -0
  46. package/dist/lib/schemas.d.ts +38 -0
  47. package/dist/lib/schemas.js +57 -0
  48. package/dist/lib/schemas.js.map +1 -0
  49. package/package.json +61 -0
  50. package/schemas/README.md +3 -0
  51. package/schemas/agent-profile.schema.json +129 -0
  52. package/schemas/context-changelog.schema.json +150 -0
  53. package/schemas/context-index.schema.json +137 -0
  54. package/schemas/context-manifest.schema.json +253 -0
  55. package/schemas/decision-record.schema.json +84 -0
  56. package/templates/README.md +5 -0
  57. package/templates/agent-profile.md +25 -0
  58. package/templates/agents/core.md +27 -0
  59. package/templates/boot-profile.md +39 -0
  60. package/templates/decision-record.md +28 -0
  61. package/templates/docs-viewer-assets/PROVENANCE.txt +18 -0
  62. package/templates/docs-viewer-assets/docsify-sidebar-collapse.min.css +24 -0
  63. package/templates/docs-viewer-assets/docsify-sidebar-collapse.min.js +149 -0
  64. package/templates/docs-viewer-assets/docsify.min.js +1 -0
  65. package/templates/docs-viewer-assets/search.min.js +314 -0
  66. package/templates/docs-viewer-assets/vue.css +1063 -0
  67. package/templates/docs-viewer.html +63 -0
  68. 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
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { run } from './index.js';
3
+ process.exit(await run(process.argv.slice(2)));
4
+ //# sourceMappingURL=cli.js.map
@@ -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('&', '&amp;')
23
+ .replaceAll('<', '&lt;')
24
+ .replaceAll('>', '&gt;')
25
+ .replaceAll('"', '&quot;')
26
+ .replaceAll("'", '&#39;');
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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"}