@gobing-ai/knowledge-kit 0.0.37 → 0.1.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/.claude-plugin/marketplace.json +1 -1
- package/dist/board/collect/index.css +654 -16
- package/dist/board/collect/index.js +191 -64
- package/dist/board/daily-voice/index.css +679 -12
- package/dist/board/daily-voice/index.js +185 -66
- package/dist/board/itc/index.css +679 -13
- package/dist/board/itc/index.js +188 -66
- package/dist/index.js +2864 -139
- package/package.json +3 -2
- package/plugins/generations/content-gen/dist/index.js +158 -12
- package/plugins/generations/core-facts-gen/dist/index.js +158 -12
- package/plugins/generations/daily-article-gen/dist/index.js +158 -12
- package/plugins/generations/dailynews-gen/dist/index.js +158 -12
- package/plugins/generations/episode-plan-gen/dist/index.js +158 -12
- package/plugins/generations/gemini-tts/README.md +4 -3
- package/plugins/generations/gemini-tts/dist/index.js +158 -12
- package/plugins/generations/image-gen/dist/index.js +158 -12
- package/plugins/generations/news-report-gen/dist/index.js +158 -12
- package/plugins/generations/omni-voice-gen/README.md +15 -4
- package/plugins/generations/omni-voice-gen/profiles.json +1 -2
- package/plugins/generations/voice-gen/dist/index.js +158 -12
- package/plugins/generations/voxcpm-tts/README.md +21 -1
- package/plugins/generations/voxcpm-tts/dist/index.js +173 -13
- package/plugins/generations/voxcpm-tts/scripts/audit-speaker-consistency.py +109 -0
- package/plugins/generations/voxcpm-tts/src/voxcpm-client.ts +28 -1
- package/plugins/ingestions/aihot-ingest/dist/index.js +158 -12
- package/plugins/ingestions/horizon-ingest/dist/index.js +158 -12
- package/plugins/ingestions/last30days-ingest/dist/index.js +158 -12
- package/plugins/ingestions/web-search/dist/index.js +158 -12
- package/plugins/kk/commands/workflow-run.md +56 -18
- package/plugins/kk/decisions/kk-itc.yaml +46 -0
- package/plugins/kk/decisions/kk-seo-keyword.yaml +57 -0
- package/plugins/kk/decisions/kk-seo-page.yaml +230 -0
- package/plugins/kk/decisions/kk-seo-pair.yaml +21 -0
- package/plugins/kk/decisions/kk-seo-site.yaml +90 -0
- package/plugins/kk/plugin.json +1 -1
- package/plugins/kk/publish-presets/chinese-article.json +14 -0
- package/plugins/kk/publish-presets/technical-article.json +14 -0
- package/plugins/kk/references/smart-seo-attribution.md +85 -0
- package/plugins/kk/scripts/daily-stages.ts +42 -2
- package/plugins/kk/scripts/itc-stages.ts +25 -118
- package/plugins/kk/scripts/kk-workflow-stages.ts +38 -1
- package/plugins/kk/scripts/seo-decisions.ts +477 -0
- package/plugins/kk/scripts/seo-enrich.ts +728 -0
- package/plugins/kk/scripts/seo-facts.ts +610 -0
- package/plugins/kk/scripts/seo-fetch.ts +766 -0
- package/plugins/kk/scripts/seo-report.ts +460 -0
- package/plugins/kk/scripts/seo-rules.ts +1882 -0
- package/plugins/kk/scripts/seo-stages.ts +1044 -0
- package/plugins/kk/skills/article-adapt/SKILL.md +2 -2
- package/plugins/kk/skills/smart-seo/SKILL.md +183 -0
- package/plugins/kk/skills/smart-seo/references/method.md +126 -0
- package/plugins/kk/skills/smart-seo/references/narrative.md +125 -0
- package/plugins/kk/skills/smart-seo/references/operations.md +202 -0
- package/plugins/kk/skills/smart-seo/references/seo-geo-sources.md +90 -0
- package/plugins/kk/workflows/feature-verification.yaml +28 -24
- package/plugins/kk/workflows/kk-daily-ai-voice2.yaml +85 -17
- package/plugins/kk/workflows/kk-itc-author.yaml +406 -0
- package/plugins/kk/workflows/kk-itc.yaml +208 -682
- package/plugins/kk/workflows/kk-smart-seo.yaml +251 -0
- package/plugins/publishings/bilibili-pub/dist/index.js +21782 -0
- package/plugins/publishings/bilibili-pub/dist/profile-check.js +20410 -0
- package/plugins/publishings/bilibili-pub/package.json +20 -0
- package/plugins/publishings/bilibili-pub/plugin.json +12 -0
- package/plugins/publishings/bilibili-pub/src/flow.ts +1296 -0
- package/plugins/publishings/bilibili-pub/src/index.ts +139 -0
- package/plugins/publishings/bilibili-pub/src/profile-check.ts +19 -0
- package/plugins/publishings/bilibili-pub/src/selectors.ts +222 -0
- package/plugins/publishings/bilibili-pub/tsconfig.json +4 -0
- package/plugins/publishings/cnblogs-pub/dist/index.js +21383 -0
- package/plugins/publishings/cnblogs-pub/dist/profile-check.js +20412 -0
- package/plugins/publishings/cnblogs-pub/package.json +19 -0
- package/plugins/publishings/cnblogs-pub/plugin.json +12 -0
- package/plugins/publishings/cnblogs-pub/src/flow.ts +708 -0
- package/plugins/publishings/cnblogs-pub/src/index.ts +172 -0
- package/plugins/publishings/cnblogs-pub/src/profile-check.ts +24 -0
- package/plugins/publishings/cnblogs-pub/src/selectors.ts +104 -0
- package/plugins/publishings/cnblogs-pub/tsconfig.json +4 -0
- package/plugins/publishings/csdn-pub/dist/index.js +21552 -0
- package/plugins/publishings/csdn-pub/dist/profile-check.js +20963 -0
- package/plugins/publishings/csdn-pub/package.json +19 -0
- package/plugins/publishings/csdn-pub/plugin.json +12 -0
- package/plugins/publishings/csdn-pub/src/index.ts +1178 -0
- package/plugins/publishings/csdn-pub/src/profile-check.ts +23 -0
- package/plugins/publishings/csdn-pub/tsconfig.json +4 -0
- package/plugins/publishings/emdash-pub/dist/index.js +158 -12
- package/plugins/publishings/emdash-pub/plugin.json +22 -1
- package/plugins/publishings/infoq-pub/dist/index.js +160 -12
- package/plugins/publishings/infoq-pub/dist/profile-check.js +160 -12
- package/plugins/publishings/infoq-pub/plugin.json +21 -0
- package/plugins/publishings/jike-pub/dist/index.js +21379 -0
- package/plugins/publishings/jike-pub/dist/profile-check.js +20951 -0
- package/plugins/publishings/jike-pub/package.json +19 -0
- package/plugins/publishings/jike-pub/plugin.json +12 -0
- package/plugins/publishings/jike-pub/src/index.ts +800 -0
- package/plugins/publishings/jike-pub/src/profile-check.ts +21 -0
- package/plugins/publishings/jike-pub/tsconfig.json +4 -0
- package/plugins/publishings/juejin-pub/dist/index.js +160 -12
- package/plugins/publishings/juejin-pub/dist/profile-check.js +160 -12
- package/plugins/publishings/juejin-pub/plugin.json +21 -0
- package/plugins/publishings/medium-pub/dist/index.js +158 -12
- package/plugins/publishings/medium-pub/plugin.json +22 -1
- package/plugins/publishings/podcast-pub/dist/index.js +158 -12
- package/plugins/publishings/qiita-pub/dist/index.js +158 -12
- package/plugins/publishings/qiita-pub/plugin.json +24 -1
- package/plugins/publishings/shipinhao-pub/dist/index.js +21715 -0
- package/plugins/publishings/shipinhao-pub/dist/profile-check.js +20431 -0
- package/plugins/publishings/shipinhao-pub/package.json +19 -0
- package/plugins/publishings/shipinhao-pub/plugin.json +12 -0
- package/plugins/publishings/shipinhao-pub/src/flow.ts +1323 -0
- package/plugins/publishings/shipinhao-pub/src/index.ts +173 -0
- package/plugins/publishings/shipinhao-pub/src/profile-check.ts +25 -0
- package/plugins/publishings/shipinhao-pub/src/selectors.ts +241 -0
- package/plugins/publishings/shipinhao-pub/tsconfig.json +4 -0
- package/plugins/publishings/sspai-pub/dist/index.js +21371 -0
- package/plugins/publishings/sspai-pub/dist/profile-check.js +20414 -0
- package/plugins/publishings/sspai-pub/package.json +19 -0
- package/plugins/publishings/sspai-pub/plugin.json +12 -0
- package/plugins/publishings/sspai-pub/src/flow.ts +624 -0
- package/plugins/publishings/sspai-pub/src/index.ts +167 -0
- package/plugins/publishings/sspai-pub/src/profile-check.ts +25 -0
- package/plugins/publishings/sspai-pub/src/selectors.ts +122 -0
- package/plugins/publishings/sspai-pub/tsconfig.json +4 -0
- package/plugins/publishings/substack-pub/dist/index.js +160 -12
- package/plugins/publishings/substack-pub/dist/profile-check.js +160 -12
- package/plugins/publishings/substack-pub/plugin.json +21 -0
- package/plugins/publishings/surfdash-pub/dist/index.js +158 -12
- package/plugins/publishings/surfdash-pub/plugin.json +22 -1
- package/plugins/publishings/threads-pub/dist/index.js +158 -12
- package/plugins/publishings/wechatmp-pub/dist/index.js +1669 -502
- package/plugins/publishings/wechatmp-pub/dist/profile-check.js +204 -21
- package/plugins/publishings/wechatmp-pub/plugin.json +32 -0
- package/plugins/publishings/wechatmp-pub/src/cgi-draft.ts +540 -0
- package/plugins/publishings/wechatmp-pub/src/cgi-media.ts +380 -0
- package/plugins/publishings/wechatmp-pub/src/cgi.ts +485 -0
- package/plugins/publishings/wechatmp-pub/src/flow.ts +104 -0
- package/plugins/publishings/weibo-pub/dist/index.js +160 -12
- package/plugins/publishings/weibo-pub/dist/profile-check.js +160 -12
- package/plugins/publishings/weibo-pub/plugin.json +35 -0
- package/plugins/publishings/x-pub/dist/index.js +160 -12
- package/plugins/publishings/x-pub/dist/profile-check.js +160 -12
- package/plugins/publishings/x-pub/plugin.json +40 -0
- package/plugins/publishings/xhs-pub/dist/index.js +160 -12
- package/plugins/publishings/xhs-pub/dist/profile-check.js +160 -12
- package/plugins/publishings/xhs-pub/plugin.json +37 -0
- package/plugins/publishings/xiaoyuzhou-pub/dist/index.js +21491 -0
- package/plugins/publishings/xiaoyuzhou-pub/dist/profile-check.js +20417 -0
- package/plugins/publishings/xiaoyuzhou-pub/package.json +19 -0
- package/plugins/publishings/xiaoyuzhou-pub/plugin.json +12 -0
- package/plugins/publishings/xiaoyuzhou-pub/src/flow.ts +826 -0
- package/plugins/publishings/xiaoyuzhou-pub/src/index.ts +174 -0
- package/plugins/publishings/xiaoyuzhou-pub/src/profile-check.ts +26 -0
- package/plugins/publishings/xiaoyuzhou-pub/src/selectors.ts +125 -0
- package/plugins/publishings/xiaoyuzhou-pub/tsconfig.json +4 -0
- package/plugins/publishings/zenn-pub/dist/index.js +158 -12
- package/plugins/publishings/zenn-pub/plugin.json +22 -1
- package/plugins/generations/omni-voice-gen/voices/robin-news.wav +0 -0
|
@@ -9,8 +9,8 @@ description: This skill should be used when a kk-itc publish plan contains varia
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
This skill adapts one published article into one Content file per `publish-plan.json` variant with
|
|
12
|
-
`adapt: true`. It is deliberately **thin**: the plan (built
|
|
13
|
-
`
|
|
12
|
+
`adapt: true`. It is deliberately **thin**: the plan (built by `buildItcPublishPlan` in
|
|
13
|
+
`apps/cli/src/itc-plan.ts` from each publish manifest's declared profiles) carries the per-variant locale, format and limits — this skill
|
|
14
14
|
holds **no platform table** and never decides which variants exist.
|
|
15
15
|
|
|
16
16
|
## Inputs
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: smart-seo
|
|
3
|
+
description: >-
|
|
4
|
+
Run, review and interpret a read-only Smart SEO/GEO audit and turn its evidence into a prioritized
|
|
5
|
+
action plan. Use when the user says "audit this site for SEO", "run a smart SEO audit", "review
|
|
6
|
+
this saved audit", "explain these SEO findings", or "check GEO readiness". Read-only results: no
|
|
7
|
+
site mutation, no ranking and no citation promises.
|
|
8
|
+
version: 0.1.0
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# smart-seo — evidence-grounded SEO/GEO audits
|
|
12
|
+
|
|
13
|
+
One entry point for the whole audit lifecycle: intake → run → review evidence → interpret →
|
|
14
|
+
narrative → offline rescoring. There is no second command, subagent or plugin kind; this skill plus
|
|
15
|
+
`plugins/kk/workflows/kk-smart-seo.yaml` and the `kk stage run seo-*` stages are the entire surface.
|
|
16
|
+
|
|
17
|
+
## 0. The contract — state it before you report
|
|
18
|
+
|
|
19
|
+
- **Read-only.** The audit fetches public pages and writes artifacts under `.works/`; it never edits
|
|
20
|
+
the site, submits URLs, changes accounts, publishes or schedules anything. Remediation is an
|
|
21
|
+
operator action you may recommend, never perform.
|
|
22
|
+
- **No ranking, citation, traffic or revenue promise.** The audit cannot observe indexing, ranking,
|
|
23
|
+
visits, revenue, bot behavior or answer-engine citations. Say "not measured" where that is true.
|
|
24
|
+
- **Label the evidence class** in every statement: observed **measurement**, **heuristic**,
|
|
25
|
+
**unknown**, **model judgment / advisory**, or **consultation**. `references/method.md` §1.
|
|
26
|
+
- **Crawler purposes stay separate:** search-indexing, model-training and user-triggered fetchers
|
|
27
|
+
have different policies and different consequences. `references/seo-geo-sources.md` §2.
|
|
28
|
+
- **`vendors/` is reference-only:** excluded from runtime and install resolution; never read or copy
|
|
29
|
+
it into a shipped file, and never wrap the vendor Python.
|
|
30
|
+
- **Specialist advice is consultation.** Legal, hosting, server, analytics or i18n work outside the
|
|
31
|
+
collected evidence is flagged as consultation with the missing evidence named — never presented
|
|
32
|
+
as an audit finding.
|
|
33
|
+
- **No phantom metrics.** Every number in your answer comes from an artifact field, and you name it.
|
|
34
|
+
|
|
35
|
+
## 1. Route the request
|
|
36
|
+
|
|
37
|
+
| Input | Path |
|
|
38
|
+
| --- | --- |
|
|
39
|
+
| A public URL / domain | Full workflow run (§3→§4) with a written options file. |
|
|
40
|
+
| A saved audit directory (`audit.json` present) | Skip to review/interpret (§5→§6); rescore with §8 when the rubric or inputs changed. |
|
|
41
|
+
| A question about an existing report | Read `report.json` + `audit.json` + `checks.json`, answer from the fields (§5). |
|
|
42
|
+
| "Make the site rank / get cited" | Explain what the evidence does and does not support (§0, §6) and produce the action plan. |
|
|
43
|
+
|
|
44
|
+
Never invent a run id, work directory or artifact path. Resolve them from the run-bound file
|
|
45
|
+
`.spur/run/<runId>-seo-work-dir.txt`, and read the package root from the running executable
|
|
46
|
+
(`references/operations.md` §1) — never from a hard-coded checkout path.
|
|
47
|
+
|
|
48
|
+
## 2. Intake and the options file
|
|
49
|
+
|
|
50
|
+
Confirm with the operator: the exact URL, the intended audience locale, any business context worth
|
|
51
|
+
recording (≤ 2,000 characters — operator context, not website evidence), whether measured
|
|
52
|
+
performance evidence is desired (PSI/CrUX keys present), and whether an imported visibility
|
|
53
|
+
snapshot exists. Then write a strict JSON options file — fields, defaults, bounds and the imported
|
|
54
|
+
snapshot contract are in `references/operations.md` §3, §5. Pass the file **path** as data; never
|
|
55
|
+
template a user value into shell source, and refuse NUL input.
|
|
56
|
+
|
|
57
|
+
## 3. Register the decision catalogs (integrated runs)
|
|
58
|
+
|
|
59
|
+
The workflow's preflight passes `require_decisions=true`: the four SEO catalogs (22 decisions) must
|
|
60
|
+
be served, or preflight fails with the registration pointer. The supported setup is an **append** of
|
|
61
|
+
`<package-root>/plugins/kk/decisions` to the consumer project's `decisions.paths`, preserving every
|
|
62
|
+
existing entry and every maker setting. Verify with `spur decision list|show|status --json` — that
|
|
63
|
+
is configuration evidence, never model health. Exact steps and failure text:
|
|
64
|
+
`references/operations.md` §2. Collection-only stage use and the offline fixture mode need no
|
|
65
|
+
catalogs.
|
|
66
|
+
|
|
67
|
+
## 4. Run it, bounded
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
spur workflow run <package-root>/plugins/kk/workflows/kk-smart-seo.yaml \
|
|
71
|
+
--vars '{"options_file":"/abs/path/options.json"}' --quiet
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`preflight → collect → check → enrich → judge → score → narrative → report`, terminal
|
|
75
|
+
`done | partial | failed`. Leave `work_dir` empty so preflight allocates it and records the
|
|
76
|
+
run-bound path. Show progress from the workflow trace and the run record, not by guessing a
|
|
77
|
+
directory; report `partial` as successful delivery with named missing evidence, and `failed` as the
|
|
78
|
+
only failure state. Full command/artifact/progress detail: `references/operations.md` §6–§8.
|
|
79
|
+
For a run with no network access (fixture mode), the same workflow executes offline — say so
|
|
80
|
+
explicitly and never present fixture-derived numbers as a live site audit.
|
|
81
|
+
|
|
82
|
+
## 5. Review the evidence before reading any judgment
|
|
83
|
+
|
|
84
|
+
1. `report.json` — `status`, `narrativeStatus`, `scores`, `coverage`, `actions` (already ordered).
|
|
85
|
+
2. Start with `P1`, then `P2` actions. For each: severity, category, affected pages, `effort` band,
|
|
86
|
+
and `heuristic` (true = rule-of-thumb over observed facts, false = directly observed).
|
|
87
|
+
3. Confirm each action against `checks.json`: `status`, `reason`, `evidenceRefs`, `sourceUrl`,
|
|
88
|
+
`sourceCheckedAt`. A failing row with a `heuristic: true` flag is a hypothesis to verify, not a
|
|
89
|
+
defect.
|
|
90
|
+
4. Read `audit.json` `/limits/*` and `coverage` (`assessed`, `unknown`, `ratio`, `hitLimit`). A
|
|
91
|
+
limit or a large `unknown` count bounds every conclusion; orphan/depth/duplicate observations are
|
|
92
|
+
"among sampled pages".
|
|
93
|
+
5. Treat policy-looking rows carefully: robots directives, intentional `noindex`, canonical-to-other
|
|
94
|
+
and missing optional files (`robots_missing`, `sitemap_missing`, `not_in_sitemap`,
|
|
95
|
+
`llms_txt_missing`) are **signals requiring operator intent**, not automatic defects.
|
|
96
|
+
6. When a row's `sourceCheckedAt` is old relative to the question, refresh the primary source first
|
|
97
|
+
(`references/seo-geo-sources.md` §3) instead of asserting behavior from memory.
|
|
98
|
+
|
|
99
|
+
## 6. Interpret for the operator — per area
|
|
100
|
+
|
|
101
|
+
Explain each area from the recorded evidence and stop where the evidence stops. Rubric, scoring and
|
|
102
|
+
the full "cannot conclude" list live in `references/method.md` §3 and §6.
|
|
103
|
+
|
|
104
|
+
| Area | What the evidence supports | What it must not become |
|
|
105
|
+
| --- | --- | --- |
|
|
106
|
+
| **Technical** | Observed HTTP status/redirect chains, canonical and hreflang tags, indexability directives, headers, HTTPS/host variants, response size and timings. | Proof of indexing, rendering or crawl-budget impact. `js_dependent` is raw-HTML shell suspicion only — no browser rendering. |
|
|
107
|
+
| **On-page** | Title/meta/H1–H3/outline/link/anchor/image facts, structured-data syntax and type inventory, Open Graph presence. | A ranking explanation. Length thresholds are heuristics; structured data is data, and its presence is not an AI or rich-result eligibility requirement. |
|
|
108
|
+
| **Content / editorial** | Visible word counts (an editorial measure, language-limited), duplicate/thin-content observations, sampled text. | A quality verdict or an audience judgment. Model judgments (`source: model`) are advisory with a maker and a confidence. |
|
|
109
|
+
| **Local** | Declared locale/origin facts, contact and business-entity signals, `kk-seo-local-intent`. | Local-pack or maps presence claims — not measured. |
|
|
110
|
+
| **Ecommerce** | Product/category page-type and action classifications, duplicated titles/meta, canonical and redirect observations on product URLs. | Price, feed or merchant-center diagnostics — out of scope; name that as consultation. |
|
|
111
|
+
| **International** | hreflang observations, locale declarations, duplicate-content observations across host/path variants. | A statement that a market does or does not receive traffic. Machine-translation quality is consultation. |
|
|
112
|
+
| **GEO (generative engines)** | Editorial diagnostics — intent satisfaction, first-hand specificity, verifiable provenance, entity consistency, answer-first structure — plus imported citation/visibility snapshots when supplied. | Any citation, mention or inclusion promise. Blocking a training crawler is a policy choice, not a search defect. |
|
|
113
|
+
|
|
114
|
+
### Crawler policies, kept separate
|
|
115
|
+
|
|
116
|
+
Explain a robots observation by the crawler's **purpose**, never as one verdict, and report the
|
|
117
|
+
observed directive plus the path it applies to:
|
|
118
|
+
|
|
119
|
+
- **Search indexing** (`Googlebot`, `Bingbot`, `OAI-SearchBot`) — affects eligibility for search and
|
|
120
|
+
search-grounded answer surfaces. A deliberate restriction is an operator decision, not a defect.
|
|
121
|
+
- **Model training** (`GPTBot`, `Google-Extended`, `ClaudeBot`, `CCBot`) — a data-policy choice.
|
|
122
|
+
Blocking it is not a search defect; allowing it promises nothing.
|
|
123
|
+
- **User-triggered fetch** (`ChatGPT-User` and similar) — serves a user's explicit request; a block
|
|
124
|
+
limits that session, not search eligibility.
|
|
125
|
+
|
|
126
|
+
A router robots group (the audit's own user agent) is likewise an operator policy. Never claim what
|
|
127
|
+
any crawler actually did: the audit observes the site's declared policy and its own requests only.
|
|
128
|
+
|
|
129
|
+
### When evidence is missing
|
|
130
|
+
|
|
131
|
+
Say which of the three it is — **not collected** (outside the bounded crawl), **collected but
|
|
132
|
+
unknown** (`status: unknown`), or **not applicable** (`not-applicable`) — and name the input that
|
|
133
|
+
would settle it (a larger budget, a supplied visibility snapshot, an operator answer about intent).
|
|
134
|
+
Consultation is the right label for work this method cannot evidence at all.
|
|
135
|
+
|
|
136
|
+
## 7. Write the narrative
|
|
137
|
+
|
|
138
|
+
Two different artifacts — keep them apart:
|
|
139
|
+
|
|
140
|
+
- **Validated narrative** (`narrative.json`): the §9 schema — allow-listed headings, fixed
|
|
141
|
+
`fact | action | limit` templates and `evidenceRef` pointers into the materialized `audit.json`,
|
|
142
|
+
bound to its sha256 and run id. Selecting and ordering recorded references is allowed; adding a
|
|
143
|
+
measurement, diagnosis or prediction is not, and any invalid claim rejects the whole narrative
|
|
144
|
+
(which then degrades to the labeled deterministic summary). Schema, worked example and rejection
|
|
145
|
+
cases: `references/narrative.md`.
|
|
146
|
+
- **Advisory prose** (your answer): the explanation, prioritization and operator-facing advice.
|
|
147
|
+
Label evidence classes, cite artifact fields, mark consultation, and describe what the report
|
|
148
|
+
deliberately cannot say. The report accepts no free prose — `references/narrative.md` §4.
|
|
149
|
+
|
|
150
|
+
## 8. Offline rescoring
|
|
151
|
+
|
|
152
|
+
To re-score a saved audit without touching the network, the source run or its artifacts:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
kk stage run seo-score <saved-work_dir> <new-revision_dir> # revision dir must be absent/empty
|
|
156
|
+
kk stage run seo-report <new-revision_dir>
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
The revision copies the validated inputs beside a fresh `audit.json`, keeps the original `runId`
|
|
160
|
+
and adds a `revisionId`, so source artifacts stay immutable and two revisions are comparable as
|
|
161
|
+
durable directories. Never compare scores across runs with different coverage or `methodVersion`,
|
|
162
|
+
and never patch a saved artifact in place. Details: `references/operations.md` §9.
|
|
163
|
+
|
|
164
|
+
## 9. Failures and limits — report, never paper over
|
|
165
|
+
|
|
166
|
+
Unserved catalogs, invalid options, a populated `work_dir`, a refused network target
|
|
167
|
+
(local/reserved address), budget exhaustion, unavailable optional PSI/CrUX evidence, maker
|
|
168
|
+
fallback/low confidence, malformed predecessors, a rejected narrative and cancellation each have a
|
|
169
|
+
defined handling in `references/operations.md` §8. Three habits matter most: never present an
|
|
170
|
+
exhausted crawl as complete, never turn an unknown or a low-confidence judgment into a defect, and
|
|
171
|
+
never convert "not measured" into "passed".
|
|
172
|
+
|
|
173
|
+
## 10. References
|
|
174
|
+
|
|
175
|
+
| Reference | Owns |
|
|
176
|
+
| --- | --- |
|
|
177
|
+
| `references/operations.md` | Package-root resolution, catalog registration and failure text, options fields, imported snapshot, run/progress commands, per-artifact map, failure handling, offline rescoring. |
|
|
178
|
+
| `references/method.md` | Evidence classes, the 52-rule checklist structure, score/coverage formulas, action ordering and priorities, how to read a check back to its evidence, and the method's explicit limits. |
|
|
179
|
+
| `references/seo-geo-sources.md` | Primary source registry with checked-at dates and per-source limits, crawler-purpose separation, the source refresh procedure, GEO limits, and the `vendors/` reference-only rule. |
|
|
180
|
+
| `references/narrative.md` | The validated narrative schema, worked example, rejection matrix, and the boundary with advisory prose. |
|
|
181
|
+
|
|
182
|
+
Attribution for the adapted rubric, rule identities and upstream notice:
|
|
183
|
+
`plugins/kk/references/smart-seo-attribution.md`.
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# Smart SEO — method, rubric and score interpretation
|
|
2
|
+
|
|
3
|
+
The rubric is a **versioned internal checklist**, not a vendor grade and not a search-engine
|
|
4
|
+
verdict. Every claim below is what the artifacts record; nothing here measures indexing, ranking,
|
|
5
|
+
traffic, revenue, bot behavior or citation visibility.
|
|
6
|
+
|
|
7
|
+
## 1. Evidence classes — label them every time you report
|
|
8
|
+
|
|
9
|
+
| Class | Meaning | Recorded where |
|
|
10
|
+
| --- | --- | --- |
|
|
11
|
+
| **Measurement / observed fact** | A value the audit actually observed within its stated budget (HTTP status, title text, robots directive, response bytes, timings). | `crawl.json` page facts, `checks.json` row `evidenceRefs`, `audit.json` `/facts/*` |
|
|
12
|
+
| **Heuristic** | A bounded rule-of-thumb over observed facts. `heuristic: true` on the check row and on the derived action. Examples: page depth, thin content (visible word count), title length, heading skips, orphan/depth observations "among sampled pages". | `checks.json` / `audit.json` `heuristic` |
|
|
13
|
+
| **Unknown** | Not assessed or not assessable: malformed, unavailable, budget-exhausted, blocked, or outside the collected scope. Never rendered as a pass. | `checks.json` status `unknown`, `coverage.unknown`, `audit.json` `/limits/*` |
|
|
14
|
+
| **Model judgment** | An editorial output from a typed decision (`source: model`, with maker and confidence). Advisory, never a measurement. | `judgments.json`, `audit.json` `/judgments` |
|
|
15
|
+
| **Imported evidence** | A supplied visibility snapshot or PSI/CrUX measurement, with source, collection window and `measurementKind`. | `performance.json`, `visibility.json` |
|
|
16
|
+
| **Consultation** | Advice outside the collected evidence (specialist legal/hosting/i18n/analytics work). Say so explicitly and name the missing evidence. | nowhere — it must not enter the report as a finding |
|
|
17
|
+
|
|
18
|
+
Rule of thumb: a value is a **measurement** only if some artifact records it with a source; a
|
|
19
|
+
judgment, an inference or a threshold over content is a **heuristic** or **advisory** statement.
|
|
20
|
+
|
|
21
|
+
## 2. Checklist structure
|
|
22
|
+
|
|
23
|
+
52 deterministic rules across five categories — `technical` (22), `content` (16), `crawl` (8),
|
|
24
|
+
`security` (4), `geo` (2). Each rule keeps a stable identity, a severity, an effort band, a
|
|
25
|
+
`heuristic` flag, a primary `sourceUrl` and a `sourceCheckedAt` date, and one disposition from the
|
|
26
|
+
vendor review (`native`, `adapted`, `informational`, `deferred`).
|
|
27
|
+
|
|
28
|
+
Every eligible rule/subject pair yields exactly **one** row:
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"ruleId": "title_missing",
|
|
33
|
+
"subjectId": "<hashed subject identity>",
|
|
34
|
+
"subjectUrl": "https://example.com/a",
|
|
35
|
+
"status": "fail",
|
|
36
|
+
"category": "content",
|
|
37
|
+
"severity": "high",
|
|
38
|
+
"evidenceRefs": ["https://example.com/a"],
|
|
39
|
+
"sourceUrl": "https://developers.google.com/search/docs/appearing-in-search/...",
|
|
40
|
+
"sourceCheckedAt": "2026-10-14",
|
|
41
|
+
"heuristic": false,
|
|
42
|
+
"reason": "Human-readable reason for this status"
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`status` is `pass | fail | unknown | not-applicable`. **The absence of a row is an error, never a
|
|
47
|
+
pass.** `not-applicable` means the rule cannot apply to that subject (for example a site-level rule
|
|
48
|
+
against a page-level subject); it is excluded from both the score and the coverage denominator.
|
|
49
|
+
A `deferred`-disposition rule applies but is not assessed in this method version, so it emits
|
|
50
|
+
`unknown` and counts in `coverage.unknown`.
|
|
51
|
+
|
|
52
|
+
Intent-sensitive observations are not defects by themselves: a robots directive, an intentional
|
|
53
|
+
`noindex`, a canonical pointing elsewhere, a missing optional file or missing optional metadata all
|
|
54
|
+
require operator intent before they become a recommendation. Missing SEO files never turn into
|
|
55
|
+
indexing requirements in the narrative.
|
|
56
|
+
|
|
57
|
+
## 3. Scores
|
|
58
|
+
|
|
59
|
+
Category score = `round(100 * pass / (pass + fail))` over the **applicable assessed** rows of that
|
|
60
|
+
category, and `null` when that category has no assessed row:
|
|
61
|
+
|
|
62
|
+
| Field | Meaning |
|
|
63
|
+
| --- | --- |
|
|
64
|
+
| `scores.technical` / `content` / `crawl` / `security` | The checklist pass ratio in that category. `null` = undefined denominator, never 0. |
|
|
65
|
+
| `coverage.totalRules` | 52 (the checklist size). |
|
|
66
|
+
| `coverage.applicableRules` | Rows whose status is not `not-applicable`. |
|
|
67
|
+
| `coverage.assessed` | Rows with `pass` or `fail`. |
|
|
68
|
+
| `coverage.unknown` | Applicable rows left `unknown`. |
|
|
69
|
+
| `coverage.ratio` | `assessed / (assessed + unknown)` — how much of the applicable checklist was actually assessed. |
|
|
70
|
+
|
|
71
|
+
Interpretation rules:
|
|
72
|
+
|
|
73
|
+
- There is **no blended overall SEO/GEO grade** in this method version. Report the four category
|
|
74
|
+
ratios with their coverage side by side; a ratio published without its coverage is misleading.
|
|
75
|
+
- `geo` rules are assessed and prioritized like any other row but produce no category score.
|
|
76
|
+
- `informational` rules (vendor-review disposition) are reported but never enter a category score:
|
|
77
|
+
they neither deduct on `fail` nor pad the ratio on `pass`.
|
|
78
|
+
- Unknown and review values never count as successes, and a disabled or unavailable optional area
|
|
79
|
+
is reported as such rather than folded into a pass.
|
|
80
|
+
- Never compare scores across two runs with different coverage, different `methodVersion`, or
|
|
81
|
+
different options — record the denominators with the number.
|
|
82
|
+
- A high score means "few sampled rules failed", not "the site is healthy". A low score does not
|
|
83
|
+
prove a traffic or ranking problem.
|
|
84
|
+
|
|
85
|
+
## 4. Action ordering and priorities
|
|
86
|
+
|
|
87
|
+
One action per failing rule, carrying the full affected-subject list (aggregation must not hide
|
|
88
|
+
unknown subjects). Stable ID `SEO-<ruleId>-<first12(sha256(subject identity))>` — sorting never
|
|
89
|
+
renumbers an ID, so IDs stay comparable across revisions.
|
|
90
|
+
|
|
91
|
+
Order: severity (`critical`, `high`, `medium`, `low`, `info`) → affected-page count descending →
|
|
92
|
+
effort band ascending → stable ID.
|
|
93
|
+
|
|
94
|
+
| Priority | Meaning |
|
|
95
|
+
| --- | --- |
|
|
96
|
+
| `P1` | Confirmed `critical` or `high` severity failures. |
|
|
97
|
+
| `P2` | Confirmed `medium` severity failures. |
|
|
98
|
+
| `P3` | Confirmed low-severity failures. |
|
|
99
|
+
| `review` | Uncertain items — kept out of fix-first lists until the operator confirms intent or supplies missing evidence. |
|
|
100
|
+
|
|
101
|
+
`effort` is a declared planning band (1–4), not a quote or an estimate in hours. Severity expresses
|
|
102
|
+
the rule's consequence, not its certainty; read it together with `heuristic` and with `coverage`.
|
|
103
|
+
Editorial (content/GEO) scores can justify a review suggestion, never confirm a technical fact.
|
|
104
|
+
|
|
105
|
+
## 5. Reading a check back to its evidence
|
|
106
|
+
|
|
107
|
+
1. Take the action's `ruleId`, `affectedSubjects` and stable ID.
|
|
108
|
+
2. Open `checks.json` and select the rows with that `ruleId`; read `status`, `reason`,
|
|
109
|
+
`evidenceRefs`, `sourceUrl` and `sourceCheckedAt`.
|
|
110
|
+
3. Read the underlying page facts in `crawl.json` (or the referenced `evidence/pages/*.json`).
|
|
111
|
+
4. Read `audit.json` `/limits/*` and `coverage` before drawing any conclusion: a limit or a high
|
|
112
|
+
`unknown` count bounds what the finding can mean.
|
|
113
|
+
5. If the primary source page has changed since `sourceCheckedAt`, refresh the source before
|
|
114
|
+
relying on the rule's stated rationale (see `seo-geo-sources.md`).
|
|
115
|
+
|
|
116
|
+
## 6. What this method cannot conclude
|
|
117
|
+
|
|
118
|
+
- Whether a URL is indexed, ranking, receiving traffic or being cited by any AI answer engine.
|
|
119
|
+
- What a crawler actually did: the audit observes the site's **declared** policy
|
|
120
|
+
(robots.txt, meta robots, sitemap) and its own requests — never another crawler's behavior.
|
|
121
|
+
- How a browser renders a JavaScript-dependent page: rendering is deferred, so a raw-HTML shell is
|
|
122
|
+
suspicion only.
|
|
123
|
+
- Whether an editorial judgment is correct: adequacy of a value proposition, content depth or
|
|
124
|
+
citability is an opinion with a maker and a confidence, not a fact.
|
|
125
|
+
- Performance causes or fixes: PSI lab metrics and CrUX field data, when supplied, describe
|
|
126
|
+
observed timing, not the work needed to change it.
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# Smart SEO — the validated narrative contract
|
|
2
|
+
|
|
3
|
+
The narrative is **optional** and **fully validated**. It selects and orders references into the
|
|
4
|
+
materialized `audit.json`; it cannot introduce a measurement, unit, diagnosis or prediction. When
|
|
5
|
+
it is absent or rejected the report is still delivered with `narrativeStatus: omitted|rejected`
|
|
6
|
+
plus a deterministic summary — an optional agent failure is never an audit failure.
|
|
7
|
+
|
|
8
|
+
## 1. Schema (schema version 1)
|
|
9
|
+
|
|
10
|
+
```json
|
|
11
|
+
{
|
|
12
|
+
"schemaVersion": 1,
|
|
13
|
+
"runId": "<audit.runId>",
|
|
14
|
+
"auditDigest": "<sha256 of the exact audit.json bytes>",
|
|
15
|
+
"sections": [
|
|
16
|
+
{
|
|
17
|
+
"heading": "summary | priorities | limits",
|
|
18
|
+
"claims": [
|
|
19
|
+
{ "template": "fact | action | limit", "evidenceRef": { "artifact": "audit.json", "jsonPointer": "/facts/<key> | /actions/<id> | /limits/<key>" } }
|
|
20
|
+
]
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Hard limits and exact shapes — every key below is the *only* accepted key at its level:
|
|
27
|
+
|
|
28
|
+
| Rule | Value |
|
|
29
|
+
| --- | --- |
|
|
30
|
+
| Root keys | exactly `schemaVersion`, `runId`, `auditDigest`, `sections` |
|
|
31
|
+
| `schemaVersion` | `1` |
|
|
32
|
+
| `runId` | must equal the audit's `runId` |
|
|
33
|
+
| `auditDigest` | must equal the sha256 of the materialized `audit.json` (stale or unknown digest rejects) |
|
|
34
|
+
| `sections[]` keys | exactly `heading`, `claims`; at least one section; `claims` non-empty |
|
|
35
|
+
| `heading` | one of `summary`, `priorities`, `limits` |
|
|
36
|
+
| `claims[]` keys | exactly `template`, `evidenceRef`; unknown keys anywhere (including a free-prose `text`) reject the whole narrative |
|
|
37
|
+
| `template` | one of `fact`, `action`, `limit` |
|
|
38
|
+
| `evidenceRef` keys | exactly `artifact`, `jsonPointer`; `artifact` must be `audit.json` |
|
|
39
|
+
| `jsonPointer` | exactly two segments: the template's own collection plus one key — `/facts/<key>` for `fact`, `/actions/<actionId>` for `action`, `/limits/<key>` for `limit` |
|
|
40
|
+
| Pointer targets | `fact` must address a recorded scalar observed fact (`/facts/pages_visited`, `/facts/requests_made`, `/facts/total_bytes`, …); `action` must address an existing action **by stable id** (never an array index); `limit` must address a recorded limit (`pagesVisited`, `requestsMade`, `totalBytes`, `elapsedSeconds`, `hitLimit`) |
|
|
41
|
+
| Bounds | ≤ 32 KiB raw and ≤ 30 claims |
|
|
42
|
+
|
|
43
|
+
Prototype keys (`__proto__`, `constructor`, `prototype`) and out-of-collection pointers are refused.
|
|
44
|
+
Rendering is method-owned: each claim becomes an `**Observed fact**`, `**Planned action**` or
|
|
45
|
+
`**Recorded limit**` line with the audit's own value, unit and source, and Markdown-escaped.
|
|
46
|
+
|
|
47
|
+
## 2. Worked example
|
|
48
|
+
|
|
49
|
+
Given an audit whose `runId` is `8f2c…`, whose `auditDigest` is the real sha256 of `audit.json`,
|
|
50
|
+
and whose actions include `SEO-title_missing-0f115db062b7`:
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
{
|
|
54
|
+
"schemaVersion": 1,
|
|
55
|
+
"runId": "8f2c1d0e-…",
|
|
56
|
+
"auditDigest": "<sha256 of audit.json>",
|
|
57
|
+
"sections": [
|
|
58
|
+
{
|
|
59
|
+
"heading": "summary",
|
|
60
|
+
"claims": [
|
|
61
|
+
{ "template": "fact", "evidenceRef": { "artifact": "audit.json", "jsonPointer": "/facts/pages_visited" } },
|
|
62
|
+
{ "template": "fact", "evidenceRef": { "artifact": "audit.json", "jsonPointer": "/facts/requests_made" } }
|
|
63
|
+
]
|
|
64
|
+
},
|
|
65
|
+
{ "heading": "priorities", "claims": [{ "template": "action", "evidenceRef": { "artifact": "audit.json", "jsonPointer": "/actions/SEO-title_missing-0f115db062b7" } }] },
|
|
66
|
+
{ "heading": "limits", "claims": [{ "template": "limit", "evidenceRef": { "artifact": "audit.json", "jsonPointer": "/limits/hitLimit" } }] }
|
|
67
|
+
]
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Renders as (in `report.md`, section 3):
|
|
72
|
+
|
|
73
|
+
```markdown
|
|
74
|
+
### Summary
|
|
75
|
+
|
|
76
|
+
- **Observed fact** `pages_visited` = `12` count — source: `crawl.json`
|
|
77
|
+
- **Observed fact** `requests_made` = `34` count — source: `crawl.json`
|
|
78
|
+
|
|
79
|
+
### Priorities
|
|
80
|
+
|
|
81
|
+
- **Planned action** `SEO-title_missing-0f115db062b7` (high, P1) — Fix title missing — 3 affected page(s), effort band 1
|
|
82
|
+
|
|
83
|
+
### Limits
|
|
84
|
+
|
|
85
|
+
- **Recorded limit** `hitLimit` = `none`
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
To produce this in a run: read `<work_dir>/audit.json`, compute its sha256 over the exact file
|
|
89
|
+
bytes, keep the same `runId`, then write the object to `<work_dir>/narrative.json` **before**
|
|
90
|
+
`seo-report` runs (the workflow's `agent.run` step does exactly that, with
|
|
91
|
+
`answerFile: <work_dir>/narrative.json`). Replying `{}` is the documented way to decline: the
|
|
92
|
+
report then falls back to its deterministic summary.
|
|
93
|
+
|
|
94
|
+
## 3. Rejection cases (each rejects the entire narrative)
|
|
95
|
+
|
|
96
|
+
| Case | Diagnostic shape |
|
|
97
|
+
| --- | --- |
|
|
98
|
+
| Free prose added to a claim (`text: "…"`) or any unknown key | `sections[0].claims[1]: unknown key "text"` |
|
|
99
|
+
| Heading outside the allow list (`"Overview"`) | `sections[0]: heading "Overview" is not an allow-listed heading` |
|
|
100
|
+
| Template mismatch with the pointer (`fact` + `/limits/…`) | `template "fact" requires a pointer into /facts, got "/limits"` |
|
|
101
|
+
| Invented fact or limit key | `unknown fact id "domain_authority"` / `unknown limit id "ranking_position"` |
|
|
102
|
+
| Invented or misspelled action id, or an array index (`/actions/0`) | `unknown action id "0"` |
|
|
103
|
+
| Pointer deeper or shallower than two segments (`/facts/a/b`) | `jsonPointer "…" must address exactly one record (collection/key)` |
|
|
104
|
+
| Stale digest (audit re-scored after the narrative was written) | `narrative auditDigest does not match the materialized audit.json digest (stale or unknown digest)` |
|
|
105
|
+
| Wrong run id | `narrative runId "…" does not match the audit runId` |
|
|
106
|
+
| Non-object root, non-JSON bytes, empty sections, > 32 KiB, > 30 claims | `narrative root must be a JSON object` / `narrative is not valid JSON: …` / `narrative sections must not be empty` |
|
|
107
|
+
|
|
108
|
+
On rejection the stage writes `narrative-diagnostics.json` beside the audit, sets
|
|
109
|
+
`report.json.narrativeStatus: 'rejected'`, and renders the deterministic summary labeled
|
|
110
|
+
`**Narrative rejected**`. Never edit the saved `narrative.json` to force acceptance, and never move
|
|
111
|
+
a rejected claim into the deterministic summary by hand: the summary is method-owned.
|
|
112
|
+
|
|
113
|
+
## 4. Advisory prose lives outside the report
|
|
114
|
+
|
|
115
|
+
The validated narrative deliberately cannot carry commentary. If the operator asks for
|
|
116
|
+
explanation, interpretation or an action plan in prose, that is the **skill's** output: keep it in
|
|
117
|
+
conversation (or a separate operator-authored document) and:
|
|
118
|
+
|
|
119
|
+
- label every statement as measurement, heuristic, model judgment or unknown (see `method.md`);
|
|
120
|
+
- cite the artifact plus the field (`report.json` action id, `checks.json` row, `audit.json`
|
|
121
|
+
`/facts/*`) for every number;
|
|
122
|
+
- mark specialist advice outside the collected evidence as **consultation**, naming the missing
|
|
123
|
+
evidence and the source that would be needed;
|
|
124
|
+
- make no promise about rankings, citations, traffic, revenue or crawler behavior, and propose no
|
|
125
|
+
change that mutates the site — the audit is read-only, and any remediation is an operator action.
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# Smart SEO — operations
|
|
2
|
+
|
|
3
|
+
Setup, run, progress, and failure handling for the `kk-smart-seo` audit. The workflow is the
|
|
4
|
+
orchestrator; `kk` owns every step (ADR-007). Nothing here mutates a site.
|
|
5
|
+
|
|
6
|
+
## 1. Resolve the KK package root
|
|
7
|
+
|
|
8
|
+
Both the stages and the decision catalogs live in the **installed** package, not in a checkout:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
# The real path of the executable that is actually running (follows symlinks):
|
|
12
|
+
readlink -f "$(command -v kk)" # -> <pkg>/dist/index.js
|
|
13
|
+
PKG_ROOT="$(dirname "$(dirname "$(readlink -f "$(command -v kk)")")")"
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
`kk stage run` resolves `plugins/kk/scripts/kk-workflow-stages.ts` next to its own entry: a real
|
|
17
|
+
checkout uses the checked-in runner, an installed entry always uses its own package copy. An
|
|
18
|
+
unrelated ancestor `plugins/` directory is never selected, so a run from an unrelated cwd is safe.
|
|
19
|
+
|
|
20
|
+
## 2. Register the SEO decision catalogs (required for the integrated workflow)
|
|
21
|
+
|
|
22
|
+
`decisions.paths` in the consumer project config (`.spur/config.yaml`) is the whole search path.
|
|
23
|
+
**Append** one entry — never replace the list:
|
|
24
|
+
|
|
25
|
+
```yaml
|
|
26
|
+
decisions:
|
|
27
|
+
paths:
|
|
28
|
+
- <existing entry> # keep every existing entry
|
|
29
|
+
- <PKG_ROOT>/plugins/kk/decisions # appended: the 4 SEO catalogs, 22 decisions
|
|
30
|
+
maker: <existing maker> # keep the maker and makers: mapping untouched
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`seo-preflight` with `require_decisions=true` (the workflow's own call) fails with this pointer
|
|
34
|
+
when the catalogs are not served:
|
|
35
|
+
|
|
36
|
+
```text
|
|
37
|
+
seo-preflight: SEO decision catalogs are not served — missing catalog file(s): kk-seo-site.yaml, ...
|
|
38
|
+
Registration fix: append `<kk-package-root>/plugins/kk/decisions` to the project config `decisions.paths`
|
|
39
|
+
(.spur/config.yaml), preserving every existing entry and maker setting, then re-run preflight.
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Verify without dispatching a maker (configuration evidence, never model health):
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
spur decision list --json # must serve the 22 kk-seo-* ids
|
|
46
|
+
spur decision show kk-seo-page-type --json # -> catalog, layer, effective maker + source
|
|
47
|
+
spur decision status --json # -> load errors, duplicate ids, per-decision effective maker
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Collection-only stage use (`require_decisions=false`) and the explicit offline fixture mode need
|
|
51
|
+
no catalogs. Do not run an audit that silently writes user-global configuration; the append above
|
|
52
|
+
is a one-time operator setup, and it is repeated only when an upgrade changes the resolved
|
|
53
|
+
package root.
|
|
54
|
+
|
|
55
|
+
**The catalogs' declared schema must resolve from where they live.** Each shipped SEO catalog
|
|
56
|
+
declares `$schema: "@gobing-ai/spur/schemas/decision-catalog.schema.json"`, and Spur resolves that
|
|
57
|
+
schema through the module tree above the catalog's own directory. A global install satisfies this
|
|
58
|
+
by construction (both packages are siblings in the global tree); with a project-local KK install,
|
|
59
|
+
keep `spur` resolvable from the installed package's location or `decision status --json` reports
|
|
60
|
+
the catalogs as `loadErrors` (`Cannot resolve package schema …`) and preflight fails. A load error
|
|
61
|
+
is a setup defect to fix, never a reason to fall back to a maker default silently.
|
|
62
|
+
|
|
63
|
+
## 3. Options file
|
|
64
|
+
|
|
65
|
+
Write a UTF-8 JSON object (≤ 64 KiB, strict schema — unknown keys are refused) and pass its path
|
|
66
|
+
as data, never as shell source:
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
{
|
|
70
|
+
"url": "https://example.com",
|
|
71
|
+
"locale": "en-US",
|
|
72
|
+
"business_context": "",
|
|
73
|
+
"max_pages": 60,
|
|
74
|
+
"max_depth": 5,
|
|
75
|
+
"max_requests": 200,
|
|
76
|
+
"deadline_seconds": 600,
|
|
77
|
+
"semantic_pages": 20,
|
|
78
|
+
"max_pairs": 10,
|
|
79
|
+
"max_keywords": 10,
|
|
80
|
+
"max_decision_calls": 320,
|
|
81
|
+
"performance": false,
|
|
82
|
+
"visibility_file": "",
|
|
83
|
+
"fixture": ""
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
| Field | Meaning and bounds |
|
|
88
|
+
| --- | --- |
|
|
89
|
+
| `url` | Required HTTP(S) URL or bare domain (bare becomes HTTPS). Userinfo, nonstandard ports, local/reserved addresses and malformed hostnames are refused. |
|
|
90
|
+
| `locale` | BCP-47; describes the report audience and the imported query market. It never assumes the site's language. |
|
|
91
|
+
| `business_context` | Optional operator context ≤ 2,000 UTF-16 units. It is context, not website evidence. |
|
|
92
|
+
| `max_pages` / `max_depth` | 60 / 5 by default; maxima 200 / 10. The page quota counts every attempt, including blocked, redirect and error records. |
|
|
93
|
+
| `max_requests` / `deadline_seconds` | 200 / 600 by default; maxima 500 / 1800. One shared quota and deadline covers robots, sitemaps, pages, probes and link targets. |
|
|
94
|
+
| `semantic_pages` / `max_pairs` / `max_keywords` | 20 / 10 / 10 by default; maxima 60 / 40 / 30. Selection is deterministic (homepage, observed depth/inlinks, URL tie break). |
|
|
95
|
+
| `max_decision_calls` | 320 by default, maximum 1000. Every attempt consumes one; there is no retry on an unknown-charge call. Exhaustion is an explicitly partial assessment. |
|
|
96
|
+
| `performance` | `false` by default. `true` requests PSI mobile/desktop and CrUX for at most 3 selected pages; the stage shares a 300 s deadline and a 12-call cap. |
|
|
97
|
+
| `visibility_file` | Optional normalized local JSON snapshot (§4). Never an arbitrary provider dump. |
|
|
98
|
+
| `fixture` | Explicit offline fixture **directory**. With a fixture, network access and real maker dispatch are forbidden. Production never falls back to fixture data. |
|
|
99
|
+
|
|
100
|
+
## 4. Audit scope — the submitted host plus its exact `www` toggle
|
|
101
|
+
|
|
102
|
+
Scope is the normalized submitted hostname with **one** leading `www.` removed when present, or
|
|
103
|
+
added when absent — nothing else. `shop.example.com` admits `shop.example.com` and
|
|
104
|
+
`www.shop.example.com` only: no registrable apex, no sibling subdomains, and default ports stay
|
|
105
|
+
origin-specific. Off-scope redirects and links are recorded, never followed; `www`/non-`www` and
|
|
106
|
+
HTTP/HTTPS transitions are observations, not defects. There is no browser rendering and no outbound
|
|
107
|
+
external-link fetching in this version: state that assessment as **not collected** rather than
|
|
108
|
+
assuming a result.
|
|
109
|
+
|
|
110
|
+
## 5. Imported visibility snapshot
|
|
111
|
+
```json
|
|
112
|
+
{
|
|
113
|
+
"schemaVersion": 1,
|
|
114
|
+
"siteOrigin": "https://example.com",
|
|
115
|
+
"locale": "en-US",
|
|
116
|
+
"source": "search-console-export|analytics-export|manual",
|
|
117
|
+
"collectedAt": "2026-10-01T00:00:00Z",
|
|
118
|
+
"measurementKind": "measured",
|
|
119
|
+
"rows": [{ "query": "example query", "url": "https://example.com/a", "clicks": 4, "impressions": 120, "position": 8.5, "citations": 2, "sampleCount": 500, "platform": "chatgpt" }]
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Maximum 1 MiB / 500 rows; strings bounded; counts are finite nonnegative numbers; `position` ≥ 1;
|
|
124
|
+
`citations` ≤ `sampleCount` with `platform` required; any `url` must belong to the audit scope.
|
|
125
|
+
Unknown properties, a conflicting locale or site identity, a future timestamp, or evidence older
|
|
126
|
+
than 90 days fails validation before use. Keywords come from this data — never invented volumes.
|
|
127
|
+
|
|
128
|
+
## 6. Run the workflow
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
spur workflow run <PKG_ROOT>/plugins/kk/workflows/kk-smart-seo.yaml \
|
|
132
|
+
--vars '{"options_file":"/abs/path/options.json"}' --quiet
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Leave `work_dir` empty: `seo-preflight` allocates `.works/kk-smart-seo/<UTC-stamp>-<runId>` with
|
|
136
|
+
create-new semantics (an existing non-empty directory is refused) and writes the resolved absolute
|
|
137
|
+
path to `.spur/run/<runId>-seo-work-dir.txt`, which the workflow reads back into `work_dir` before
|
|
138
|
+
collection. A fixed `work_dir` must name an absent or empty directory.
|
|
139
|
+
|
|
140
|
+
Stages: `preflight → collect → check → enrich → judge → score → narrative → report`, terminal
|
|
141
|
+
`done | partial | failed`. `done` means every requested stage assessed its eligible evidence — not
|
|
142
|
+
that the site has no SEO issues. `partial` is successful artifact delivery with missing requested
|
|
143
|
+
evidence (an unavailable optional PSI/CrUX/typed-decision input is never a failure). `failed` is the
|
|
144
|
+
sole failure state.
|
|
145
|
+
|
|
146
|
+
Individual stages stay independently callable for review and re-scoring:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
kk stage run seo-preflight <work_dir> <options_file> [run_id] [require_decisions]
|
|
150
|
+
kk stage run seo-collect <work_dir>
|
|
151
|
+
kk stage run seo-check <work_dir>
|
|
152
|
+
kk stage run seo-enrich <work_dir>
|
|
153
|
+
kk stage run seo-judge <work_dir>
|
|
154
|
+
kk stage run seo-score <work_dir> [new_revision_dir]
|
|
155
|
+
kk stage run seo-report <work_dir>
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## 7. Progress
|
|
159
|
+
|
|
160
|
+
- `spur workflow trace <run-id>` — the action trace; the state id tells you which stage is running.
|
|
161
|
+
- `.spur/run/<runId>-seo-work-dir.txt` — the run-bound workspace path (never guess it).
|
|
162
|
+
- `.spur/memory/runs/<run-id>.md` — the two-file run record.
|
|
163
|
+
- Stage progress goes to stderr and is captured in the run record; long collection stages are
|
|
164
|
+
bounded by `max_requests`/`deadline_seconds`, so a run always terminates.
|
|
165
|
+
|
|
166
|
+
Artifacts under the work dir, one run id shared by all of them: `options.json`, `manifest.json`,
|
|
167
|
+
`crawl.json`, `checks.json`, `judgments.json`, `performance.json`, `visibility.json`, `audit.json`,
|
|
168
|
+
`digest.md`, `narrative.json` (optional), `report.json`, `report.md`. Every JSON object carries
|
|
169
|
+
`schemaVersion`, `methodVersion`, `runId`, `createdAt`, `inputDigest` and predecessor digests;
|
|
170
|
+
wrong run, version or digest is a hard error. Writes are sibling temp file + rename, so a reader
|
|
171
|
+
never sees a partially written value. The saved audit is durable under `.works/`, not volatile
|
|
172
|
+
`.spur/run/`.
|
|
173
|
+
|
|
174
|
+
## 8. Failure handling
|
|
175
|
+
|
|
176
|
+
| Symptom | Handling |
|
|
177
|
+
| --- | --- |
|
|
178
|
+
| Missing/unserved SEO catalogs | Apply §2, re-run. Preflight failure text names the missing files/ids. |
|
|
179
|
+
| Invalid options | Fix the named field; the file is validated before any collection. |
|
|
180
|
+
| `work_dir` already populated | Use the run's own bound directory, or a new empty one. Never clear a directory from a previous run — preflight never deletes it. |
|
|
181
|
+
| Network policy refusal (local/reserved address, peer mismatch, redirect off-scope) | The collected observation is recorded as unavailable/blocked. Do not work around it: an off-scope or non-public target is out of the audit's trust boundary. |
|
|
182
|
+
| Budget exhaustion (`limits.hitLimit`) | Report it as a limit. Never present an exhausted crawl as a complete one. |
|
|
183
|
+
| Optional evidence unavailable (no PSI/CrUX key, insufficient sample, 429, timeout) | Recorded as an availability outcome; the report is `partial`, never failed and never a zero score. |
|
|
184
|
+
| Maker fallback / low confidence | The judgment stays `unknown`/`review`. It is not a site defect and not a measurement. |
|
|
185
|
+
| Malformed predecessor artifact | Hard error, nonzero exit — malformed input is never reinterpreted as an empty result. |
|
|
186
|
+
| Narrative absent or rejected | The report is still delivered with `narrativeStatus: omitted|rejected` and a deterministic summary; an optional agent failure is not an audit failure. |
|
|
187
|
+
| Cancellation | Active sockets and decision children are aborted, completed artifacts are preserved with a cancellation marker, and no new requests or decisions start. |
|
|
188
|
+
|
|
189
|
+
## 9. Offline rescoring
|
|
190
|
+
|
|
191
|
+
Score a saved audit again without touching the network or the source run:
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
kk stage run seo-score <saved-work_dir> <new-revision_dir> # revision dir must be absent/empty
|
|
195
|
+
kk stage run seo-report <new-revision_dir>
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
The revision copies the validated bounded inputs (`options.json`, `manifest.json`, `crawl.json`,
|
|
199
|
+
`checks.json`) beside a fresh `audit.json`, keeps the original `runId` and adds a new `revisionId`.
|
|
200
|
+
Source artifacts stay immutable, so a comparison between revisions reads two durable directories.
|
|
201
|
+
Do not compare scores across runs with different coverage: `coverage` records the denominators,
|
|
202
|
+
and the score is undefined (`null`) when no row of that category is applicable.
|