Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 141 additions & 0 deletions .agents/skills/docs-site/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
---
name: docs-site
description: 'The Astro + Starlight toolchain candy for the opencharly.ai documentation
site, and the

check-docs R10 bed that builds and proves it.

Use when working with the docs-site candy, the docs-site-app box, the check-docs
bed, or when

the documentation site fails to build.'
---

# docs-site — build the opencharly.ai site inside a box

**Audience: this page describes maintenance the charly repository performs on itself.**
`docs-site` builds *this* documentation site, so every `task …` command below names a
gate this project runs over its own sources from a charly checkout — never a step a
reader runs. They are named because the candy's contract is what keeps the published
site honest, not because you invoke them.

## Candy Properties

| Property | Value |
|---|---|
| Requires | `nodejs` |
| Packages | `git` (fedora section) |
| Builds into | `/srv/docs` (source), `/srv/docs/dist` (built site) |
| Consumed by | the `docs-site-app` box → the `check-docs` bed |

`docs-site` clones the published `opencharly/docs` repository, runs `npm ci` against its
committed lockfile, and runs the production Astro build. A build failure — a bad frontmatter
scalar, a Starlight config a version bump invalidated, a page whose markdown will not parse —
fails the **image build**, from the same input Cloudflare Pages builds, so the box catches it
before a deploy can.

## Why it clones the published repo

The docs repo is standalone — charly no longer carries it as a submodule — and the candy
cannot read another directory even if it did. The escapes are closed by design:

- `copy:` rejects `..` — `copy: "../../docs/x" may not contain .. (no traversal)` at validate.
- There is **no** field for pointing a candy at another directory. (An older revision of
`/charly-image:layer` documented a `directory:` field for exactly this; that field does not
exist in `spec/schema/candy.cue`, in the generated spec types, or in the loader, and
`charly box validate` rejects it as `field not allowed`.)

Cloning the published repo is the schema-legal path, and it tests the more useful thing: the
exact source Cloudflare builds.

**Ordering consequence.** The candy fetches an IMMUTABLE COMMIT, never a branch, so `DOCS_REF`
must be re-pinned to each new docs merge before this box — and the `check-docs` bed above it —
prove the new content. `task docs:pin` fails when `DOCS_REF` is not the docs repo's current
`main` head (it fetches `ls-remote` and compares — charly has no `docs` gitlink anymore: the
docs repo is standalone, and this candy's pin is the only thing that fixes what the bed tests).
That re-pin is a CONFIG edit and changes the landing's gate and attribution tier — see
`/charly-internals:git-workflow` (B6a step 4, and B2 "the derivation stops at the gitlink" for
the general rule), which owns landing mechanics.

**The pin is TWO occurrences, and both are asserted.** The sha appears once as `DOCS_REF`
(the clone's cache key) and once as a LITERAL in the `docs-site-pinned-commit` check's
`contains:` matcher. The duplication is deliberate and cannot be collapsed: a check step's
substitution does not resolve a candy `var:`, so writing `${DOCS_REF}` in the matcher makes
charly SKIP the step with "unresolved variables: DOCS_REF" — turning the anti-staleness guard
into a non-event while the bed still reports PASS, which is the exact failure the guard exists
to catch. `task docs:pin` therefore asserts BOTH: it compares `DOCS_REF` against the docs
repo's current `main` head AND requires the sha to occur at least twice in the file, so a
re-pin that updates only one of them fails the gate instead of leaving the check asserting a
commit nobody builds. Re-pinning means editing both.

A branch name here is a correctness bug, not a convenience: `var:` values are emitted as `ENV`
above the steps, so with `main` the clone layer's cache key never moves and the bed silently
rebuilds whatever commit that layer first captured. That shipped once, and every run passed while
proving stale content until review caught it.

| Var | Default |
|---|---|
| `DOCS_REPO` | `https://github.com/opencharly/docs.git` |
| `DOCS_REF` | the docs repo commit this bed tests — an immutable commit, **never a branch** (see above) |

## Verification

Every check is build-context, so `charly check box docs-site-app` proves the whole site builds
and has the right shape without deploying anything.

| Check | Asserts |
|---|---|
| `docs-site-node-present` | node reports a parseable major version |
| `docs-site-index-built` | `/srv/docs/dist/index.html` exists |
| `docs-site-cli-page` | a generated CLI reference page rendered |
| `docs-site-candy-acceptance-plan` | a candy page publishes its `plan:` as an acceptance spec |
| `docs-site-recipe-card` | a recipe card rendered with its cross-references rewritten to site links |
| `docs-site-runtime-plugin-page` | **the load-bearing one** — see below |
| `docs-site-provider-index` | the provider cross-index rendered |

### The load-bearing check

`docs-site-runtime-plugin-page` reads the page of `plugin-cdp`, which is **not** in
`compiled_plugins:` — it loads out-of-process over gRPC — and asserts both its rendered placement
and its CUE parameter schema.

That step is what fails if the generator ever narrows to documenting only the default-active set
(the binary's own command model, or the enabled-boxes list). It is deliberately the assertion
that breaks first, because silently omitting everything that is not compiled in is the most
plausible way for this catalog to go quietly wrong.

## The check-docs bed

```bash
charly check run check-docs
```

A `disposable: true` pod deploy of `docs-site-app`. Reaching steady state proves the box builds,
starts and stays up; the shape assertions already ran at image build.

The generator's own cross-reference integrity gate (an unresolvable
`/charly-<plugin>:<skill>` reference fails generation) is covered by the plugin's Go tests —
this bed covers the artifact those produce. The committed content's staleness against the
pinned charly is gated by the docs repo's own deploy workflow, not here.

## Troubleshooting

| Symptom | Cause |
|---|---|
| `docs-site-pinned-commit` FAILS | the clone layer is stale, or `DOCS_REF` was re-pinned without rebuilding |
| `task docs:pin` fails | `DOCS_REF` (or the check's literal sha) is not the docs repo's current `main` head — bump it in a charly PR |
| fetch fails `upload-pack: not our ref <sha>` | `DOCS_REF` names a commit not reachable from ANY ref in the docs repo — never pushed, or garbage-collected |
| fetch fails `couldn't find remote ref <x>` | `DOCS_REF` is not a FULL 40-hex sha — either a ref name (which the pinned-commit contract forbids) or an abbreviated sha, which `git fetch` rejects even when the commit exists |
| `npm ci` fails on a lockfile mismatch | `package.json` and `package-lock.json` disagree — regenerate the lockfile in the docs repo |
| a shape check fails but `index.html` exists | the generator emitted a different tree layout; regenerate in the docs repo (its deploy workflow) and check the diff in a docs PR |

## Cross-References

- `/charly-build:docs` — the `charly docs generate` verb that produces the content this builds.
- `/charly-coder:nodejs` — the node runtime this candy requires.
- `/charly-check:check` — the bed model and the check-verb catalog.

## When to Use This Skill

Invoke when working with the `docs-site` candy, the `docs-site-app` box, the `check-docs` bed, or
when the documentation site fails to build.
129 changes: 0 additions & 129 deletions charly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -285,132 +285,3 @@ docs-site-skill:
the documentation site fails to build.
triggers:
- "`charly docs` / the opencharly.ai site / the opencharly/docs repo / `candy/docs-site` / the check-docs bed / Starlight/Astro"
content: |
# docs-site — build the opencharly.ai site inside a box

**Audience: this page describes maintenance the charly repository performs on itself.**
`docs-site` builds *this* documentation site, so every `task …` command below names a
gate this project runs over its own sources from a charly checkout — never a step a
reader runs. They are named because the candy's contract is what keeps the published
site honest, not because you invoke them.

## Candy Properties

| Property | Value |
|---|---|
| Requires | `nodejs` |
| Packages | `git` (fedora section) |
| Builds into | `/srv/docs` (source), `/srv/docs/dist` (built site) |
| Consumed by | the `docs-site-app` box → the `check-docs` bed |

`docs-site` clones the published `opencharly/docs` repository, runs `npm ci` against its
committed lockfile, and runs the production Astro build. A build failure — a bad frontmatter
scalar, a Starlight config a version bump invalidated, a page whose markdown will not parse —
fails the **image build**, from the same input Cloudflare Pages builds, so the box catches it
before a deploy can.

## Why it clones the published repo

The docs repo is standalone — charly no longer carries it as a submodule — and the candy
cannot read another directory even if it did. The escapes are closed by design:

- `copy:` rejects `..` — `copy: "../../docs/x" may not contain .. (no traversal)` at validate.
- There is **no** field for pointing a candy at another directory. (An older revision of
`/charly-image:layer` documented a `directory:` field for exactly this; that field does not
exist in `spec/schema/candy.cue`, in the generated spec types, or in the loader, and
`charly box validate` rejects it as `field not allowed`.)

Cloning the published repo is the schema-legal path, and it tests the more useful thing: the
exact source Cloudflare builds.

**Ordering consequence.** The candy fetches an IMMUTABLE COMMIT, never a branch, so `DOCS_REF`
must be re-pinned to each new docs merge before this box — and the `check-docs` bed above it —
prove the new content. `task docs:pin` fails when `DOCS_REF` is not the docs repo's current
`main` head (it fetches `ls-remote` and compares — charly has no `docs` gitlink anymore: the
docs repo is standalone, and this candy's pin is the only thing that fixes what the bed tests).
That re-pin is a CONFIG edit and changes the landing's gate and attribution tier — see
`/charly-internals:git-workflow` (B6a step 4, and B2 "the derivation stops at the gitlink" for
the general rule), which owns landing mechanics.

**The pin is TWO occurrences, and both are asserted.** The sha appears once as `DOCS_REF`
(the clone's cache key) and once as a LITERAL in the `docs-site-pinned-commit` check's
`contains:` matcher. The duplication is deliberate and cannot be collapsed: a check step's
substitution does not resolve a candy `var:`, so writing `${DOCS_REF}` in the matcher makes
charly SKIP the step with "unresolved variables: DOCS_REF" — turning the anti-staleness guard
into a non-event while the bed still reports PASS, which is the exact failure the guard exists
to catch. `task docs:pin` therefore asserts BOTH: it compares `DOCS_REF` against the docs
repo's current `main` head AND requires the sha to occur at least twice in the file, so a
re-pin that updates only one of them fails the gate instead of leaving the check asserting a
commit nobody builds. Re-pinning means editing both.

A branch name here is a correctness bug, not a convenience: `var:` values are emitted as `ENV`
above the steps, so with `main` the clone layer's cache key never moves and the bed silently
rebuilds whatever commit that layer first captured. That shipped once, and every run passed while
proving stale content until review caught it.

| Var | Default |
|---|---|
| `DOCS_REPO` | `https://github.com/opencharly/docs.git` |
| `DOCS_REF` | the docs repo commit this bed tests — an immutable commit, **never a branch** (see above) |

## Verification

Every check is build-context, so `charly check box docs-site-app` proves the whole site builds
and has the right shape without deploying anything.

| Check | Asserts |
|---|---|
| `docs-site-node-present` | node reports a parseable major version |
| `docs-site-index-built` | `/srv/docs/dist/index.html` exists |
| `docs-site-cli-page` | a generated CLI reference page rendered |
| `docs-site-candy-acceptance-plan` | a candy page publishes its `plan:` as an acceptance spec |
| `docs-site-recipe-card` | a recipe card rendered with its cross-references rewritten to site links |
| `docs-site-runtime-plugin-page` | **the load-bearing one** — see below |
| `docs-site-provider-index` | the provider cross-index rendered |

### The load-bearing check

`docs-site-runtime-plugin-page` reads the page of `plugin-cdp`, which is **not** in
`compiled_plugins:` — it loads out-of-process over gRPC — and asserts both its rendered placement
and its CUE parameter schema.

That step is what fails if the generator ever narrows to documenting only the default-active set
(the binary's own command model, or the enabled-boxes list). It is deliberately the assertion
that breaks first, because silently omitting everything that is not compiled in is the most
plausible way for this catalog to go quietly wrong.

## The check-docs bed

```bash
charly check run check-docs
```

A `disposable: true` pod deploy of `docs-site-app`. Reaching steady state proves the box builds,
starts and stays up; the shape assertions already ran at image build.

The generator's own cross-reference integrity gate (an unresolvable
`/charly-<plugin>:<skill>` reference fails generation) is covered by the plugin's Go tests —
this bed covers the artifact those produce. The committed content's staleness against the
pinned charly is gated by the docs repo's own deploy workflow, not here.

## Troubleshooting

| Symptom | Cause |
|---|---|
| `docs-site-pinned-commit` FAILS | the clone layer is stale, or `DOCS_REF` was re-pinned without rebuilding |
| `task docs:pin` fails | `DOCS_REF` (or the check's literal sha) is not the docs repo's current `main` head — bump it in a charly PR |
| fetch fails `upload-pack: not our ref <sha>` | `DOCS_REF` names a commit not reachable from ANY ref in the docs repo — never pushed, or garbage-collected |
| fetch fails `couldn't find remote ref <x>` | `DOCS_REF` is not a FULL 40-hex sha — either a ref name (which the pinned-commit contract forbids) or an abbreviated sha, which `git fetch` rejects even when the commit exists |
| `npm ci` fails on a lockfile mismatch | `package.json` and `package-lock.json` disagree — regenerate the lockfile in the docs repo |
| a shape check fails but `index.html` exists | the generator emitted a different tree layout; regenerate in the docs repo (its deploy workflow) and check the diff in a docs PR |

## Cross-References

- `/charly-build:docs` — the `charly docs generate` verb that produces the content this builds.
- `/charly-coder:nodejs` — the node runtime this candy requires.
- `/charly-check:check` — the bed model and the check-verb catalog.

## When to Use This Skill

Invoke when working with the `docs-site` candy, the `docs-site-app` box, the `check-docs` bed, or
when the documentation site fails to build.
Loading