@profile-as-code/cli 0.3.6 → 0.3.7
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 +410 -14
- package/dist/index.js +8212 -3772
- package/package.json +3 -2
- package/skills/README.md +39 -5
- package/skills/devto/SKILL.md +82 -0
- package/skills/devto/longform.yaml +33 -0
- package/skills/devto/post.md +96 -0
- package/skills/ghost/SKILL.md +85 -0
- package/skills/ghost/longform.yaml +36 -0
- package/skills/ghost/post.md +98 -0
- package/skills/github-org/SKILL.md +1 -2
- package/skills/github-org/checks.yaml +1 -1
- package/skills/github-org/playbook.md +2 -3
- package/skills/github-readme/checks.yaml +1 -1
- package/skills/github-repo-meta/SKILL.md +82 -0
- package/skills/github-repo-meta/checks.yaml +50 -0
- package/skills/github-repo-meta/playbook.md +45 -0
- package/skills/hashnode/SKILL.md +79 -0
- package/skills/hashnode/longform.yaml +33 -0
- package/skills/hashnode/post.md +90 -0
- package/skills/hotmart/checks.yaml +1 -1
- package/skills/kofi/checks.yaml +1 -1
- package/skills/listmonk/SKILL.md +83 -0
- package/skills/listmonk/longform.yaml +33 -0
- package/skills/listmonk/post.md +93 -0
- package/skills/medium/SKILL.md +86 -0
- package/skills/medium/longform.yaml +33 -0
- package/skills/medium/post.md +96 -0
- package/skills/substack/SKILL.md +83 -0
- package/skills/substack/longform.yaml +36 -0
- package/skills/substack/post.md +91 -0
- package/skills/wordpress/SKILL.md +84 -0
- package/skills/wordpress/longform.yaml +33 -0
- package/skills/wordpress/post.md +99 -0
|
@@ -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
|
+
```
|