arcane-cli 0.21.1 → 0.22.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.
@@ -0,0 +1,258 @@
1
+ ---
2
+ name: Spell — Make Discoverable
3
+ description: Audit and fix a web property's search and AI-assistant discoverability — per-route share metadata, robots and sitemap policy, non-production noindexing, and search-engine registration. Read-only by default.
4
+ argument-hint: Optional path or origin of the property to audit (defaults to the repository root). Add --apply only after reviewing a dry run.
5
+ agent: agent
6
+ ---
7
+
8
+ ## Executive Summary
9
+
10
+ - This spell audits a web property's discoverability to search engines and, by
11
+ extension, to the AI assistants that retrieve from those engines' indexes —
12
+ per-route share metadata, robots/sitemap policy, non-production noindexing,
13
+ and search-engine registration — then proposes fixes.
14
+ - **The default run changes nothing.** It produces an audit and a proposed
15
+ change set, and stops at an explicit approval gate. Nothing is written
16
+ without `--apply` *and* that approval.
17
+ - **It never performs an outward-facing action on its own** — a DNS write, a
18
+ console submission, a crawl-notification ping. Every such action is
19
+ confirmed individually, with the current live state printed first, never
20
+ batched and never assumed from a prior run.
21
+ - **It verifies by content, never by confirmation.** Every claimed fix is
22
+ checked by re-fetching the actual served response or re-reading the actual
23
+ external state, per `EV-01`/`EV-02`.
24
+ - **This spell audits a property that is already live.** It does not deploy
25
+ one — run it after your team's own deploy/ship workflow has put something
26
+ live at `{SITE_ORIGIN}`, not instead of it.
27
+
28
+ ---
29
+
30
+ ## Placeholders
31
+
32
+ Every placeholder below follows the same resolution rule: pull it from
33
+ `.arcane.json`, the invoking frontmatter, or the repository's own existing
34
+ config; if it is not resolvable from any of those, **ask** — never assume.
35
+ A wrong guess in this spell is not a local mistake, it is published to the
36
+ public internet.
37
+
38
+ | Placeholder | What it is | Resolution rule |
39
+ | --- | --- | --- |
40
+ | `{SITE_ORIGIN}` | The production origin (scheme + host) of the property being audited. | Resolve from `.arcane.json` / frontmatter / the repo's own deploy config; if unset, ask — never assume, a wrong guess here is published to the public internet. |
41
+ | `{SERVING_LAYER}` | The component that already serves the built app's HTML responses (a static host, an edge/SSR function, a CDN in front of the origin). | Resolve from `.arcane.json` / frontmatter / the repo's own build-and-deploy config; if unset, ask rather than assuming a serving layer that isn't actually there. |
42
+ | `{ENV_SIGNAL}` | The repo's own existing signal for which environment a given request is running in (an env var, a build-time flag, a hostname pattern) — used to gate non-production noindexing. | Resolve from `.arcane.json` / frontmatter / the repo's own config; if unset, ask — guessing wrong here either noindexes production or leaves staging indexable. |
43
+ | `{PUBLIC_ASSET_PREFIX}` | The path prefix or origin that serves publicly-fetchable static/share assets (e.g. share-card images) without authentication. | Resolve from `.arcane.json` / frontmatter / the repo's own asset/storage config; if unset, ask. |
44
+ | `{RESTRICTED_PREFIX}` | The path prefix for admin, internal, or otherwise non-public routes that must never be indexable or listed in a sitemap. | Resolve from `.arcane.json` / frontmatter / the repo's own routing config; if unset, ask. |
45
+ | `{NONPROD_ORIGINS}` | The staging, preview, and development origins for this property that must never be indexed. | Resolve from `.arcane.json` / frontmatter / the repo's own deploy config; if unset, ask — never assume a short list you found is exhaustive. |
46
+ | `{DNS_ZONE}` | The DNS zone that holds, or will hold, this property's domain-ownership verification record for search-engine registration. | Resolve from `.arcane.json` / frontmatter / the repo's own infrastructure config; if unset, ask — never assume (see `EV-05`). |
47
+ | `{PUBLIC_VISIBILITY_PREDICATE}` | The app's own existing rule for what counts as publicly listed/indexable content, as distinct from private, draft, or restricted content. | Resolve from `.arcane.json` / frontmatter / the app's own existing authorization or visibility logic; if unset, ask — never invent a visibility rule the app doesn't already enforce. |
48
+
49
+ ## Context files
50
+
51
+ - [governance/web-discoverability-standards.md](../../.arcane/governance/web-discoverability-standards.md) —
52
+ every `WD-nn` rule this spell checks against. Rules are **cited by ID
53
+ only** in this spell, never restated. **If the file is not installed**,
54
+ this spell still runs in full — findings just cite each check as
55
+ `WD-nn (rationale unavailable — install the standards doc)` instead of
56
+ linking to it.
57
+ - [governance/external-verification-standards.md](../../.arcane/governance/external-verification-standards.md) —
58
+ every `EV-nn` rule the Verify phase (and the outward-facing-action gating
59
+ in Phase 4b) checks against, cited by ID only. **If the file is not
60
+ installed**, this spell still runs, and Phase 5 still re-fetches and
61
+ re-reads rather than trusting a write's own success signal — it just
62
+ cites those checks as `EV-nn (rationale unavailable — install the
63
+ standards doc)` instead of linking to it.
64
+ - An existing decisions or architecture doc, if one is present (e.g.
65
+ `DECISIONS.md` or an equivalent) — check it for any prior decision that
66
+ already governs this property's discoverability approach before proposing
67
+ something that would conflict with it. **If none is present**, proceed
68
+ without it and note in Phase 6 that no prior decision was found.
69
+
70
+ ## Preconditions
71
+
72
+ Stop and report, without changing anything, if any of these fail:
73
+
74
+ 1. **`{SITE_ORIGIN}` resolves**, per the Placeholders rule above, or the
75
+ operator supplies it when asked. If it cannot be resolved and the
76
+ operator does not answer, stop before Phase 1.
77
+ 2. **At least one HTTP(S) response is actually reachable at
78
+ `{SITE_ORIGIN}`.** This spell audits a property that is already live; if
79
+ nothing responds, stop and report that a deploy needs to happen first
80
+ (see Related) rather than auditing an empty origin.
81
+ 3. **`--apply` never substitutes for approval.** If `--apply` is passed up
82
+ front, the explicit approval gate in Phase 3 still applies in full before
83
+ Phase 4 runs anything.
84
+
85
+ ## Phase 1 — Inventory (always read-only)
86
+
87
+ Enumerate the current state before proposing anything to change. Do not
88
+ judge it yet — just record what is actually there, for `{SITE_ORIGIN}` and
89
+ for each of `{NONPROD_ORIGINS}`:
90
+
91
+ - **Per-route metadata**, sampled across a small set of distinct route
92
+ types (not just the homepage): `<title>`, meta description, canonical
93
+ link, Open Graph / social-card tags, and any structured-data block —
94
+ fetched with a plain HTTP client that does not execute JavaScript.
95
+ - **`robots.txt`** — full raw contents, and whether it exists at all.
96
+ - **`sitemap.xml`** (or wherever `robots.txt` points) — full raw contents,
97
+ and whether it parses as well-formed.
98
+ - **Non-production noindex signals** — for each `{NONPROD_ORIGINS}` host,
99
+ the response headers and parsed `<head>` of one sampled route.
100
+ - **DNS state at `{DNS_ZONE}`** relevant to search-engine registration —
101
+ the full current record set for the relevant name, queried directly
102
+ rather than read from a console (per `EV-01`).
103
+ - **Anything unreachable** — a route, origin, or DNS name that doesn't
104
+ resolve at all. Report it; never silently skip it.
105
+
106
+ ## Phase 2 — Audit
107
+
108
+ For each check below, capture the stated evidence and mark it
109
+ `PASS`/`FAIL`. **Do not restate any rule's justification here — cite the ID
110
+ only**; the reasoning lives in `web-discoverability-standards.md`.
111
+
112
+ **Per-route share metadata**
113
+
114
+ | Rule ID | Check | Evidence to capture |
115
+ | --- | --- | --- |
116
+ | `WD-01` | Fetch `{SITE_ORIGIN}`/&lt;a specific deep route, not the homepage&gt; with a plain HTTP client that does not execute JavaScript. Does the raw response body carry a `<title>` and meta description distinct from the homepage's? | The two raw response bodies, with the extracted `<title>`/description values highlighted. |
117
+ | `WD-02` | In that same response, do the Open Graph / social-card tags (`og:title`, `og:description`, `og:image`) reflect the route's own content, not the homepage's or a hard-coded default? | Extracted `og:*` tag values per sampled route. |
118
+ | `WD-03` | Does each fetched route carry exactly one `<link rel="canonical">`, self-referential or pointing at the one intended canonical URL, with no second conflicting canonical tag? | Canonical URL(s) found per route; flag any route with more than one. |
119
+ | `WD-04` | Where the route type is expected to carry structured data, is an `application/ld+json` block present and does it parse as valid JSON? | The raw JSON-LD block per route, and the parse result. |
120
+ | `WD-13` | Where a share-metadata or structured-data value is built from user- or content-supplied text (e.g. a title containing a quote or ampersand), is it correctly escaped for both contexts it appears in — JSON-escaped inside the JSON-LD payload, and separately HTML-escaped in surrounding markup — with no raw pass-through? | One sampled route whose content contains a quote, ampersand, or angle bracket, with the rendered JSON-LD and HTML attribute shown side by side. |
121
+ | `WD-15` | Fetch a sampled `{PUBLIC_ASSET_PREFIX}` share-image URL with a plain, unauthenticated HTTP client. Does it return `200` with an image content-type and no auth redirect? | Response status, `Content-Type` header, and byte size for the sampled asset. |
122
+
123
+ **Robots and sitemap policy**
124
+
125
+ | Rule ID | Check | Evidence to capture |
126
+ | --- | --- | --- |
127
+ | `WD-05` | Fetch `{SITE_ORIGIN}/robots.txt` directly. Does it exist, return `200`, parse as valid, and avoid disallowing any route intended to be indexed? | Raw `robots.txt` body, and any `Disallow` line matching an intended-public route. |
128
+ | `WD-06` | 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. |
129
+ | `WD-07` | Does every URL listed in the sitemap satisfy `{PUBLIC_VISIBILITY_PREDICATE}`? | Any sitemap entries that fail the predicate. |
130
+ | `WD-14` | 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. |
131
+
132
+ **Non-production noindexing**
133
+
134
+ | Rule ID | Check | Evidence to capture |
135
+ | --- | --- | --- |
136
+ | `WD-08` | For each `{NONPROD_ORIGINS}` host: is a site-wide `Disallow: /` in `robots.txt` the *only* suppression signal present — no accompanying noindex header or meta tag? | The host's `robots.txt` plus presence/absence of a noindex header and meta tag on a sampled route. |
137
+ | `WD-09` | For each `{NONPROD_ORIGINS}` host: does a sampled route serve **both** an `X-Robots-Tag: noindex` response header **and** an HTML `<meta name="robots" content="noindex">` tag, gated by `{ENV_SIGNAL}`? | Response headers and parsed `<head>` for one sampled route per non-prod host. |
138
+
139
+ **Search-engine registration**
140
+
141
+ | Rule ID | Check | Evidence to capture |
142
+ | --- | --- | --- |
143
+ | `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`. |
144
+ | `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. |
145
+ | `WD-12` | In the webmaster console for the search engine with the largest share of query volume in the target market, does `{SITE_ORIGIN}` show confirmed index coverage with no outstanding manual-action or coverage blocker? | The console's own coverage/status readout, copied verbatim — this is what downstream AI-assistant retrieval actually depends on, not the site's own claim of being indexable. |
146
+
147
+ ## Phase 3 — Propose
148
+
149
+ Render every `FAIL` from Phase 2 as one line:
150
+
151
+ ```text
152
+ WD-08 FAIL — <what was actually observed, in one sentence>
153
+ → see web-discoverability-standards.md § non-production noindexing
154
+ ```
155
+
156
+ Group the findings under the same four headings used in Phase 2. For each,
157
+ give the exact remediation: what changes in-repo (Phase 4a) versus what
158
+ requires an outward-facing action (Phase 4b).
159
+
160
+ End with the full proposed change set and one explicit question:
161
+
162
+ **"Approve this change set? (yes / edit / cancel)"**
163
+
164
+ Stop here. Nothing before this point writes anything, with or without
165
+ `--apply`.
166
+
167
+ ## Phase 4a — Apply: in-repo changes
168
+
169
+ Only with `--apply` **and** after Phase 3 approval. Typical fixes:
170
+
171
+ - Inject the missing per-route title/description/`og:*`/canonical tags at
172
+ `{SERVING_LAYER}` (server-rendered or edge-injected — never
173
+ client-side-only, per `WD-01`).
174
+ - Add or correct `robots.txt` and `sitemap.xml`, and exclude
175
+ `{RESTRICTED_PREFIX}` from the sitemap.
176
+ - Add the missing noindex header and meta tag for `{NONPROD_ORIGINS}`,
177
+ gated by `{ENV_SIGNAL}` so production never picks it up by accident.
178
+ - Fix the escaping gap found under `WD-13`.
179
+
180
+ Each fix lands as its own commit, so any one of them can be reverted on its
181
+ own (`git revert`, by commit) without touching the others.
182
+
183
+ ## Phase 4b — Apply: outward-facing actions
184
+
185
+ Only with `--apply` **and** after Phase 3 approval. Every item here is
186
+ confirmed **individually**, one at a time, with current state printed
187
+ first — never batched:
188
+
189
+ - **DNS TXT write at `{DNS_ZONE}`.** Print the CURRENT full record
190
+ verbatim (re-queried live, not from Phase 1's cached inventory). State
191
+ explicitly that the operation is an ADD/APPEND, never a create-or-replace
192
+ (`EV-05`). Require the operator to confirm the resulting full record
193
+ before writing. If the underlying tool only offers a replace operation,
194
+ refuse and require the operator to make the change manually instead.
195
+ - **Search-engine registration and verification-value entry.** This spell
196
+ *prepares* the exact value(s) the webmaster console needs and hands them
197
+ to the operator to enter — it does not attempt to drive the console's UI
198
+ itself.
199
+ - **Crawl/recrawl instant-notification pings (`WD-11`).** Confirmed once
200
+ per batch of changed URLs. Never auto-re-fired on a later re-run without
201
+ a fresh, explicit confirmation for that run.
202
+
203
+ ## Phase 5 — Verify
204
+
205
+ Cite `EV-01` and `EV-02` explicitly for every check below.
206
+
207
+ - For every in-repo fix from Phase 4a: re-fetch the actual served response
208
+ at `{SITE_ORIGIN}` (a fresh request, independent of the deploy that
209
+ shipped the fix) and compare it against the Phase 3 proposal, allowing a
210
+ short, bounded propagation window before treating a fresh mismatch as
211
+ failure (`EV-02`).
212
+ - For the DNS write from Phase 4b: re-query `{DNS_ZONE}` independently
213
+ (a different resolver path than whatever console performed the write) and
214
+ compare the full record against what was confirmed.
215
+ - For any console value entered by the operator: re-read it back from the
216
+ console rather than trusting the entry UI's own confirmation (`EV-01`).
217
+
218
+ Report each item as `PASS` / `FAIL` / `UNVERIFIED`. An item that could not
219
+ actually be re-checked is `UNVERIFIED` — never silently reported as `PASS`
220
+ just because nothing threw an error.
221
+
222
+ ## Phase 6 — Report
223
+
224
+ Summarize what changed, what's still open, and close with a plain-language,
225
+ user-verifiable acceptance checklist:
226
+
227
+ - [ ] A plain fetch (no JavaScript) of three distinct deep routes returns
228
+ three distinct `<title>` values.
229
+ - [ ] `{SITE_ORIGIN}/robots.txt` and its sitemap both return `200`, and the
230
+ sitemap contains no `{RESTRICTED_PREFIX}` or `{NONPROD_ORIGINS}` URLs.
231
+ - [ ] Each `{NONPROD_ORIGINS}` host serves both a noindex header and a
232
+ noindex meta tag on a sampled route.
233
+ - [ ] The DNS verification record at `{DNS_ZONE}`, re-queried
234
+ independently, matches what the registration expects.
235
+ - [ ] The webmaster console for each target search engine shows the
236
+ property verified with no outstanding coverage blocker.
237
+
238
+ ## Rules
239
+
240
+ This spell defers entirely to
241
+ [governance/web-discoverability-standards.md](../../.arcane/governance/web-discoverability-standards.md)
242
+ for what "discoverable" means and why, and to
243
+ [governance/external-verification-standards.md](../../.arcane/governance/external-verification-standards.md)
244
+ for how any claimed fix is verified. Findings and gates in this spell cite
245
+ rule IDs from both; they do not re-explain them. Where the two disagree with
246
+ what this spell says to do, the governance docs win.
247
+
248
+ ## Related
249
+
250
+ - **Consumes from:** the spell your team uses to ship/deploy a build (in
251
+ this repository, `spell-ship`). Run `spell-make-discoverable` only after a
252
+ deploy has put something live at `{SITE_ORIGIN}` — it audits an
253
+ already-deployed property, it does not deploy one.
254
+ - **Feeds into:** `spell-product-review` — once discoverability fixes are
255
+ applied and verified, carry the before/after state into the next
256
+ product-review cycle as one more measured signal.
257
+ - [Web Discoverability Standards](../../.arcane/governance/web-discoverability-standards.md)
258
+ - [External Verification Standards](../../.arcane/governance/external-verification-standards.md)
package/dist/index.js CHANGED
@@ -288,7 +288,9 @@ var PROFILE_CONFIGS = [
288
288
  "product-excellence-standards",
289
289
  "rca-process-standard",
290
290
  "universal-agent-rules",
291
- "spell-authoring-standards"
291
+ "spell-authoring-standards",
292
+ "external-verification-standards",
293
+ "web-discoverability-standards"
292
294
  ]
293
295
  }
294
296
  ];
@@ -431,6 +433,16 @@ var COMPONENTS = [
431
433
  description: "Supersession, tombstone, and retention conventions for records repositories",
432
434
  files: [".arcane/governance/records-conventions.md"]
433
435
  },
436
+ {
437
+ name: "external-verification-standards",
438
+ description: "Rules for confirming a write took effect in a system you don't control \u2014 consoles, pipelines, DNS, deployed endpoints",
439
+ files: [".arcane/governance/external-verification-standards.md"]
440
+ },
441
+ {
442
+ name: "web-discoverability-standards",
443
+ description: "Search-engine and AI-assistant discoverability rules for web properties",
444
+ files: [".arcane/governance/web-discoverability-standards.md"]
445
+ },
434
446
  // ─── Spells ────────────────────────────────────────────────────────────────
435
447
  // Split from the former monolithic `spell-prompts` + `claude-commands` pair
436
448
  // so a profile can select spells by capability (see ARC-0NN). Grouping lives
@@ -535,7 +547,9 @@ var COMPONENTS = [
535
547
  ".github/prompts/spell-enchant.prompt.md",
536
548
  ".claude/commands/spell-enchant.md",
537
549
  ".github/prompts/spell-generate-bot-icons.prompt.md",
538
- ".claude/commands/spell-generate-bot-icons.md"
550
+ ".claude/commands/spell-generate-bot-icons.md",
551
+ ".github/prompts/spell-make-discoverable.prompt.md",
552
+ ".claude/commands/spell-make-discoverable.md"
539
553
  ]
540
554
  },
541
555
  {