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
36 changes: 36 additions & 0 deletions .claude/skills/publish-site/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
name: publish-site
description: "Build the court's homepage (backlog, ledger, Features, records and every doc in one page) and publish it as a private Claude artifact, or refresh the one already published. Use it when the Operator asks to publish, refresh or update the site, the dashboard or the homepage, or says they want to read the repository without the terminal. Outward-facing: only on the Operator's ask, never during a night shift."
---

# Publish the site

The page is built by `devhost-site` (`share/python/site.py`), which turns the tracked docs and
records into one self-contained HTML page and knows nothing of where it is hosted. This skill
is the host adapter for Claude artifacts; another host (GitHub Pages, a file share) takes the
same file (`task devhost:site OUT=...`, and `devhost-site --standalone` for a full document).

## Rules

- **Only on the Operator's ask.** Publishing leaves the devhost, so it is never part of a
routine close, a back's job, or a night shift (`/night-shift` forbids outward-facing work).
- **Private by default.** An artifact starts private; this skill never shares it or changes who
can see it. Sharing is the Operator's act, in their own claude.ai.
- The page carries only what is tracked and has passed the name guard; do not add to it by hand.
If something in it should not be public, fix the source record and rebuild.

## Steps

1. **Build:** `task devhost:site PRS=1` (drop `PRS=1` when `gh` is not signed in; the page notes
it). It prints the path and size of `.devhost/site/index.html`.
2. **Publish with the Artifact tool**, file `.devhost/site/index.html` (a fragment: the artifact
host wraps it).
- **First time** (no `.devhost/site-url`): publish with `icon: "home"` and a one-sentence
`description` ("The court's dashboard: what needs the Operator, the backlog, the ledger, the
Features and every doc."). Then write the artifact URL from the result into
`.devhost/site-url` (one line; the file is ignored by git).
- **Afterwards:** read the URL from `.devhost/site-url` and publish the same file with that
`url`, so the link stays the same. If the publish is refused because the page was read-only
or changed elsewhere, say so and ask; never `force`.
3. **Report** the link and the page's build line (branch, commit, whether it had uncommitted
changes), in a sentence. Nothing is committed by this skill.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,7 @@ __pycache__/
.env
.env.*
!.env.example

# The generated homepage and the URL it was published at (devhost-site, publish-site skill)
.devhost/site/
.devhost/site-url
9 changes: 9 additions & 0 deletions src/devhost/NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,15 @@ All four: one `claude -p --model <model> --settings '{"disableAllHooks": true}'

`DEVHOST_STATE` (`/var/lib/devcontainer-features/devhost`, the named volume `devhost-state-${devcontainerId}`) holds `<job>/offsets/<session id>` (how far each back has read each transcript), `<job>/lock`, `<job>/job.log`, `<job>/pending-<session id>` and `since-<session id>` (the scribe), `tester/code_hash` and `last_result`, and `runs.jsonl`. Nothing of it belongs in the checkout, so a synced checkout never carries it and the ledger diff is only the entries. The post-create hook (`devhost-setup`, also `task devhost:setup`) creates the directory and hands it to the remote user through `dcf-persist`, and reports what backs it; for the host disk, bind-mount a host folder on the same target as the other Features of this collection do. `task devhost:log` tails every `job.log`; `task devhost:jobs:state` lists sessions read, pending items, held locks and the tester's last verdict.

## The site

`devhost-site` (`task devhost:site`, variables `OUT` and `PRS=1`) turns the repository's tracked docs and records into **one self-contained HTML page**, so the Operator reads the backlog, the ledger, the Features, the records and every doc in a browser, on a phone too, instead of the terminal or raw markdown. It is built in two halves on purpose:

- **The generator** (`share/python/site.py`, template `share/python/templates/site/index.html`): standard library only, deterministic (the same tree gives the same bytes, except `meta.generated`). It reads the working-tree content of `git ls-files`, builds one JSON document (what needs the Operator, backlog, ledger with corrections linked, cost headlines, Features, ADRs, proposals, night-shift reports, journal, optionally the open pull requests via `gh`, and every `*.md`), passes every string through the name guard (`devhost_common.redact_text`, the rule of `devhost-redact`) and embeds it in the page as `<script type="application/json" id="site-data">` with `<` escaped, so no document can end the script. The page renders everything client-side, fetches nothing (markdown comes from `marked` and `DOMPurify`, diagrams from `mermaid`, pinned on cdnjs), routes with plain hash tokens (`#home`, `#backlog`, `#ledger~<entry>`, `#d~docs~adr~0001-x.md`) and searches the title and text of every doc.
- **The host adapter**: the `publish-site` skill runs `task devhost:site PRS=1` and publishes `.devhost/site/index.html` with the Artifact tool (first time with the icon `home`, afterwards updating the URL kept in `.devhost/site-url`; both paths are ignored by git). Claude artifacts are only an easy site host. The output is a **fragment** (`<title>`, `<style>`, markup, scripts; no `html`, `head` or `body`) because that host wraps it; `devhost-site --standalone` writes a full document for any other host (GitHub Pages, a file share), which takes the same data and page unchanged.

Publishing is outward-facing: the skill runs only on the Operator's ask, never in a night shift, and an artifact is private until the Operator shares it. `task docs` regenerates this Feature's README from this file.

## Settings

Every script reads the same variables; a repository overrides them in `containerEnv` (or `.devcontainer/.env`), none needs a rebuild. Paths are relative to the root unless absolute.
Expand Down
11 changes: 10 additions & 1 deletion src/devhost/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

# Devhost (host session, backs, ledger) (devhost)

The way of working of this collection's consumer repositories as an installable Feature: one host Claude Code session per repository (claude-host, from the claude-code Feature), cons (sub-agents) and backs - background agents that run from Claude Code's Stop and SubagentStop hooks after every turn and keep the ledger (scribe), the backlog (secretary) and the lexicon and ADRs (librarian) without the person waiting, plus a model-free tester that runs the suite when code changed. Installs devhost-run-job (the runner), the four jobs with their briefs and guards, the name guard (devhost-redact), the transcript condenser (devhost-condense), the briefing, cost report, ADR, verify and init tools, a named volume at DEVHOST_STATE (/var/lib/devcontainer-features/devhost) for the jobs' offsets, locks, logs and metrics, a post-create hook (devhost-setup) that makes it usable and reports whether the repository is wired, and the Feature Taskfile (task devhost:init scaffolds a repository). Every path and model is a DEVHOST_* / <JOB>_MODEL variable with a default. Needs python3 (standard library only; installed with apt when missing) and claude on PATH.
The way of working of this collection's consumer repositories as an installable Feature: one host Claude Code session per repository (claude-host, from the claude-code Feature), cons (sub-agents) and backs - background agents that run from Claude Code's Stop and SubagentStop hooks after every turn and keep the ledger (scribe), the backlog (secretary) and the lexicon and ADRs (librarian) without the person waiting, plus a model-free tester that runs the suite when code changed. Installs devhost-run-job (the runner), the four jobs with their briefs and guards, the name guard (devhost-redact), the transcript condenser (devhost-condense), the briefing, cost report, ADR, verify and init tools, the site generator (devhost-site: the repository's docs and records as one self-contained HTML page, published by the publish-site skill), a named volume at DEVHOST_STATE (/var/lib/devcontainer-features/devhost) for the jobs' offsets, locks, logs and metrics, a post-create hook (devhost-setup) that makes it usable and reports whether the repository is wired, and the Feature Taskfile (task devhost:init scaffolds a repository). Every path and model is a DEVHOST_* / <JOB>_MODEL variable with a default. Needs python3 (standard library only; installed with apt when missing) and claude on PATH.

## Example Usage

Expand Down Expand Up @@ -77,6 +77,15 @@ All four: one `claude -p --model <model> --settings '{"disableAllHooks": true}'

`DEVHOST_STATE` (`/var/lib/devcontainer-features/devhost`, the named volume `devhost-state-${devcontainerId}`) holds `<job>/offsets/<session id>` (how far each back has read each transcript), `<job>/lock`, `<job>/job.log`, `<job>/pending-<session id>` and `since-<session id>` (the scribe), `tester/code_hash` and `last_result`, and `runs.jsonl`. Nothing of it belongs in the checkout, so a synced checkout never carries it and the ledger diff is only the entries. The post-create hook (`devhost-setup`, also `task devhost:setup`) creates the directory and hands it to the remote user through `dcf-persist`, and reports what backs it; for the host disk, bind-mount a host folder on the same target as the other Features of this collection do. `task devhost:log` tails every `job.log`; `task devhost:jobs:state` lists sessions read, pending items, held locks and the tester's last verdict.

## The site

`devhost-site` (`task devhost:site`, variables `OUT` and `PRS=1`) turns the repository's tracked docs and records into **one self-contained HTML page**, so the Operator reads the backlog, the ledger, the Features, the records and every doc in a browser, on a phone too, instead of the terminal or raw markdown. It is built in two halves on purpose:

- **The generator** (`share/python/site.py`, template `share/python/templates/site/index.html`): standard library only, deterministic (the same tree gives the same bytes, except `meta.generated`). It reads the working-tree content of `git ls-files`, builds one JSON document (what needs the Operator, backlog, ledger with corrections linked, cost headlines, Features, ADRs, proposals, night-shift reports, journal, optionally the open pull requests via `gh`, and every `*.md`), passes every string through the name guard (`devhost_common.redact_text`, the rule of `devhost-redact`) and embeds it in the page as `<script type="application/json" id="site-data">` with `<` escaped, so no document can end the script. The page renders everything client-side, fetches nothing (markdown comes from `marked` and `DOMPurify`, diagrams from `mermaid`, pinned on cdnjs), routes with plain hash tokens (`#home`, `#backlog`, `#ledger~<entry>`, `#d~docs~adr~0001-x.md`) and searches the title and text of every doc.
- **The host adapter**: the `publish-site` skill runs `task devhost:site PRS=1` and publishes `.devhost/site/index.html` with the Artifact tool (first time with the icon `home`, afterwards updating the URL kept in `.devhost/site-url`; both paths are ignored by git). Claude artifacts are only an easy site host. The output is a **fragment** (`<title>`, `<style>`, markup, scripts; no `html`, `head` or `body`) because that host wraps it; `devhost-site --standalone` writes a full document for any other host (GitHub Pages, a file share), which takes the same data and page unchanged.

Publishing is outward-facing: the skill runs only on the Operator's ask, never in a night shift, and an artifact is private until the Operator shares it. `task docs` regenerates this Feature's README from this file.

## Settings

Every script reads the same variables; a repository overrides them in `containerEnv` (or `.devcontainer/.env`), none needs a rebuild. Paths are relative to the root unless absolute.
Expand Down
4 changes: 2 additions & 2 deletions src/devhost/devcontainer-feature.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"id": "devhost",
"version": "1.4.1",
"version": "1.5.0",
"name": "Devhost (host session, backs, ledger)",
"description": "The way of working of this collection's consumer repositories as an installable Feature: one host Claude Code session per repository (claude-host, from the claude-code Feature), cons (sub-agents) and backs - background agents that run from Claude Code's Stop and SubagentStop hooks after every turn and keep the ledger (scribe), the backlog (secretary) and the lexicon and ADRs (librarian) without the person waiting, plus a model-free tester that runs the suite when code changed. Installs devhost-run-job (the runner), the four jobs with their briefs and guards, the name guard (devhost-redact), the transcript condenser (devhost-condense), the briefing, cost report, ADR, verify and init tools, a named volume at DEVHOST_STATE (/var/lib/devcontainer-features/devhost) for the jobs' offsets, locks, logs and metrics, a post-create hook (devhost-setup) that makes it usable and reports whether the repository is wired, and the Feature Taskfile (task devhost:init scaffolds a repository). Every path and model is a DEVHOST_* / <JOB>_MODEL variable with a default. Needs python3 (standard library only; installed with apt when missing) and claude on PATH.",
"description": "The way of working of this collection's consumer repositories as an installable Feature: one host Claude Code session per repository (claude-host, from the claude-code Feature), cons (sub-agents) and backs - background agents that run from Claude Code's Stop and SubagentStop hooks after every turn and keep the ledger (scribe), the backlog (secretary) and the lexicon and ADRs (librarian) without the person waiting, plus a model-free tester that runs the suite when code changed. Installs devhost-run-job (the runner), the four jobs with their briefs and guards, the name guard (devhost-redact), the transcript condenser (devhost-condense), the briefing, cost report, ADR, verify and init tools, the site generator (devhost-site: the repository's docs and records as one self-contained HTML page, published by the publish-site skill), a named volume at DEVHOST_STATE (/var/lib/devcontainer-features/devhost) for the jobs' offsets, locks, logs and metrics, a post-create hook (devhost-setup) that makes it usable and reports whether the repository is wired, and the Feature Taskfile (task devhost:init scaffolds a repository). Every path and model is a DEVHOST_* / <JOB>_MODEL variable with a default. Needs python3 (standard library only; installed with apt when missing) and claude on PATH.",
"documentationURL": "https://github.com/nabil-becker/devcontainer-features/tree/main/src/devhost",
"licenseURL": "https://github.com/nabil-becker/devcontainer-features/blob/main/LICENSE",
"options": {
Expand Down
3 changes: 3 additions & 0 deletions src/devhost/share/bin/entrypoints/site
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/usr/bin/env bash
# devhost-site: runs the devhost's site.py (installed under /usr/local/share/devhost).
exec python3 /usr/local/share/devhost/python/site.py "$@"
1 change: 1 addition & 0 deletions src/devhost/share/python/init.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"claude/skills/drop-a-seed/journalist.md": ".claude/skills/drop-a-seed/journalist.md",
"claude/skills/night-shift/SKILL.md": ".claude/skills/night-shift/SKILL.md",
"claude/skills/new-adr/SKILL.md": ".claude/skills/new-adr/SKILL.md",
"claude/skills/publish-site/SKILL.md": ".claude/skills/publish-site/SKILL.md",
"devhost/gitignore": ".devhost/.gitignore",
"devhost/CLAUDE.devhost.md": ".devhost/CLAUDE.devhost.md",
}
Expand Down
Loading
Loading