diff --git a/.agents/skills/markdown-frontmatter/SKILL.md b/.agents/skills/markdown-frontmatter/SKILL.md new file mode 100644 index 0000000..79f0c12 --- /dev/null +++ b/.agents/skills/markdown-frontmatter/SKILL.md @@ -0,0 +1,176 @@ +--- +name: markdown-frontmatter +description: Use when adding, fixing, or validating YAML frontmatter on managed Markdown governed by the project-standards Markdown Frontmatter Standard; covers structure, field values, id generation, and validation. +compatibility: Claude Code and Codex CLI +license: MIT +metadata: + author: Chris Purcell + version: '1.4' +--- + +# Markdown Frontmatter + +## Overview + +Author and fix YAML frontmatter for **managed Markdown documents** under the [project-standards Markdown Frontmatter Standard](https://github.com/L3DigitalNet/project-standards/blob/v5.10.0/standards/markdown-frontmatter/versions/1.6/README.md). + +This skill ships with the standard package and is installed repo-local at `.agents/skills/markdown-frontmatter` when a repository adopts the standard. That path is deliberate: both Claude Code and Codex CLI can discover it without a global skill owner. + +**Core principle: the schema is authoritative, not this file.** The machine contract is `markdown-frontmatter.schema.json` in project-standards, enforced by `project-standards validate`. This skill is the operating layer for the rules agents get wrong most often. On any conflict, the schema and current standard pages win. + +## When to use + +- Creating or editing a managed Markdown file (typically `README.md`, `docs/**/*.md`). +- A `project-standards validate`, `validate-frontmatter`, or `format-frontmatter --check` run failed and you need to fix the block. +- Deciding which `doc_type` / `status` / other controlled value to set. + +**When NOT to use: files that must NEVER carry frontmatter.** Agent-instruction and agent-skill files are harness config, not managed documents: `CLAUDE.md`, `AGENTS.md`, and anything under `.claude/`, `.agents/`, `.codex/`. That includes this installed skill at `.agents/skills/markdown-frontmatter`. Exclude those paths through the package's `exclude` option in `.standards/config.toml` instead of adding metadata. A repo may also exclude its root `README.md` if it prefers no metadata table on its landing page. + +## Required fields (the eleven) + +Every managed document opens with a `---` fenced YAML block carrying at least these, in this order: + +```yaml +--- +schema_version: '1.1' +id: 'note-xxxxxx-human-title' +title: 'Human Title' +description: 'One-sentence description of the document.' +doc_type: 'note' +status: 'draft' +created: 'YYYY-MM-DD' +updated: 'YYYY-MM-DD' +tags: [] +aliases: [] +related: [] +--- +``` + +For most docs, add the standard-profile optionals after `updated` in canonical order: `reviewed` (date|null), `owner` (stable person/team/role), `consumer` (enum), then after `related`: `source` (array), `confidence` (enum), `visibility` (enum), `license` (string|null). Relationship fields are optional, used only when needed: `supersedes`, `superseded_by`, `depends_on`, `applies_to`. + +## Formatting rules that actually fail validation + +These are the machine-checked rules an agent skips by habit: + +- **Quote every string, including dates.** `created: '2026-06-07'`, never `created: 2026-06-07`. +- **Identifier-like numbers are strings.** `schema_version: '1.1'`, not `1.1`. +- **Non-empty lists use block style** (`- 'item'` per line); **empty lists use `[]`**. No duplicate items. +- **No unknown top-level fields.** A stray `version:` or `type:` is rejected. Project- or tool-specific keys go under the `publish`, `project`, or `x_project` extension objects only. +- **Canonical key order** when keys are present: + + ```text + schema_version, id, title, description, doc_type, status, created, updated, + reviewed, owner, consumer, tags, aliases, related, supersedes, superseded_by, + depends_on, applies_to, source, confidence, visibility, license, + publish, project, x_project + ``` + +## Controlled values + +These fields accept only these values (the schema is the source of truth): + +| Field | Allowed values | +| --- | --- | +| `doc_type` | `index`, `note`, `concept`, `reference`, `runbook`, `spec`, `plan`, `adr`, `decision`, `research`, `template`, `log`, `prompt`, `schema` | +| `status` | `draft`, `active`, `review`, `deprecated`, `archived`, `superseded`, `stub` | +| `confidence` | `high`, `medium`, `low`, `unknown` | +| `visibility` | `private`, `internal`, `public` | +| `consumer` | `user`, `agent`, `mix`, `unknown` | + +- `README.md` and `index.md` → `doc_type: 'index'`. Files under `docs/research/` → `doc_type: 'research'`. +- `stub` is a **status**, never a `doc_type`. Use `doc_type`, never `type`. +- Canonical global tags include `frontmatter`, `metadata`, `standard`, `validation`, `infrastructure`, `it`, and `network`; repos may add documented local tags when the global set is insufficient. + +## The `id` field — standard-enforced format + +> **This is a standard rule, not a local addition.** `markdown-frontmatter@1.6` enforces the id format below via `validate-id` (run by `project-standards validate` and the V5 CI workflow). An id whose leading segment is not a valid `doc_type` **fails validation** with `prefix '' is not a valid doc_type`. Earlier repo-name-prefixed ids no longer pass. + +```text +{doc_type}-{base36-6}-{document-name} +``` + +The `doc_type` (one of the controlled values above), then a random 6-character base36 token, then a readable document slug, all lower kebab-case (e.g. `runbook-0f943i-restart-netbox-after-config-change`). The token keeps the id globally unique; the slug is frozen at creation and does **not** change when the title is edited. + +**Generate the id with the script. Never invent the token yourself.** An LLM asked for a "random" base36 token produces low-entropy, collision-prone strings and reuses tokens already in context, defeating the uniqueness goal. `scripts/` is this skill's own directory (invoke by absolute path if your cwd is elsewhere): + +```bash +scripts/new-doc-id # bare id, doc_type 'note' +scripts/new-doc-id --doc-type runbook # bare id, 'runbook' prefix +scripts/new-doc-id --scaffold --doc-type runbook # full canonical frontmatter block +``` + +The `--doc-type` value becomes both the id prefix and (in `--scaffold`) the `doc_type` field, so the two always agree; it defaults to `note`. `--doc-type` and `--status` must be standard-controlled values. `--scaffold` emits the eleven required fields in canonical order with today's date correctly quoted. Replace the `REPLACE:` description placeholder before committing. + +ADRs are the exception: follow the standard's ADR id form (`adr-{NNNN}-{repo-name}-{title}`, e.g. `adr-0001-homelab-use-postgresql-for-persistent-storage`), not the `doc_type`-prefixed format. Do not use the script for ADR ids. + +## Worked example (compliant standard profile) + +```yaml +--- +schema_version: '1.1' +id: 'runbook-0f943i-restart-netbox-after-config-change' +title: 'Restart netbox after config change' +description: 'Procedure to safely reload netbox after editing its configuration.' +doc_type: 'runbook' +status: 'active' +created: '2026-03-10' +updated: '2026-06-07' +reviewed: '2026-06-07' +owner: 'platform-team' +consumer: 'user' +tags: + - 'infrastructure' + - 'network' + - 'operations' + - 'runbook' +aliases: + - 'netbox-restart' +related: + - 'docs/architecture.md' +source: [] +confidence: 'high' +visibility: 'internal' +license: null +--- +# Restart netbox after config change + +...document body... +``` + +## Validate + +Compliance = `project-standards validate` exits `0`. Run it from the repository root: + +```bash +project-standards validate +``` + +That command runs schema validation, ID-format validation, and reference validation. Exit codes: `0` all matched files valid (or none matched); `1` one or more documents failed; `2` config/schema error. + +Use the formatter check for canonical quote style, key order, and list layout: + +```bash +format-frontmatter --check +``` + +To check or repair a single file's id: `validate-id ` (add `--fix` to rewrite an invalid id through the platform executor). + +## Common mistakes + +| Mistake | Fix | +| --- | --- | +| `type:` instead of `doc_type:` | Rename; `type` is not a field. | +| Unquoted date `created: 2026-06-07` | Quote it: `'2026-06-07'`. | +| `doc_type: 'readme'` for a README | README/index → `doc_type: 'index'`. | +| Extra top-level key (`version:`, `category:`) | Move under `project:`/`x_project:`, or drop it. | +| Frontmatter added to `CLAUDE.md` / `.claude/**` / `.agents/**` | Remove it; add the path to the package `exclude` option. | +| Omitting required arrays (`tags`/`aliases`/`related`) | Always present; empty = `[]`. | +| `doc_type: 'stub'` | `stub` is a `status`, not a `doc_type`. | + +## Authoritative references + +- [Standard README](https://github.com/L3DigitalNet/project-standards/blob/v5.10.0/standards/markdown-frontmatter/versions/1.6/README.md) — overview and adoption surface. +- [Structure Requirements](https://github.com/L3DigitalNet/project-standards/blob/v5.10.0/standards/markdown-frontmatter/versions/1.6/structure.md) — hard fields, key order, scalar/list rules, IDs, and validation. +- [Field Values](https://github.com/L3DigitalNet/project-standards/blob/v5.10.0/standards/markdown-frontmatter/versions/1.6/field-values.md) — lifecycle, ownership, canonical tags, aliases, relationships, sources, and extensions. +- [Adoption guide](https://github.com/L3DigitalNet/project-standards/blob/v5.10.0/standards/markdown-frontmatter/versions/1.6/adopt.md) — unified config, CI workflow, repo-local skill install, and compliance procedure. +- `standards/markdown-frontmatter/versions/1.6/schemas/markdown-frontmatter.schema.json` (in project-standards) — the selected package contract; wins on any conflict. diff --git a/.agents/skills/markdown-frontmatter/agents/openai.yaml b/.agents/skills/markdown-frontmatter/agents/openai.yaml new file mode 100644 index 0000000..ca9e0e3 --- /dev/null +++ b/.agents/skills/markdown-frontmatter/agents/openai.yaml @@ -0,0 +1,6 @@ +interface: + display_name: "Markdown Frontmatter" + short_description: "Create and maintain markdown frontmatter." + +policy: + allow_implicit_invocation: true diff --git a/.agents/skills/markdown-frontmatter/scripts/new-doc-id b/.agents/skills/markdown-frontmatter/scripts/new-doc-id new file mode 100755 index 0000000..8d71e52 --- /dev/null +++ b/.agents/skills/markdown-frontmatter/scripts/new-doc-id @@ -0,0 +1,198 @@ +#!/usr/bin/env bash +# new-doc-id - generate a standard-conformant document id for the +# markdown-frontmatter skill: {doc_type}-{base36-6}-{document-name} +# +# This is the id format package 1.2 enforces via `validate-id` +# (run by `project-standards validate` and the V5 CI workflow): an id whose +# leading segment is not a valid doc_type is REJECTED. The prefix therefore +# is the doc_type, not the repo name (a repo-name prefix fails validation). +# +# The random token exists because LLMs cannot generate randomness: a model +# asked for a "random" base36 token produces low-entropy, collision-prone +# strings and tends to reuse tokens already in its context, defeating the +# global-uniqueness goal. /dev/urandom supplies real entropy. +# +# Usage: new-doc-id [--scaffold] [--doc-type TYPE] [--status S] +# --doc-type TYPE doc_type - becomes the id prefix AND the scaffold's +# doc_type field (default: note). MUST be a valid +# doc_type or the id fails validate-id. +# --scaffold emit a full canonical-order frontmatter block (the +# eleven required fields, strings and dates quoted, +# empty lists as []) instead of the bare id +# --status S scaffold status (default: draft) +# +# The document name is sanitized with project_standards.id_format.slugify when +# available (spaces/punctuation collapse, Unicode is ASCII-normalized, long +# slugs are capped), with a stdlib fallback matching that algorithm. A trailing +# .md is dropped before slugging. Exit codes: 0 ok; 2 usage error. +# +# Requirements: bash, coreutils, Python 3. +# ADR ids are NOT this format - they follow the standard's ADR form +# adr-{NNNN}-{repo-name}-{title} (see SKILL.md); do not use this script for ADRs. + +set -euo pipefail + +usage() { + printf 'usage: new-doc-id [--scaffold] [--doc-type TYPE] [--status S] \n' >&2 + exit 2 +} + +valid_doc_type() { + case "$1" in + index | note | concept | reference | runbook | spec | plan | adr | decision | research | template | log | prompt | schema) + return 0 + ;; + *) + return 1 + ;; + esac +} + +valid_status() { + case "$1" in + draft | active | review | deprecated | archived | superseded | stub) + return 0 + ;; + *) + return 1 + ;; + esac +} + +SCAFFOLD=0 +DOC_TYPE="note" +STATUS="draft" +DOC_NAME="" +while (($# > 0)); do + case "$1" in + --scaffold) + SCAFFOLD=1 + shift + ;; + --doc-type) + [[ $# -ge 2 ]] || usage + DOC_TYPE="$2" + shift 2 + ;; + --status) + [[ $# -ge 2 ]] || usage + STATUS="$2" + shift 2 + ;; + -*) + usage + ;; + *) + [[ -z "$DOC_NAME" ]] || usage + DOC_NAME="$1" + shift + ;; + esac +done +[[ -n "$DOC_NAME" ]] || usage +if ! valid_doc_type "$DOC_TYPE"; then + printf 'new-doc-id: invalid doc_type: %s\n' "$DOC_TYPE" >&2 + exit 2 +fi +if [[ "$DOC_TYPE" == "adr" ]]; then + printf 'new-doc-id: do not use this script for ADR ids; use adr-{NNNN}-{repo-name}-{title}\n' >&2 + exit 2 +fi +if ! valid_status "$STATUS"; then + printf 'new-doc-id: invalid status: %s\n' "$STATUS" >&2 + exit 2 +fi + +SCRIPT_PATH="${BASH_SOURCE[0]}" +slug="$( + NEW_DOC_ID_SCRIPT="$SCRIPT_PATH" DOC_NAME="$DOC_NAME" python3 - <<'PY' +import os +import re +import sys +import unicodedata +from pathlib import Path + + +def fallback_slugify(text: str) -> str: + text = unicodedata.normalize("NFKD", text).encode("ascii", "ignore").decode("ascii") + text = text.lower() + text = re.sub(r"[^a-z0-9]+", "-", text).strip("-") + if len(text) > 60: + head = text[:60] + if "-" in head: + head = head[: head.rfind("-")] + text = head.strip("-") + return text + + +def load_slugify(): + script = Path(os.environ["NEW_DOC_ID_SCRIPT"]).resolve() + candidates = [Path.cwd() / "src"] + candidates.extend(parent / "src" for parent in script.parents) + for candidate in candidates: + if (candidate / "project_standards" / "id_format.py").exists(): + sys.path.insert(0, str(candidate)) + break + try: + from project_standards.id_format import slugify + except Exception: + return fallback_slugify + return slugify + + +doc_name = os.environ["DOC_NAME"] +if doc_name.endswith(".md"): + doc_name = doc_name[:-3] +print(load_slugify()(doc_name)) +PY +)" +if [[ -z "$slug" ]]; then + printf 'new-doc-id: document name sanitized to nothing: %q\n' "$DOC_NAME" >&2 + exit 2 +fi + +# 6 chars of base36 from /dev/urandom. head-then-tr (not tr-then-head) so tr +# never takes a SIGPIPE under pipefail; loop in case a chunk yields fewer +# than 6 valid chars. +token="" +while ((${#token} < 6)); do + chunk="$(head -c 256 /dev/urandom | tr -dc '0-9a-z')" + token="${token}${chunk}" +done +token="${token:0:6}" + +# The doc_type prefix is what validate-id keys on - a non-doc_type prefix fails. +id="${DOC_TYPE}-${token}-${slug}" + +if ((SCAFFOLD == 0)); then + printf '%s\n' "$id" + exit 0 +fi + +# Scaffold: the eleven required fields in canonical order, quoted per the +# rules that actually fail validation (quoted dates, quoted identifier-like +# numbers, [] for empty lists). Title = slug in Title Case as a starting +# point; description is a placeholder the author must replace. +title="$( + SLUG="$slug" python3 - <<'PY' +import os + +print(os.environ["SLUG"].replace("-", " ").title()) +PY +)" +today="$(date +%F)" +cat <` for all Python execution; do not activate `.venv` manually. - **Dependencies are exact-pinned** (`pydantic`, `jsonschema`, `PyYAML`, `yamllint`). Bump intentionally and re-run all validation stages before committing a version change. - **YAML linting → yamllint** (`.yamllint.yml`). All hand-authored YAML in `examples/` must pass before changes are considered complete. diff --git a/docs/research/2026-06-02-json-schema-pydantic-drift-gate.md b/docs/research/2026-06-02-json-schema-pydantic-drift-gate.md index 0ec409a..6a73a65 100644 --- a/docs/research/2026-06-02-json-schema-pydantic-drift-gate.md +++ b/docs/research/2026-06-02-json-schema-pydantic-drift-gate.md @@ -1,3 +1,21 @@ +--- +schema_version: '1.1' +id: 'research-g3szvl-ci-drift-gate-json-schema-draft-2020-12-vs-pydantic-v2-semantic-equivalence' +title: 'CI Drift Gate: JSON Schema Draft 2020-12 vs Pydantic v2 Semantic Equivalence' +description: 'Research supporting the repository schema-to-Pydantic drift gate design.' +doc_type: 'research' +status: 'active' +created: '2026-06-02' +updated: '2026-06-03' +tags: + - 'python' + - 'research' + - 'schema' + - 'validation' +aliases: [] +related: [] +--- + # CI Drift Gate: JSON Schema Draft 2020-12 vs Pydantic v2 Semantic Equivalence Mode: research · Topic: CI drift gate for JSON Schema Draft 2020-12 vs Pydantic v2 model equivalence · Saved: docs/research/2026-06-02-json-schema-pydantic-drift-gate.md diff --git a/examples/opentofu/proxmox-example.tf b/examples/opentofu/proxmox-example.tf index 8e3b991..13a2c7a 100644 --- a/examples/opentofu/proxmox-example.tf +++ b/examples/opentofu/proxmox-example.tf @@ -24,7 +24,7 @@ terraform { source = "bpg/proxmox" # Pin to the minor you have tested. Verify the current version against # https://search.opentofu.org/provider/bpg/proxmox/latest before bumping. - # This file was `tofu validate`-checked against bpg/proxmox 0.108.0. + # This file was `tofu validate`-checked against bpg/proxmox 0.111.1. version = "~> 0.66" } } diff --git a/generators/proxmox-opentofu-mapping-guide.md b/generators/proxmox-opentofu-mapping-guide.md index 087d4b2..caf591b 100644 --- a/generators/proxmox-opentofu-mapping-guide.md +++ b/generators/proxmox-opentofu-mapping-guide.md @@ -5,7 +5,7 @@ objects map to the [bpg/proxmox](https://search.opentofu.org/provider/bpg/proxmo OpenTofu provider, and where Proxmox/cloud-init specifics require adaptation. The companion artifact `examples/opentofu/proxmox-example.tf` is the rendered output for `lxc-pihole` and `vm-docker-apps`; it has been `tofu validate`-checked -against bpg/proxmox **0.108.0**. +against bpg/proxmox **0.111.1**. Legacy note: older guidance often references the **Telmate** provider. Use **bpg/proxmox**; Telmate is mentioned only as historical context and its diff --git a/network-infrastructure-schema.code-workspace b/network-infrastructure-schema.code-workspace deleted file mode 100644 index 5709732..0000000 --- a/network-infrastructure-schema.code-workspace +++ /dev/null @@ -1,8 +0,0 @@ -{ - "folders": [ - { - "path": "." - } - ], - "settings": {} -}