@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.
- package/README.md +536 -45
- package/dist/index.js +11097 -5110
- package/package.json +3 -2
- package/skills/README.md +40 -6
- 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 +17 -2
- package/skills/github-org/checks.yaml +5 -2
- package/skills/github-org/playbook.md +2 -3
- package/skills/github-readme/SKILL.md +1 -1
- package/skills/github-readme/checks.yaml +5 -2
- package/skills/github-readme/playbook.md +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 +5 -2
- package/skills/kofi/checks.yaml +6 -3
- 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,82 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: github-repo-meta
|
|
3
|
+
description: >
|
|
4
|
+
Superficie de presencia `github-repo-meta` (D23, #262): la metadata del
|
|
5
|
+
artefacto de código — descripción del repo, topics, social preview, release
|
|
6
|
+
notes y la página npm derivada. Convierte un release en presencia visible
|
|
7
|
+
sobre el repo, sin inventar hechos: la descripción y las notas salen del
|
|
8
|
+
release plan (`pasco release plan`) y del SSOT, nunca de la memoria del agente.
|
|
9
|
+
model: mecánico (la copy es corta y acotada; el juicio de posicionamiento vive en
|
|
10
|
+
la skill de la README del perfil)
|
|
11
|
+
applies_to: github-repo-meta
|
|
12
|
+
projection: agent-agnostic (AionUi, OpenHands, Claude Code, Cursor, pi…)
|
|
13
|
+
version: 0.1.0
|
|
14
|
+
status: draft (D23, #262)
|
|
15
|
+
sources: "platforms/registry.yaml (kind: repo-meta) · docs/SPEC.md §22"
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
# GitHub repo metadata — skill de superficie repo-meta (D23)
|
|
19
|
+
|
|
20
|
+
## Misión
|
|
21
|
+
|
|
22
|
+
Que un release exista como **presencia**: el repo se encuentra, se entiende y se
|
|
23
|
+
comparte. El motor no renderiza esta superficie (no hay renderer: es HTML/API de
|
|
24
|
+
GitHub); lo que el motor SÍ da es el **plan**: `pasco release plan <id>` nombra la
|
|
25
|
+
superficie, su estado derivado del ledger y la **espina factual** del release. Esta
|
|
26
|
+
skill es la capa de juicio que convierte esa espina en metadata publicable.
|
|
27
|
+
|
|
28
|
+
## Regla de oro
|
|
29
|
+
|
|
30
|
+
> La metadata del repo describe el **artefacto**, no a la persona. El perfil
|
|
31
|
+
> (github-readme) es la voz; `github-repo-meta` es la ficha: qué es esto, para
|
|
32
|
+
> quién, y qué cambió.
|
|
33
|
+
|
|
34
|
+
- La descripción NO repite el positioning personal: dice qué hace el repo.
|
|
35
|
+
- Los facts (versión, fecha, link) salen del release row y del SSOT; si un dato
|
|
36
|
+
no está, no se escribe.
|
|
37
|
+
- Un release = una pasada. No optimices metadata sin release: sin evento, la
|
|
38
|
+
superficie queda `never` en el plan (y eso es correcto, D23).
|
|
39
|
+
|
|
40
|
+
## Flujo
|
|
41
|
+
|
|
42
|
+
1. **Plan**: `pasco release plan <release-id> --json` → la fila
|
|
43
|
+
`github-repo-meta` (estado) + `baseText[kind=repo-meta]` (la espina factual).
|
|
44
|
+
Sin `releases[]` en el workspace, el comando falla con la razón: agregá el
|
|
45
|
+
release primero (schema v1.15).
|
|
46
|
+
2. **Composición** (juicio, esta skill):
|
|
47
|
+
- `description` ≤350 chars, la promesa del artefacto + una señal de para quién.
|
|
48
|
+
- `topics` ≤20, minúsculas, guiones; genéricos primero, específicos después.
|
|
49
|
+
- release notes: `baseText` como primer párrafo + `changedUnits` como bullets
|
|
50
|
+
verificables; nada de métricas sin fuente.
|
|
51
|
+
- social preview 1280×640 (el preview se sube a mano: no hay API).
|
|
52
|
+
3. **Deploy** (API de GitHub, D9):
|
|
53
|
+
```sh
|
|
54
|
+
gh repo edit --description "<description>" --add-topic <topic> …
|
|
55
|
+
gh release edit <tag> --notes-file <notas.md>
|
|
56
|
+
```
|
|
57
|
+
4. **Verificación** (playbook.md, sección "Verificación post-deploy"): la
|
|
58
|
+
lectura de vuelta con `gh repo view --json description,repositoryTopics` y la
|
|
59
|
+
inspección visual del social preview.
|
|
60
|
+
5. **Cierre del loop**: en v1 el ledger solo direcciona las superficies del enum
|
|
61
|
+
de perfiles; `github-repo-meta` no tiene record y el plan la lee `never`. Es
|
|
62
|
+
un límite declarado (D23): el plan nombra lo que falta tocar; extender la
|
|
63
|
+
clave de superficie del ledger es un cambio de contrato aparte.
|
|
64
|
+
|
|
65
|
+
## Límites y trampas
|
|
66
|
+
|
|
67
|
+
- **Descripción duplicada**: si el repo y la página npm dicen lo mismo, es señal
|
|
68
|
+
de que el release no cambió nada relevante; no reescribas por inercia.
|
|
69
|
+
- **Topics como hashtags**: no son keywords de marketing; son el índice por el
|
|
70
|
+
que un desconocido encuentra el artefacto.
|
|
71
|
+
- **Notas = changelog completo**: el release note es la parte que le importa a
|
|
72
|
+
quien evalúa adoptar; el detalle vive en CHANGELOG.
|
|
73
|
+
- **Social preview desactualizado**: es la única pieza sin API; verificala a
|
|
74
|
+
mano o no cuenta como publicada.
|
|
75
|
+
|
|
76
|
+
## Checks
|
|
77
|
+
|
|
78
|
+
`checks.yaml` declara los criterios con la misma forma que el resto de las skills
|
|
79
|
+
(`rule` del catálogo, `platform: github-repo-meta`). En v1 `pasco check` no
|
|
80
|
+
ejecuta una superficie fuera del enum de perfiles: el catálogo queda declarado
|
|
81
|
+
para cuando la superficie tenga renderer, y hoy la verificación es la del
|
|
82
|
+
playbook.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# checks.yaml — criterios de la superficie `github-repo-meta` (D23, #262)
|
|
2
|
+
# Misma forma que el catálogo de `pasco check` (rule + value + note). v1: el
|
|
3
|
+
# engine no renderiza esta superficie y `pasco check` no la ejecuta (no está en
|
|
4
|
+
# el enum de perfiles); el catálogo queda declarado y la verificación es la del
|
|
5
|
+
# playbook. Los caps citados son límites oficiales de GitHub.
|
|
6
|
+
|
|
7
|
+
- id: repo-description-max
|
|
8
|
+
platform: github-repo-meta
|
|
9
|
+
rule: max-chars
|
|
10
|
+
value: 350
|
|
11
|
+
surface: bio
|
|
12
|
+
note: "La descripción del repositorio se trunca a 350 caracteres en la UI. [OFICIAL] docs.github.com (customizing-your-repository), leído 2026-10-29."
|
|
13
|
+
|
|
14
|
+
- id: repo-description-min
|
|
15
|
+
platform: github-repo-meta
|
|
16
|
+
rule: min-chars
|
|
17
|
+
value: 40
|
|
18
|
+
surface: bio
|
|
19
|
+
note: "Una descripción de menos de 40 chars no dice qué hace el artefacto; el piso es juicio de esta skill, no un límite de GitHub."
|
|
20
|
+
|
|
21
|
+
- id: repo-topic-max-chars
|
|
22
|
+
platform: github-repo-meta
|
|
23
|
+
rule: max-chars
|
|
24
|
+
value: 50
|
|
25
|
+
surface: headline
|
|
26
|
+
note: "Cada topic tiene un máximo de 50 caracteres (regla de largo, no de conteo). GitHub permite además hasta 20 topics por repositorio: ese tope es de CANTIDAD y el catálogo no tiene una regla de conteo — lo verifica el playbook (paso 5), no esta fila. [OFICIAL] docs.github.com (customizing-your-repository), leído 2026-10-29."
|
|
27
|
+
|
|
28
|
+
- id: repo-facts-ssot
|
|
29
|
+
platform: github-repo-meta
|
|
30
|
+
rule: fact-present
|
|
31
|
+
value: true
|
|
32
|
+
note: "La versión, la fecha y el link de la metadata salen del release row (`pasco release plan`) y del SSOT; un dato que la superficie agrega y el SSOT no tiene es un FAIL de revisión (no lo ejecuta pasco check en v1)."
|
|
33
|
+
|
|
34
|
+
- id: repo-links-identity
|
|
35
|
+
platform: github-repo-meta
|
|
36
|
+
rule: link-in-identity
|
|
37
|
+
value: true
|
|
38
|
+
note: "Toda URL escrita en la descripción o las notas vive en `identity.links` (regla drift §15.4)."
|
|
39
|
+
|
|
40
|
+
- id: repo-geo-consistent
|
|
41
|
+
platform: github-repo-meta
|
|
42
|
+
rule: geo-consistent
|
|
43
|
+
value: true
|
|
44
|
+
note: "Los hechos de la metadata coinciden con el resto de superficies (README, npm): misma versión, mismo nombre, mismo link canónico."
|
|
45
|
+
|
|
46
|
+
- id: repo-lang-en
|
|
47
|
+
platform: github-repo-meta
|
|
48
|
+
rule: lang-present
|
|
49
|
+
value: en
|
|
50
|
+
note: "La metadata del artefacto va en EN (D8/§11) aunque las notas del release puedan traducirse; el repo es la superficie internacional."
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# GitHub repo metadata — deploy playbook
|
|
2
|
+
|
|
3
|
+
Deploy = **API de GitHub** (D9): la metadata se escribe con `gh` (REST/GraphQL),
|
|
4
|
+
no se pega a mano en la UI. El social preview es la única pieza sin API — se
|
|
5
|
+
sube a mano y se verifica visualmente.
|
|
6
|
+
|
|
7
|
+
Superficie: `github-repo-meta` (`kind: repo-meta`, D23/#262). Estado esperado en
|
|
8
|
+
el plan: `never` mientras el ledger no pueda direccionar la superficie (límite
|
|
9
|
+
declarado v1).
|
|
10
|
+
|
|
11
|
+
## Flujo
|
|
12
|
+
|
|
13
|
+
1. **Plan**: `pasco release plan <release-id> --json` → `surfaces[]` trae la fila
|
|
14
|
+
`github-repo-meta` y `baseText` la espina factual del release.
|
|
15
|
+
2. **Componer** (ver SKILL.md): `description` ≤350 chars · `topics` ≤20 ·
|
|
16
|
+
release notes desde `changedUnits`.
|
|
17
|
+
3. **Deploy**:
|
|
18
|
+
```sh
|
|
19
|
+
gh repo edit --description "<description>"
|
|
20
|
+
gh repo edit --add-topic <topic> # repetir por topic nuevo
|
|
21
|
+
gh release edit <tag> --notes-file <notas.md>
|
|
22
|
+
```
|
|
23
|
+
4. **Social preview**: subir 1280×640 (Settings → Social preview). Sin API.
|
|
24
|
+
5. **Verificar** (ver abajo) y reportar el resultado en el PR/issue del release.
|
|
25
|
+
|
|
26
|
+
## Verificación post-deploy
|
|
27
|
+
|
|
28
|
+
- [ ] `gh repo view --json description --jq .description` devuelve la descripción
|
|
29
|
+
publicada, y no pasa de 350 chars.
|
|
30
|
+
- [ ] `gh repo view --json repositoryTopics --jq '.repositoryTopics[].name'`
|
|
31
|
+
devuelve exactamente los topics declarados (≤20), sin duplicados.
|
|
32
|
+
- [ ] `gh release view <tag> --json body --jq .body` devuelve las notas y el
|
|
33
|
+
primer párrafo cita la versión + fecha del release row.
|
|
34
|
+
- [ ] El social preview se ve en la página del repo (1280×640, sin texto
|
|
35
|
+
recortado) — inspección visual, es la excepción sin API.
|
|
36
|
+
- [ ] La descripción describe el artefacto, no repite el positioning personal de
|
|
37
|
+
`github-readme`.
|
|
38
|
+
- [ ] Ningún número de las notas carece de fuente (regla de evidencia).
|
|
39
|
+
|
|
40
|
+
## Rollback
|
|
41
|
+
|
|
42
|
+
`gh repo edit` es aditivo y versionable en git no: guardá la descripción y los
|
|
43
|
+
topics anteriores antes de escribir (`gh repo view --json description,repositoryTopics`).
|
|
44
|
+
Revertir = repetir el paso 3 con los valores previos; las release notes se
|
|
45
|
+
revierte con `gh release edit <tag> --notes-file <previas.md>`.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: hashnode-post
|
|
3
|
+
description: >
|
|
4
|
+
Long-form post skill for Hashnode. Says what a Hashnode article is for and how
|
|
5
|
+
the agent writes it; the machine contract is
|
|
6
|
+
skills/hashnode/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`.
|
|
10
|
+
model: juicio (frontier) — the technical claim and its example are judgment; the
|
|
11
|
+
mechanical parts (markdown, front matter) are not
|
|
12
|
+
applies_to: hashnode
|
|
13
|
+
projection: agent-agnostic (any harness that reads Markdown and can run the CLI)
|
|
14
|
+
version: 0.1.0
|
|
15
|
+
status: draft (#361)
|
|
16
|
+
sources: skills/hashnode/longform.yaml
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# Hashnode long-form — skill
|
|
20
|
+
|
|
21
|
+
## Mission
|
|
22
|
+
|
|
23
|
+
Turn one canonical seed into a Hashnode article a developer can build from: the
|
|
24
|
+
problem, a runnable example, the reasoning, the caveats. The skill is the
|
|
25
|
+
judgment layer (D5); the contract plus `pasco post check` are the deterministic
|
|
26
|
+
part.
|
|
27
|
+
|
|
28
|
+
## When it runs
|
|
29
|
+
|
|
30
|
+
`pasco post task <seed> --format longform --channel hashnode` emits a task bundle
|
|
31
|
+
that points at the contract (`longform.yaml`) and the judgment file (`post.md`);
|
|
32
|
+
it does **not** carry this `SKILL.md`. This file is the channel's strategy
|
|
33
|
+
reference for whoever opens it directly: read the contract for the fields and the
|
|
34
|
+
band, then `post.md` for how to write.
|
|
35
|
+
|
|
36
|
+
Do **not** run `pasco refine hashnode` for this channel. Refine is the profile
|
|
37
|
+
command: with a `SKILL.md` now present it resolves this file instead of answering
|
|
38
|
+
"no skill for hashnode yet", and emits a **profile** task ("write N variants of
|
|
39
|
+
the hashnode profile") that saves to `profiles/hashnode/variants/<id>.md` — not a
|
|
40
|
+
post workflow. Telling a profile skill from a post skill is an engine decision,
|
|
41
|
+
not something this skill can fix.
|
|
42
|
+
|
|
43
|
+
## Flow
|
|
44
|
+
|
|
45
|
+
1. Read `longform.yaml` + `post.md`; do not re-derive the limits.
|
|
46
|
+
2. Write N variants as YAML front matter + markdown body, at
|
|
47
|
+
`posts/<seed>/hashnode/hashnode-<n>.md`.
|
|
48
|
+
3. The operator picks one; record it with `pasco post choose`.
|
|
49
|
+
4. `pasco post check` — fix the findings.
|
|
50
|
+
5. `pasco post export <seed> --target postiz` for the API draft payload.
|
|
51
|
+
6. Publish manually, then verify the live page (title, tags, canonical).
|
|
52
|
+
|
|
53
|
+
## Contract pointers (single source each)
|
|
54
|
+
|
|
55
|
+
| Question | Home |
|
|
56
|
+
|---|---|
|
|
57
|
+
| required / optional fields | `longform.yaml` `fields` |
|
|
58
|
+
| editorial word band | `longform.yaml` `limits.words` (not a platform cap) |
|
|
59
|
+
| api vs paste | `longform.yaml` `export` |
|
|
60
|
+
| link policy | `longform.yaml` `links.policy` (`identity-ssot-only`) |
|
|
61
|
+
| dated platform sources | `longform.yaml` `sources[]` |
|
|
62
|
+
| how to write the piece | `post.md` |
|
|
63
|
+
|
|
64
|
+
## GEO and i18n
|
|
65
|
+
|
|
66
|
+
Public, indexed article page; tags are the arrival paths; the citation unit is
|
|
67
|
+
the snippet plus the claim it proves. Write `en` by default, and treat a Spanish
|
|
68
|
+
article as a separate seed for that audience — never a duplicate of the English
|
|
69
|
+
one under a shared canonical. The crawler stance is the repo's global `ai.policy`
|
|
70
|
+
(`platforms/registry.yaml`, v1, 2026-10-27).
|
|
71
|
+
|
|
72
|
+
## Verification
|
|
73
|
+
|
|
74
|
+
- `pasco post check` exit 0 (no `missing-field`, `word-count` or
|
|
75
|
+
`url-not-in-ssot` errors).
|
|
76
|
+
- Every code block runs as printed; every version is stated.
|
|
77
|
+
- Facts byte-identical to the SSOT; every URL from the identity SSOT.
|
|
78
|
+
- Report any contract gap as a finding — the engine is not extended from a
|
|
79
|
+
skill (#361).
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# hashnode 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: hashnode
|
|
8
|
+
format: longform
|
|
9
|
+
export: api
|
|
10
|
+
settings:
|
|
11
|
+
__type: hashnode
|
|
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://apidocs.hashnode.com/
|
|
32
|
+
date: 2026-10-29
|
|
33
|
+
note: "Hashnode's GraphQL API publishes posts with a title, tags and markdown content."
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Hashnode 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 Hashnode piece is for
|
|
11
|
+
|
|
12
|
+
A technical article published on a developer blogging network — the network feed
|
|
13
|
+
plus, optionally, a publication. It is the "write it once, let it be found by
|
|
14
|
+
developers" surface: an idea, a working example and the reasoning between them.
|
|
15
|
+
It is not a copy of the newsletter issue with a different header.
|
|
16
|
+
|
|
17
|
+
## Shape
|
|
18
|
+
|
|
19
|
+
1. **Title** — what the reader can build or decide after reading. No curiosity
|
|
20
|
+
gap that the body does not close.
|
|
21
|
+
2. **Dek** — one line of scope: the problem, the approach, the result. The
|
|
22
|
+
contract requires it as a field and the engine delivers it as front matter,
|
|
23
|
+
never as the body's first lines. No platform mapping is declared; confirm on
|
|
24
|
+
the first publish how Hashnode renders it [?].
|
|
25
|
+
3. **Sections** — an opening that states the problem, then one H2 per step of
|
|
26
|
+
the solution, then the caveats. Code is the evidence: show it, state the
|
|
27
|
+
version, and keep it runnable.
|
|
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 in its header), not a platform cap. It is a guardrail for a
|
|
37
|
+
piece read in one sitting.
|
|
38
|
+
- **Links:** `links.policy` is `identity-ssot-only`. `canonical` points at the
|
|
39
|
+
canonical home from the SSOT when the piece is syndicated; a network URL is
|
|
40
|
+
never the canonical for another channel's copy unless the SSOT says so.
|
|
41
|
+
- **Facts:** `{{fact:<pointer>}}` for every number, date, version and URL.
|
|
42
|
+
- **Voice:** first person technical, second person for the reader ("you"); no
|
|
43
|
+
hype, no "revolutionary".
|
|
44
|
+
|
|
45
|
+
## Export: `api`
|
|
46
|
+
|
|
47
|
+
The contract declares `export: api`: the body is markdown published through
|
|
48
|
+
Hashnode's GraphQL API, with the title, tags and markdown content in the mutation
|
|
49
|
+
[OFICIAL — the contract's source, `sources[]` in `longform.yaml`, dated
|
|
50
|
+
2026-10-29]. Consequences for the writing:
|
|
51
|
+
|
|
52
|
+
- Markdown only; no raw HTML or editor-only embeds.
|
|
53
|
+
- `publication` is a payload field (which blog the post belongs to) — it is not
|
|
54
|
+
a body line and it is not the author profile.
|
|
55
|
+
- The export produces a Postiz draft payload; publishing stays manual (no
|
|
56
|
+
scheduler, no OAuth — `docs/post-mvp.md`).
|
|
57
|
+
|
|
58
|
+
## GEO (how this channel is read and cited)
|
|
59
|
+
|
|
60
|
+
Hashnode article pages are public and indexed, and the tags are the paths readers
|
|
61
|
+
arrive from. The quotable unit is the code block plus the sentence that says what
|
|
62
|
+
it proves — so keep the claim adjacent to the snippet. The crawler stance is the
|
|
63
|
+
repo's global `ai.policy` (`platforms/registry.yaml`, v1, 2026-10-27); no
|
|
64
|
+
per-channel rule is invented here. Consistency check: version numbers, dates and
|
|
65
|
+
claims match the SSOT byte for byte, same as the profile.
|
|
66
|
+
|
|
67
|
+
## i18n
|
|
68
|
+
|
|
69
|
+
The network's audience and tag vocabulary are English-first: write `en` by
|
|
70
|
+
default. A Spanish article is its own seed for that audience, not a translation
|
|
71
|
+
of the English one, and it must not share a canonical with it.
|
|
72
|
+
|
|
73
|
+
## Anti-patterns
|
|
74
|
+
|
|
75
|
+
- A tutorial whose code cannot run as printed.
|
|
76
|
+
- A "getting started" post that is only a link to someone else's docs.
|
|
77
|
+
- Tag stuffing to reach feeds the piece does not belong to.
|
|
78
|
+
- Cross-posting without `canonical`.
|
|
79
|
+
- Numbers with no method, date or source.
|
|
80
|
+
|
|
81
|
+
## Output contract
|
|
82
|
+
|
|
83
|
+
Save each variant as YAML front matter + a markdown body at
|
|
84
|
+
`posts/<seed>/hashnode/hashnode-<n>.md`, then record the choice and check it:
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
pasco post choose <seed> --channel hashnode --variant hashnode-1
|
|
88
|
+
pasco post check <seed> # missing-field · word-count · url-not-in-ssot · unknown-field
|
|
89
|
+
pasco post export <seed> --target postiz
|
|
90
|
+
```
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# checks.yaml — criterios machine-readable para `pasco check hotmart`
|
|
2
|
-
#
|
|
2
|
+
# Misma rule catalog que el schema; la unión con content/checks.yaml decide qué
|
|
3
|
+
# premisas corren (fact-present) y los caps de caracteres salen del catálogo del
|
|
4
|
+
# workspace cuando declara checks — entero, sin merge por fila — o de los defaults
|
|
5
|
+
# del paquete; nunca de estas filas (SPEC §7, pasco check).
|
|
3
6
|
#
|
|
4
7
|
# Fuente de los valores: research/SOTA-2026.md (todas las fuentes fechadas
|
|
5
8
|
# 2026-10-05). Hotmart NO documenta límite para el cuerpo de perfil [?] — no
|
|
@@ -16,7 +19,7 @@
|
|
|
16
19
|
platform: hotmart
|
|
17
20
|
rule: fact-present
|
|
18
21
|
value: true
|
|
19
|
-
note: "
|
|
22
|
+
note: "El cuerpo que escribió `pasco build` se compara contra el render actual del SSOT en los dos sentidos — URL, fecha o número agregado u omitido, y texto sin token que lo explique (WARN, D12; `pasco audit` lo gradúa); el cuerpo es el cierre de confianza del storefront (SOTA-2026 §1). `link-in-identity` es una premisa aparte."
|
|
20
23
|
|
|
21
24
|
- id: h-links-identity
|
|
22
25
|
platform: hotmart
|
package/skills/kofi/checks.yaml
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# checks.yaml — criterios machine-readable para `pasco check kofi`
|
|
2
|
-
#
|
|
2
|
+
# Misma rule catalog que el schema; la unión con content/checks.yaml decide qué
|
|
3
|
+
# premisas corren (fact-present) y los caps de caracteres salen del catálogo del
|
|
4
|
+
# workspace cuando declara checks — entero, sin merge por fila — o de los defaults
|
|
5
|
+
# del paquete; nunca de estas filas (SPEC §7, pasco check).
|
|
3
6
|
#
|
|
4
7
|
# Fuente de los valores: research/SOTA-2026.md (todas las fuentes fechadas
|
|
5
8
|
# 2026-10-05). Ko-fi NO documenta límite para el About [?] — el max-chars es
|
|
@@ -15,7 +18,7 @@
|
|
|
15
18
|
platform: kofi
|
|
16
19
|
rule: fact-present
|
|
17
20
|
value: true
|
|
18
|
-
note: "
|
|
21
|
+
note: "El About que escribió `pasco build` se compara contra el render actual del SSOT en los dos sentidos — URL, fecha o número agregado u omitido, y texto sin token que lo explique (WARN, D12; `pasco audit` lo gradúa); el About es el cuerpo del perfil (SOTA-2026 §5). `link-in-identity` es una premisa aparte."
|
|
19
22
|
|
|
20
23
|
- id: k-links-identity
|
|
21
24
|
platform: kofi
|
|
@@ -39,4 +42,4 @@
|
|
|
39
42
|
platform: kofi
|
|
40
43
|
rule: lang-present
|
|
41
44
|
value: en
|
|
42
|
-
note: "EN requerido (D8/§11); compile no hace fallback por fila de bios — artefacto vacío = gap visible."
|
|
45
|
+
note: "EN requerido (D8/§11); compile no hace fallback por fila de bios — artefacto vacío = gap visible."
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: listmonk-post
|
|
3
|
+
description: >
|
|
4
|
+
Long-form post skill for listmonk — a self-hosted email campaign to named
|
|
5
|
+
lists, not a feed. Says what the issue is for and how the agent writes it; the
|
|
6
|
+
machine contract is skills/listmonk/longform.yaml (required/optional fields,
|
|
7
|
+
the tightest editorial word band of the long-form channels, export: api). The
|
|
8
|
+
judgment layer — subject line, shape, GEO, i18n — lives in post.md. The agent
|
|
9
|
+
writes N variants, the operator picks one and the list, and the choice is
|
|
10
|
+
recorded with `pasco post choose`.
|
|
11
|
+
model: juicio (frontier) — the subject line and the single action are judgment;
|
|
12
|
+
the mechanical parts (markdown, front matter) are not
|
|
13
|
+
applies_to: listmonk
|
|
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/listmonk/longform.yaml
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
# listmonk long-form — skill
|
|
21
|
+
|
|
22
|
+
## Mission
|
|
23
|
+
|
|
24
|
+
Turn one canonical seed into one email issue that a subscriber reads to the end
|
|
25
|
+
and acts on once. The reader opted in, so the piece owes them a letter: a
|
|
26
|
+
subject that states the value, a body that delivers it, one action at the close.
|
|
27
|
+
The skill is the judgment layer (D5); the contract plus `pasco post check` are
|
|
28
|
+
the deterministic part.
|
|
29
|
+
|
|
30
|
+
## When it runs
|
|
31
|
+
|
|
32
|
+
`pasco post task <seed> --format longform --channel listmonk` 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 listmonk` 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 listmonk yet", and emits a **profile** task ("write N variants of
|
|
41
|
+
the listmonk profile") that saves to `profiles/listmonk/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 band is narrower here, and the contract
|
|
48
|
+
— not this file — is where its numbers live.
|
|
49
|
+
2. Write N variants as YAML front matter + markdown body, at
|
|
50
|
+
`posts/<seed>/listmonk/listmonk-<n>.md`.
|
|
51
|
+
3. The operator picks one and the target `list`; record the choice with
|
|
52
|
+
`pasco post choose`.
|
|
53
|
+
4. `pasco post check` — fix the findings.
|
|
54
|
+
5. `pasco post export <seed> --target postiz` for the campaign draft payload.
|
|
55
|
+
6. Send manually; the operator owns consent, unsubscribes and deliverability.
|
|
56
|
+
|
|
57
|
+
## Contract pointers (single source each)
|
|
58
|
+
|
|
59
|
+
| Question | Home |
|
|
60
|
+
|---|---|
|
|
61
|
+
| required / optional fields | `longform.yaml` `fields` (`list` selects the audience) |
|
|
62
|
+
| editorial word band | `longform.yaml` `limits.words` (not a platform cap) |
|
|
63
|
+
| api vs paste | `longform.yaml` `export` |
|
|
64
|
+
| link policy | `longform.yaml` `links.policy` (`identity-ssot-only`) |
|
|
65
|
+
| dated platform sources | `longform.yaml` `sources[]` |
|
|
66
|
+
| how to write the issue | `post.md` |
|
|
67
|
+
|
|
68
|
+
## GEO and i18n
|
|
69
|
+
|
|
70
|
+
Email is not crawlable: citation depends on the same text existing on a public
|
|
71
|
+
surface, with facts byte-identical to this issue. One list per language, one
|
|
72
|
+
language per issue; `en` by default. The crawler stance is the repo's global
|
|
73
|
+
`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
|
+
- Subject (title), dek and preview are three different sentences doing three
|
|
80
|
+
different jobs.
|
|
81
|
+
- Exactly one call to action; 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,33 @@
|
|
|
1
|
+
# listmonk 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: listmonk
|
|
8
|
+
format: longform
|
|
9
|
+
export: api
|
|
10
|
+
settings:
|
|
11
|
+
__type: listmonk
|
|
12
|
+
fields:
|
|
13
|
+
required:
|
|
14
|
+
- title
|
|
15
|
+
- dek
|
|
16
|
+
optional:
|
|
17
|
+
- preview
|
|
18
|
+
- publication
|
|
19
|
+
- list
|
|
20
|
+
limits:
|
|
21
|
+
words:
|
|
22
|
+
min: 400
|
|
23
|
+
max: 1500
|
|
24
|
+
links:
|
|
25
|
+
policy: identity-ssot-only
|
|
26
|
+
structure:
|
|
27
|
+
- title
|
|
28
|
+
- dek
|
|
29
|
+
- sections
|
|
30
|
+
sources:
|
|
31
|
+
- url: https://listmonk.app/docs/apis/apis/
|
|
32
|
+
date: 2026-10-29
|
|
33
|
+
note: "listmonk's API creates a campaign with a body and one or more list ids; the audience is the list, not a channel."
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# listmonk 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 listmonk piece is for
|
|
11
|
+
|
|
12
|
+
One email campaign to one or more lists the reader already subscribed to. The
|
|
13
|
+
audience is the list, not a feed: there is no discovery, no search surface and no
|
|
14
|
+
second chance — the subject line decides whether the issue is read at all. The
|
|
15
|
+
piece is a letter to a named group, not a post that hopes to be found.
|
|
16
|
+
|
|
17
|
+
## Shape
|
|
18
|
+
|
|
19
|
+
1. **Title** — the subject line. It is what the reader sees first and the only
|
|
20
|
+
line that competes with an inbox; state the value of opening, not the topic.
|
|
21
|
+
2. **Dek** — the line that sets up the issue: why this, why now. Where the
|
|
22
|
+
channel renders a preview line, the optional `preview` field carries it — the
|
|
23
|
+
contract declares both, and they must not say the same thing.
|
|
24
|
+
3. **Sections** — an email is read top to bottom in one sitting: lead with the
|
|
25
|
+
point, then the evidence, then one action. Short paragraphs, no nested
|
|
26
|
+
headings, no "read more" link as the only payload.
|
|
27
|
+
|
|
28
|
+
## Rules
|
|
29
|
+
|
|
30
|
+
- **Front matter is the field contract.** `longform.yaml` declares the fields this
|
|
31
|
+
channel consumes, required and optional alike; the engine exports only those,
|
|
32
|
+
and an undeclared field is dropped (`pasco post check` warns `unknown-field`).
|
|
33
|
+
The field that selects the audience is a targeting decision, not prose: choose
|
|
34
|
+
it deliberately, and never send one issue to a list it was not written for.
|
|
35
|
+
- **Length:** the band in `limits.words` is the operator's editorial default (the
|
|
36
|
+
contract says so) — and it is the tightest of the long-form channels, because
|
|
37
|
+
the channel is read in one sitting, not skimmed. Do not pad to reach it and do
|
|
38
|
+
not assume the ceiling is the goal.
|
|
39
|
+
- **Links:** `links.policy` is `identity-ssot-only`. An email has no canonical
|
|
40
|
+
field in the contract, so the SSOT is the only URL source and the archive copy
|
|
41
|
+
(if any) is the only place a URL can be cited from.
|
|
42
|
+
- **Facts:** `{{fact:<pointer>}}` for every number, date and URL.
|
|
43
|
+
- **Voice:** direct, personal, no marketing cadence. Match `brandVoice`; a
|
|
44
|
+
newsletter that sounds like a press release is an unsubscribe.
|
|
45
|
+
|
|
46
|
+
## Export: `api`
|
|
47
|
+
|
|
48
|
+
The contract declares `export: api`: listmonk's API creates a campaign with a
|
|
49
|
+
body and one or more list ids [OFICIAL — the contract's source, `sources[]` in
|
|
50
|
+
`longform.yaml`, dated 2026-10-29]. Consequences:
|
|
51
|
+
|
|
52
|
+
- The body is markdown/HTML the campaign carries; keep it paste-safe and
|
|
53
|
+
email-safe (no scripts, no layout that only a browser renders).
|
|
54
|
+
- The audience is a `list` id, not a channel: the send is a targeting decision
|
|
55
|
+
the operator makes, and this skill never picks it for them.
|
|
56
|
+
- The export produces a draft payload; sending stays manual.
|
|
57
|
+
- Consent, unsubscribe mechanics and deliverability are the operator's
|
|
58
|
+
responsibility and out of scope here.
|
|
59
|
+
|
|
60
|
+
## GEO (how this channel is read and cited)
|
|
61
|
+
|
|
62
|
+
Email is not a crawlable surface: an issue that exists only in an inbox is
|
|
63
|
+
invisible to search and to citation. Citability therefore depends on the same
|
|
64
|
+
text also existing where it can be linked and quoted — if it matters, mirror it
|
|
65
|
+
to a public surface whose URL the SSOT already carries, and keep the two copies
|
|
66
|
+
byte-identical in their facts. The crawler stance is the repo's global
|
|
67
|
+
`ai.policy` (`platforms/registry.yaml`, v1, 2026-10-27); no per-channel rule is
|
|
68
|
+
invented here.
|
|
69
|
+
|
|
70
|
+
## i18n
|
|
71
|
+
|
|
72
|
+
The list is the segmentation: one list per language, and one language per issue.
|
|
73
|
+
Write `en` by default; a Spanish issue goes to the Spanish list, not to every
|
|
74
|
+
list. Do not machine-translate an issue and send both to the same audience.
|
|
75
|
+
|
|
76
|
+
## Anti-patterns
|
|
77
|
+
|
|
78
|
+
- A subject line that is the article title ("On X") with no reason to open.
|
|
79
|
+
- A dek and a preview that repeat each other.
|
|
80
|
+
- An issue with five calls to action.
|
|
81
|
+
- Marketing cadence in a letter ("Don't miss out!").
|
|
82
|
+
- Sending a piece written for one audience to a list it does not fit.
|
|
83
|
+
|
|
84
|
+
## Output contract
|
|
85
|
+
|
|
86
|
+
Save each variant as YAML front matter + a markdown body at
|
|
87
|
+
`posts/<seed>/listmonk/listmonk-<n>.md`, then record the choice and check it:
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
pasco post choose <seed> --channel listmonk --variant listmonk-1
|
|
91
|
+
pasco post check <seed> # missing-field · word-count · url-not-in-ssot · unknown-field
|
|
92
|
+
pasco post export <seed> --target postiz
|
|
93
|
+
```
|