diff --git a/.gitignore b/.gitignore index 14b4aed..02fe7a0 100644 --- a/.gitignore +++ b/.gitignore @@ -19,8 +19,10 @@ Thumbs.db npm-debug.log* # Template forks: build.js writes to docs/ by default. -# Uncomment the line below if you want to keep generated output out of git -# and deploy via the bundled workflow instead. +# Option A (recommended): Uncomment docs/ below. The bundled GitHub Actions +# workflow builds and deploys automatically — no need to commit generated output. +# Option B: Commit docs/ so GitHub Pages can serve it without a workflow. +# This repo uses Option B for its /demo/ output; template forks should use Option A. # docs/ # Environment diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index da99b26..ca9f037 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,7 +23,8 @@ Every entity file starts with YAML frontmatter between `---` delimiters. See exi **Primary entities:** ```yaml --- -title: Data Encryption +id: data-encryption +name: Data Encryption group: technical last_verified: 2025-01-15 --- @@ -32,7 +33,7 @@ last_verified: 2025-01-15 **Container entities:** ```yaml --- -title: ISO 27001 +name: ISO 27001 status: active authority: iso last_verified: 2025-01-15 @@ -42,8 +43,8 @@ last_verified: 2025-01-15 **Authority entities:** ```yaml --- -title: International Organization for Standardization -type: standards-body +id: iso +name: International Organization for Standardization last_verified: 2025-01-15 --- ``` @@ -132,7 +133,7 @@ Container entity files have a specific structure with a timeline table and provi ```markdown --- -title: Example Framework +name: Example Framework status: active authority: org-id --- diff --git a/README.md b/README.md index 8a3488b..9f7e229 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,11 @@ A template for building structured, version-controlled knowledge bases with an o **[Knowledge as Code](https://knowledge-as-code.com)** is a pattern created for [PAICE.work](https://paice.work/) PBC. It applies software engineering practices to knowledge management: plain text, Git-native, zero-dependency, ontology-driven, multi-output from a single source. -## Live Examples +## What it produces + +**[Live demo →](https://knowledge-as-code.com/demo/)** — the output of this template's example data, deployed on GitHub Pages. Includes a searchable HTML site, coverage matrix, timeline, comparison tool, and JSON API. + +Built examples using this template: - [AI Tool Watch](https://aitool.watch) — AI model capabilities across 12 products - [Every AI Law](https://everyailaw.com) — global AI regulatory landscape @@ -12,11 +16,11 @@ A template for building structured, version-controlled knowledge bases with an o ## Quick Start -1. **Use this template** -- click "Use this template" on GitHub, or clone locally -2. **Edit `project.yml`** -- define your domain entities, groups, colors, and site identity -3. **Replace example data** -- see [Replacing example data](#replacing-example-data) below -4. **Build** -- `node scripts/build.js` (or `npm run build`) -5. **Deploy** -- push to GitHub, Pages deploys automatically +1. **Use this template** -- on GitHub, click the green "Use this template" button (not "Clone"). This creates a new repo with no git history and no upstream connection. Cloning is fine for local exploration but won't give you a clean starting repo. +2. **Edit `project.yml`** -- start with the entity names (`entities.primary.name`, `entities.container.name`, etc.) and groups. You can adjust colors and navigation later. Leave `url` until your GitHub repo is configured. +3. **Replace example data** -- delete the files in `data/examples/requirements/`, `data/examples/frameworks/`, `data/examples/organizations/`, and `data/examples/mapping/index.yml`, then add your own. See [Replacing example data](#replacing-example-data) and [`data/_schema.md`](data/_schema.md) for the format. +4. **Build** -- `node scripts/build.js`. A successful build prints `Build complete — N HTML pages, N JSON API files`. Check the `docs/` directory for the output, and open any HTML file in a browser to verify it looks right. +5. **Deploy** -- push to GitHub. The included workflow deploys to GitHub Pages automatically. ## What You Get @@ -63,6 +67,50 @@ Authority → Container → Provision → Primary Primaries are stable; containers are unstable. When a framework is amended, its provisions change, but the underlying requirements persist. +### How the pieces connect + +``` +project.yml data/examples/ +(names, colors, config) (one .md file per entity) + \ / + \ / + +----> scripts/build.js <----+ data/examples/mapping/index.yml + | (connects containers to primaries) + +------------+------------+ + | | | + docs/ docs/ docs/api/v1/ + (HTML site) llms.txt (JSON API) + (sitemap) agents.json context.jsonld +``` + +You edit `project.yml` and `data/`. The build script reads both and writes everything under `docs/`. You never edit `docs/` directly. + +### gist semantic layer + +By default, every built site includes a semantic layer backed by [gist](https://semanticarts.com/gist/) — a minimalist upper ontology for the enterprise by Semantic Arts. This adds: + +- `@type` annotations on every item in the JSON API (e.g. `"@type": "gist:KnowledgeConcept"`) +- `api/v1/context.jsonld` — a JSON-LD context file mapping KaC terms to gist IRIs +- `ontology` block in `api/v1/index.json` with the framework name, base IRI, and attribution + +The default role-to-class mapping: + +| KaC role | gist class | +|----------|-----------| +| primary | `gist:KnowledgeConcept` | +| container | `gist:Specification` | +| authority | `gist:Organization` | +| secondary | `gist:Commitment` | + +Override any mapping in `project.yml` under `ontology.entities..gist_class`. To disable entirely: + +```yaml +ontology: + enabled: false +``` + +When enabled, CC BY 4.0 attribution to Semantic Arts is required on any published site. The attribution string is included automatically in `api/v1/index.json`. + ## Configuration All domain-specific settings live in `project.yml`: @@ -94,6 +142,8 @@ The template ships with example data in `data/examples/` (ISO 27001, NIST CSF). The build script looks for data in `data/examples/` first, then `data/`. You can rename `data/examples/` to `data/` if you prefer a flatter structure. +**What to keep:** Only `data/` contents and `project.yml` values need replacing. Do not delete `scripts/`, `.github/workflows/`, `mcp-server.js`, `mcp.json`, or `package.json` — these are the template engine and deployment config. + ## Commands ```bash @@ -143,16 +193,36 @@ The MCP server exposes your knowledge base as tools that AI agents can call. Too node mcp-server.js ``` -The server reads `project.yml` at startup and exposes tools for listing and retrieving each entity type. For example, with the default config you get tools like `list_requirements`, `get_requirement`, `list_frameworks`, `get_framework`, etc. The exact tool names depend on your entity configuration. +The server reads `project.yml` at startup and exposes tools for listing and retrieving each entity type. Tool names are derived from your entity config by lowercasing and replacing non-alphanumeric characters with hyphens: + +| Config value | Tool name | +|-------------|-----------| +| `plural: Requirements` | `list_requirements` | +| `name: Requirement` | `get_requirement` | +| `plural: Frameworks` | `list_frameworks` | +| `name: Framework` | `get_framework` | +| `plural: Organizations` | `list_organizations` | +| `name: Organization` | `get_organization` | + +Two fixed tools are always present regardless of config: `get_matrix` and `get_mappings`. + +## Validation + +`node scripts/validate.js` checks cross-references before building. Common errors and fixes: + +| Error message | Cause | Fix | +|--------------|-------|-----| +| `Mapping "X" references unknown container "Y"` | `regulation` field in mapping doesn't match any container filename | Check the container file exists and the `regulation` value matches the filename (without `.md`) | +| `Mapping "X" references unknown primary "Y"` | `obligations` entry doesn't match any primary filename | Check the primary file exists and the ID matches | +| `Mapping "X" references unknown authority "Y"` | `authority` field doesn't match any authority filename | Check the authority file exists | +| `Container "X" references unknown authority "Y"` | `authority` frontmatter in container file doesn't match any authority | Create the authority file or correct the ID | +| `No data directory found` | Neither `data/examples/` nor `data/` exists | Check your data directory path and config | ## Verification -Knowledge as Code includes a verification scaffold for detecting stale data: +`node scripts/verify.js` detects stale entities and validates cross-reference completeness. A weekly GitHub Actions workflow runs it automatically and opens an issue on drift. -- Add `last_verified: YYYY-MM-DD` to entity frontmatter -- Run `node scripts/verify.js` to check for staleness -- Configure threshold in `project.yml` under `verification.staleness_days` -- See [VERIFICATION.md](VERIFICATION.md) for details on adding AI-assisted verification +See [VERIFICATION.md](VERIFICATION.md) for the full guide: staleness thresholds, CI integration, and AI-assisted content review. ## Ecosystem @@ -180,6 +250,8 @@ Read the full pattern definition at [knowledge-as-code.com](https://knowledge-as Knowledge as Code is a [PAICE.work](https://paice.work/) project. See [ATTRIBUTION.md](ATTRIBUTION.md) for details. +The default semantic layer uses [gist](https://semanticarts.com/gist/) by Semantic Arts, Inc., licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). Required attribution: "Semantic Arts, Inc. gist ontology (CC BY 4.0) https://semanticarts.com/gist". + ## Deploying When you use this template, update the following: diff --git a/VERIFICATION.md b/VERIFICATION.md index 85c8fe7..507a70a 100644 --- a/VERIFICATION.md +++ b/VERIFICATION.md @@ -22,30 +22,16 @@ The script validates that: - Every mapping references valid primary entity IDs - Every mapping references valid container IDs -## Setting `last_verified` in Entity Frontmatter +## Setup -Add a `last_verified` field with an ISO date to any entity's frontmatter: - -```yaml ---- -title: Data Encryption -group: technical -last_verified: 2025-01-15 ---- -``` - -When you review an entity and confirm its content is current, update this date. The verification script will then consider it fresh until the staleness threshold is exceeded. - -## Configuring the Staleness Threshold - -In `project.yml`, set the number of days before an entity is considered stale: +Add `last_verified: YYYY-MM-DD` to any entity's frontmatter and set the staleness window in `project.yml`: ```yaml verification: - staleness_days: 90 + staleness_days: 90 # default; use 30 for fast-moving domains, 180 for stable reference data ``` -The default is 90 days. Adjust this based on how frequently your domain changes. Fast-moving regulatory environments may warrant 30 days; stable reference data might use 180. +When you review an entity and confirm its content is current, update the date. Entities without `last_verified` are reported as "never verified." ## Running Verification diff --git a/data/_schema.md b/data/_schema.md index b8599ed..e65a30e 100644 --- a/data/_schema.md +++ b/data/_schema.md @@ -146,16 +146,19 @@ The heading name should match your container entity's plural name (e.g., "Regula The mapping file connects containers to primaries through secondary (provision) entities: ```yaml -- id: iso-27001-access-control # Unique provision ID - regulation: iso-27001 # Container file name (without .md) - authority: iso # Authority ID - source_heading: Information Security Controls (Annex A) # Must match an ## H2 in the container file - obligations: # List of primary entity IDs this provision maps to +- id: iso-27001-access-control # Required. Unique provision ID (kebab-case) + regulation: iso-27001 # Required. Container file name (without .md) + authority: iso # Required. Authority ID + source_heading: Information Security Controls (Annex A) # Required. Must match an ## H2 in the container file + source_file: data/examples/frameworks/iso-27001.md # Optional. Path to container file for tooling/traceability + obligations: # Required. List of primary entity IDs this provision maps to - access-control ``` The `regulation` field should use your container entity name from config (the field name comes from `project.yml`). The `obligations` field should use your primary entity name from config. +`source_file` is informational — the build script resolves container files by `regulation` ID, not this path. It is useful for documentation tooling and traceability. If included, use the path relative to the repo root and match your configured `entities.container.directory`. + ## File naming - All files use kebab-case: `access-control.md`, `iso-27001.md` diff --git a/data/examples/mapping/index.yml b/data/examples/mapping/index.yml index 5409b9b..1ddea45 100644 --- a/data/examples/mapping/index.yml +++ b/data/examples/mapping/index.yml @@ -1,7 +1,7 @@ - id: iso-27001-access-control regulation: iso-27001 authority: iso - source_file: data/examples/container/iso-27001.md + source_file: data/examples/frameworks/iso-27001.md source_heading: Information Security Controls (Annex A) obligations: - access-control @@ -9,7 +9,7 @@ - id: iso-27001-data-quality regulation: iso-27001 authority: iso - source_file: data/examples/container/iso-27001.md + source_file: data/examples/frameworks/iso-27001.md source_heading: Data Quality Requirements (Clause 7.5) obligations: - data-quality @@ -17,7 +17,7 @@ - id: nist-csf-incident-response regulation: nist-csf authority: nist - source_file: data/examples/container/nist-csf.md + source_file: data/examples/frameworks/nist-csf.md source_heading: Incident Response (RS.AN, RS.MI) obligations: - incident-response @@ -25,7 +25,7 @@ - id: nist-csf-access-control regulation: nist-csf authority: nist - source_file: data/examples/container/nist-csf.md + source_file: data/examples/frameworks/nist-csf.md source_heading: Access Control (PR.AA) obligations: - access-control diff --git a/demo/agents.json b/demo/agents.json index 7436f96..c24146b 100644 --- a/demo/agents.json +++ b/demo/agents.json @@ -107,7 +107,7 @@ "robots": "https://knowledge-as-code.com/demo/robots.txt" }, "meta": { - "last_updated": "2026-04-09", + "last_updated": "2026-04-19", "built_with": "Knowledge as Code", "pattern_url": "https://knowledge-as-code.com", "template_url": "https://github.com/snapsynapse/knowledge-as-code-template" diff --git a/demo/api/v1/authorities.json b/demo/api/v1/authorities.json index deff160..5d53c0d 100644 --- a/demo/api/v1/authorities.json +++ b/demo/api/v1/authorities.json @@ -1,15 +1,17 @@ { "meta": { - "generated": "2026-04-09T23:00:18.276Z", + "generated": "2026-04-19T02:53:43.767Z", "count": 2 }, "items": [ { + "@type": "gist:Organization", "id": "iso", "name": "International Organization for Standardization", "jurisdiction": "International" }, { + "@type": "gist:Organization", "id": "nist", "name": "National Institute of Standards and Technology", "jurisdiction": "Federal" diff --git a/demo/api/v1/comparisons.json b/demo/api/v1/comparisons.json index d7c3075..b520da2 100644 --- a/demo/api/v1/comparisons.json +++ b/demo/api/v1/comparisons.json @@ -1,6 +1,6 @@ { "meta": { - "generated": "2026-04-09T23:00:18.277Z" + "generated": "2026-04-19T02:53:43.767Z" }, "comparisons": [ { diff --git a/demo/api/v1/containers.json b/demo/api/v1/containers.json index 266c541..a6c87d2 100644 --- a/demo/api/v1/containers.json +++ b/demo/api/v1/containers.json @@ -1,10 +1,11 @@ { "meta": { - "generated": "2026-04-09T23:00:18.276Z", + "generated": "2026-04-19T02:53:43.766Z", "count": 2 }, "items": [ { + "@type": "gist:Specification", "id": "iso-27001", "name": "ISO 27001", "status": "active", @@ -12,6 +13,7 @@ "provision_count": 2 }, { + "@type": "gist:Specification", "id": "nist-csf", "name": "NIST Cybersecurity Framework", "status": "active", diff --git a/demo/api/v1/context.jsonld b/demo/api/v1/context.jsonld new file mode 100644 index 0000000..1ffd446 --- /dev/null +++ b/demo/api/v1/context.jsonld @@ -0,0 +1,10 @@ +{ + "@context": { + "gist": "https://ontologies.semanticarts.com/gist/", + "kac": "https://knowledge-as-code.com/demo/api/v1/", + "gist:KnowledgeConcept": "https://ontologies.semanticarts.com/gist/KnowledgeConcept", + "gist:Specification": "https://ontologies.semanticarts.com/gist/Specification", + "gist:Organization": "https://ontologies.semanticarts.com/gist/Organization", + "gist:Commitment": "https://ontologies.semanticarts.com/gist/Commitment" + } +} \ No newline at end of file diff --git a/demo/api/v1/index.json b/demo/api/v1/index.json index c1f762c..2ea73bb 100644 --- a/demo/api/v1/index.json +++ b/demo/api/v1/index.json @@ -1,8 +1,14 @@ { "meta": { - "generated": "2026-04-09T23:00:18.277Z", + "generated": "2026-04-19T02:53:43.767Z", "version": "1.0", - "project": "example" + "project": "example", + "ontology": { + "framework": "gist", + "base_iri": "https://ontologies.semanticarts.com/gist/", + "context": "context.jsonld", + "attribution": "Semantic Arts, Inc. gist ontology (CC BY 4.0) https://semanticarts.com/gist" + } }, "files": { "primaries": { diff --git a/demo/api/v1/mappings.json b/demo/api/v1/mappings.json index b0326ce..6608dcf 100644 --- a/demo/api/v1/mappings.json +++ b/demo/api/v1/mappings.json @@ -1,6 +1,6 @@ { "meta": { - "generated": "2026-04-09T23:00:18.277Z", + "generated": "2026-04-19T02:53:43.767Z", "count": 4 }, "items": [ @@ -11,7 +11,7 @@ ], "regulation": "iso-27001", "authority": "iso", - "source_file": "data/examples/container/iso-27001.md", + "source_file": "data/examples/frameworks/iso-27001.md", "source_heading": "Information Security Controls (Annex A)" }, { @@ -21,7 +21,7 @@ ], "regulation": "iso-27001", "authority": "iso", - "source_file": "data/examples/container/iso-27001.md", + "source_file": "data/examples/frameworks/iso-27001.md", "source_heading": "Data Quality Requirements (Clause 7.5)" }, { @@ -31,7 +31,7 @@ ], "regulation": "nist-csf", "authority": "nist", - "source_file": "data/examples/container/nist-csf.md", + "source_file": "data/examples/frameworks/nist-csf.md", "source_heading": "Incident Response (RS.AN, RS.MI)" }, { @@ -41,7 +41,7 @@ ], "regulation": "nist-csf", "authority": "nist", - "source_file": "data/examples/container/nist-csf.md", + "source_file": "data/examples/frameworks/nist-csf.md", "source_heading": "Access Control (PR.AA)" } ] diff --git a/demo/api/v1/matrix.json b/demo/api/v1/matrix.json index e3a2e06..99ccbee 100644 --- a/demo/api/v1/matrix.json +++ b/demo/api/v1/matrix.json @@ -1,6 +1,6 @@ { "meta": { - "generated": "2026-04-09T23:00:18.277Z" + "generated": "2026-04-19T02:53:43.767Z" }, "matrix": { "access-control": { diff --git a/demo/api/v1/primaries.json b/demo/api/v1/primaries.json index e5cca5d..680b12c 100644 --- a/demo/api/v1/primaries.json +++ b/demo/api/v1/primaries.json @@ -1,22 +1,25 @@ { "meta": { - "generated": "2026-04-09T23:00:18.275Z", + "generated": "2026-04-19T02:53:43.765Z", "count": 3 }, "items": [ { + "@type": "gist:KnowledgeConcept", "id": "access-control", "name": "Access Control", "group": "governance", "status": "active" }, { + "@type": "gist:KnowledgeConcept", "id": "data-quality", "name": "Data Quality", "group": "technical", "status": "active" }, { + "@type": "gist:KnowledgeConcept", "id": "incident-response", "name": "Incident Response", "group": "operational", diff --git a/demo/index.xml b/demo/index.xml index 743e6fc..0c283f5 100644 --- a/demo/index.xml +++ b/demo/index.xml @@ -5,7 +5,7 @@ https://knowledge-as-code.com/demo/ A structured reference tracking example entities across categories. - Thu, 09 Apr 2026 23:00:18 GMT + Sun, 19 Apr 2026 02:53:43 GMT ISO 27001 https://knowledge-as-code.com/demo/container/iso-27001/ diff --git a/demo/sitemap.xml b/demo/sitemap.xml index 22f4042..0e98b75 100644 --- a/demo/sitemap.xml +++ b/demo/sitemap.xml @@ -1,25 +1,25 @@ - https://knowledge-as-code.com/demo/2026-04-09 - https://knowledge-as-code.com/demo/containers.html2026-04-09 - https://knowledge-as-code.com/demo/primaries.html2026-04-09 - https://knowledge-as-code.com/demo/matrix.html2026-04-09 - https://knowledge-as-code.com/demo/timeline.html2026-04-09 - https://knowledge-as-code.com/demo/compare.html2026-04-09 - https://knowledge-as-code.com/demo/about.html2026-04-09 - https://knowledge-as-code.com/demo/pattern.html2026-04-09 - https://knowledge-as-code.com/demo/container/iso-27001/2026-04-09 - https://knowledge-as-code.com/demo/container/nist-csf/2026-04-09 - https://knowledge-as-code.com/demo/primary/access-control/2026-04-09 - https://knowledge-as-code.com/demo/primary/data-quality/2026-04-09 - https://knowledge-as-code.com/demo/primary/incident-response/2026-04-09 - https://knowledge-as-code.com/demo/authority/iso/2026-04-09 - https://knowledge-as-code.com/demo/authority/nist/2026-04-09 - https://knowledge-as-code.com/demo/requires/iso-27001/access-control/2026-04-09 - https://knowledge-as-code.com/demo/requires/iso-27001/data-quality/2026-04-09 - https://knowledge-as-code.com/demo/requires/nist-csf/incident-response/2026-04-09 - https://knowledge-as-code.com/demo/requires/nist-csf/access-control/2026-04-09 - https://knowledge-as-code.com/demo/compare/iso-27001-vs-nist-csf/2026-04-09 - https://knowledge-as-code.com/demo/applies-to/international/2026-04-09 - https://knowledge-as-code.com/demo/applies-to/federal/2026-04-09 + https://knowledge-as-code.com/demo/2026-04-19 + https://knowledge-as-code.com/demo/containers.html2026-04-19 + https://knowledge-as-code.com/demo/primaries.html2026-04-19 + https://knowledge-as-code.com/demo/matrix.html2026-04-19 + https://knowledge-as-code.com/demo/timeline.html2026-04-19 + https://knowledge-as-code.com/demo/compare.html2026-04-19 + https://knowledge-as-code.com/demo/about.html2026-04-19 + https://knowledge-as-code.com/demo/pattern.html2026-04-19 + https://knowledge-as-code.com/demo/container/iso-27001/2026-04-19 + https://knowledge-as-code.com/demo/container/nist-csf/2026-04-19 + https://knowledge-as-code.com/demo/primary/access-control/2026-04-19 + https://knowledge-as-code.com/demo/primary/data-quality/2026-04-19 + https://knowledge-as-code.com/demo/primary/incident-response/2026-04-19 + https://knowledge-as-code.com/demo/authority/iso/2026-04-19 + https://knowledge-as-code.com/demo/authority/nist/2026-04-19 + https://knowledge-as-code.com/demo/requires/iso-27001/access-control/2026-04-19 + https://knowledge-as-code.com/demo/requires/iso-27001/data-quality/2026-04-19 + https://knowledge-as-code.com/demo/requires/nist-csf/incident-response/2026-04-19 + https://knowledge-as-code.com/demo/requires/nist-csf/access-control/2026-04-19 + https://knowledge-as-code.com/demo/compare/iso-27001-vs-nist-csf/2026-04-19 + https://knowledge-as-code.com/demo/applies-to/international/2026-04-19 + https://knowledge-as-code.com/demo/applies-to/federal/2026-04-19 \ No newline at end of file diff --git a/project.yml b/project.yml index 125a528..83bd0de 100644 --- a/project.yml +++ b/project.yml @@ -64,6 +64,9 @@ entities: directory: organizations secondary: + # Secondary entities are defined inline in container files (as provision sections) + # and in the mapping file — not as standalone .md files. The directory field is + # unused by the build script; name/plural/relationship are used for display only. name: Provision plural: Provisions directory: provisions @@ -151,6 +154,29 @@ social: verification: staleness_days: 90 +# --------------------------------------------------------------------------- +# Ontology framework — optional semantic layer (opt-in, zero cost when off) +# +# Adds @type to JSON API items and emits api/v1/context.jsonld. +# gist is a minimalist enterprise upper ontology by Semantic Arts (CC BY 4.0). +# Attribution required when publishing sites that use gist IRIs. +# To disable: set enabled: false (removes @type fields and context.jsonld). +# --------------------------------------------------------------------------- +ontology: + enabled: true + framework: "gist" + base_iri: "https://ontologies.semanticarts.com/gist/" + attribution: "Semantic Arts, Inc. gist ontology (CC BY 4.0) https://semanticarts.com/gist" + entities: + primary: + gist_class: "gist:KnowledgeConcept" + container: + gist_class: "gist:Specification" + authority: + gist_class: "gist:Organization" + secondary: + gist_class: "gist:Commitment" + # --------------------------------------------------------------------------- # Ecosystem — related projects shown on pattern/about pages # --------------------------------------------------------------------------- diff --git a/scripts/build.js b/scripts/build.js index cd20be7..5efcf00 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -1273,16 +1273,31 @@ function build() { const data = { primaries, containers, authorities, mappingIndex, matrix, comparisons, totalProvisions }; const configCSS = generateConfigCSS(config); + // Ontology soft-label helpers (Tier 1). Active only when project.yml sets ontology.enabled: true. + const ont = (config.ontology?.enabled === true || config.ontology?.enabled === 'true') ? config.ontology : null; + function ontType(role) { return ont?.entities?.[role]?.gist_class || null; } + function withType(role, obj) { const t = ontType(role); return t ? { '@type': t, ...obj } : obj; } + // --- JSON API --- - fs.writeFileSync(path.join(API_DIR, 'primaries.json'), JSON.stringify({ meta: { generated: new Date().toISOString(), count: primaries.length }, items: primaries.map(p => ({ id: p.id, name: p.name || humanizeId(p.id), group: p.group || '', status: p.status || 'active' })) }, null, 2)); - fs.writeFileSync(path.join(API_DIR, 'containers.json'), JSON.stringify({ meta: { generated: new Date().toISOString(), count: containers.length }, items: containers.map(c => ({ id: c.id, name: c.name, status: c.status, effective: c.effective, provision_count: c.provisions.length })) }, null, 2)); - fs.writeFileSync(path.join(API_DIR, 'authorities.json'), JSON.stringify({ meta: { generated: new Date().toISOString(), count: authorities.length }, items: authorities.map(a => ({ id: a.id, name: a.name || humanizeId(a.id), jurisdiction: a.jurisdiction || '' })) }, null, 2)); + fs.writeFileSync(path.join(API_DIR, 'primaries.json'), JSON.stringify({ meta: { generated: new Date().toISOString(), count: primaries.length }, items: primaries.map(p => withType('primary', { id: p.id, name: p.name || humanizeId(p.id), group: p.group || '', status: p.status || 'active' })) }, null, 2)); + fs.writeFileSync(path.join(API_DIR, 'containers.json'), JSON.stringify({ meta: { generated: new Date().toISOString(), count: containers.length }, items: containers.map(c => withType('container', { id: c.id, name: c.name, status: c.status, effective: c.effective, provision_count: c.provisions.length })) }, null, 2)); + fs.writeFileSync(path.join(API_DIR, 'authorities.json'), JSON.stringify({ meta: { generated: new Date().toISOString(), count: authorities.length }, items: authorities.map(a => withType('authority', { id: a.id, name: a.name || humanizeId(a.id), jurisdiction: a.jurisdiction || '' })) }, null, 2)); fs.writeFileSync(path.join(API_DIR, 'mappings.json'), JSON.stringify({ meta: { generated: new Date().toISOString(), count: mappingIndex.length }, items: mappingIndex }, null, 2)); fs.writeFileSync(path.join(API_DIR, 'matrix.json'), JSON.stringify({ meta: { generated: new Date().toISOString() }, matrix }, null, 2)); fs.writeFileSync(path.join(API_DIR, 'comparisons.json'), JSON.stringify({ meta: { generated: new Date().toISOString() }, comparisons }, null, 2)); - fs.writeFileSync(path.join(API_DIR, 'index.json'), JSON.stringify({ meta: { generated: new Date().toISOString(), version: '1.0', project: config.short_name || 'kac' }, files: { primaries: { path: 'primaries.json' }, containers: { path: 'containers.json' }, authorities: { path: 'authorities.json' }, mappings: { path: 'mappings.json' }, matrix: { path: 'matrix.json' }, comparisons: { path: 'comparisons.json' } } }, null, 2)); + const indexMeta = { generated: new Date().toISOString(), version: '1.0', project: config.short_name || 'kac' }; + if (ont) { indexMeta.ontology = { framework: ont.framework, base_iri: ont.base_iri, context: 'context.jsonld', attribution: ont.attribution }; } + fs.writeFileSync(path.join(API_DIR, 'index.json'), JSON.stringify({ meta: indexMeta, files: { primaries: { path: 'primaries.json' }, containers: { path: 'containers.json' }, authorities: { path: 'authorities.json' }, mappings: { path: 'mappings.json' }, matrix: { path: 'matrix.json' }, comparisons: { path: 'comparisons.json' } } }, null, 2)); + + if (ont) { + const ctx = { '@context': { gist: ont.base_iri, kac: (config.url || '').replace(/\/?$/, '/') + 'api/v1/' } }; + for (const [role, cfg] of Object.entries(ont.entities || {})) { + if (cfg.gist_class) ctx['@context'][cfg.gist_class] = ont.base_iri + cfg.gist_class.replace(/^gist:/, ''); + } + fs.writeFileSync(path.join(API_DIR, 'context.jsonld'), JSON.stringify(ctx, null, 2)); + } - console.log(' JSON API: 6 files'); + console.log(` JSON API: ${ont ? 7 : 6} files${ont ? ' (+ context.jsonld)' : ''}`); // --- HTML pages --- const sitemapPages = [];