@profile-as-code/cli 0.3.6 → 0.4.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,86 @@
1
+ ---
2
+ name: medium-post
3
+ description: >
4
+ Long-form post skill for Medium. Says what a Medium essay is for and how the
5
+ agent writes it; the machine contract is
6
+ skills/medium/longform.yaml (required/optional fields, editorial word band,
7
+ export: api). The judgment layer — shape, voice, GEO, i18n — lives in post.md.
8
+ The agent writes N variants, the operator picks one, and the choice is recorded
9
+ with `pasco post choose`. The contract's api export is mapped, not guaranteed:
10
+ verify API access before relying on it.
11
+ model: juicio (frontier) — the position and its stakes are judgment; the
12
+ mechanical parts (markdown, front matter) are not
13
+ applies_to: medium
14
+ projection: agent-agnostic (any harness that reads Markdown and can run the CLI)
15
+ version: 0.1.0
16
+ status: draft (#361)
17
+ sources: skills/medium/longform.yaml
18
+ ---
19
+
20
+ # Medium long-form — skill
21
+
22
+ ## Mission
23
+
24
+ Turn one canonical seed into an essay that argues a position a professional
25
+ audience can disagree with: the situation, the tension, what was done, what it
26
+ cost. The skill is the judgment layer (D5); the contract plus `pasco post check`
27
+ are the deterministic part.
28
+
29
+ ## When it runs
30
+
31
+ `pasco post task <seed> --format longform --channel medium` emits a task bundle
32
+ that points at the contract (`longform.yaml`) and the judgment file (`post.md`);
33
+ it does **not** carry this `SKILL.md`. This file is the channel's strategy
34
+ reference for whoever opens it directly: read the contract for the fields and the
35
+ band, then `post.md` for how to write.
36
+
37
+ Do **not** run `pasco refine medium` for this channel. Refine is the profile
38
+ command: with a `SKILL.md` now present it resolves this file instead of answering
39
+ "no skill for medium yet", and emits a **profile** task ("write N variants of the
40
+ medium profile") that saves to `profiles/medium/variants/<id>.md` — not a post
41
+ workflow. Telling a profile skill from a post skill is an engine decision, not
42
+ something this skill can fix.
43
+
44
+ ## Flow
45
+
46
+ 1. Read `longform.yaml` + `post.md`; the contract owns every limit.
47
+ 2. Write N variants as YAML front matter + markdown body, at
48
+ `posts/<seed>/medium/medium-<n>.md`.
49
+ 3. The operator picks one; record it with `pasco post choose`.
50
+ 4. `pasco post check` — fix the findings.
51
+ 5. `pasco post export <seed> --target postiz` for the API draft payload.
52
+ 6. Publish manually and verify the live page (title, tags, canonical).
53
+
54
+ ## Contract pointers (single source each)
55
+
56
+ | Question | Home |
57
+ |---|---|
58
+ | required / optional fields | `longform.yaml` `fields` |
59
+ | editorial word band | `longform.yaml` `limits.words` (not a platform cap) |
60
+ | api vs paste | `longform.yaml` `export` |
61
+ | link policy | `longform.yaml` `links.policy` (`identity-ssot-only`) |
62
+ | dated platform sources | `longform.yaml` `sources[]` |
63
+ | how to write the piece | `post.md` |
64
+
65
+ ## Open precondition
66
+
67
+ The contract maps `export: api` onto the Medium posting API, but whether an
68
+ operator can still obtain write access is not stated by the contract's source:
69
+ treat it as unverified [?] and check before promising an API publish.
70
+
71
+ ## GEO and i18n
72
+
73
+ Public, indexed essay pages; tags are browsing paths, not search intent; the
74
+ citation unit is the stated position plus the evidence under it. Write `en` by
75
+ default; a Spanish essay is a separate seed for that audience, never a translation
76
+ sharing a canonical. The crawler stance is the repo's global `ai.policy`
77
+ (`platforms/registry.yaml`, v1, 2026-10-27).
78
+
79
+ ## Verification
80
+
81
+ - `pasco post check` exit 0 (no `missing-field`, `word-count` or
82
+ `url-not-in-ssot` errors).
83
+ - The title is arguable; the dek states the stake.
84
+ - Facts byte-identical to the SSOT; canonical (when set) points at the SSOT home.
85
+ - Report any contract gap as a finding — the engine is not extended from a
86
+ skill (#361).
@@ -0,0 +1,33 @@
1
+ # medium long-form rules — machine-readable (#266)
2
+ #
3
+ # Read by `pasco post task|check|export` when the seed is `format: longform`.
4
+ # The word range is an EDITORIAL default (the operator's house style), NOT a
5
+ # platform limit: cite a platform source before encoding a hard cap here.
6
+
7
+ platform: medium
8
+ format: longform
9
+ export: api
10
+ settings:
11
+ __type: medium
12
+ fields:
13
+ required:
14
+ - title
15
+ - dek
16
+ optional:
17
+ - tags
18
+ - canonical
19
+ - publication
20
+ limits:
21
+ words:
22
+ min: 600
23
+ max: 3000
24
+ links:
25
+ policy: identity-ssot-only
26
+ structure:
27
+ - title
28
+ - dek
29
+ - sections
30
+ sources:
31
+ - url: https://github.com/Medium/medium-api-docs
32
+ date: 2026-10-29
33
+ note: "Medium's posting API takes the title, tags and canonical URL in the post payload; the body is markdown."
@@ -0,0 +1,96 @@
1
+ # Medium long-form post — skill (MVP)
2
+
3
+ > Long-form post skill. The contract is `longform.yaml`: required/optional
4
+ > fields, the editorial word band and `export` live there and are **not**
5
+ > restated here — this file is the judgment layer the agent reads before
6
+ > writing. The contract's `sources[]` carries the dated URL for every platform
7
+ > claim; re-verify before making a hard one, and cite a source (date + tier)
8
+ > before calling anything a limit.
9
+
10
+ ## What a Medium piece is for
11
+
12
+ An essay for a professional audience that reads to *understand a position*, not
13
+ to copy a snippet. Medium is a reading surface with tags and publications as its
14
+ two distribution paths; the piece competes for a reader's attention against other
15
+ essays, so the argument — not the feature list — is what travels. The technical
16
+ depth lives on the other channels; here the decision, its cost and its
17
+ consequence carry the piece.
18
+
19
+ ## Shape
20
+
21
+ 1. **Title** — the position in one line. Concrete and arguable; if nobody could
22
+ disagree, it is not a title for this channel.
23
+ 2. **Dek** — the stake: what the reader gets by finishing. The contract requires
24
+ it as a field and the engine delivers it as front matter, never as the body's
25
+ first lines. No platform mapping is declared; confirm on the first publish how
26
+ Medium renders it [?].
27
+ 3. **Sections** — narrative arc, not a manual: situation → tension → what was
28
+ done → what it cost → what follows. H2 per move; short paragraphs; one idea
29
+ per paragraph.
30
+
31
+ ## Rules
32
+
33
+ - **Front matter is the field contract.** `longform.yaml` declares the fields this
34
+ channel consumes, required and optional alike; the engine exports only those,
35
+ and an undeclared field is dropped (`pasco post check` warns `unknown-field`) —
36
+ never smuggle one into the body instead.
37
+ - **Length:** the band in `limits.words` is the operator's editorial default (the
38
+ contract says so), not a Medium limit — Medium does not publish a cap this
39
+ contract relies on.
40
+ - **Links:** `links.policy` is `identity-ssot-only`. When the piece is syndicated,
41
+ `canonical` points at the canonical home from the SSOT — a Medium URL is never
42
+ the canonical for the original.
43
+ - **Facts:** `{{fact:<pointer>}}` for every number, date, URL and claim of
44
+ achievement.
45
+ - **Voice:** first person, essayistic, no hype and no "lessons learned" filler.
46
+ Match `brandVoice`; Medium's audience rewards specificity, not enthusiasm.
47
+
48
+ ## Export: `api`
49
+
50
+ The contract declares `export: api`: the Medium posting API takes the title, tags
51
+ and canonical URL in the post payload and the body as markdown [OFICIAL — the
52
+ contract's source, `sources[]` in `longform.yaml`, dated 2026-10-29]. Two
53
+ consequences:
54
+
55
+ - The writer must not depend on a formatting feature the API payload does not
56
+ carry: markdown, no raw HTML.
57
+ - Whether the operator can still obtain write access to that API is **not**
58
+ stated by the contract's source: verify access before relying on `export: api`
59
+ for a real publish [?]. The export itself only produces a Postiz draft payload,
60
+ and publishing stays manual (no scheduler, no OAuth — `docs/post-mvp.md`).
61
+
62
+ ## GEO (how this channel is read and cited)
63
+
64
+ Medium pages are public and indexed, and the tags are browsing paths rather than
65
+ search intent. The quotable unit is a stated position plus the evidence
66
+ immediately under it, so keep the claim next to its proof instead of building a
67
+ reveal. The crawler stance is the repo's global `ai.policy`
68
+ (`platforms/registry.yaml`, v1, 2026-10-27); no per-channel rule is invented
69
+ here. Consistency check: every fact agrees with the profile and with the other
70
+ channels' projections, byte for byte.
71
+
72
+ ## i18n
73
+
74
+ Write `en` by default: the audience and the tag vocabulary are English-first, and
75
+ a publication is per language. A Spanish essay is its own seed for that audience,
76
+ not a translated copy of the English one — two canonicals of one text split the
77
+ citation.
78
+
79
+ ## Anti-patterns
80
+
81
+ - A "10 lessons" listicle with no evidence per lesson.
82
+ - An essay that opens with a dictionary definition.
83
+ - A `canonical` invented for the occasion.
84
+ - Syndicating the original without a canonical back to the SSOT home.
85
+ - Enthusiasm in place of an argument.
86
+
87
+ ## Output contract
88
+
89
+ Save each variant as YAML front matter + a markdown body at
90
+ `posts/<seed>/medium/medium-<n>.md`, then record the choice and check it:
91
+
92
+ ```
93
+ pasco post choose <seed> --channel medium --variant medium-1
94
+ pasco post check <seed> # missing-field · word-count · url-not-in-ssot · unknown-field
95
+ pasco post export <seed> --target postiz
96
+ ```
@@ -0,0 +1,83 @@
1
+ ---
2
+ name: substack-post
3
+ description: >
4
+ Long-form post skill for Substack. Says what a Substack issue is for and how
5
+ the agent writes it; the machine contract is
6
+ skills/substack/longform.yaml (required/optional fields, editorial word band,
7
+ export: paste). The judgment layer — shape, voice, GEO, i18n — lives in
8
+ post.md. The agent writes N variants, the operator picks one, and the choice is
9
+ recorded with `pasco post choose`; the deploy is a manual paste driven by the
10
+ generated checklist, because Substack exposes no body-publish API.
11
+ model: juicio (frontier) — the letter's opening and its closing are judgment; the
12
+ mechanical parts (markdown, front matter, paste checklist) are not
13
+ applies_to: substack
14
+ projection: agent-agnostic (any harness that reads Markdown and can run the CLI)
15
+ version: 0.1.0
16
+ status: draft (#361)
17
+ sources: skills/substack/longform.yaml
18
+ ---
19
+
20
+ # Substack long-form — skill
21
+
22
+ ## Mission
23
+
24
+ Turn one canonical seed into a newsletter issue that works twice: as the email a
25
+ subscriber opens and as the public archive page a visitor lands on. One idea, a
26
+ stake, an argument developed once, and a closing that says what comes next. The
27
+ skill is the judgment layer (D5); the contract plus `pasco post check` are the
28
+ deterministic part.
29
+
30
+ ## When it runs
31
+
32
+ `pasco post task <seed> --format longform --channel substack` emits a task bundle
33
+ that points at the contract (`longform.yaml`) and the judgment file (`post.md`);
34
+ it does **not** carry this `SKILL.md`. This file is the channel's strategy
35
+ reference for whoever opens it directly: read the contract for the fields and the
36
+ band, then `post.md` for how to write.
37
+
38
+ Do **not** run `pasco refine substack` for this channel. Refine is the profile
39
+ command: with a `SKILL.md` now present it resolves this file instead of answering
40
+ "no skill for substack yet", and emits a **profile** task ("write N variants of
41
+ the substack profile") that saves to `profiles/substack/variants/<id>.md` — not a
42
+ post workflow. Telling a profile skill from a post skill is an engine decision,
43
+ not something this skill can fix.
44
+
45
+ ## Flow
46
+
47
+ 1. Read `longform.yaml` + `post.md`; the contract owns every limit.
48
+ 2. Write N variants as YAML front matter + markdown body, at
49
+ `posts/<seed>/substack/substack-<n>.md`.
50
+ 3. The operator picks one; record it with `pasco post choose`.
51
+ 4. `pasco post check` — fix the findings.
52
+ 5. `pasco post export <seed>` → `paste-checklist.md` (no body API in v1).
53
+ 6. Paste the body and each field into the editor, tick the checklist, and verify
54
+ the live archive page before calling it published.
55
+
56
+ ## Contract pointers (single source each)
57
+
58
+ | Question | Home |
59
+ |---|---|
60
+ | required / optional fields | `longform.yaml` `fields` |
61
+ | editorial word band | `longform.yaml` `limits.words` (not a platform cap) |
62
+ | api vs paste | `longform.yaml` `export: paste` (no body API) |
63
+ | link policy | `longform.yaml` `links.policy` (`identity-ssot-only`) |
64
+ | dated platform sources | `longform.yaml` `sources[]` |
65
+ | how to write the issue | `post.md` |
66
+
67
+ ## GEO and i18n
68
+
69
+ The archive page is public and indexable while the email copy is not; the section
70
+ heading plus its claim is the citation unit, and it resolves to the archive URL.
71
+ Keep facts byte-identical to the profile. One publication per language; `en` by
72
+ default, a Spanish publication as its own surface. The crawler stance is the
73
+ repo's global `ai.policy` (`platforms/registry.yaml`, v1, 2026-10-27).
74
+
75
+ ## Verification
76
+
77
+ - `pasco post check` exit 0 (no `missing-field`, `word-count` or
78
+ `url-not-in-ssot` errors).
79
+ - The pasted body kept headings, links and quotes; the archive page renders the
80
+ dek and title as intended.
81
+ - Facts byte-identical to the SSOT; every URL from the identity SSOT.
82
+ - Report any contract gap as a finding — the engine is not extended from a
83
+ skill (#361).
@@ -0,0 +1,36 @@
1
+ # substack long-form rules — machine-readable (#266)
2
+ #
3
+ # Read by `pasco post task|check|export` when the seed is `format: longform`.
4
+ # The word range is an EDITORIAL default (the operator's house style), NOT a
5
+ # platform limit: cite a platform source before encoding a hard cap here.
6
+ # `export: paste` because Substack exposes no body-publish API — the post is
7
+ # pasted into the editor (#266 decision).
8
+
9
+ platform: substack
10
+ format: longform
11
+ export: paste
12
+ settings:
13
+ __type: substack
14
+ fields:
15
+ required:
16
+ - title
17
+ - dek
18
+ optional:
19
+ - preview
20
+ - tags
21
+ - canonical
22
+ - list
23
+ limits:
24
+ words:
25
+ min: 600
26
+ max: 3000
27
+ links:
28
+ policy: identity-ssot-only
29
+ structure:
30
+ - title
31
+ - dek
32
+ - sections
33
+ sources:
34
+ - url: https://support.substack.com/
35
+ date: 2026-10-29
36
+ note: "Substack publishes from its editor and has no public post-publish API; v1 treats it as a paste surface (#266)."
@@ -0,0 +1,91 @@
1
+ # Substack long-form post — skill (MVP)
2
+
3
+ > Long-form post skill. The contract is `longform.yaml`: required/optional
4
+ > fields, the editorial word band and `export` live there and are **not**
5
+ > restated here — this file is the judgment layer the agent reads before
6
+ > writing. The contract's `sources[]` carries the dated URL for every platform
7
+ > claim; re-verify before making a hard one, and cite a source (date + tier)
8
+ > before calling anything a limit.
9
+
10
+ ## What a Substack piece is for
11
+
12
+ A newsletter issue for subscribers that also becomes a public archive page. Two
13
+ readers, one text: the subscriber who opens it in an inbox and the visitor who
14
+ lands on the archive from a link. Substack is a reading surface with a
15
+ subscription relation attached — the issue is a letter that ends with a reason to
16
+ keep receiving it, not a post that closes with a hashtag.
17
+
18
+ ## Shape
19
+
20
+ 1. **Title** — the line that earns the open in an inbox and the click in search.
21
+ State the value of reading, not just the topic.
22
+ 2. **Dek** — the line under the title in the archive and the setup in the email:
23
+ one sentence of stake. The contract requires it; how the publication renders
24
+ it is editor-controlled [?] — verify on the first publish.
25
+ 3. **Sections** — the letter opens with the point, develops it once, and closes
26
+ with what comes next. H2 per move; the closing line is written, never left to
27
+ "thanks for reading".
28
+
29
+ ## Rules
30
+
31
+ - **Front matter is the field contract.** `longform.yaml` declares the fields this
32
+ channel consumes, required and optional alike; the engine exports only those,
33
+ and an undeclared field is dropped (`pasco post check` warns `unknown-field`) —
34
+ never smuggle one into the body instead.
35
+ - **Length:** the band in `limits.words` is the operator's editorial default (the
36
+ contract says so), not a Substack limit. It is the budget for an issue read in
37
+ one sitting.
38
+ - **Links:** `links.policy` is `identity-ssot-only`. `canonical` is only set when
39
+ the SSOT already carries the target URL; the archive page is a public URL of
40
+ its own and, once live, is what other channels can point at — but that pointer
41
+ enters the SSOT first, never the prose.
42
+ - **Facts:** `{{fact:<pointer>}}` for every number, date and URL.
43
+ - **Voice:** first person, letter-like, no hype. Match `brandVoice`; a newsletter
44
+ is where the operator's actual voice is the differentiator.
45
+
46
+ ## Export: `paste`
47
+
48
+ The contract declares `export: paste` because Substack publishes from its editor
49
+ and exposes no body-publish API [OFICIAL — the contract's source, `sources[]` in
50
+ `longform.yaml`, dated 2026-10-29]. Consequences for the writing and the deploy:
51
+
52
+ - The body is **pasted into the editor**; keep the markdown paste-safe
53
+ (headings, paragraphs, lists, bold/italic, block quotes, links). Re-check
54
+ formatting after pasting — nothing should arrive flattened.
55
+ - Fields are pasted one by one; the generated `paste-checklist.md` is the record
56
+ of what was pasted and in what order.
57
+ - The export is a checklist, not a publish: sending stays a manual action.
58
+
59
+ ## GEO (how this channel is read and cited)
60
+
61
+ The archive page is public and indexable; the email copy is not. The citation
62
+ unit is a section heading plus the claim under it, resolving to the archive URL —
63
+ which is why the piece must read as well on the web as in the inbox. The crawler
64
+ stance is the repo's global `ai.policy` (`platforms/registry.yaml`, v1,
65
+ 2026-10-27); no per-channel rule is invented here. Consistency check: the issue's
66
+ facts are byte-identical to the profile and to the other channels' projections.
67
+
68
+ ## i18n
69
+
70
+ A publication is per language: one publication, one subscriber base, one
71
+ language. Write `en` by default; a Spanish publication is a separate surface with
72
+ its own archive. Do not send the same text in two languages to one list.
73
+
74
+ ## Anti-patterns
75
+
76
+ - An issue that opens with "welcome back" instead of the point.
77
+ - A dek that repeats the title with different words.
78
+ - A "read the full piece on…" issue that is only a teaser.
79
+ - Rich formatting that the editor flattens on paste.
80
+ - A closing that asks for shares but not for a reply or a next step.
81
+
82
+ ## Output contract
83
+
84
+ Save each variant as YAML front matter + a markdown body at
85
+ `posts/<seed>/substack/substack-<n>.md`, then record the choice and check it:
86
+
87
+ ```
88
+ pasco post choose <seed> --channel substack --variant substack-1
89
+ pasco post check <seed> # missing-field · word-count · url-not-in-ssot · unknown-field
90
+ pasco post export <seed> # paste: writes posts/<seed>/substack/paste-checklist.md
91
+ ```
@@ -0,0 +1,84 @@
1
+ ---
2
+ name: wordpress-post
3
+ description: >
4
+ Long-form post skill for WordPress. Says what a WordPress post is for and how
5
+ the agent writes it; the machine contract is
6
+ skills/wordpress/longform.yaml (required/optional fields, editorial word band,
7
+ export: api). The dek→excerpt mapping is NOT declared by the contract: it is an
8
+ open question [?], not a promise. The judgment layer — shape,
9
+ voice, GEO, i18n — lives in post.md. The agent writes N variants, the operator
10
+ picks one, and the choice is recorded with `pasco post choose`.
11
+ model: juicio (frontier) — what the page answers and how it is structured is
12
+ judgment; the mechanical parts (markdown, front matter) are not
13
+ applies_to: wordpress
14
+ projection: agent-agnostic (any harness that reads Markdown and can run the CLI)
15
+ version: 0.1.0
16
+ status: draft (#361)
17
+ sources: skills/wordpress/longform.yaml
18
+ ---
19
+
20
+ # WordPress long-form — skill
21
+
22
+ ## Mission
23
+
24
+ Turn one canonical seed into a durable page on a site the operator owns: the
25
+ question answered first, the reasoning under it, the caveats at the end, and a
26
+ heading structure that a reader or an extractor can navigate. The skill is the
27
+ judgment layer (D5); the contract plus `pasco post check` are the deterministic
28
+ part.
29
+
30
+ ## When it runs
31
+
32
+ `pasco post task <seed> --format longform --channel wordpress` emits a task bundle
33
+ that points at the contract (`longform.yaml`) and the judgment file (`post.md`);
34
+ it does **not** carry this `SKILL.md`. This file is the channel's strategy
35
+ reference for whoever opens it directly: read the contract for the fields and the
36
+ band, then `post.md` for how to write.
37
+
38
+ Do **not** run `pasco refine wordpress` for this channel. Refine is the profile
39
+ command: with a `SKILL.md` now present it resolves this file instead of answering
40
+ "no skill for wordpress yet", and emits a **profile** task ("write N variants of
41
+ the wordpress profile") that saves to `profiles/wordpress/variants/<id>.md` — not
42
+ a post workflow. Telling a profile skill from a post skill is an engine decision,
43
+ not something this skill can fix.
44
+
45
+ ## Flow
46
+
47
+ 1. Read `longform.yaml` + `post.md`; the contract owns every limit.
48
+ 2. Write N variants as YAML front matter + markdown body, at
49
+ `posts/<seed>/wordpress/wordpress-<n>.md`.
50
+ 3. The operator picks one; record it with `pasco post choose`.
51
+ 4. `pasco post check` — fix the findings.
52
+ 5. `pasco post export <seed> --target postiz` for the API draft payload.
53
+ 6. Publish manually and verify the live page (title, dek, tags, canonical).
54
+
55
+ ## Contract pointers (single source each)
56
+
57
+ | Question | Home |
58
+ |---|---|
59
+ | required / optional fields | `longform.yaml` `fields` |
60
+ | editorial word band | `longform.yaml` `limits.words` (not a platform cap) |
61
+ | api vs paste | `longform.yaml` `export` |
62
+ | link policy | `longform.yaml` `links.policy` (`identity-ssot-only`) |
63
+ | dated platform sources | `longform.yaml` `sources[]` |
64
+ | how to write the page | `post.md` |
65
+
66
+ ## GEO and i18n
67
+
68
+ A page on the operator's own domain: public, indexed, stable — the most citable
69
+ surface of the set. The headings are the outline, the claim sits directly under
70
+ the heading that names it, and the dek is a field whose rendering the contract
71
+ leaves unmapped [?]. Write `en` by default; a
72
+ Spanish post belongs to the Spanish site with its own URL. The crawler stance is
73
+ the repo's global `ai.policy` (`platforms/registry.yaml`, v1, 2026-10-27).
74
+
75
+ ## Verification
76
+
77
+ - `pasco post check` exit 0 (no `missing-field`, `word-count` or
78
+ `url-not-in-ssot` errors).
79
+ - Heading hierarchy is structural (H2 → H3), not visual.
80
+ - The dek→excerpt mapping is unverified [?]: confirm on the first publish whether
81
+ the theme's excerpt is the dek or a separate field.
82
+ - Facts byte-identical to the SSOT; every URL from the identity SSOT.
83
+ - Report any contract gap as a finding — the engine is not extended from a
84
+ skill (#361).
@@ -0,0 +1,33 @@
1
+ # wordpress long-form rules — machine-readable (#266)
2
+ #
3
+ # Read by `pasco post task|check|export` when the seed is `format: longform`.
4
+ # The word range is an EDITORIAL default (the operator's house style), NOT a
5
+ # platform limit: cite a platform source before encoding a hard cap here.
6
+
7
+ platform: wordpress
8
+ format: longform
9
+ export: api
10
+ settings:
11
+ __type: wordpress
12
+ fields:
13
+ required:
14
+ - title
15
+ - dek
16
+ optional:
17
+ - tags
18
+ - canonical
19
+ - publication
20
+ limits:
21
+ words:
22
+ min: 600
23
+ max: 3000
24
+ links:
25
+ policy: identity-ssot-only
26
+ structure:
27
+ - title
28
+ - dek
29
+ - sections
30
+ sources:
31
+ - url: https://developer.wordpress.org/rest-api/reference/posts/
32
+ date: 2026-10-29
33
+ note: "The WordPress REST posts endpoint takes title, content, tags and an excerpt."
@@ -0,0 +1,99 @@
1
+ # WordPress long-form post — skill (MVP)
2
+
3
+ > Long-form post skill. The contract is `longform.yaml`: required/optional
4
+ > fields, the editorial word band and `export` live there and are **not**
5
+ > restated here — this file is the judgment layer the agent reads before
6
+ > writing. The contract's `sources[]` carries the dated URL for every platform
7
+ > claim; re-verify before making a hard one, and cite a source (date + tier)
8
+ > before calling anything a limit.
9
+
10
+ ## What a WordPress piece is for
11
+
12
+ The piece on a site the operator controls: a durable page with its own URL,
13
+ its own theme and its own permanence. WordPress supports the longest shelf life
14
+ of any channel here — a well-structured post keeps being found and cited after
15
+ the feeds move on — so write for the reader who arrives six months later with a
16
+ question, not only for the reader who subscribes today.
17
+
18
+ ## Shape
19
+
20
+ 1. **Title** — the question the post answers or the outcome it delivers, in the
21
+ words a reader would search.
22
+ 2. **Dek** — the summary. The contract requires it as a field and declares no
23
+ mapping to the REST `excerpt`: the endpoint takes an excerpt per the
24
+ contract's source, but whether the engine's `dek` arrives there is unverified
25
+ [?]. Write it as a standalone summary and confirm the mapping on the first
26
+ publish.
27
+ 3. **Sections** — one H2 per step or idea, H3 only for sub-steps; the heading
28
+ hierarchy is the page's table of contents and its extractable structure. Open
29
+ with the answer, then the reasoning, then the caveats.
30
+
31
+ ## Rules
32
+
33
+ - **Front matter is the field contract.** `longform.yaml` declares the fields this
34
+ channel consumes, required and optional alike; the engine exports only those,
35
+ and an undeclared field is dropped (`pasco post check` warns `unknown-field`) —
36
+ never smuggle one into the body instead.
37
+ - **Length:** the band in `limits.words` is the operator's editorial default (the
38
+ contract says so), not a WordPress limit. It is the budget for a page that
39
+ answers one question completely.
40
+ - **Links:** `links.policy` is `identity-ssot-only`. On a site the operator owns,
41
+ internal links are still SSOT links — do not type a URL the SSOT does not
42
+ carry, and set `canonical` only when the piece is syndicated and the SSOT
43
+ carries the original.
44
+ - **Facts:** `{{fact:<pointer>}}` for every number, date and URL.
45
+ - **Voice:** first person, explanatory, no hype. Match `brandVoice`; a durable
46
+ page is written to be re-read, so avoid references that only make sense "this
47
+ week" unless the date is stated.
48
+
49
+ ## Export: `api`
50
+
51
+ The contract declares `export: api`: the WordPress REST posts endpoint takes the
52
+ title, content, tags and an excerpt [OFICIAL — the contract's source, `sources[]`
53
+ in `longform.yaml`, dated 2026-10-29]. Consequences for the writing:
54
+
55
+ - The body is content the endpoint receives; keep it valid markdown/HTML with a
56
+ clean heading hierarchy — no layout that depends on the editor's blocks.
57
+ - The contract declares no dek→excerpt mapping. The endpoint takes an excerpt per
58
+ its source, and whether the engine's `dek` lands there is unverified [?] —
59
+ confirm on the first publish. Either way the dek is a field, never the body's
60
+ first paragraph.
61
+ - The export produces a Postiz draft payload; publishing stays manual (no
62
+ scheduler, no OAuth — `docs/post-mvp.md`).
63
+
64
+ ## GEO (how this channel is read and cited)
65
+
66
+ A page on the operator's own domain is the most citable surface here: public,
67
+ indexed, stable URL. Structure is what gets extracted — a question-shaped title, a
68
+ summary in the dek field (mapped to the excerpt only if the site does that, [?]),
69
+ and claims stated directly under the heading that
70
+ names them. The crawler stance is the repo's global `ai.policy`
71
+ (`platforms/registry.yaml`, v1, 2026-10-27); no per-channel rule is invented
72
+ here. Consistency check: every fact matches the profile byte for byte, and the
73
+ page's dates stay truthful as it ages.
74
+
75
+ ## i18n
76
+
77
+ A WordPress site is per language (one site, one locale, or a multilingual setup
78
+ the operator already runs). Write `en` by default; a Spanish post belongs to the
79
+ Spanish site, and each translation is its own post with its own URL — never a
80
+ duplicate of the English one under the same canonical.
81
+
82
+ ## Anti-patterns
83
+
84
+ - A page that buries the answer under three paragraphs of context.
85
+ - Heading levels used for visual size instead of structure.
86
+ - A dek repeated verbatim as the first paragraph.
87
+ - Time-bound language with no date ("recently", "this week").
88
+ - Syndicated content with no canonical back to the original.
89
+
90
+ ## Output contract
91
+
92
+ Save each variant as YAML front matter + a markdown body at
93
+ `posts/<seed>/wordpress/wordpress-<n>.md`, then record the choice and check it:
94
+
95
+ ```
96
+ pasco post choose <seed> --channel wordpress --variant wordpress-1
97
+ pasco post check <seed> # missing-field · word-count · url-not-in-ssot · unknown-field
98
+ pasco post export <seed> --target postiz
99
+ ```