arcane-cli 0.34.1 โ 0.35.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.
- package/README.md +4 -1
- package/dist/assets/.arcane/governance/web-discoverability-standards.md +35 -1
- package/dist/assets/.github/prompts/spell-make-discoverable.prompt.md +31 -2
- package/dist/assets/report/show-report.template.html +382 -0
- package/dist/index.js +672 -7
- package/dist/index.js.map +1 -1
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -116,7 +116,7 @@ Arcane isn't a prompt snippet โ it's a full framework. Everything installs int
|
|
|
116
116
|
| ๐ **Spells** | **<!--count:spells-->41<!--/count:spells-->** prompt-driven workflows spanning the entire lifecycle โ planning, architecture, implementation, testing, review, shipping, session management, and ops. |
|
|
117
117
|
| โ๏ธ **Governance** | **<!--count:governance-->25<!--/count:governance-->** battle-tested standards as drop-in templates: git conventions, testing standards, CI/CD, threat model, ADR format, naming, security hardening, and more. |
|
|
118
118
|
| ๐ค **Agents** | **<!--count:agents-->12<!--/count:agents-->** ready-made agent personas with roles, clusters, and a gamified autonomy model โ assign work and power levels per repo. |
|
|
119
|
-
| ๐ ๏ธ **CLI** | `spell init / add / update / status / uninstall` โ install by profile or ร la carte, and keep everything in sync as new versions ship. |
|
|
119
|
+
| ๐ ๏ธ **CLI** | `spell init / add / update / status / uninstall` โ install by profile or ร la carte, and keep everything in sync as new versions ship. `spell report` turns a program's `PLAN.md` into a Show Report โ a generated completion page โ offline. |
|
|
120
120
|
|
|
121
121
|
<!-- arcane:start -->
|
|
122
122
|
<details>
|
|
@@ -195,6 +195,9 @@ spell agents init
|
|
|
195
195
|
# Keep your installed governance current
|
|
196
196
|
spell status # what's installed + available updates
|
|
197
197
|
spell update # pull the latest
|
|
198
|
+
|
|
199
|
+
# When a program in docs/plans/<slug>/ has shipped, generate its Show Report -- offline
|
|
200
|
+
spell report # writes docs/plans/<slug>/show-report.{json,html}
|
|
198
201
|
```
|
|
199
202
|
|
|
200
203
|
> **`spell` or `arcane`?** Both commands invoke the same CLI. `spell` ties to the Spell Loop; `arcane` is there for when you reach for the brand name. Use whichever you like.
|
|
@@ -18,6 +18,7 @@ How a web property becomes retrievable by search-engine crawlers and by the AI a
|
|
|
18
18
|
- Any value that legitimately differs per environment โ a canonical hostname, an "is this production" flag โ must be resolved at runtime from the running environment, never baked into a shared build artifact (WD-04, WD-05).
|
|
19
19
|
- Crawler traffic must never pass through endpoints carrying human-traffic side effects, and any crawler-facing content listing must reuse the application's own visibility rule rather than a hand-maintained copy of it (WD-06, WD-07).
|
|
20
20
|
- Crawl control (robots exclusion) and index control (a response header) close different gaps and are both required on non-production origins; search-index presence, not a static convention file alone, is the real lever for AI-assistant retrieval (WD-09, WD-12).
|
|
21
|
+
- AI assistants and answer engines reach a site through *retrieval* user-agents that are distinct from *training* crawlers; disallowing a retrieval agent removes the property from AI answers while a training opt-out does not โ decide the two separately, and never let one edit silently cover both (WD-16). Measure the result through a dedicated AI-referrer channel, and keep sitemap `lastmod` tied to real content dates so freshness is a signal rather than a constant (WD-17, WD-18).
|
|
21
22
|
|
|
22
23
|
---
|
|
23
24
|
|
|
@@ -40,6 +41,9 @@ How a web property becomes retrievable by search-engine crawlers and by the AI a
|
|
|
40
41
|
| WD-13 | User-supplied metadata text needs HTML-attribute escaping AND, for inline JSON-LD, "<"-character escaping. |
|
|
41
42
|
| WD-14 | Share images follow the ~1.91:1 convention, keep a safe-content margin, and stay modest in file size. |
|
|
42
43
|
| WD-15 | AI image generation can't reproduce exact type/logos โ composite the real assets over generated artwork instead. |
|
|
44
|
+
| WD-16 | Never disallow a *retrieval*-class AI user-agent when AI-assistant visibility is a goal; a *training*-crawler opt-out is a separate, explicit decision, declared as named per-agent groups. |
|
|
45
|
+
| WD-17 | Measure AI-assistant referrals through a dedicated referrer channel, an access-log watch on retrieval-class user-agents, and each engine's own AI-visibility report. |
|
|
46
|
+
| WD-18 | Sitemap `lastmod` values derive from each URL's real content change date โ never one build-time constant shared by every URL. |
|
|
43
47
|
|
|
44
48
|
---
|
|
45
49
|
|
|
@@ -140,6 +144,8 @@ Disallow: /
|
|
|
140
144
|
|
|
141
145
|
**Why:** see "Why two noindex mechanisms, not one" below.
|
|
142
146
|
|
|
147
|
+
**Non-canonical production hostnames are not non-production origins.** A hosting platform commonly serves the production artifact on an auto-assigned default hostname (and often a `www` alias) in addition to the canonical origin, and a static host may be unable to vary response headers by hostname. Those origins serve the *real* artifact, so WD-09's dual suppression must never be applied to the artifact itself โ it would noindex production. The mitigation is the one WD-01 already requires: a canonical tag on every route pointing at the canonical origin, plus registering only the canonical origin with each search console. Inventory such hostnames explicitly (spell-make-discoverable Phase 1) so their state is observed rather than assumed; a canonical tag is a hint an engine may decline, so an unlisted default hostname that has already been indexed is a finding, not noise.
|
|
148
|
+
|
|
143
149
|
---
|
|
144
150
|
|
|
145
151
|
## Why two noindex mechanisms, not one
|
|
@@ -166,7 +172,35 @@ Using only the robots-exclusion rule leaves the bare-URL-listing gap open. Using
|
|
|
166
172
|
|
|
167
173
|
### WD-12 โ Search-index presence outweighs a static AI-readable file
|
|
168
174
|
|
|
169
|
-
**Rule/observation: For the specific goal of being retrievable *by AI assistants* โ as distinct from being found by a human searching directly โ being present and current in a major search engine's index is a substantially stronger lever than any single static "AI-readable" convention file placed at a well-known path (for example, a plain-text site-description file such as the emerging `llms.txt` convention). Several AI assistants answer by drawing on a search engine's index rather than crawling the web independently, so an assistant's ability to surface a page is frequently bottlenecked on that page's search-index presence, not on whether a convention file exists at all. A convention file is still worth publishing โ it costs little and helps the assistants that do read it directly โ but it should not be mistaken for the primary lever. The everything-else in this document (WD-01 through WD-11) is the primary lever. Enforcement: structured spell gate (ARC-023) โ spell-make-discoverable's Phase 2 Audit table (Search-engine registration, WD-12) reads
|
|
175
|
+
**Rule/observation: For the specific goal of being retrievable *by AI assistants* โ as distinct from being found by a human searching directly โ being present and current in a major search engine's index is a substantially stronger lever than any single static "AI-readable" convention file placed at a well-known path (for example, a plain-text site-description file such as the emerging `llms.txt` convention). Several AI assistants answer by drawing on a search engine's index rather than crawling the web independently, so an assistant's ability to surface a page is frequently bottlenecked on that page's search-index presence, not on whether a convention file exists at all. A convention file is still worth publishing โ it costs little and helps the assistants that do read it directly โ but it should not be mistaken for the primary lever. The everything-else in this document (WD-01 through WD-11, and WD-16 through WD-18) is the primary lever. Enforcement: structured spell gate (ARC-023) โ spell-make-discoverable's Phase 2 Audit table (Search-engine registration, WD-12) reads each relevant webmaster console's own coverage status verbatim before Phase 3's approval gate allows any fix to proceed.**
|
|
176
|
+
|
|
177
|
+
Three refinements, each from measurement rather than from the conventions' own claims:
|
|
178
|
+
|
|
179
|
+
- **The index that matters is often not the largest one.** Several AI answer engines and assistant search features build on a *second* engine's index rather than the one with the largest human query share. Coverage therefore has to be confirmed in the console of each engine whose index an AI answer engine draws on โ and those consoles are also where the only citation-level AI-visibility reports live. Registering with the largest-share engine alone leaves the AI-retrieval path unverified.
|
|
180
|
+
- **The measured audience for a convention file is coding agents, not answer engines.** Large-sample crawl logs show answer engines essentially never request `llms.txt`; the agents that do are IDE assistants and coding agents fetching documentation, which also commonly ask for a Markdown representation of a page. Publish the file for that audience, and where the property serves developer documentation, serve a Markdown representation of each documentation route alongside it โ that is what those agents actually read.
|
|
181
|
+
- **Structured data behaves the same way.** Inline JSON-LD earns rich results and gives entity-aware consumers a machine-readable anchor, but controlled measurement shows no independent effect on AI-assistant citation. Publish it for the former reasons; do not count it as an AI lever.
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## AI retrieval, freshness, and measurement
|
|
186
|
+
|
|
187
|
+
### WD-16 โ Retrieval agents are never disallowed; training opt-out is a separate decision
|
|
188
|
+
|
|
189
|
+
**Rule: A robots-exclusion file must never disallow a *retrieval*-class AI user-agent โ the agents an AI assistant or answer engine fetches a page with at answer time, or builds its own search index with โ when being retrievable by AI assistants is a goal of the property. Opting out of *training* crawlers (the agents that collect data for model training and have no effect on answer-time retrieval) is a legitimate, separate decision: record it explicitly, and declare any policy as named per-agent groups so the two classes are visibly distinct in the file. Classify each agent against its vendor's current published crawler documentation, never from memory โ the names, and which class each belongs to, change. Enforcement: structured spell gate (ARC-023) โ spell-make-discoverable's Phase 2 Audit table (Robots and sitemap policy, WD-16) parses the live `robots.txt` group by group and requires that no retrieval-class user-agent is matched by a `Disallow:` covering an intended-public route before Phase 3's approval gate allows any fix to proceed.**
|
|
190
|
+
|
|
191
|
+
**Why:** Vendors publish separate user-agents for the two jobs, and the common failure is a well-intentioned "block the AI scrapers" edit that lists every AI agent name it can find. Human search rankings look untouched afterward โ the ordinary search crawler was never in the list โ while the property silently disappears from AI answers, because the retrieval agent was. Nothing in a search console reports that loss. A broad wildcard `Allow` is the correct posture for visibility, but it is fragile precisely because it is implicit: naming the retrieval agents as their own allow-groups makes the intent legible to the next editor and gives the audit something to assert against. Note that some user-initiated fetch agents disregard robots exclusion entirely; a `Disallow` aimed at them is a policy statement, not a control, and edge-level blocking is the actual mechanism if one is wanted.
|
|
192
|
+
|
|
193
|
+
### WD-17 โ Measure AI referrals, or the rest of this document is unverifiable
|
|
194
|
+
|
|
195
|
+
**Rule: Give AI-assistant traffic its own measurement path: a dedicated referrer channel in the property's analytics matching the assistant hostnames that send clicks, an access-log watch on retrieval-class user-agents (so fetches that never become clicks are still visible), and each search console's AI-visibility report where one exists. Treat the resulting counts as a floor, never a total. Enforcement: explicitly advisory (ARC-023) โ which analytics stack, which assistant hostnames, and which console reports exist is deployment-specific; spell-make-discoverable carries it as a Phase 6 acceptance-checklist line the operator verifies, not as a Phase 2 gate.**
|
|
196
|
+
|
|
197
|
+
**Why:** Most AI-assistant exposure never produces a referred click โ the answer is consumed inside the assistant, and much of what does click through arrives with no referrer and lands as direct traffic. Without a dedicated channel and a log-level view of the retrieval agents themselves, the effect of WD-01 through WD-16 is invisible: a property can be cited daily and show nothing in its analytics, or be silently dropped and show nothing either. The measurement is cheap and the alternative is guessing.
|
|
198
|
+
|
|
199
|
+
### WD-18 โ Sitemap `lastmod` tracks real content dates
|
|
200
|
+
|
|
201
|
+
**Rule: Each sitemap `lastmod` value must derive from that URL's actual content change date โ a content file's own date, the newest entry in a changelog the page renders, a commit timestamp โ never a single build-time constant shared by every URL. Enforcement: structured spell gate (ARC-023) โ spell-make-discoverable's Phase 2 Audit table (Robots and sitemap policy, WD-18) parses the live sitemap and fails the check when every `lastmod` carries one shared value, or when a sampled URL's `lastmod` predates content that demonstrably changed later, before Phase 3's approval gate allows any fix to proceed.**
|
|
202
|
+
|
|
203
|
+
**Why:** Freshness is a measured citation factor for AI answer engines, and `lastmod` is the one freshness signal a crawler can read without fetching the page. A constant shared across every URL says either "nothing has changed since this date" or "everything changed at once" โ both false โ so an engine learns to ignore the field, and a page that genuinely changed yesterday looks as stale as one untouched for a year. The constant also tends to be set once at launch and never moved, which is the quiet failure mode: it passes every structural check while actively misreporting the property's freshness.
|
|
170
204
|
|
|
171
205
|
---
|
|
172
206
|
|
|
@@ -94,8 +94,24 @@ for each of `{NONPROD_ORIGINS}`:
|
|
|
94
94
|
link, Open Graph / social-card tags, and any structured-data block โ
|
|
95
95
|
fetched with a plain HTTP client that does not execute JavaScript.
|
|
96
96
|
- **`robots.txt`** โ full raw contents, and whether it exists at all.
|
|
97
|
+
- **AI user-agent policy in `robots.txt`** โ every `User-agent:` group
|
|
98
|
+
present, and which vendor-published *retrieval* agents (the ones an AI
|
|
99
|
+
assistant or answer engine fetches with at answer time or indexes with)
|
|
100
|
+
versus *training* crawlers each group's rules match โ checked against each
|
|
101
|
+
vendor's current crawler documentation, not memory, because the names
|
|
102
|
+
change.
|
|
103
|
+
- **AI-readable convention files** โ whether `/llms.txt` exists, its raw
|
|
104
|
+
contents, and whether documentation routes also serve a Markdown
|
|
105
|
+
representation.
|
|
106
|
+
- **Non-canonical production hostnames** โ every hostname the platform
|
|
107
|
+
serves the production artifact on besides `{SITE_ORIGIN}` (a
|
|
108
|
+
platform-assigned default hostname, a `www` alias), with the canonical tag
|
|
109
|
+
and robots meta of one sampled route on each. These are not
|
|
110
|
+
`{NONPROD_ORIGINS}` โ they serve the real artifact โ so record them
|
|
111
|
+
separately.
|
|
97
112
|
- **`sitemap.xml`** (or wherever `robots.txt` points) โ full raw contents,
|
|
98
|
-
|
|
113
|
+
whether it parses as well-formed, and the set of distinct `lastmod` values
|
|
114
|
+
it carries.
|
|
99
115
|
- **Non-production noindex signals** โ for each `{NONPROD_ORIGINS}` host,
|
|
100
116
|
the response headers and parsed `<head>` of one sampled route.
|
|
101
117
|
- **DNS state at `{DNS_ZONE}`** relevant to search-engine registration โ
|
|
@@ -133,6 +149,8 @@ rule ID for it.
|
|
|
133
149
|
| *(supplementary)* | Does `robots.txt` contain a `Sitemap:` directive, and does fetching that URL return a well-formed sitemap? | The `Sitemap:` line and the fetched sitemap's parse result. |
|
|
134
150
|
| `WD-07` | Does every URL listed in the sitemap satisfy `{PUBLIC_VISIBILITY_PREDICATE}`? | Any sitemap entries that fail the predicate. |
|
|
135
151
|
| `WD-08` | Where `{PUBLIC_ASSET_PREFIX}` (or another specific public path referenced by page metadata) falls under a broader disallowed prefix in `robots.txt`, is an explicit `Allow:` rule declared for that specific path? | The relevant `Allow:`/`Disallow:` lines from `robots.txt`, and confirmation the specific path is fetchable by a crawler despite the broader disallow. |
|
|
152
|
+
| `WD-16` | Parse `{SITE_ORIGIN}/robots.txt` group by group. Is any *retrieval*-class AI user-agent (per each vendor's current crawler documentation) matched by a `Disallow:` that covers an intended-public route? A training-crawler `Disallow:` is not a failure โ record it as the operator's declared policy. | Every `User-agent:` group with its rules, each agent name classified as retrieval or training with the vendor page that says so, and the resolved allow/disallow verdict per retrieval agent for one sampled public route. |
|
|
153
|
+
| `WD-18` | Parse the sitemap's `lastmod` values. Do they differ across URLs and track each URL's actual content change date โ or does every entry carry one shared constant? | The distinct `lastmod` values with their counts, and for one sampled URL, the content's own change date (file date, changelog entry, or commit) next to its `lastmod`. |
|
|
136
154
|
| *(supplementary)* | For a sampled route under `{RESTRICTED_PREFIX}`: is it absent from the sitemap, does it carry a noindex signal, and is it unreachable via any link from a route that satisfies `{PUBLIC_VISIBILITY_PREDICATE}`? | Sitemap membership (should be none), noindex signal, and an inbound-link check from the public route set. |
|
|
137
155
|
|
|
138
156
|
**Non-production noindexing**
|
|
@@ -148,7 +166,7 @@ rule ID for it.
|
|
|
148
166
|
| --- | --- | --- |
|
|
149
167
|
| `WD-10` | Query `{DNS_ZONE}` directly (independent of any console) for the domain-ownership verification record this property's registration depends on. Is it present, and does it match the value the registration actually expects? | Raw DNS query result โ record type, name, and full current value โ captured before any write, per `EV-05`. |
|
|
150
168
|
| `WD-11` | Is a crawl/recrawl notification path (e.g. IndexNow) wired for URLs that change, and has `{SITE_ORIGIN}` been submitted through the webmaster console for each target search engine? | Notification endpoint configuration (if any), and the submission status shown in each webmaster console. |
|
|
151
|
-
| `WD-12` | In the webmaster console
|
|
169
|
+
| `WD-12` | In the webmaster console of the search engine with the largest share of query volume in the target market, **and** in the console of each engine whose index an AI answer engine draws on (these are frequently different engines), does `{SITE_ORIGIN}` show confirmed index coverage with no outstanding manual-action or coverage blocker? | Each console's own coverage/status readout, copied verbatim, plus its AI-visibility report (citations, grounding queries, or generative-AI impressions) where the console offers one โ this is what downstream AI-assistant retrieval actually depends on, not the site's own claim of being indexable. |
|
|
152
170
|
|
|
153
171
|
## Phase 3 โ Propose
|
|
154
172
|
|
|
@@ -181,6 +199,11 @@ Only with `--apply` **and** after Phase 3 approval. Typical fixes:
|
|
|
181
199
|
`{RESTRICTED_PREFIX}` from the sitemap.
|
|
182
200
|
- Add the missing noindex header and meta tag for `{NONPROD_ORIGINS}`,
|
|
183
201
|
gated by `{ENV_SIGNAL}` so production never picks it up by accident.
|
|
202
|
+
- Declare named per-agent `robots.txt` groups so retrieval and training
|
|
203
|
+
agents are visibly separate and a later edit cannot silently block both
|
|
204
|
+
(`WD-16`); derive sitemap `lastmod` from content dates (`WD-18`); publish
|
|
205
|
+
`/llms.txt` and, for documentation routes, a Markdown representation
|
|
206
|
+
(`WD-12`).
|
|
184
207
|
- Fix the escaping gap found under `WD-13`.
|
|
185
208
|
|
|
186
209
|
Each fix lands as its own commit, so any one of them can be reverted on its
|
|
@@ -236,6 +259,12 @@ user-verifiable acceptance checklist:
|
|
|
236
259
|
sitemap contains no `{RESTRICTED_PREFIX}` or `{NONPROD_ORIGINS}` URLs.
|
|
237
260
|
- [ ] Each `{NONPROD_ORIGINS}` host serves both a noindex header and a
|
|
238
261
|
noindex meta tag on a sampled route.
|
|
262
|
+
- [ ] `{SITE_ORIGIN}/robots.txt` disallows no retrieval-class AI user-agent
|
|
263
|
+
(`WD-16`), and the sitemap's `lastmod` values are not one shared
|
|
264
|
+
constant (`WD-18`).
|
|
265
|
+
- [ ] The property's analytics has a dedicated AI-referrer channel and its
|
|
266
|
+
access log is watched for retrieval-class user-agents (`WD-17`) โ so
|
|
267
|
+
the effect of everything above is measurable rather than assumed.
|
|
239
268
|
- [ ] The DNS verification record at `{DNS_ZONE}`, re-queried
|
|
240
269
|
independently, matches what the registration expects.
|
|
241
270
|
- [ ] The webmaster console for each target search engine shows the
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<!--
|
|
3
|
+
show-report template v0 (interim) -- SR-02.
|
|
4
|
+
The hand-written completion-ledger CSS/markup from this repository's own
|
|
5
|
+
two prior programs, converted to the Mustache grammar so the data contract
|
|
6
|
+
and the --check/--fix automation are real today. The v0 look is a
|
|
7
|
+
placeholder: the shipped design comes from arcane-ui's static-export
|
|
8
|
+
build (SR-05b) and drops into this same file slot in SR-06, at which point
|
|
9
|
+
the header below records the arcane-ui version and license notice it was
|
|
10
|
+
compiled from. Nothing here is arcane-ui source; this CSS was authored in
|
|
11
|
+
this MIT repository.
|
|
12
|
+
Rendered by: src/modules/show-report/render.ts (mustache). Zero runtime JS.
|
|
13
|
+
-->
|
|
14
|
+
<html lang="en">
|
|
15
|
+
<head>
|
|
16
|
+
<meta charset="utf-8">
|
|
17
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
18
|
+
<title>{{masthead.title}}</title>
|
|
19
|
+
<style>
|
|
20
|
+
:root {
|
|
21
|
+
--bg: #eef0f4;
|
|
22
|
+
--surface: #ffffff;
|
|
23
|
+
--ink: #1a1d2b;
|
|
24
|
+
--ink-soft: #565b72;
|
|
25
|
+
--ink-faint: #8a8fa3;
|
|
26
|
+
--accent: #b8802e;
|
|
27
|
+
--accent-2: #3e7a72;
|
|
28
|
+
--line: #dadde3;
|
|
29
|
+
--row-alt: #f6f7f9;
|
|
30
|
+
--shadow: rgba(26, 29, 43, 0.06);
|
|
31
|
+
--needs: #a6503a;
|
|
32
|
+
--needs-bg: #f7e6e1;
|
|
33
|
+
|
|
34
|
+
--cat-spell: #b8802e; --cat-spell-bg: #f6ead6;
|
|
35
|
+
--cat-feature: #3e7a72; --cat-feature-bg: #dcede9;
|
|
36
|
+
--cat-governance: #7a5c8a; --cat-governance-bg: #ece2f0;
|
|
37
|
+
--cat-decision: #4a5d8a; --cat-decision-bg: #e0e4f0;
|
|
38
|
+
--cat-fix: #a6503a; --cat-fix-bg: #f3ded8;
|
|
39
|
+
--cat-process: #6b7a3f; --cat-process-bg: #e6ead9;
|
|
40
|
+
--cat-docs: #6b7290; --cat-docs-bg: #e6e7ee;
|
|
41
|
+
--cat-platform: #39525a; --cat-platform-bg: #dde4e5;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@media (prefers-color-scheme: dark) {
|
|
45
|
+
:root:not([data-theme="light"]) {
|
|
46
|
+
--bg: #12141c;
|
|
47
|
+
--surface: #181b26;
|
|
48
|
+
--ink: #ede9e0;
|
|
49
|
+
--ink-soft: #9da3b8;
|
|
50
|
+
--ink-faint: #6c728a;
|
|
51
|
+
--accent: #d9a44e;
|
|
52
|
+
--accent-2: #6fafa5;
|
|
53
|
+
--line: #2a2e3d;
|
|
54
|
+
--row-alt: #1d202c;
|
|
55
|
+
--shadow: rgba(0, 0, 0, 0.35);
|
|
56
|
+
--needs: #e08a71;
|
|
57
|
+
--needs-bg: #3a2620;
|
|
58
|
+
|
|
59
|
+
--cat-spell: #e0b064; --cat-spell-bg: #3a2e1a;
|
|
60
|
+
--cat-feature: #7fc2b8; --cat-feature-bg: #17322e;
|
|
61
|
+
--cat-governance: #c19bd4; --cat-governance-bg: #2f2438;
|
|
62
|
+
--cat-decision: #8fa3d6; --cat-decision-bg: #202942;
|
|
63
|
+
--cat-fix: #e08a71; --cat-fix-bg: #3a2620;
|
|
64
|
+
--cat-process: #a3b876; --cat-process-bg: #2a3020;
|
|
65
|
+
--cat-docs: #adb2c9; --cat-docs-bg: #262a38;
|
|
66
|
+
--cat-platform: #7fa3ac; --cat-platform-bg: #1c2c2f;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
:root[data-theme="dark"] {
|
|
71
|
+
--bg: #12141c;
|
|
72
|
+
--surface: #181b26;
|
|
73
|
+
--ink: #ede9e0;
|
|
74
|
+
--ink-soft: #9da3b8;
|
|
75
|
+
--ink-faint: #6c728a;
|
|
76
|
+
--accent: #d9a44e;
|
|
77
|
+
--accent-2: #6fafa5;
|
|
78
|
+
--line: #2a2e3d;
|
|
79
|
+
--row-alt: #1d202c;
|
|
80
|
+
--shadow: rgba(0, 0, 0, 0.35);
|
|
81
|
+
--needs: #e08a71;
|
|
82
|
+
--needs-bg: #3a2620;
|
|
83
|
+
|
|
84
|
+
--cat-spell: #e0b064; --cat-spell-bg: #3a2e1a;
|
|
85
|
+
--cat-feature: #7fc2b8; --cat-feature-bg: #17322e;
|
|
86
|
+
--cat-governance: #c19bd4; --cat-governance-bg: #2f2438;
|
|
87
|
+
--cat-decision: #8fa3d6; --cat-decision-bg: #202942;
|
|
88
|
+
--cat-fix: #e08a71; --cat-fix-bg: #3a2620;
|
|
89
|
+
--cat-process: #a3b876; --cat-process-bg: #2a3020;
|
|
90
|
+
--cat-docs: #adb2c9; --cat-docs-bg: #262a38;
|
|
91
|
+
--cat-platform: #7fa3ac; --cat-platform-bg: #1c2c2f;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
* { box-sizing: border-box; }
|
|
95
|
+
|
|
96
|
+
body {
|
|
97
|
+
margin: 0;
|
|
98
|
+
background: var(--bg);
|
|
99
|
+
color: var(--ink);
|
|
100
|
+
font-family: "IBM Plex Sans", -apple-system, "Segoe UI", sans-serif;
|
|
101
|
+
line-height: 1.5;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
a { color: var(--accent-2); }
|
|
105
|
+
a:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px; }
|
|
106
|
+
code { font-family: "IBM Plex Mono", monospace; font-size: 0.92em; }
|
|
107
|
+
|
|
108
|
+
.page { max-width: 920px; margin: 0 auto; padding: 3rem 1.5rem 5rem; }
|
|
109
|
+
|
|
110
|
+
header.masthead { margin-bottom: 2.25rem; }
|
|
111
|
+
|
|
112
|
+
.eyebrow {
|
|
113
|
+
font-family: "IBM Plex Mono", monospace;
|
|
114
|
+
font-size: 0.72rem;
|
|
115
|
+
letter-spacing: 0.14em;
|
|
116
|
+
text-transform: uppercase;
|
|
117
|
+
color: var(--accent);
|
|
118
|
+
margin: 0 0 0.6rem;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
h1 {
|
|
122
|
+
font-family: "Fraunces", Georgia, serif;
|
|
123
|
+
font-weight: 600;
|
|
124
|
+
font-size: clamp(2rem, 5vw, 2.85rem);
|
|
125
|
+
letter-spacing: -0.01em;
|
|
126
|
+
margin: 0 0 0.5rem;
|
|
127
|
+
text-wrap: balance;
|
|
128
|
+
color: var(--ink);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.dek { font-size: 1.02rem; color: var(--ink-soft); max-width: 62ch; margin: 0 0 1.5rem; }
|
|
132
|
+
|
|
133
|
+
dl.stat-rail {
|
|
134
|
+
display: flex;
|
|
135
|
+
flex-wrap: wrap;
|
|
136
|
+
gap: 0.5rem 1.75rem;
|
|
137
|
+
margin: 0;
|
|
138
|
+
padding: 1rem 1.25rem;
|
|
139
|
+
background: var(--surface);
|
|
140
|
+
border: 1px solid var(--line);
|
|
141
|
+
border-radius: 10px;
|
|
142
|
+
box-shadow: 0 1px 2px var(--shadow);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.stat { display: flex; flex-direction: column; gap: 0.15rem; margin: 0; }
|
|
146
|
+
|
|
147
|
+
.stat dd {
|
|
148
|
+
margin: 0;
|
|
149
|
+
font-family: "IBM Plex Mono", monospace;
|
|
150
|
+
font-variant-numeric: tabular-nums;
|
|
151
|
+
font-size: 1.3rem;
|
|
152
|
+
font-weight: 600;
|
|
153
|
+
color: var(--ink);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.stat dt { font-size: 0.72rem; letter-spacing: 0.06em; text-transform: uppercase; color: var(--ink-faint); }
|
|
157
|
+
|
|
158
|
+
.needs-you {
|
|
159
|
+
margin-top: 1.25rem;
|
|
160
|
+
padding: 0.9rem 1.1rem;
|
|
161
|
+
border-radius: 10px;
|
|
162
|
+
border: 1px solid var(--needs);
|
|
163
|
+
background: var(--needs-bg);
|
|
164
|
+
}
|
|
165
|
+
.needs-you h2 { margin: 0 0 0.4rem; font-size: 0.95rem; color: var(--needs); font-family: "IBM Plex Sans", sans-serif; }
|
|
166
|
+
.needs-you ul { margin: 0; padding-left: 1.1rem; font-size: 0.88rem; }
|
|
167
|
+
.needs-you li { margin: 0.2rem 0; }
|
|
168
|
+
.needs-you.none { border-color: var(--line); background: var(--surface); color: var(--ink-soft); }
|
|
169
|
+
.needs-you.none h2 { color: var(--accent-2); }
|
|
170
|
+
|
|
171
|
+
.outcome { margin: 1rem 0 0; font-size: 0.92rem; color: var(--ink-soft); }
|
|
172
|
+
|
|
173
|
+
.legend {
|
|
174
|
+
display: flex;
|
|
175
|
+
flex-wrap: wrap;
|
|
176
|
+
gap: 0.5rem 1rem;
|
|
177
|
+
margin: 1.75rem 0 0.25rem;
|
|
178
|
+
padding: 1.5rem 0 0;
|
|
179
|
+
border-top: 1px solid var(--line);
|
|
180
|
+
list-style: none;
|
|
181
|
+
}
|
|
182
|
+
.legend-item { display: inline-flex; align-items: center; gap: 0.4rem; font-size: 0.76rem; color: var(--ink-soft); }
|
|
183
|
+
.swatch { width: 0.6rem; height: 0.6rem; border-radius: 50%; flex: none; }
|
|
184
|
+
|
|
185
|
+
section.phase { margin-top: 2.5rem; }
|
|
186
|
+
|
|
187
|
+
.phase-label { display: flex; align-items: baseline; gap: 0.75rem; margin-bottom: 0.9rem; }
|
|
188
|
+
.phase-label h2 {
|
|
189
|
+
font-family: "Fraunces", Georgia, serif;
|
|
190
|
+
font-weight: 600;
|
|
191
|
+
font-size: 1.15rem;
|
|
192
|
+
color: var(--ink);
|
|
193
|
+
margin: 0;
|
|
194
|
+
white-space: nowrap;
|
|
195
|
+
}
|
|
196
|
+
.phase-label .rule { flex: 1; height: 1px; background: var(--line); }
|
|
197
|
+
.phase-note { font-size: 0.82rem; color: var(--ink-faint); margin: -0.4rem 0 0.9rem; }
|
|
198
|
+
|
|
199
|
+
ol.ledger {
|
|
200
|
+
list-style: none;
|
|
201
|
+
margin: 0;
|
|
202
|
+
padding: 0;
|
|
203
|
+
background: var(--surface);
|
|
204
|
+
border: 1px solid var(--line);
|
|
205
|
+
border-radius: 10px;
|
|
206
|
+
overflow: hidden;
|
|
207
|
+
box-shadow: 0 1px 2px var(--shadow);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.row {
|
|
211
|
+
display: grid;
|
|
212
|
+
grid-template-columns: minmax(180px, 26%) 1fr auto;
|
|
213
|
+
gap: 1rem;
|
|
214
|
+
padding: 0.85rem 1.1rem;
|
|
215
|
+
border-top: 1px solid var(--line);
|
|
216
|
+
align-items: center;
|
|
217
|
+
}
|
|
218
|
+
.row:first-child { border-top: none; }
|
|
219
|
+
.row:nth-child(even) { background: var(--row-alt); }
|
|
220
|
+
|
|
221
|
+
.item-title { display: flex; align-items: flex-start; gap: 0.55rem; font-weight: 600; font-size: 0.94rem; color: var(--ink); }
|
|
222
|
+
.item-title .emoji { font-size: 1.05rem; line-height: 1.3; flex: none; }
|
|
223
|
+
.item-title a { color: inherit; text-decoration: none; }
|
|
224
|
+
.item-title a:hover { text-decoration: underline; }
|
|
225
|
+
.item-id { font-family: "IBM Plex Mono", monospace; font-size: 0.7rem; color: var(--ink-faint); margin-left: 0.4rem; font-weight: 500; }
|
|
226
|
+
|
|
227
|
+
.item-desc { font-size: 0.87rem; color: var(--ink-soft); max-width: 58ch; }
|
|
228
|
+
.item-desc.unwritten { color: var(--ink-faint); font-style: italic; }
|
|
229
|
+
|
|
230
|
+
.pill {
|
|
231
|
+
justify-self: end;
|
|
232
|
+
font-size: 0.7rem;
|
|
233
|
+
font-weight: 600;
|
|
234
|
+
letter-spacing: 0.02em;
|
|
235
|
+
padding: 0.28rem 0.6rem;
|
|
236
|
+
border-radius: 999px;
|
|
237
|
+
white-space: nowrap;
|
|
238
|
+
}
|
|
239
|
+
.pill.spell { color: var(--cat-spell); background: var(--cat-spell-bg); }
|
|
240
|
+
.pill.feature { color: var(--cat-feature); background: var(--cat-feature-bg); }
|
|
241
|
+
.pill.governance { color: var(--cat-governance); background: var(--cat-governance-bg); }
|
|
242
|
+
.pill.decision { color: var(--cat-decision); background: var(--cat-decision-bg); }
|
|
243
|
+
.pill.fix { color: var(--cat-fix); background: var(--cat-fix-bg); }
|
|
244
|
+
.pill.process { color: var(--cat-process); background: var(--cat-process-bg); }
|
|
245
|
+
.pill.docs { color: var(--cat-docs); background: var(--cat-docs-bg); }
|
|
246
|
+
.pill.platform { color: var(--cat-platform); background: var(--cat-platform-bg); }
|
|
247
|
+
|
|
248
|
+
.parked-list, .cast-list { margin: 0; padding-left: 1.1rem; font-size: 0.87rem; color: var(--ink-soft); }
|
|
249
|
+
.parked-list li, .cast-list li { margin: 0.3rem 0; }
|
|
250
|
+
.parked-list strong { color: var(--ink); }
|
|
251
|
+
|
|
252
|
+
footer.colophon {
|
|
253
|
+
margin-top: 3rem;
|
|
254
|
+
padding-top: 1.5rem;
|
|
255
|
+
border-top: 1px solid var(--line);
|
|
256
|
+
font-size: 0.8rem;
|
|
257
|
+
color: var(--ink-faint);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
@media (max-width: 640px) {
|
|
261
|
+
.row { grid-template-columns: 1fr; gap: 0.4rem; }
|
|
262
|
+
.pill { justify-self: start; }
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
@media print {
|
|
266
|
+
body { background: #fff; color: #000; }
|
|
267
|
+
.page { padding: 0; max-width: none; }
|
|
268
|
+
ol.ledger, dl.stat-rail, .needs-you { box-shadow: none; }
|
|
269
|
+
a { color: inherit; }
|
|
270
|
+
}
|
|
271
|
+
</style>
|
|
272
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
273
|
+
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,500;9..144,600&family=IBM+Plex+Sans:wght@400;500;600&family=IBM+Plex+Mono:wght@500;600&display=swap">
|
|
274
|
+
</head>
|
|
275
|
+
<body>
|
|
276
|
+
<div class="page">
|
|
277
|
+
<main>
|
|
278
|
+
|
|
279
|
+
<header class="masthead">
|
|
280
|
+
<p class="eyebrow">{{masthead.eyebrow}}</p>
|
|
281
|
+
<h1>{{masthead.title}}</h1>
|
|
282
|
+
<p class="dek">{{masthead.dek}}</p>
|
|
283
|
+
|
|
284
|
+
<dl class="stat-rail">
|
|
285
|
+
{{#stats}}
|
|
286
|
+
<div class="stat"><dd>{{value}}</dd><dt>{{label}}</dt></div>
|
|
287
|
+
{{/stats}}
|
|
288
|
+
</dl>
|
|
289
|
+
|
|
290
|
+
{{#needsYouCount}}
|
|
291
|
+
<section class="needs-you" aria-labelledby="needs-you-heading">
|
|
292
|
+
<h2 id="needs-you-heading">Needs you: {{needsYouCount}}</h2>
|
|
293
|
+
<ul>
|
|
294
|
+
{{#needsYou}}
|
|
295
|
+
<li><strong>{{id}}</strong> โ {{title}}{{#href}} ยท <a href="{{href}}">open</a>{{/href}}</li>
|
|
296
|
+
{{/needsYou}}
|
|
297
|
+
</ul>
|
|
298
|
+
</section>
|
|
299
|
+
{{/needsYouCount}}
|
|
300
|
+
{{^needsYouCount}}
|
|
301
|
+
<section class="needs-you none" aria-labelledby="needs-you-heading">
|
|
302
|
+
<h2 id="needs-you-heading">Nothing needs you</h2>
|
|
303
|
+
</section>
|
|
304
|
+
{{/needsYouCount}}
|
|
305
|
+
|
|
306
|
+
{{#hasOutcome}}<p class="outcome">{{outcome}}</p>{{/hasOutcome}}
|
|
307
|
+
|
|
308
|
+
<ul class="legend" aria-label="Category legend">
|
|
309
|
+
{{#legend}}
|
|
310
|
+
<li class="legend-item"><span class="swatch" style="background:var(--cat-{{key}})" aria-hidden="true"></span>{{label}}</li>
|
|
311
|
+
{{/legend}}
|
|
312
|
+
</ul>
|
|
313
|
+
</header>
|
|
314
|
+
|
|
315
|
+
{{#sections}}
|
|
316
|
+
<section class="phase" aria-labelledby="{{id}}-heading">
|
|
317
|
+
<div class="phase-label"><h2 id="{{id}}-heading">{{title}}</h2><span class="rule" aria-hidden="true"></span></div>
|
|
318
|
+
{{#hasNote}}<p class="phase-note">{{note}}</p>{{/hasNote}}
|
|
319
|
+
<ol class="ledger">
|
|
320
|
+
{{#rows}}
|
|
321
|
+
<li class="row">
|
|
322
|
+
<div class="item-title">{{#hasGlyph}}<span class="emoji" aria-hidden="true">{{glyph}}</span>{{/hasGlyph}}<span>{{#hasHref}}<a href="{{href}}">{{title}}</a>{{/hasHref}}{{^hasHref}}{{title}}{{/hasHref}}<span class="item-id">{{id}}</span></span></div>
|
|
323
|
+
{{#isUnwritten}}<div class="item-desc unwritten">unwritten</div>{{/isUnwritten}}
|
|
324
|
+
{{^isUnwritten}}<div class="item-desc">{{{descriptionHtml}}}</div>{{/isUnwritten}}
|
|
325
|
+
<span class="pill {{category}}">{{categoryLabel}}</span>
|
|
326
|
+
</li>
|
|
327
|
+
{{/rows}}
|
|
328
|
+
</ol>
|
|
329
|
+
</section>
|
|
330
|
+
{{/sections}}
|
|
331
|
+
|
|
332
|
+
{{#hasCorrections}}
|
|
333
|
+
{{#corrections}}
|
|
334
|
+
<section class="phase" aria-labelledby="corrections-heading">
|
|
335
|
+
<div class="phase-label"><h2 id="corrections-heading">Calibration</h2><span class="rule" aria-hidden="true"></span></div>
|
|
336
|
+
<p class="phase-note">{{checked}} claims checked ยท {{corrected}} corrected ยท {{unverifiable}} unverifiable. {{scopeNote}}</p>
|
|
337
|
+
<ol class="ledger">
|
|
338
|
+
{{#highlights}}
|
|
339
|
+
<li class="row">
|
|
340
|
+
<div class="item-title"><span>{{title}}</span></div>
|
|
341
|
+
{{#isUnwritten}}<div class="item-desc unwritten">unwritten</div>{{/isUnwritten}}
|
|
342
|
+
{{^isUnwritten}}<div class="item-desc">{{{descriptionHtml}}}</div>{{/isUnwritten}}
|
|
343
|
+
<span class="pill {{category}}">{{categoryLabel}}</span>
|
|
344
|
+
</li>
|
|
345
|
+
{{/highlights}}
|
|
346
|
+
</ol>
|
|
347
|
+
</section>
|
|
348
|
+
{{/corrections}}
|
|
349
|
+
{{/hasCorrections}}
|
|
350
|
+
|
|
351
|
+
{{#hasParked}}
|
|
352
|
+
<section class="phase" aria-labelledby="parked-heading">
|
|
353
|
+
<div class="phase-label"><h2 id="parked-heading">Parked โ needs operator</h2><span class="rule" aria-hidden="true"></span></div>
|
|
354
|
+
<ul class="parked-list">
|
|
355
|
+
{{#parked}}
|
|
356
|
+
<li><strong>{{title}}</strong> โ {{reason}}</li>
|
|
357
|
+
{{/parked}}
|
|
358
|
+
</ul>
|
|
359
|
+
</section>
|
|
360
|
+
{{/hasParked}}
|
|
361
|
+
|
|
362
|
+
{{#hasCast}}
|
|
363
|
+
<section class="phase" aria-labelledby="cast-heading">
|
|
364
|
+
<div class="phase-label"><h2 id="cast-heading">Cast</h2><span class="rule" aria-hidden="true"></span></div>
|
|
365
|
+
<p class="phase-note">Self-reported from commit trailers in this program's version window.</p>
|
|
366
|
+
<ul class="cast-list">
|
|
367
|
+
{{#cast}}
|
|
368
|
+
<li>{{name}} โ {{commits}} commits</li>
|
|
369
|
+
{{/cast}}
|
|
370
|
+
</ul>
|
|
371
|
+
</section>
|
|
372
|
+
{{/hasCast}}
|
|
373
|
+
|
|
374
|
+
</main>
|
|
375
|
+
|
|
376
|
+
<footer class="colophon">
|
|
377
|
+
Compiled {{compiledAtDate}} from <code>{{sourcesJoined}}</code>. Program <code>{{program.slug}}</code>, status <code>{{program.status}}</code>{{#hasVersionSpan}}, <code>{{program.versionSpan.from}}</code> โ <code>{{program.versionSpan.to}}</code>{{/hasVersionSpan}}. Template <code>{{colophon.templateVersion}}</code>. Every stat above is derived, never hand-typed.
|
|
378
|
+
</footer>
|
|
379
|
+
|
|
380
|
+
</div>
|
|
381
|
+
</body>
|
|
382
|
+
</html>
|