anymorph 0.4.0 → 0.5.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 +3 -3
- package/dist/index.js +78 -6
- package/package.json +1 -1
- package/dist/skillpacks/geo/scaffold/AGENTS.md +0 -38
- package/dist/skillpacks/geo/scaffold/CLAUDE.md +0 -33
- package/dist/skillpacks/geo/shared/evidence-principles.md +0 -35
- package/dist/skillpacks/geo/shared/geo-principles.md +0 -281
- package/dist/skillpacks/geo/shared/vertical-playbooks/beauty.md +0 -65
- package/dist/skillpacks/geo/shared/vertical-playbooks/commerce.md +0 -65
- package/dist/skillpacks/geo/shared/vertical-playbooks/ota.md +0 -62
- package/dist/skillpacks/geo/shared/vertical-playbooks/saas.md +0 -64
- package/dist/skillpacks/geo/skills/brand-owned-diagnosis/SKILL.md +0 -54
- package/dist/skillpacks/geo/skills/brand-owned-diagnosis/agents/openai.yaml +0 -4
- package/dist/skillpacks/geo/skills/brand-owned-diagnosis/references/diagnosis-contract.md +0 -95
- package/dist/skillpacks/geo/skills/brand-owned-diagnosis/references/workflow.md +0 -194
- package/dist/skillpacks/geo/skills/geo-generating-actions/SKILL.md +0 -188
- package/dist/skillpacks/geo/skills/geo-generating-actions/agents/openai.yaml +0 -4
- package/dist/skillpacks/geo/skills/geo-generating-actions/references/orchestrator.workflow.md +0 -440
- package/dist/skillpacks/geo/skills/geo-generating-actions/scripts/geo-scaffold.mjs +0 -358
- package/dist/skillpacks/geo/skills/geo-generating-actions/scripts/geo.mjs +0 -66
- package/dist/skillpacks/geo/skills/geo-initializing-strategy/SKILL.md +0 -50
- package/dist/skillpacks/geo/skills/geo-initializing-strategy/agents/openai.yaml +0 -4
- package/dist/skillpacks/geo/skills/geo-initializing-strategy/references/external-authority-diagnosis.md +0 -66
- package/dist/skillpacks/geo/skills/geo-initializing-strategy/references/foundation-diagnosis.md +0 -86
- package/dist/skillpacks/geo/skills/geo-initializing-strategy/references/memory-contract.md +0 -15
- package/dist/skillpacks/geo/skills/geo-initializing-strategy/references/semantic-clusters-diagnosis.md +0 -58
- package/dist/skillpacks/geo/skills/geo-initializing-strategy/references/strategy-map-contract.md +0 -26
- package/dist/skillpacks/geo/skills/geo-initializing-strategy/references/visibility-diagnosis.md +0 -50
- package/dist/skillpacks/geo/skills/geo-local-setup/SKILL.md +0 -66
- package/dist/skillpacks/geo/skills/geo-local-setup/agents/openai.yaml +0 -4
- package/dist/skillpacks/geo/skills/geo-page-writer/SKILL.md +0 -81
- package/dist/skillpacks/geo/skills/geo-page-writer/agents/openai.yaml +0 -4
- package/dist/skillpacks/geo/skills/geo-page-writer/references/research.md +0 -61
- package/dist/skillpacks/geo/skills/geo-page-writer/references/validation.md +0 -59
- package/dist/skillpacks/geo/skills/geo-page-writer/references/writing.md +0 -55
- package/dist/skillpacks/geo/skills/geo-page-writer/scripts/check-page-mdx.mjs +0 -210
- package/dist/skillpacks/geo/skills/geo-page-writer/scripts/collect-page-sources.mjs +0 -303
- package/dist/skillpacks/geo/skills/geo-pages-diagnosis/SKILL.md +0 -51
- package/dist/skillpacks/geo/skills/geo-pages-diagnosis/agents/openai.yaml +0 -4
- package/dist/skillpacks/geo/skills/geo-pages-diagnosis/references/diagnosis-contract.md +0 -125
- package/dist/skillpacks/geo/skills/geo-pages-diagnosis/references/workflow.md +0 -269
- package/dist/skillpacks/geo/skills/geo-writer/SKILL.md +0 -234
- package/dist/skillpacks/geo/skills/geo-writer/agents/openai.yaml +0 -4
- package/dist/skillpacks/geo/skills/geo-writer/references/content-writer-contract.md +0 -316
- package/dist/skillpacks/geo/skills/geo-writer/references/direct-sql.md +0 -187
- package/dist/skillpacks/geo/skills/geo-writer/references/seo-geo-insights.md +0 -269
- package/dist/skillpacks/geo/skills/seo-ecommerce-opportunity/SKILL.md +0 -82
- package/dist/skillpacks/geo/skills/seo-ecommerce-opportunity/agents/openai.yaml +0 -5
- package/dist/skillpacks/geo/skills/seo-ecommerce-opportunity/references/ecommerce-rules.md +0 -31
- package/dist/skillpacks/geo/skills/seo-internal-link-opportunity/SKILL.md +0 -57
- package/dist/skillpacks/geo/skills/seo-internal-link-opportunity/agents/openai.yaml +0 -5
- package/dist/skillpacks/geo/skills/seo-internal-link-opportunity/references/link-rules.md +0 -28
- package/dist/skillpacks/geo/skills/seo-opportunity-audit/SKILL.md +0 -141
- package/dist/skillpacks/geo/skills/seo-opportunity-audit/agents/openai.yaml +0 -5
- package/dist/skillpacks/geo/skills/seo-opportunity-audit/references/action-contract.md +0 -62
- package/dist/skillpacks/geo/skills/seo-opportunity-audit/scripts/seo-toolkit.mjs +0 -248
- package/dist/skillpacks/geo/skills/seo-page-diagnosis/SKILL.md +0 -56
- package/dist/skillpacks/geo/skills/seo-page-diagnosis/agents/openai.yaml +0 -5
- package/dist/skillpacks/geo/skills/seo-page-diagnosis/references/page-checks.md +0 -38
- package/dist/skillpacks/geo/skills/seo-serp-opportunity-research/SKILL.md +0 -66
- package/dist/skillpacks/geo/skills/seo-serp-opportunity-research/agents/openai.yaml +0 -5
- package/dist/skillpacks/geo/skills/seo-serp-opportunity-research/references/page-type-taxonomy.md +0 -40
- package/dist/skillpacks/geo/skills/seo-serp-opportunity-research/references/serp-methodology.md +0 -38
- package/dist/skillpacks/geo/skills/seo-technical-diagnosis/SKILL.md +0 -64
- package/dist/skillpacks/geo/skills/seo-technical-diagnosis/agents/openai.yaml +0 -5
- package/dist/skillpacks/geo/skills/seo-technical-diagnosis/references/checks.md +0 -58
- package/dist/skillpacks/geo/skills/third-party-diagnosis/SKILL.md +0 -54
- package/dist/skillpacks/geo/skills/third-party-diagnosis/agents/openai.yaml +0 -4
- package/dist/skillpacks/geo/skills/third-party-diagnosis/references/diagnosis-contract.md +0 -111
- package/dist/skillpacks/geo/skills/third-party-diagnosis/references/workflow.md +0 -174
- package/dist/skillpacks/geo/skills/third-party-execution-planning/SKILL.md +0 -64
- package/dist/skillpacks/geo/skills/third-party-execution-planning/agents/openai.yaml +0 -4
- package/dist/skillpacks/geo/skills/third-party-execution-planning/references/execution-contract.md +0 -90
- package/dist/skillpacks/geo/skills/third-party-execution-planning/references/non-social-surface-playbooks.md +0 -123
- package/dist/skillpacks/geo/skills/third-party-execution-planning/references/reddit-rules.md +0 -69
- package/dist/skillpacks/geo/skills/third-party-execution-planning/references/social-platform-playbooks.md +0 -59
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
# Strategy Map: Semantic Clusters Diagnosis
|
|
2
|
-
|
|
3
|
-
Map the brand's long-term opportunity space into semantic clusters.
|
|
4
|
-
|
|
5
|
-
Each cluster should describe:
|
|
6
|
-
|
|
7
|
-
- intent
|
|
8
|
-
- AI decision criteria
|
|
9
|
-
- current coverage state
|
|
10
|
-
- missing surface types
|
|
11
|
-
- known overlap or cannibalization risk
|
|
12
|
-
- confidence
|
|
13
|
-
|
|
14
|
-
Do not include recommended actions, briefs, or page creation instructions. The orchestrator will turn cluster diagnosis into long-term strategy.
|
|
15
|
-
|
|
16
|
-
Use DataForSEO-backed tools first for keyword demand, SERP shape, ranked-keyword gaps, and competitor content gaps:
|
|
17
|
-
|
|
18
|
-
- `mcp__anymorph__search_keywords`
|
|
19
|
-
- `mcp__anymorph__serp_snapshot`
|
|
20
|
-
- `mcp__anymorph__dataforseo_research`
|
|
21
|
-
- `mcp__anymorph__get_competitor_gap`
|
|
22
|
-
|
|
23
|
-
Use Ahrefs only when available for in-content internal-link hub evidence. If Ahrefs returns "API units limit reached", times out, fails auth, or is unavailable, stop calling Ahrefs and omit `hubSurfaceId` unless a real non-Ahrefs surface id is known.
|
|
24
|
-
|
|
25
|
-
Never emit `null`. Omit optional fields like `hubSurfaceId` and `overlaps` when unknown. Required unknown enum fields must use `unknown`.
|
|
26
|
-
|
|
27
|
-
Submit exactly one section artifact with `mcp__strategy_map_artifacts__submit_strategy_map_section`.
|
|
28
|
-
|
|
29
|
-
```json
|
|
30
|
-
{
|
|
31
|
-
"section": "semanticClusters",
|
|
32
|
-
"artifact": [
|
|
33
|
-
{
|
|
34
|
-
"id": "cl-short-stable-id",
|
|
35
|
-
"name": "Cluster name",
|
|
36
|
-
"intent": "User intent this cluster captures",
|
|
37
|
-
"aiDecisionCriteria": ["Criteria AI answers compare"],
|
|
38
|
-
"priority": "high | medium | low",
|
|
39
|
-
"coverageState": "covered | gap | weak | overlap | monitor | unknown",
|
|
40
|
-
"hubSurfaceId": "optional existing hub surface id",
|
|
41
|
-
"missingSurfaceTypes": ["proof_surface", "comparison_page"],
|
|
42
|
-
"overlaps": ["optional overlapping cluster ids"],
|
|
43
|
-
"confidence": "high | medium | low",
|
|
44
|
-
"evidenceRefs": ["ev-semantic-1"]
|
|
45
|
-
}
|
|
46
|
-
],
|
|
47
|
-
"evidence": [
|
|
48
|
-
{
|
|
49
|
-
"id": "ev-semantic-1",
|
|
50
|
-
"type": "signal | workspace | source | research",
|
|
51
|
-
"ref": "tool or source reference",
|
|
52
|
-
"summary": "Specific evidence summary."
|
|
53
|
-
}
|
|
54
|
-
]
|
|
55
|
-
}
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
Final response: one short sentence naming the saved section.
|
package/dist/skillpacks/geo/skills/geo-initializing-strategy/references/strategy-map-contract.md
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
# Strategy Map Contract
|
|
2
|
-
|
|
3
|
-
The Strategy Map is a diagnosis artifact, not an execution plan.
|
|
4
|
-
|
|
5
|
-
Include:
|
|
6
|
-
|
|
7
|
-
- Owned-site foundation status and blockers.
|
|
8
|
-
- Current AI visibility posture.
|
|
9
|
-
- Semantic clusters with priority and coverage state.
|
|
10
|
-
- External authority gaps.
|
|
11
|
-
- Long-term investment mix and guardrails.
|
|
12
|
-
|
|
13
|
-
Do not include:
|
|
14
|
-
|
|
15
|
-
- Recommended page actions.
|
|
16
|
-
- Outreach copy.
|
|
17
|
-
- Concrete prompt panels.
|
|
18
|
-
- Measurement targets.
|
|
19
|
-
- Weekly success criteria.
|
|
20
|
-
|
|
21
|
-
Evidence rules:
|
|
22
|
-
|
|
23
|
-
- Use DataForSEO On-Page as the primary owned-site crawl and internal-link source.
|
|
24
|
-
- Use Ahrefs only as optional enrichment for in-content internal-link and crawl evidence.
|
|
25
|
-
- Use workspace MCP tools before freeform research.
|
|
26
|
-
- Mark missing evidence as `unknown`; do not infer certainty from absence.
|
package/dist/skillpacks/geo/skills/geo-initializing-strategy/references/visibility-diagnosis.md
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
# Strategy Map: Visibility Diagnosis
|
|
2
|
-
|
|
3
|
-
Summarize current AI visibility.
|
|
4
|
-
|
|
5
|
-
Classify durable patterns by cluster or engine:
|
|
6
|
-
|
|
7
|
-
- `absent`
|
|
8
|
-
- `cited_not_mentioned`
|
|
9
|
-
- `mentioned_not_recommended`
|
|
10
|
-
- `cited_not_recommended`
|
|
11
|
-
- `recommended`
|
|
12
|
-
- `declining`
|
|
13
|
-
- `unknown`
|
|
14
|
-
|
|
15
|
-
Do not paste raw AI answers. Do not propose weekly actions.
|
|
16
|
-
|
|
17
|
-
Never emit `null`. Omit optional fields like `clusterId` and `engine` when unknown. Required unknown enum fields must use `unknown`.
|
|
18
|
-
|
|
19
|
-
Submit exactly one section artifact with `mcp__strategy_map_artifacts__submit_strategy_map_section`.
|
|
20
|
-
|
|
21
|
-
```json
|
|
22
|
-
{
|
|
23
|
-
"section": "visibility",
|
|
24
|
-
"artifact": {
|
|
25
|
-
"overall": "strong | medium | weak | unknown",
|
|
26
|
-
"patterns": [
|
|
27
|
-
{
|
|
28
|
-
"id": "vis-short-stable-id",
|
|
29
|
-
"clusterId": "optional-cluster-id",
|
|
30
|
-
"engine": "optional engine name",
|
|
31
|
-
"state": "absent | cited_not_mentioned | mentioned_not_recommended | cited_not_recommended | recommended | declining | unknown",
|
|
32
|
-
"summary": "Cluster- or engine-level visibility pattern.",
|
|
33
|
-
"competitors": ["Competitor names"],
|
|
34
|
-
"evidenceRefs": ["ev-visibility-1"]
|
|
35
|
-
}
|
|
36
|
-
],
|
|
37
|
-
"evidenceRefs": ["ev-visibility-1"]
|
|
38
|
-
},
|
|
39
|
-
"evidence": [
|
|
40
|
-
{
|
|
41
|
-
"id": "ev-visibility-1",
|
|
42
|
-
"type": "signal | workspace | source | research",
|
|
43
|
-
"ref": "tool or source reference",
|
|
44
|
-
"summary": "Specific evidence summary."
|
|
45
|
-
}
|
|
46
|
-
]
|
|
47
|
-
}
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
Final response: one short sentence naming the saved section.
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: geo-local-setup
|
|
3
|
-
description: Use when setting up, checking, repairing, or preparing a tenant repo for local Anymorph GEO work.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# GEO Local Setup
|
|
7
|
-
|
|
8
|
-
Use this skill before local GEO action generation in a tenant repo.
|
|
9
|
-
|
|
10
|
-
## CLI
|
|
11
|
-
|
|
12
|
-
Use the top-level `anymorph` CLI. Run commands from the tenant repo root.
|
|
13
|
-
|
|
14
|
-
## Setup
|
|
15
|
-
|
|
16
|
-
Run these from the tenant repo root:
|
|
17
|
-
|
|
18
|
-
```bash
|
|
19
|
-
anymorph status
|
|
20
|
-
anymorph login
|
|
21
|
-
anymorph init
|
|
22
|
-
anymorph doctor
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
If credentials are missing, authenticated commands start device login
|
|
26
|
-
automatically. Use `login` directly when you want to authenticate first.
|
|
27
|
-
For non-interactive runs, set `ANYMORPH_ACCESS_TOKEN` and optionally
|
|
28
|
-
`ANYMORPH_API_URL`.
|
|
29
|
-
|
|
30
|
-
`init` installs managed skills, contracts, run folders, and missing memory files.
|
|
31
|
-
`doctor` reports exact missing, extra, or drifted managed files. Use
|
|
32
|
-
`doctor --fix` to repair managed files without touching memory or run artifacts.
|
|
33
|
-
|
|
34
|
-
To compare against a known source checkout instead of the installed copy:
|
|
35
|
-
|
|
36
|
-
```bash
|
|
37
|
-
anymorph doctor --skills-source /path/to/anymorph-geo-skills/skills
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
## Prepare
|
|
41
|
-
|
|
42
|
-
```bash
|
|
43
|
-
anymorph status
|
|
44
|
-
anymorph workspaces
|
|
45
|
-
anymorph prepare <workspaceId>
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
After the agent writes `actions.json`:
|
|
49
|
-
|
|
50
|
-
```bash
|
|
51
|
-
anymorph validate {runId}
|
|
52
|
-
git add agent/runs/{runId} agent/STRATEGY.md agent/LEARNINGS.md
|
|
53
|
-
git commit -m "chore: add geo strategy run {runId}"
|
|
54
|
-
git push origin main
|
|
55
|
-
anymorph submit {runId}
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
`submit` registers or updates the backend run and immediately materializes it
|
|
59
|
-
in Dashboard. There is no separate approve step.
|
|
60
|
-
A submitted run is immutable: once it is `executed`, re-submitting the same
|
|
61
|
-
`runId` (even with an edited `actions.json`) is a no-op. To add or change
|
|
62
|
-
actions, run a fresh `prepare` for a new `runId` and submit that new run.
|
|
63
|
-
For commerce workspaces, `validate` requires every `geo_page` create action to
|
|
64
|
-
include `target.targetProductIds` from the prepared product files.
|
|
65
|
-
|
|
66
|
-
The `anymorph` CLI owns local GEO setup, prepare, validate, and submit.
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: geo-page-writer
|
|
3
|
-
description: Use when writing or updating the actual MDX for an Anymorph-managed GEO page in a CMS tenant repo after a geo_page action or generated-page opportunity has been selected.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# GEO Page Writer
|
|
7
|
-
|
|
8
|
-
Use this skill to write or revise a real CMS MDX page for a selected
|
|
9
|
-
`geo_page` action. This is an execution skill, not a strategy-planning skill.
|
|
10
|
-
|
|
11
|
-
## Scope
|
|
12
|
-
|
|
13
|
-
Write or update:
|
|
14
|
-
|
|
15
|
-
- A tenant repo MDX page for a generated GEO page.
|
|
16
|
-
- The supporting page-writer evidence artifacts under `agent/page-writer/`.
|
|
17
|
-
|
|
18
|
-
Do not:
|
|
19
|
-
|
|
20
|
-
- Write `actions.json`.
|
|
21
|
-
- Submit GEO strategy runs.
|
|
22
|
-
- Replace the backend CMS generation pipeline.
|
|
23
|
-
- Invent claims, product facts, prices, image URLs, testimonials, quotes, or
|
|
24
|
-
source names.
|
|
25
|
-
|
|
26
|
-
## References
|
|
27
|
-
|
|
28
|
-
Read these references before writing:
|
|
29
|
-
|
|
30
|
-
- `references/research.md` for source collection, Exa Deep Search, KB search,
|
|
31
|
-
image search, and source-pack rules.
|
|
32
|
-
- `references/writing.md` for MDX structure, frontmatter, components, images,
|
|
33
|
-
CTA, and GEO writing rules.
|
|
34
|
-
- `references/validation.md` for agent-owned brand and fact validation plus
|
|
35
|
-
script-backed MDX static checks.
|
|
36
|
-
|
|
37
|
-
## Workflow
|
|
38
|
-
|
|
39
|
-
1. Identify the selected `geo_page` action from `agent/runs/{runId}/actions.json`.
|
|
40
|
-
2. Collect page sources:
|
|
41
|
-
|
|
42
|
-
```bash
|
|
43
|
-
node .agents/skills/geo-page-writer/scripts/collect-page-sources.mjs all \
|
|
44
|
-
--run-id <runId> \
|
|
45
|
-
--action-id <actionId> \
|
|
46
|
-
--workspace <workspace-id-or-domain>
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
Use `.claude/skills/...` instead of `.agents/skills/...` when running under
|
|
50
|
-
Claude Code.
|
|
51
|
-
|
|
52
|
-
3. Read `agent/page-writer/{runId}/{actionId}/sources/source-pack.json`.
|
|
53
|
-
4. Write or edit the MDX manually using the source pack and `references/writing.md`.
|
|
54
|
-
5. Self-review brand and fact claims with `references/validation.md`.
|
|
55
|
-
6. Run the static MDX checker:
|
|
56
|
-
|
|
57
|
-
```bash
|
|
58
|
-
node .agents/skills/geo-page-writer/scripts/check-page-mdx.mjs \
|
|
59
|
-
--file <path/to/page.mdx> \
|
|
60
|
-
--sources agent/page-writer/<runId>/<actionId>/sources/source-pack.json
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
7. Fix every error. Review warnings before commit.
|
|
64
|
-
|
|
65
|
-
## Artifact Contract
|
|
66
|
-
|
|
67
|
-
Write page-writer artifacts here:
|
|
68
|
-
|
|
69
|
-
```text
|
|
70
|
-
agent/page-writer/{runId}/{actionId}/
|
|
71
|
-
sources/
|
|
72
|
-
exa-research.md
|
|
73
|
-
exa-response.json
|
|
74
|
-
kb-results.json
|
|
75
|
-
image-results.json
|
|
76
|
-
source-pack.json
|
|
77
|
-
checks/
|
|
78
|
-
mdx-static.json
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
Commit the MDX and relevant `agent/page-writer/...` artifacts together.
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
# Research
|
|
2
|
-
|
|
3
|
-
The research phase builds the source pack used to write the page. It does not
|
|
4
|
-
write the MDX.
|
|
5
|
-
|
|
6
|
-
## Source Order
|
|
7
|
-
|
|
8
|
-
Use sources in this order:
|
|
9
|
-
|
|
10
|
-
1. Selected `geo_page` action and run context.
|
|
11
|
-
2. Tenant workspace KB / living document.
|
|
12
|
-
3. Product catalog or action `targetProductIds`.
|
|
13
|
-
4. Exa Deep Search for external evidence.
|
|
14
|
-
5. Image search for existing brand assets.
|
|
15
|
-
|
|
16
|
-
Prefer first-party and workspace facts over external summaries. Use external
|
|
17
|
-
research to fill market, comparison, definition, and citation gaps.
|
|
18
|
-
|
|
19
|
-
## Exa Deep Search
|
|
20
|
-
|
|
21
|
-
`collect-page-sources.mjs exa` uses Exa `/search` with `type:
|
|
22
|
-
"deep-reasoning"`. Exa's older `/research/v1` API is deprecated, so do not use
|
|
23
|
-
it for new work.
|
|
24
|
-
|
|
25
|
-
The Exa result is a research input, not page copy. Treat its synthesized answer
|
|
26
|
-
as a draft finding that still needs source-aware judgment.
|
|
27
|
-
|
|
28
|
-
## KB Search
|
|
29
|
-
|
|
30
|
-
Use KB search for brand-specific facts:
|
|
31
|
-
|
|
32
|
-
- product positioning
|
|
33
|
-
- process and methodology
|
|
34
|
-
- pricing or packaging claims
|
|
35
|
-
- company history
|
|
36
|
-
- differentiators
|
|
37
|
-
- support, FAQ, and policy details
|
|
38
|
-
|
|
39
|
-
If KB search is unavailable, mark the missing query in the source pack and avoid
|
|
40
|
-
brand-specific factual claims that need that evidence.
|
|
41
|
-
|
|
42
|
-
## Image Search
|
|
43
|
-
|
|
44
|
-
Use image search only for existing workspace assets. Do not generate images in
|
|
45
|
-
this skill.
|
|
46
|
-
|
|
47
|
-
Every MDX image URL should come from:
|
|
48
|
-
|
|
49
|
-
- the source pack image results
|
|
50
|
-
- product catalog image inputs
|
|
51
|
-
- an existing tenant asset already present in the MDX/repo
|
|
52
|
-
|
|
53
|
-
## Source Pack Rules
|
|
54
|
-
|
|
55
|
-
The source pack must make writing decisions auditable:
|
|
56
|
-
|
|
57
|
-
- Keep source URLs and titles.
|
|
58
|
-
- Keep KB snippets separate from external web sources.
|
|
59
|
-
- Keep image results separate from factual sources.
|
|
60
|
-
- Record failures instead of silently omitting a source class.
|
|
61
|
-
- Do not promote a source into a claim unless the source actually supports it.
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
# Validation
|
|
2
|
-
|
|
3
|
-
Validation has two owners:
|
|
4
|
-
|
|
5
|
-
- The agent owns brand and fact validation.
|
|
6
|
-
- `check-page-mdx.mjs` owns deterministic MDX static validation.
|
|
7
|
-
|
|
8
|
-
## Brand Validation
|
|
9
|
-
|
|
10
|
-
Before running the script, review the MDX against the source pack and tenant
|
|
11
|
-
brand context.
|
|
12
|
-
|
|
13
|
-
Check:
|
|
14
|
-
|
|
15
|
-
- Tone matches the brand guidance.
|
|
16
|
-
- Blocklisted phrases are absent.
|
|
17
|
-
- Required positioning is represented.
|
|
18
|
-
- "Must avoid" claims or angles are not used.
|
|
19
|
-
- The brand role is honest and not forced into unrelated claims.
|
|
20
|
-
- Competitive comparisons are measured and sourced.
|
|
21
|
-
|
|
22
|
-
If unsure, soften the claim or remove it.
|
|
23
|
-
|
|
24
|
-
## Fact Validation
|
|
25
|
-
|
|
26
|
-
Every concrete factual claim must be supported by the source pack or existing
|
|
27
|
-
tenant/product data.
|
|
28
|
-
|
|
29
|
-
Remove or soften:
|
|
30
|
-
|
|
31
|
-
- numbers not present in sources
|
|
32
|
-
- dates not present in sources
|
|
33
|
-
- "best", "#1", "first", "only", "guaranteed", or "clinically proven" without
|
|
34
|
-
direct evidence
|
|
35
|
-
- product efficacy claims without product/KB/source support
|
|
36
|
-
- competitor comparisons without a source for both sides
|
|
37
|
-
- exact quotes not present verbatim in a source
|
|
38
|
-
|
|
39
|
-
Use hedged language when evidence is directional but not conclusive.
|
|
40
|
-
|
|
41
|
-
## MDX Static Validation
|
|
42
|
-
|
|
43
|
-
Run:
|
|
44
|
-
|
|
45
|
-
```bash
|
|
46
|
-
node .agents/skills/geo-page-writer/scripts/check-page-mdx.mjs \
|
|
47
|
-
--file <path/to/page.mdx> \
|
|
48
|
-
--sources agent/page-writer/<runId>/<actionId>/sources/source-pack.json
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
The checker validates:
|
|
52
|
-
|
|
53
|
-
- frontmatter presence
|
|
54
|
-
- MDX/JSX parse when a compiler is available
|
|
55
|
-
- import and component allowlists
|
|
56
|
-
- image URL provenance
|
|
57
|
-
- link and CTA URL shape
|
|
58
|
-
|
|
59
|
-
Fix all errors. Review warnings before committing.
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
# Writing
|
|
2
|
-
|
|
3
|
-
Write the actual MDX directly. Do not use a writing script.
|
|
4
|
-
|
|
5
|
-
## Page Shape
|
|
6
|
-
|
|
7
|
-
The page should be useful to a human reader and easy for AI engines to cite.
|
|
8
|
-
|
|
9
|
-
Use:
|
|
10
|
-
|
|
11
|
-
- Answer-first opening.
|
|
12
|
-
- Clear H2 sections that each answer one question.
|
|
13
|
-
- Self-contained paragraphs with explicit entities.
|
|
14
|
-
- Specific product, process, or methodology details when sourced.
|
|
15
|
-
- Tables or lists only when they improve comparison or extraction.
|
|
16
|
-
|
|
17
|
-
Avoid:
|
|
18
|
-
|
|
19
|
-
- Generic SEO filler.
|
|
20
|
-
- Keyword stuffing.
|
|
21
|
-
- Unsupported rankings or superlatives.
|
|
22
|
-
- Fake quotes, fake studies, fake customers, or invented numbers.
|
|
23
|
-
- Images that are merely decorative when product/source evidence is needed.
|
|
24
|
-
|
|
25
|
-
## Frontmatter
|
|
26
|
-
|
|
27
|
-
Include the tenant's expected MDX frontmatter fields. At minimum:
|
|
28
|
-
|
|
29
|
-
- `title`
|
|
30
|
-
- `description`
|
|
31
|
-
- `lang`
|
|
32
|
-
|
|
33
|
-
Include `heroImage` only when the URL is from the source pack, product catalog,
|
|
34
|
-
or an existing tenant asset.
|
|
35
|
-
|
|
36
|
-
## Components
|
|
37
|
-
|
|
38
|
-
Use only components already supported by the tenant CMS renderer. Prefer plain
|
|
39
|
-
MDX when unsure.
|
|
40
|
-
|
|
41
|
-
For commerce/product pages:
|
|
42
|
-
|
|
43
|
-
- Only mention products present in the source pack or product context.
|
|
44
|
-
- Use product image, price, URL, and title verbatim.
|
|
45
|
-
- Do not compute discounts or localize prices yourself.
|
|
46
|
-
|
|
47
|
-
## CTA
|
|
48
|
-
|
|
49
|
-
Use CTA URLs from tenant inputs or existing safe routes. Do not invent checkout,
|
|
50
|
-
demo, pricing, or contact URLs.
|
|
51
|
-
|
|
52
|
-
## Images
|
|
53
|
-
|
|
54
|
-
Use image URLs verbatim. Add useful alt text. Do not crop product imagery in a
|
|
55
|
-
way that hides packaging, texture, label, or use context.
|
|
@@ -1,210 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
-
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
4
|
-
import { pathToFileURL } from "node:url";
|
|
5
|
-
|
|
6
|
-
const args = parseArgs(process.argv.slice(2));
|
|
7
|
-
|
|
8
|
-
try {
|
|
9
|
-
const result = await check(args);
|
|
10
|
-
console.log(JSON.stringify(result, null, 2));
|
|
11
|
-
process.exit(result.ok ? 0 : 1);
|
|
12
|
-
} catch (err) {
|
|
13
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
14
|
-
console.error(message);
|
|
15
|
-
process.exit(1);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function parseArgs(argv) {
|
|
19
|
-
const out = {};
|
|
20
|
-
for (let i = 0; i < argv.length; i += 1) {
|
|
21
|
-
const arg = argv[i];
|
|
22
|
-
if (arg === "--file") out.file = requireValue(argv, ++i, arg);
|
|
23
|
-
else if (arg === "--sources") out.sources = requireValue(argv, ++i, arg);
|
|
24
|
-
else if (arg === "--out") out.out = requireValue(argv, ++i, arg);
|
|
25
|
-
else throw new Error(`Unknown argument: ${arg}`);
|
|
26
|
-
}
|
|
27
|
-
return out;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function requireValue(argv, index, flag) {
|
|
31
|
-
const value = argv[index];
|
|
32
|
-
if (!value || value.startsWith("--")) throw new Error(`${flag} requires a value`);
|
|
33
|
-
return value;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
async function check(input) {
|
|
37
|
-
const file = required(input.file, "--file");
|
|
38
|
-
const sourcePath = input.sources;
|
|
39
|
-
const mdxPath = resolve(file);
|
|
40
|
-
const mdx = await readFile(mdxPath, "utf8");
|
|
41
|
-
const sourcePack = sourcePath ? JSON.parse(await readFile(resolve(sourcePath), "utf8")) : null;
|
|
42
|
-
const errors = [];
|
|
43
|
-
const warnings = [];
|
|
44
|
-
const frontmatter = parseFrontmatter(mdx, errors);
|
|
45
|
-
checkFrontmatter(frontmatter, errors);
|
|
46
|
-
checkImports(mdx, errors, warnings);
|
|
47
|
-
checkComponents(mdx, errors);
|
|
48
|
-
checkLinks(mdx, warnings);
|
|
49
|
-
checkImages(mdx, sourcePack, warnings);
|
|
50
|
-
await checkMdxCompile(mdx, errors, warnings);
|
|
51
|
-
const result = {
|
|
52
|
-
ok: errors.length === 0,
|
|
53
|
-
file: mdxPath,
|
|
54
|
-
checkedAt: new Date().toISOString(),
|
|
55
|
-
errors,
|
|
56
|
-
warnings,
|
|
57
|
-
};
|
|
58
|
-
if (input.out) {
|
|
59
|
-
const outPath = isAbsolute(input.out) ? input.out : resolve(input.out);
|
|
60
|
-
await mkdir(dirname(outPath), { recursive: true });
|
|
61
|
-
await writeFile(outPath, `${JSON.stringify(result, null, 2)}\n`);
|
|
62
|
-
}
|
|
63
|
-
return result;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function parseFrontmatter(mdx, errors) {
|
|
67
|
-
if (!mdx.startsWith("---\n")) {
|
|
68
|
-
errors.push("frontmatter: file must start with YAML frontmatter");
|
|
69
|
-
return {};
|
|
70
|
-
}
|
|
71
|
-
const end = mdx.indexOf("\n---", 4);
|
|
72
|
-
if (end === -1) {
|
|
73
|
-
errors.push("frontmatter: closing --- not found");
|
|
74
|
-
return {};
|
|
75
|
-
}
|
|
76
|
-
const raw = mdx.slice(4, end).trim();
|
|
77
|
-
const data = {};
|
|
78
|
-
for (const line of raw.split(/\r?\n/)) {
|
|
79
|
-
const trimmed = line.trim();
|
|
80
|
-
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
81
|
-
const match = trimmed.match(/^([A-Za-z0-9_-]+):\s*(.*)$/);
|
|
82
|
-
if (!match) continue;
|
|
83
|
-
data[match[1]] = unquote(match[2]);
|
|
84
|
-
}
|
|
85
|
-
return data;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function unquote(value) {
|
|
89
|
-
const trimmed = value.trim();
|
|
90
|
-
if ((trimmed.startsWith('"') && trimmed.endsWith('"')) || (trimmed.startsWith("'") && trimmed.endsWith("'"))) {
|
|
91
|
-
return trimmed.slice(1, -1);
|
|
92
|
-
}
|
|
93
|
-
return trimmed;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function checkFrontmatter(frontmatter, errors) {
|
|
97
|
-
for (const field of ["title", "description", "lang"]) {
|
|
98
|
-
if (!frontmatter[field]) errors.push(`frontmatter: ${field} is required`);
|
|
99
|
-
}
|
|
100
|
-
if (frontmatter.heroImage && !isHttpUrl(frontmatter.heroImage) && !frontmatter.heroImage.startsWith("/")) {
|
|
101
|
-
errors.push("frontmatter: heroImage must be an absolute http(s) URL or root-relative path");
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const ALLOWED_IMPORTS = new Set([
|
|
106
|
-
"@/components/mdx",
|
|
107
|
-
"@/components/cms",
|
|
108
|
-
"@/components/content",
|
|
109
|
-
]);
|
|
110
|
-
|
|
111
|
-
function checkImports(mdx, errors, warnings) {
|
|
112
|
-
const imports = [...mdx.matchAll(/^\s*import\s+[^'"]+['"]([^'"]+)['"];?\s*$/gm)].map((m) => m[1]);
|
|
113
|
-
for (const specifier of imports) {
|
|
114
|
-
if (![...ALLOWED_IMPORTS].some((allowed) => specifier.startsWith(allowed))) {
|
|
115
|
-
errors.push(`import: unsupported import "${specifier}"`);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const ALLOWED_COMPONENTS = new Set([
|
|
121
|
-
"ArticleHeader",
|
|
122
|
-
"Callout",
|
|
123
|
-
"CTA",
|
|
124
|
-
"FAQ",
|
|
125
|
-
"Image",
|
|
126
|
-
"ImageGrid",
|
|
127
|
-
"ProductCard",
|
|
128
|
-
"RelatedContent",
|
|
129
|
-
"Table",
|
|
130
|
-
]);
|
|
131
|
-
|
|
132
|
-
function checkComponents(mdx, errors) {
|
|
133
|
-
const tags = [...mdx.matchAll(/<([A-Z][A-Za-z0-9.]*)\b/g)].map((m) => m[1].split(".")[0]);
|
|
134
|
-
for (const tag of new Set(tags)) {
|
|
135
|
-
if (!ALLOWED_COMPONENTS.has(tag)) errors.push(`component: unsupported component <${tag}>`);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function checkLinks(mdx, warnings) {
|
|
140
|
-
const markdownLinks = [...mdx.matchAll(/\[[^\]]+\]\(([^)]+)\)/g)].map((m) => m[1]);
|
|
141
|
-
const hrefs = [...mdx.matchAll(/\bhref=["']([^"']+)["']/g)].map((m) => m[1]);
|
|
142
|
-
for (const url of [...markdownLinks, ...hrefs]) {
|
|
143
|
-
if (url.startsWith("#") || url.startsWith("/") || isHttpUrl(url) || url.startsWith("mailto:") || url.startsWith("tel:")) continue;
|
|
144
|
-
warnings.push(`link: review non-standard URL "${url}"`);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function checkImages(mdx, sourcePack, warnings) {
|
|
149
|
-
const urls = [
|
|
150
|
-
...[...mdx.matchAll(/!\[[^\]]*\]\(([^)]+)\)/g)].map((m) => m[1]),
|
|
151
|
-
...[...mdx.matchAll(/\bsrc=["']([^"']+)["']/g)].map((m) => m[1]),
|
|
152
|
-
...[...mdx.matchAll(/\bimage=["']([^"']+)["']/g)].map((m) => m[1]),
|
|
153
|
-
...[...mdx.matchAll(/\bheroImage:\s*["']?([^"'\n]+)["']?/g)].map((m) => m[1].trim()),
|
|
154
|
-
].filter((url) => isHttpUrl(url));
|
|
155
|
-
const allowed = collectAllowedImageUrls(sourcePack);
|
|
156
|
-
for (const url of new Set(urls)) {
|
|
157
|
-
if (allowed.size > 0 && !allowed.has(url)) {
|
|
158
|
-
warnings.push(`image: "${url}" is not present in source-pack image results`);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function collectAllowedImageUrls(sourcePack) {
|
|
164
|
-
const urls = new Set();
|
|
165
|
-
const add = (value) => {
|
|
166
|
-
if (typeof value === "string" && isHttpUrl(value)) urls.add(value);
|
|
167
|
-
};
|
|
168
|
-
const walk = (value) => {
|
|
169
|
-
if (Array.isArray(value)) return value.forEach(walk);
|
|
170
|
-
if (!value || typeof value !== "object") return;
|
|
171
|
-
for (const [key, child] of Object.entries(value)) {
|
|
172
|
-
if (/^(url|imageUrl|src|heroImage)$/i.test(key)) add(child);
|
|
173
|
-
walk(child);
|
|
174
|
-
}
|
|
175
|
-
};
|
|
176
|
-
walk(sourcePack?.images);
|
|
177
|
-
walk(sourcePack?.imageResults);
|
|
178
|
-
walk(sourcePack?.action?.target);
|
|
179
|
-
return urls;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
async function checkMdxCompile(mdx, errors, warnings) {
|
|
183
|
-
try {
|
|
184
|
-
const mod = await import("@mdx-js/mdx");
|
|
185
|
-
await mod.compile(mdx, { jsx: true, development: false });
|
|
186
|
-
} catch (err) {
|
|
187
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
188
|
-
if (message.includes("Cannot find package '@mdx-js/mdx'")) {
|
|
189
|
-
warnings.push("mdx: @mdx-js/mdx is not installed in this repo; skipped compiler parse check");
|
|
190
|
-
checkBalancedBraces(mdx, warnings);
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
|
-
errors.push(`mdx: compile failed: ${message}`);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
function checkBalancedBraces(mdx, warnings) {
|
|
198
|
-
const opens = (mdx.match(/{/g) ?? []).length;
|
|
199
|
-
const closes = (mdx.match(/}/g) ?? []).length;
|
|
200
|
-
if (opens !== closes) warnings.push(`mdx: brace count differs ({=${opens}, }=${closes}); review JSX expressions`);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
function isHttpUrl(value) {
|
|
204
|
-
return /^https?:\/\//i.test(value);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
function required(value, name) {
|
|
208
|
-
if (!value) throw new Error(`${name} is required`);
|
|
209
|
-
return value;
|
|
210
|
-
}
|