@profile-as-code/cli 0.3.5 → 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/package.json CHANGED
@@ -1,9 +1,23 @@
1
1
  {
2
2
  "name": "@profile-as-code/cli",
3
- "version": "0.3.5",
4
- "description": "pasco — the profile-as-code CLI: init, validate, build, diff, status + partial refine (task template), check (offline rules M6–M8; catalog ships M3/M4), skills:export (copies the platform skills this package ships). Consumes @profile-as-code/core; never re-implements load/drift/geo/compile.",
3
+ "version": "0.3.7",
4
+ "description": "pasco \u2014 the profile-as-code CLI: init, validate, build, diff, edit (read / preview / write one contract field, comment-preserving), status + partial refine (task template), check (offline rules M6\u2013M8; catalog ships M3/M4), audit (offline findings projection), entity (the schema.org projection + paste-ready JSON-LD for surfaces the engine does not render), cite-check (deterministic citation matching over saved answer-engine answers, no network), export ai-policy (robots.txt + llms.txt + AI-POLICY.md: retrieval allowed, AI training blocked by default), export profile (the normalized, versioned profile object for programmatic consumers), preview (one self-contained HTML page of the rendered surfaces, written locally and never published), skills:export (copies the platform skills this package ships). Consumes @profile-as-code/core; never re-implements load/drift/geo/compile.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
+ "funding": [
8
+ {
9
+ "type": "github",
10
+ "url": "https://github.com/sponsors/ChristianLuciani"
11
+ },
12
+ {
13
+ "type": "kofi",
14
+ "url": "https://ko-fi.com/christianluciani"
15
+ },
16
+ {
17
+ "type": "patreon",
18
+ "url": "https://www.patreon.com/cluciani"
19
+ }
20
+ ],
7
21
  "bin": {
8
22
  "pasco": "dist/index.js"
9
23
  },
@@ -43,6 +57,7 @@
43
57
  "node": ">=22"
44
58
  },
45
59
  "dependencies": {
60
+ "marked": "^18.1.1",
46
61
  "yaml": "^2.9.1",
47
62
  "zod": "^4.6.7"
48
63
  },
package/skills/README.md CHANGED
@@ -53,10 +53,44 @@ skills/<plataforma>/
53
53
  marca (≠ voz personal), handle-matrix checks (`handle-claimed`/`handle-consistent`,
54
54
  offline en `pasco check`), trademark/impersonación. Ver `skills/github-org/`.
55
55
 
56
- ## Post skills (MVP, #264)
56
+ ## Post skills (MVP, #264/#266/#361)
57
57
 
58
58
  The week-1 post experiment uses a **different shape** from the profile skills:
59
- `skills/<platform>/post.md` (judgment) + `skills/<platform>/posting.yaml`
60
- (machine-readable limits/settings). `pasco post` reads them; `pasco refine` keeps
61
- reading `SKILL.md`. They cover two channels only (`linkedin`, `x`) and are not
62
- projected to harnesses yet — see `docs/post-mvp.md`.
59
+ `skills/<platform>/post.md` (judgment) + a machine-readable rule file.
60
+ `pasco post` reads them; `pasco refine` keeps reading `SKILL.md`.
61
+
62
+ - **Short** (`posting.yaml`): `linkedin`, `x` — char limits, hashtags.
63
+ - **Long-form** (`longform.yaml`, #266): `substack`, `ghost`, `medium`, `devto`,
64
+ `hashnode`, `wordpress`, `listmonk` — required fields (`title`/`dek`), an
65
+ editorial word band, and `export: api | paste` (Substack/Ghost are paste
66
+ surfaces). The word band is house style, not a platform limit: cite a source
67
+ before encoding a hard cap.
68
+
69
+ Every long-form channel also carries its judgment layer (#361): the `post.md` the
70
+ short channels already have, plus a `SKILL.md` that they do not ship.
71
+
72
+ - `post.md` — what a piece on that channel is for, its shape, its voice, and its
73
+ GEO and i18n policy, plus what `export: api | paste` means for the writing.
74
+ - `SKILL.md` — the channel skill document: mission, when the skill runs, the
75
+ flow, the pointers to the contract, and the suggested model in its frontmatter.
76
+ It is a strategy reference, not a bundle input: `pasco post task` carries the
77
+ contract and `post.md`, never this file.
78
+
79
+ **Do not use `pasco refine <channel>` on a post channel.** Refine is the
80
+ profile-refinement command; with a `SKILL.md` present it now resolves the file (it
81
+ used to answer "no skill yet") and emits a **profile** task that saves to
82
+ `profiles/<channel>/variants/<id>.md` — not a post workflow. The path for these
83
+ channels is `pasco post` with `longform.yaml`. Teaching the engine to tell a
84
+ profile skill from a post skill is a separate engine matter, not something a
85
+ skill can fix. (`checks.yaml` is not the cause: `pasco check` reads that catalog
86
+ and reports "no catalog" without failing when it is missing.)
87
+
88
+ `longform.yaml` stays the contract: required/optional fields, the word band and
89
+ `export` are declared there, and the prose explains them instead of restating
90
+ their numbers. A channel's *dated* platform sources live in the contract's
91
+ `sources[]`; a claim the contract does not already carry needs its own source
92
+ (URL + date + tier) before it enters either file. The post shape is Markdown
93
+ beside the rule file — not the `research/` + `playbook.md` + `checks.yaml` trio,
94
+ which belongs to the profile skills above.
95
+
96
+ Neither shape is projected to harnesses yet — see `docs/post-mvp.md`.
@@ -0,0 +1,82 @@
1
+ ---
2
+ name: devto-post
3
+ description: >
4
+ Long-form post skill for DEV (Forem). Says what a DEV article is for and how
5
+ the agent writes one; the machine contract is skills/devto/longform.yaml
6
+ (required/optional fields, editorial word band, export: api). The judgment
7
+ layer — shape, voice, GEO, i18n — lives in post.md. The agent writes N
8
+ variants, the operator picks one, and the choice is recorded with
9
+ `pasco post choose`.
10
+ model: juicio (frontier) — choosing the technical claim and its trade-off is
11
+ judgment; the mechanical parts (markdown, front matter) are not
12
+ applies_to: devto
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/devto/longform.yaml
17
+ ---
18
+
19
+ # DEV (Forem) long-form — skill
20
+
21
+ ## Mission
22
+
23
+ Turn one canonical seed into a DEV article that a developer can act on: one
24
+ claim, the smallest example that proves it, and the caveat that keeps it honest.
25
+ The skill is the judgment layer (D5); the deterministic part is the contract plus
26
+ `pasco post check`.
27
+
28
+ ## When it runs
29
+
30
+ `pasco post task <seed> --format longform --channel devto` 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 devto` 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 devto yet", and emits a **profile** task ("write N variants of the
39
+ devto profile") that saves to `profiles/devto/variants/<id>.md` — not a post
40
+ workflow. Telling a profile skill from a post skill is an engine decision, not
41
+ something this skill can fix.
42
+
43
+ ## Flow
44
+
45
+ 1. Read `longform.yaml` + `post.md`. Do not re-derive the limits: the contract
46
+ owns them.
47
+ 2. Write N variants as YAML front matter + markdown body, at
48
+ `posts/<seed>/devto/devto-<n>.md`.
49
+ 3. The operator picks one; record it with `pasco post choose`.
50
+ 4. `pasco post check` — fix what it reports instead of explaining it away.
51
+ 5. `pasco post export <seed> --target postiz` for the API draft payload.
52
+ 6. Publish manually, then verify on the live page that the front matter landed.
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
+ ## GEO and i18n
66
+
67
+ Public, indexed article page: the citation unit is the code block plus the
68
+ sentence that states what it proves. Write `en` by default; a Spanish piece is a
69
+ separate seed for that audience, never a duplicate of the English one. The
70
+ crawler stance is the repo's global `ai.policy` (`platforms/registry.yaml`, v1,
71
+ 2026-10-27) — no per-channel rule is invented here.
72
+
73
+ ## Verification
74
+
75
+ - `pasco post check` exit 0 (no `missing-field`, `word-count` or
76
+ `url-not-in-ssot` errors).
77
+ - Facts byte-identical to the SSOT; every URL from the identity SSOT.
78
+ - On the live page: title, tags and canonical URL arrived; the dek shows up as
79
+ the subtitle the channel renders, or not at all — never as a repeat of the
80
+ body's first line.
81
+ - Report any contract gap as a finding — the engine is not extended from a
82
+ skill (#361).
@@ -0,0 +1,33 @@
1
+ # devto 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: devto
8
+ format: longform
9
+ export: api
10
+ settings:
11
+ __type: devto
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://developers.forem.com/api
32
+ date: 2026-10-29
33
+ note: "The Forem/DEV articles API takes title, tags and canonical_url; the body is markdown."
@@ -0,0 +1,96 @@
1
+ # DEV (Forem) 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 DEV piece is for
11
+
12
+ A technical piece for developers who will judge it by whether the code and the
13
+ claim hold up. The reader arrives from a tag feed, from search, or from a
14
+ cross-post on another channel — so the piece must stand alone: one technique,
15
+ one result, or one decision with its trade-off. It is not a career update and
16
+ not a newsletter issue.
17
+
18
+ ## Shape
19
+
20
+ 1. **Title** — the outcome the reader gets, in concrete verbs. No numbers you
21
+ cannot source, no curiosity gap the body does not pay off.
22
+ 2. **Dek** — one line of scope: what the piece assumes and what it delivers.
23
+ The contract requires it as a field, and the engine's task bundle is explicit
24
+ that title and dek are fields, never the first lines of the body — so the dek
25
+ is delivered in the front matter, not repeated as the body's opening line. No
26
+ platform mapping is declared; confirm on the first publish whether DEV renders
27
+ it as a subtitle [?].
28
+ 3. **Sections** — one H2 per step or idea. Show the smallest example that
29
+ proves the point, then the caveat. The contract fixes the three-part
30
+ structure; how many sections that takes is editorial.
31
+
32
+ ## Rules
33
+
34
+ - **Front matter is the field contract.** Supply the fields `longform.yaml`
35
+ declares. A field the channel does not declare is not exported (`pasco post
36
+ check` warns `unknown-field`) — never smuggle it into the body instead.
37
+ - **Length:** the band in `limits.words` is the operator's editorial default,
38
+ not a DEV limit (the contract says so in its own header). Treat it as the
39
+ budget for a piece a developer finishes in one sitting, not as a ceiling to
40
+ fill.
41
+ - **Links:** `links.policy` is `identity-ssot-only` — every URL comes from the
42
+ identity SSOT. DEV syndicates, so `canonical` must point at the canonical
43
+ home from the SSOT, never at a second syndication copy.
44
+ - **Facts:** every number, date, version and URL is cited with
45
+ `{{fact:<pointer>}}`; the engine renders it and fails on a dangling pointer.
46
+ - **Voice:** first person, technical, no hype. Match `brandVoice`; the piece
47
+ should sound like an engineer explaining a decision, not a growth account.
48
+
49
+ ## Export: `api`
50
+
51
+ The contract declares `export: api`: the body is markdown that the Forem/DEV
52
+ articles API receives, with the title, tags and canonical URL carried as payload
53
+ fields [OFICIAL — the contract's source, `sources[]` in `longform.yaml`, dated
54
+ 2026-10-29]. Consequences for the writing:
55
+
56
+ - Markdown only. No raw HTML, no embeds that only an editor understands.
57
+ - `canonical` is a payload field, not a body line — do not paste the URL into
58
+ the text as a substitute.
59
+ - The export produces a Postiz draft payload; publishing stays manual (no
60
+ scheduler, no OAuth — `docs/post-mvp.md`).
61
+
62
+ ## GEO (how this channel is read and cited)
63
+
64
+ DEV article pages are public and indexed, and the quotable unit is the snippet
65
+ plus the sentence that says what it proves. Keep the version and the date inside
66
+ the code block or the sentence around it; a table of unsourced benchmarks is not
67
+ citable. The crawler stance is the repo's global `ai.policy`
68
+ (`platforms/registry.yaml`, v1, 2026-10-27) — do not invent a per-channel rule.
69
+ Consistency check: every version, date and number matches the SSOT byte for
70
+ byte, exactly as on the profile surfaces.
71
+
72
+ ## i18n
73
+
74
+ DEV's audience and tag vocabulary are English-first: write `en` by default. A
75
+ Spanish piece is its own seed for the hispano dev community, not a translation
76
+ of the English one. Do not publish the same text twice in two languages: two
77
+ canonical URLs of one text split the citation.
78
+
79
+ ## Anti-patterns
80
+
81
+ - "X is dead" / "N tools you must know" listicles with no artifact behind them.
82
+ - An intro that restates the title for three paragraphs.
83
+ - A code block that cannot run, or a version pin that is never stated.
84
+ - Cross-posting without `canonical` — two copies then compete for one query.
85
+ - A benchmark number with no method, date or source.
86
+
87
+ ## Output contract
88
+
89
+ Save each variant as YAML front matter + a markdown body at
90
+ `posts/<seed>/devto/devto-<n>.md`, then record the choice and check it:
91
+
92
+ ```
93
+ pasco post choose <seed> --channel devto --variant devto-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,85 @@
1
+ ---
2
+ name: ghost-post
3
+ description: >
4
+ Long-form post skill for Ghost. Says what a Ghost essay/newsletter issue is for
5
+ and how the agent writes it; the machine contract is
6
+ skills/ghost/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.
11
+ model: juicio (frontier) — the argument and the opening are judgment; the
12
+ mechanical parts (markdown, front matter, paste checklist) are not
13
+ applies_to: ghost
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/ghost/longform.yaml
18
+ ---
19
+
20
+ # Ghost long-form — skill
21
+
22
+ ## Mission
23
+
24
+ Turn one canonical seed into an essay worth a subscription: one argument, the
25
+ evidence that carries it, and an opening that earns the first scroll in an
26
+ inbox. Ghost is the surface the operator owns end to end (publication, domain,
27
+ archive), so the piece is written to be the citable copy — while every URL it
28
+ claims still comes from the identity SSOT.
29
+
30
+ ## When it runs
31
+
32
+ `pasco post task <seed> --format longform --channel ghost` 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 ghost` 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 ghost yet", and emits a **profile** task ("write N variants of the
41
+ ghost profile") that saves to `profiles/ghost/variants/<id>.md` — not a post
42
+ workflow. Telling a profile skill from a post skill is an engine decision, not
43
+ 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>/ghost/ghost-<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 publication editor, tick the checklist,
54
+ and verify the live 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` (decision #266) |
63
+ | link policy | `longform.yaml` `links.policy` (`identity-ssot-only`) |
64
+ | dated platform sources | `longform.yaml` `sources[]` |
65
+ | how to write the piece | `post.md` |
66
+
67
+ ## GEO and i18n
68
+
69
+ The publication's domain is the citable address; its archive is public, the email
70
+ copy is not. State the claim next to a heading so the section is extractable, and
71
+ keep every fact byte-identical to the profile. One publication, one language:
72
+ write `en` by default and treat a Spanish publication as a separate surface with
73
+ its own domain. The crawler stance is the repo's global `ai.policy`
74
+ (`platforms/registry.yaml`, v1, 2026-10-27).
75
+
76
+ ## Verification
77
+
78
+ - `pasco post check` exit 0 (no `missing-field`, `word-count` or
79
+ `url-not-in-ssot` errors).
80
+ - The pasted body kept its heading levels, code fences and links; nothing was
81
+ flattened by the editor.
82
+ - Front matter fields arrived on the live page (title + dek where the theme
83
+ renders it); canonical, if set, matches the SSOT.
84
+ - Report any contract gap as a finding — the engine is not extended from a
85
+ skill (#361).
@@ -0,0 +1,36 @@
1
+ # ghost 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` in v1 even though Ghost has an Admin API: the issue defers the
7
+ # API channel and keeps Ghost a paste surface like Substack (#266 decision).
8
+
9
+ platform: ghost
10
+ format: longform
11
+ export: paste
12
+ settings:
13
+ __type: ghost
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://ghost.org/docs/admin-api/
35
+ date: 2026-10-29
36
+ note: "Ghost ships an Admin API; v1 keeps Ghost a paste surface (paste checklist) per #266, not an API channel."
@@ -0,0 +1,98 @@
1
+ # Ghost 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 Ghost piece is for
11
+
12
+ An essay or newsletter issue that lives on a publication the operator controls,
13
+ with its own domain and archive. That makes it the surface where the canonical
14
+ URL of a piece can exist: other channels point their `canonical` here *when the
15
+ SSOT already carries that URL* — never a URL the agent typed. The reader is
16
+ either a subscriber reading an email or someone who arrived from search; write
17
+ for both without writing twice.
18
+
19
+ ## Shape
20
+
21
+ 1. **Title** — the one claim the piece makes, stated so it still reads well in
22
+ an inbox list and in a search result.
23
+ 2. **Dek** — the line that earns the next scroll: context, stake, or the
24
+ question the piece answers. The contract requires it; how the theme renders
25
+ it (subtitle, excerpt, nothing) varies per publication theme [?] — verify on
26
+ the first publish and record what you saw.
27
+ 3. **Sections** — an essay argues, so give each section one move: the situation,
28
+ the tension, the evidence, the conclusion. H2 headings are the anchors a
29
+ reader (and an extractor) navigates by.
30
+
31
+ ## Rules
32
+
33
+ - **Front matter is the field contract.** The fields `longform.yaml` declares are
34
+ the ones the channel consumes; anything else is dropped (`unknown-field`
35
+ warning) — do not re-add it in the body.
36
+ - **Length:** the band in `limits.words` is the operator's editorial default
37
+ (the contract says so). Aim for the piece the argument needs; the band is a
38
+ guardrail, not a quota.
39
+ - **Links:** `links.policy` is `identity-ssot-only`. A Ghost publication is a
40
+ URL surface like any other: every URL in the piece comes from the identity
41
+ SSOT, and `canonical` is only set when the SSOT already carries the target.
42
+ - **Facts:** `{{fact:<pointer>}}` for every number, date and URL; a dangling
43
+ pointer fails the check.
44
+ - **Voice:** first person, essayistic, no hype. Match `brandVoice`; a newsletter
45
+ issue is the one surface where a longer voice can breathe — do not turn that
46
+ into padding.
47
+
48
+ ## Export: `paste`
49
+
50
+ The contract declares `export: paste` even though Ghost ships an Admin API: v1
51
+ keeps Ghost a paste surface like Substack, by decision (#266), and the contract's
52
+ `sources[]` records the Admin API docs [OFICIAL, dated 2026-10-29]. Consequences
53
+ for the writing and the deploy:
54
+
55
+ - The body is **pasted into the publication editor**; formatting must survive a
56
+ paste. Keep markdown simple: headings, paragraphs, lists, code fences, block
57
+ quotes. No raw HTML, no footnotes the editor may flatten, no tables unless you
58
+ re-check them after pasting.
59
+ - `pasco post export` writes a paste checklist, not a publish: the deploy is
60
+ manual and the checklist is the record of what was pasted.
61
+ - Fields are pasted one by one — treat the checklist as the field contract's
62
+ human-readable form and tick each line.
63
+
64
+ ## GEO (how this channel is read and cited)
65
+
66
+ The publication's own domain is the citable address: its archive pages are
67
+ public and indexable, while the email copy is not. That is why the canonical URL
68
+ matters here more than on a network — the citation unit is a section heading
69
+ plus the claim under it, and it resolves to a URL the operator owns. The crawler
70
+ stance is the repo's global `ai.policy` (`platforms/registry.yaml`, v1,
71
+ 2026-10-27); no per-channel rule is invented here. Consistency check: the piece's
72
+ facts are byte-identical to the profile and to every other channel's projection.
73
+
74
+ ## i18n
75
+
76
+ A Ghost publication is per language: one publication, one audience language.
77
+ Write `en` by default; a Spanish publication is a separate publication with its
78
+ own domain and archive, and the piece belongs to exactly one of them. Do not
79
+ publish a machine translation of the same text under the same canonical.
80
+
81
+ ## Anti-patterns
82
+
83
+ - An essay whose first two paragraphs are a table of contents.
84
+ - A `canonical` invented for the occasion — it must come from the SSOT.
85
+ - Rich HTML that the editor flattens on paste, discovered after publishing.
86
+ - Email-only framing in a piece that is also the public archive page.
87
+ - A newsletter issue that is a link dump with no argument.
88
+
89
+ ## Output contract
90
+
91
+ Save each variant as YAML front matter + a markdown body at
92
+ `posts/<seed>/ghost/ghost-<n>.md`, then record the choice and check it:
93
+
94
+ ```
95
+ pasco post choose <seed> --channel ghost --variant ghost-1
96
+ pasco post check <seed> # missing-field · word-count · url-not-in-ssot · unknown-field
97
+ pasco post export <seed> # paste: writes posts/<seed>/ghost/paste-checklist.md
98
+ ```
@@ -77,8 +77,7 @@ se cita, se ignora.
77
77
  2. `pasco check` — handle-claimed / handle-consistent OK (offline) + `url-live` de
78
78
  cada handle claimed (network, verificación manual).
79
79
  3. `pasco build github-org --lang en` → `profiles/github-org/README.md`.
80
- 4. Copiar el README al repo del org (deploySpec: git; el humano mergea —
81
- repo-governance).
80
+ 4. Copiar el README al repo del org (deploySpec: git; el humano mergea).
82
81
  5. Verificación post-deploy: cada badge/link del README resuelve; los handles
83
82
  pending NO figuran como surfaces vivas.
84
83
 
@@ -19,7 +19,7 @@
19
19
  platform: github-org
20
20
  rule: fact-present
21
21
  value: true
22
- note: "name/mission/tagline del README byte-idénticos al SSOT (D4)."
22
+ note: "El README 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). `link-in-identity` es una premisa aparte."
23
23
 
24
24
  - id: org-links-identity
25
25
  platform: github-org
@@ -4,8 +4,7 @@
4
4
 
5
5
  - Workspace brand (`pasco init --flavor brand`) con `content/` lleno y
6
6
  `pasco validate` en verde.
7
- - Org de GitHub creado (privado hasta pasar el checklist de public-readiness —
8
- repo-governance).
7
+ - Org de GitHub creado (privado hasta pasar el checklist de public-readiness).
9
8
 
10
9
  ## Pasos
11
10
 
@@ -17,7 +16,7 @@
17
16
  3. **Build:** `pasco build github-org --lang en` →
18
17
  `profiles/github-org/README.md`.
19
18
  4. **Deploy:** copiá el README al repo `org/org` del GitHub org y commiteá en tu
20
- branch + PR — el humano mergea (repo-governance §3).
19
+ branch + PR — el humano mergea.
21
20
  5. **Verificación post-deploy:** cada badge y link del README resuelve; los
22
21
  handles `pending` NO figuran como surfaces vivas; el JSON-LD `Organization`
23
22
  está embebido (GEO §17.1).
@@ -11,7 +11,7 @@
11
11
  platform: github-readme
12
12
  rule: fact-present
13
13
  value: true
14
- note: "Los hechos del README deben estar byte-idénticos al SSOT (D4)."
14
+ note: "El README que escribió `pasco build` se compara contra el render actual del SSOT en los dos sentidos: URL, fecha o número que la superficie agrega u omite, y texto que ningún token explica (WARN, D12; `pasco audit` lo gradúa). `link-in-identity` es una premisa aparte."
15
15
 
16
16
  - id: gh-links-identity
17
17
  platform: github-readme
@@ -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.