Skip to content

Releases

v0.5.0

Choose a tag to compare

@github-actions github-actions released this 07 Nov 20:39
76d87f8

Added

  • the narrative skills that did not exist — linkedin, x, and one shared
    two-axis statement every platform skill cites (#462, part B)
    — the two
    channels that shipped post.md/posting.yaml with no SKILL.md are exactly
    the two whose narrative a session overwrote “for consistency”. They now carry
    one — reader, angle, how it deliberately differs from github-readme, the whole
    channel flow, and contract pointers (every limit cited from posting.yaml,
    never re-typed). The two-axis rule (skills/FACTS-VS-NARRATIVE.md) has ONE
    home; every platform skill links it, and no skill's own golden rule may
    contradict it (order is narrative, not a fact), so the norm cannot drift
    between skills; skills/README.md records that SKILL.md is required. pasco refine's task template names the target platform's skill as
    the source of its reader/angle and states that a variant must not restate
    another platform's headline, bio or body.
  • «facts are unique · narrative is per surface» — the norm and its advisory
    check (#462, SPEC §21.8)
    — the rule lived only in one platform skill (as a
    golden rule) and as a product goal, so a workspace could unify one headline
    across its GitHub README, LinkedIn and X “for consistency” and pasco check
    stayed green. SPEC §21.8 now states it normatively: every FACT in
    content/*.yaml (including identity.definition) is one and identical across
    surfaces and a contradiction is a defect, not an adaptation, while the
    per-platform headlines/bios rows are the narrative text; narrative
    (reader, emphasis, order, subset via selection.views, composition via the
    variant body) is per surface and is never propagated. pasco check gains the advisory
    text/same-across-platforms — a warn naming both platforms and the text
    kind when two different platforms carry a byte-identical headline or a bio
    in the same length band; exempt: the same platform across langs and
    identity.definition. No schema change, no hard gate. The skills half
    (linkedin/x SKILL.md + the shared two-axis statement) lands in the
    chained PR.
  • per-view selection.views.<v>.orderKeys — the narrative order the featured
    sort was missing (#458, SPEC §21.2/§21.3)
    — github-readme's featured list is
    fully derived (status rank → skill weight → name), so a workspace that wanted
    "these two first" for one surface had only false claims (change status) or
    invisible tiebreak tuning (change skills[].keywords); the measured case kept
    the order as hand-edited text, outside the engine. A view now declares
    orderKeys by §19.2 key: the listed entries render first, in declared order,
    per block; the remaining entries keep the renderer's comparator. It is
    workspace policy, never an SSOT field — order is narrative and two surfaces may
    legitimately disagree. The pinning rule lives once in core (pinnedRanks),
    used by the slice and by the sorting adapters through orderedForSurface;
    pasco status --selection prints the resolved order. A non-string key is a
    config error in .pascorc and a warn at the pure core seam; a key the view does
    not emit warns (selection/order-key-unknown) instead of no-op'ing silently.
    No orderKeys ⇒ byte-identical artifact and report.
  • doctor --contract-delta — the upgrade names the fields, not only the
    versions (#439, SPEC §7.6)
    — doctor reported engine 0.4.0 · schema v1.16 · requires engine >= 0.4.0 (met) and never WHAT changed between the two: a real
    0.3.0 → 0.3.7 upgrade (contract v1.9 → v1.15) moved six adoptable fields, and
    finding them took a supervision session across the CHANGELOG, the schema
    description and the geo report. pasco init now writes a "contract" floor
    into .pascorc at birth — beside the #434 "engine" floor, from the same
    generated PROFILE_SCHEMA_VERSION, never a literal — and doctor --contract-delta diffs that floor against the installed contract: one entry per
    minor with the fields it added and the surface that reads them, in five states
    (delta · none · absent with the remedy · INVALID · ahead). --json
    adds data.contractDelta only when the flag is passed, rows of exactly
    { version, fields, buys }. The exit matrix is unchanged: the floor DECLARES
    and is never enforced (v1), so doctor reports without inventing a verdict the
    gate does not back. The rows are a projection of the schema's own narration,
    pinned in both directions by consumer-contract.test.ts — a schema bump that
    forgets a row, an invented minor, or a row that omits a field its minor narrates
    fails pnpm test; a notFields label is a review judgement, as SPEC §7.6
    states.

Fixed

  • orderKeys follow-ups from the #458 dogfood — the pure core seam now warns
    (selection/order-key-unknown) when orderKeys is a non-array value
    instead of ignoring it silently; .pascorc already rejected it as a config
    error, so only direct selectFacts/compile callers see the new warning. The
    active-variant interaction the dogfood measured is now reported by the writer
    and documented: a variant body is a complete artifact written verbatim, so
    orderKeys orders the render and does not rewrite it. When the active variant
    wins and the slice carries a resolved order, build emits a permanent,
    informational
    note naming the variant and the layer (the body is
    authoritative; the order applied to the render) — not a staleness verdict and
    not a silent no-op. SPEC §16.2 step 4 owns the mechanical sequence — remove
    that platform's active.yaml entry (the other platforms stay), build the
    ordered render as the base, let the executing agent compose the body per the
    platform skill (the engine models no composition, #476), and re-record with
    refine --choose <id> --lang <l> — cited from §21.3 rule 11 and §21.6, with an
    end-to-end test that runs it and asserts the other platform's choice survives.

Internal

  • The core seam goes 0.1.3 → 0.1.4 (#458, #462) — two additions to
    @profile-as-code/core's public surface: orderedForSurface (the one
    precedence home for selection.views.<v>.orderKeys) and
    sameTextAcrossPlatforms with its TextFinding/TextKind types (the
    §21.8 advisory detector). The CLI bundles core and is unaffected; the
    cohort step skips a version already live on the registry, so an unchanged
    0.1.3 would leave the published core describing the previous tree.

v0.4.0

Choose a tag to compare

@github-actions github-actions released this 04 Nov 19:10
ec1be50

Added

  • release plan --json carries order on every row (#421, SPEC §7.8) — the
    human plan prints a 1-based ORDER column, but the machine payload had no
    equivalent: a consumer had to count array indices to reproduce it, an implicit
    contract it could get wrong. Each surfaces[] row now carries order
    (1..n, the array position), and the human column reads the same field instead
    of recomputing index + 1. Additive: the array order is unchanged and no
    consumer breaks.
  • discover --observations — the human reads the surface the machine cannot
    (#412, SPEC §7.10)
    — discover reads only schema.org Person/Organization
    JSON-LD, which a prospect's real surfaces do not publish (measured at 4560ae1:
    LinkedIn http-999, ResearchGate http-403, ORCID/X/Instagram no Person —
    0 of 5 usable), so the audit's classes had no input for the person it sells an
    audit to. --observations <file> takes YAML rows read by hand — surface,
    field (name · link · experience), value, url, retrieved_at — and
    turns each into the SAME SurfaceObservation the transport produces, so
    provisionalProfile, provenance, absent[] and discoveryFindings run
    unchanged. Rows make NO request (the url is a provenance pointer) and enter
    first, so a declared name beats a machine-read one. --json is payload v2
    (via: fetch|manual; a manual surface's status is null — no request, and a
    fabricated 200 would be a claim). A name declared differently is a
    contradiction at identity.name with both sources cited: the profile holds
    one name, and dropping the second hid the disagreement the audit exists to find.
    A malformed row is refused with its number: an impossible date (2026-02-31,
    2020-13, 2026-02-00), a non-http URL, an unknown key, and a second
    experience row for the assembly's own (surface, org) key (experienceId, so
    ACME/Acme collide and École/Cole do not) fail with the row. That key
    holds ONE row by design, so a second role at one org is refused rather than
    dropped, and a surface the transport also reads resolves to the machine row's
    key: supply one or the other.
  • achievements[] — a career impact log with metrics, targets and capture-first
    evidence (#446, D24, schema v1.16)
    — the contract recorded what a person DID
    (experience[].highlights, free text: no value, unit, baseline, target or link)
    and what can be PROVEN (projects[].evidence[], project-scoped and requiring a
    public source, so an internal outcome had no home), but not what CHANGED — so
    the effect was not captured when it happened and could not be reconstructed
    later. One ROOT block, never per-row outcomes[]: each row states a statement,
    is a metric (value/target/baseline/unit/direction) or a milestone,
    points at the units it belongs to (refs, block:id, resolved by core) and
    declares how the result is known (basis: measured | estimated | reported
    | attested) plus a REQUIRED visibility, so an internal outcome never becomes
    public by omission. A target without a value is a planned goal, and the
    status (planned | measured | achieved | partial) is DERIVED by the one
    achievementStatus() function, never stored — a stored status could disagree
    with the numbers it describes. load fails on duplicate ids, a dangling
    refs/artifactId/credentialId, a kind ↔ metric mismatch and a
    public→private registry leak (§18.5), including through refs[].
    pasco achievements [--targets] [--json] is the read-only log view (grouped by
    the first ref, with the derived status, the formatted metric, the basis and the
    pointer; scope counts the rows the view returned); pasco audit reports a
    public measured/attested row with no pointer as unsourced-claim (WARN) and
    never as a finding for reported. Additive: a workspace without the block
    validates and renders byte-identically — pasco export profile emits
    achievements: [] for it, the same loaded-shape rule every list root follows,
    which is why the root carries no minItems: an explicitly empty array stays
    schema-valid, and "declare the block only when you have rows" is an authoring
    convention the engine does not enforce.
    SPEC §5 D24 + §6 + §7 + §23, schema/README.md, docs/SCHEMA-CONTRACT.md and
    schema/consumer-contract.json (v1.16, unpublished) document the change, and
    completeness counts achievements[].statement in language coverage.
  • pasco edit reaches the answer-engine surface — and --create seeds a
    missing root (#435)
    — two addresses join the contract-path grammar with the
    same rules as the existing ones (exactly one row, --check preview, cap
    pre-flight): identity.definition.<lang> and
    faq[id=<slug>,lang=<l>].q|.a / faq[id=<slug>].source. faq[] rows are
    selected by their stable id (a repeated id is ambiguous and refused, never
    index-addressed) and q/a are langMaps, so their lang selector is
    required; an optional value the row does not carry is a 0-row address. The
    preview is the field's real published surface: identity.definition shows the
    entity JSON-LD line the github-readme/github-org artifacts embed, and
    faq[] shows the FAQPage node pasco entity --jsonld emits (no
    pasco build artifact carries it). Both are pre-flighted against the
    contract's own caps (160 / 90 / 320 chars per value, v1.10 #288): unlike a
    platform's soft cap, over one is a schema failure, so the pre-flight warns and
    the write is refused by validation. The byte-range splice cannot invent a
    root, so --create <root> is a SEPARATE operation: it writes a schema-valid
    template for a root the workspace does not declare yet (faq: 3 placeholder
    pairs, the schema minimum — never invented facts), refuses an existing root
    under any extension case (idempotent, nothing written), takes the same write
    lock and validation gate over a temp copy, runs the same cap pre-flight, and
    --check previews the seed. --list gains identity.definition and faq
    scopes. --help, the CLI README, the user guide and SPEC §7 document the new
    addresses and the seeded root.

Fixed

  • check and preview name the catalog they actually read (dogfood
    2026-11-04)
    — on a real workspace with no content/checks.yaml
    (profile-data), check printed user checks (content/checks.yaml, schema-validated catalog) over the four rows of DEFAULT_CHECKS, and
    preview captioned the same rows declared limits (content/checks.yaml): two
    published surfaces attributing the caps to a file the engine never read — the
    provenance class #455 closed in the docs, still live in runtime output.
    load() merges every content/*.y{a,}ml by root property and reports no
    source, so a filename is a claim the engine cannot make (false for an empty
    checks.yaml, false for a workspace using checks.yml). The rule "the
    workspace's catalog, else the package's conservative caps (#240)" now has ONE
    home (check-catalog.ts) instead of four copied ternaries, and every surface
    derives its wording from catalogSource(): check says workspace checks (declared in content/, …) or workspace checks: none declared — the conservative caps shipped with pasco apply (#240); preview's table captions
    declared limits (content/) or declared limits (caps shipped with pasco, #240). Tests cover the four states — declared, absent, an empty file, and rows
    in checks.yml — and assert that no surface prints content/checks.yaml.
    Values, check --json and every exit code are unchanged; the only payload
    movement is preview --json's bytes (the caption's own length).
  • post --help names which channels ship short rules and which are long-form
    (#422)
    — pasco post task <id> --channel devto exited 2 naming the file it
    wanted, so a reader learned the split by provoking the error. The help and
    docs/USER-GUIDE.md §Posts now name the short-rule channels (linkedin, x)
    and the long-form set (devto, ghost, hashnode, listmonk, medium,
    substack, wordpress); no flag and no runtime discovery. A test reads both
    sets from skills/, so a channel added later fails CI instead of drifting.
  • The engine floor is declared at birth, and an old engine's failure names the
    upgrade (#434)
    — pasco init now writes "engine": "<installed CLI version>"
    into .pascorc (in the gate's MAJOR.MINOR.PATCH grammar), so a scaffolded
    workspace can no longer leave the gate off by default; a build whose version
    carries a pre-release suffix declares no floor at all (reducing it to the
    numeric triple would pin the scaffold to a version @latest may not publish).
    When schema validation fails on an Unrecognized key, pasco validate adds
    ONE hint line naming the likely cause (data newer than the engine) and the
    remedy (npm i -g @profile-as-code/cli@latest, then pasco doctor) — as
    error.hint in --json too — unless the engine gate already named the
    mismatch (installed older than the declared floor). The verdict is unchanged: exit 1, same violations. init's
    version comes from one shared read (packages/cli/src/version.ts), never a
    literal, and docs/USER-GUIDE.md §3/§8.2.1 now state that the floor is
    declared at birth, not by hand.
  • pasco doctor names the engine-requirement state instead of letting
    "absent" look met (#436)
    — the line now reads engine requirement absent — no .pascorc declares one for a workspace with no declaration, alongside met,
    NOT MET and INVALID, and --json carries the same value as
    requirement.state (absent | met | NOT MET | INVALID). The exit code is
    unchanged: an absent declaration remains a healthy workspace, but the premise it
    actually read is now stated, never implied by a green exit.
  • **The completeness label names the policy's langs, no...
Read more

v0.3.7

Choose a tag to compare

@github-actions github-actions released this 31 Oct 19:41
65c35dd

Added

  • pasco entity emits the FAQPage node — the answer surface's consumer
    (#360)
    — --jsonld now appends the separate FAQPage block after the entity
    block when the workspace declares faq[] (never an empty node, no invented
    @id; faq[].source travels as acceptedAnswer.citation), and --json
    exposes the same assembler node as faqJsonLd (null when undeclared). --bare
    stays the entity object alone when there is no FAQPage, and is a JSON array of
    the emitted nodes otherwise. The renderers still embed jsonLd only: the
    published-page consumer remains M5 (surface.kind, D23). SPEC §7.2/§15.4
    updated, and the emitted block has a committed Code-input fixture for the Rich
    Results release step.

  • Bilingual landing /es/ (#308) — the project page mirrors the EN landing
    in Spanish at /es/, with reciprocal hreflang (en · es · x-default) on
    both pages and the same alternates in sitemap.xml. The mirror carries no
    version claim
    — a second place to forget on every release — and
    version-check now enforces "claim-free surfaces" from the LANDINGS list in
    site-config.mjs, which is also the one home the sitemap reads. Docs stay EN
    (decision #280-B; the roadmap remains the declared ES document); site-check gates the new page
    like every other.

  • pasco discover — a provisional Profile from public surfaces (#329) —
    pasco audit needs a Profile; a prospect has none. Discovery reads only what
    a surface itself declares (schema.org JSON-LD + its links), attaches per-fact
    provenance (surface · url · retrieved · attested: false), lists absent
    fields instead of defaulting them, and runs driftCheck over the result plus
    the two classes discovery owns: dangling-ref (a same-host link that did not
    resolve) and unsourced-claim (a numeric claim with no source on the surface).
    One row per surface is kept on purpose, so a cross-surface date disagreement
    surfaces as drift/duplicate-experience instead of being averaged away.
    Transport is §19.5's: closed host allowlist checked before any request
    (registry platform hosts + --allow-host), sequential, no retries, no
    credentials, redirects refused. It writes nothing, and the provisional shape is
    not a schema version (SPEC §7.10).

  • Answer-engine surface, consumer half — structuredData() projects
    identity.definition + faq[] (#316)
    — the v1.10 contract shipped in v0.3.6
    without an emitter; structuredData() now reads the fields. Person:
    fields.description + jsonLd.description from identity.definition
    ([lang] falling to en), emitted only when declared — never invented — and
    description joins the Person considered list, whose denominator is now fixed
    at 8 (counted as populated only when declared, so a person declaring the seven
    pre-v1.10 fields and no definition moves to 7/8: the declared, honestly-accounted
    delta). Brand: definition wins over mission in the same description slot —
    a definition-less brand keeps the v1.9 mission output byte-identical. The
    FAQPage JSON-LD node (StructuredDataReadiness.faqJsonLd) exists iff
    root faq[] exists — never an empty one — and carries no @id (the engine never
    invents an anchor); a faq[].source travels as Answer.citation. No consumer
    embeds faqJsonLd yet (renderers embed jsonLd only): the emitted node is the
    readiness surface the M5 web renderer will consume.

  • pasco preview — see the data without publishing it (#282) — a private
    workspace had no way to look at the rendered result; the only options were to
    publish it or to read the base texts one by one, and Pages is not the missing
    piece (on personal plans the published site is public, so a private fork with
    Pages on publishes the data). The new command renders one self-contained HTML
    file
    — inline CSS, no <script>, no external asset, no HTTP server — whose
    sections are the surfaces pasco build would write (#github-readme,
    #linkedin, …), each with the check state, plus every surface this language
    does not publish (named with its reason, never dropped). The default writes
    <workspace>/.pasco/preview/index.html and opens it; --out <dir> writes and
    opens nothing, which is the CI-artifact transport (the workflow itself lives in
    the workspace template, not in the engine). One artifact, not a second
    implementation:
    the text is renderArtifact's decision tree (active variant →
    renderer → shared paste source, with build's selection gate) and the checks are
    the report pasco check prints — that report is now a buildCheckReport()
    function instead of a private body inside runCheck, so the two commands cannot
    drift. A privacy gate refuses the write (exit 1, and no file is left behind)
    on an absolute machine path, a credential shape, an external asset, active
    content, or a source that resolves outside the workspace: evidence-root media is
    never inlined, a remote image becomes a text note instead of a fetch, and raw
    HTML in the data is escaped rather than executed. --now <iso8601> pins the
    generated <date> stamp, so the page is byte-identical across runs. Contract in
    SPEC §7.9 (command row in §7); --help, the package README and the User Guide
    document it, and the privacy gate has its own seeded tests.

  • pasco export profile --json — the normalized profile for programmatic
    consumers (#348)
    — the engine exposed build <platform> (text slices) and
    validate, but
    no stable object a consumer could read without re-implementing load() +
    normalize(). Both real consumers paid for that copy and drifted: the CV syncer
    read 9 of 14 roots (education, languages, credentials, artifacts
    hardcoded to []), dropped an explicit publications[].url in favour of a
    DOI-derived link, and treated the invariant experience[].org as a langMap.
    The new command emits
    { schemaVersion, engine, source: { commit, generatedAt }, profile }, where
    profile is exactly the normalized core.load() object: every root the
    workspace declares, plus every list root defaulted to []
    (faq[] joins
    normalizeProfile's defaults, so the shape no longer depends on whether answers
    exist); an optional object root (newsletter, podcast, audiences,
    active) appears only when declared, because the contract has no valid empty
    value for it. artifactRefs stay ids that resolve in profile.artifacts[],
    publications[].url as declared, deployments included — never a CLI-local
    builder and never compile(profile, platform)'s platform slice (§15.4).
    schemaVersion ("1") is the frozen payload version a consumer refuses if it
    does not know (the §7.1/§7.2/§7.3 key and precedent). Deterministic by
    construction:
    no clock is read — generatedAt is null unless --now <iso8601> pins it verbatim after a real-calendar check — and source.commit is
    the workspace git HEAD (null outside a repository), so two runs over one
    workspace are byte-identical and a committed golden fixture asserts the exact
    bytes. Contract in SPEC §7.5 (command row in §7); the --help output and the
    package README document it. Separately, criterion 3 — validator parity is
    now pinned by a test: validate and build both load through core.load(), so
    they resolve the same schema and the same additionalProperties policy; the
    fixture carrying projects[].artifactRefs (the v1.12/#333 witness) is accepted
    by both, and an injected unknown row field is refused by both with identical
    violations (export profile shares that load path and is asserted with them).

  • The .pascorc engine gate and pasco doctor (#348) — a workspace can now
    declare the minimum engine it requires, and the commands that do work refuse
    early instead of failing on a contract they do not know. .pascorc gains an
    optional "engine": "MAJOR.MINOR.PATCH" field (a lower bound, exactly three
    numeric components — no v, range operator or wildcard; the repo stays
    dependency-light, so there is no semver range engine). validate, build and
    export profile check it before loading or rendering: an older install exits
    1 with the required version, the installed version and the upgrade command
    (npm i -g @profile-as-code/cli@latest); a malformed value exits 1 naming the
    field, the value and the accepted syntax. Absent means no requirement, so every
    existing workspace keeps working. Every .pascorc the resolution consulted is
    read — the cwd .pascorc that pointed at the workspace AND the workspace's own
    file — and the stricter declaration wins (a malformed one in either file is
    refused); an explicit --workspace uses only the workspace's file. pasco doctor [--json] is the reading surface — chosen over overloading --version,
    which describes the binary and must work with no workspace, while doctor
    describes the pair: one line with the installed engine, the profile-contract
    version this build validates (derived from the schema $id at pnpm gen:zod,
    never a literal), the resolved workspace and whether it loads, plus the
    effective requirement and its .pascorc (met/NOT MET/INVALID). Exit 0
    healthy · 1 unhealthy · 2 usage; offline, no clock, no writes. It reports
    the same truth the gate enforces; it does not replace the gate. Contract in SPEC
    §7.6; the .pascorc field in USER-GUIDE and the package README.

  • The published schema contract for consumers without the engine (#348) — a
    consumer that only validates a profile no longer has to install the CLI.
    schema/consumer-contract.json records the contract's URL + sha256: the
    immutable copy at a released tag
    (https://raw.githubusercontent.com/ChristianLuciani/profile-as-code/v0.3.6/schema/profile.schema.json,
    schema v1.13, sha256 275340ff…) and the committed...

Read more

v0.3.6

Choose a tag to compare

@github-actions github-actions released this 27 Oct 02:56
9416e29

Added

  • identity.nationality — a citable nationality fact (#334) — the ISO 3166-1
    alpha-2 code, uppercase (VE), as a fact distinct from identity.location
    (residence). The schema pattern ^[A-Z]{2}$ rejects ve, Venezuela and VEN at
    the boundary. structuredData() emits schema.org nationality as
    { "@type": "Country", identifier: "<code>" } on the Person entity JSON-LD —
    Person-only, since a brand has no nationality. Optional and outside the
    structured-data ready gate and completeness ratio: a workspace that omits it
    validates, emits, and scores exactly as before. Schema contract v1.13 (additive,
    pre-v1.13 workspaces validate unchanged); @profile-as-code/core 0.1.1.
  • Schema v1.12 — a project names its artifact (#333) — projects[].artifactRefs
    references artifacts[].id, the third reference site after publications[] and
    press[]. It stores the edge only: published-vs-in-development is derived per
    artifact (visibility: public + locator.url), never a stored flag (§15.3/§20.1).
    load() fails on a dangling ref, and pasco check fails when the target is not
    public (§18.5 — the leak gate now covers every reference site).
  • pasco edit — a contract edit surface, S1–S5 (#248–#252; epic #247) — address
    one contract field by contract path (never line/offset): read its current value
    (S1), --set writes it with a comment-preserving byte-range splice — validated and
    atomic, one-line diff (S2) — --set --check previews the published lines the
    edit changes (S3), and writing text pre-flights the platform's published cap at
    edit time (S4; content/checks.yaml when declared, else the shared limits table).
    S3 also reports the unit's per-surface staleness from the §19 ledger. S5 freezes
    the machine-readable contract and names the #238 variant, so an agent consumes
    pasco edit without parsing prose. Writer concurrency is covered under Fixed
    below.
  • Answer-engine surface — identity.definition + faq[] (schema v1.10, #288) —
    the two things an answer engine can quote: a canonical per-language definition
    (≤ 160 chars per value, must contain the entity name) and 3–20 citable Q&A pairs
    (q ≤ 90 and unique per workspace, a ≤ 320, optional http(s) source). This
    release ships the contract; the structuredData() projection of these fields
    (Person description + the FAQPage JSON-LD node) is tracked in #316 and is not
    emitted yet.
  • Funding rails — the project is now easy to fund — funding field in the CLI
    manifest (GitHub Sponsors · Ko-fi · Patreon; npm fund and the npmjs.com package
    page surface it natively), a --help footer tip line, a Support section on the
    package README, the three badge rails in the root sponsor section, the vendored GitHub
    mark on the landing and docs chrome, and the ko_fi:/patreon: rails in
    .github/FUNDING.yml.
    Deliberately not: no post-install script, no donation nag on every command — the
    ask lives in --help and the READMEs only.

Changed

  • GEO scores move: geoCheck v2 (#289) — five citability codes join the report
    (geo/canonical-definition, geo/faq-coverage, geo/freshness,
    geo/sameas-coverage, geo/name-collision), funded by diluting the nine pre-v1.10
    checks ×0.8 (the weights still sum to 1, asserted). Reported GEO scores move for
    most workspaces
    — the five codes enter the weighted report; a workspace already at
    the ceiling on all 14 stays at 100. The new GeoCheck.severity tier is additive and
    info never changes the pass/fail story.
  • The release publishes the seam cohort in dependency order (#301) — core →
    markdown → paste → artifact → github via pnpm publish, which rewrites the
    runtime workspace:* dependency, then the CLI via npm publish over OIDC; a
    package whose version is already live is skipped.
  • Deterministic ordering is ICU-free everywhere (#305) — every text sort delegates
    to core's byteOrder (UTF-16 code units), so output is byte-stable across locales.

Fixed

  • pasco edit writer hardening (#332) — the preview refuses what the write
    refuses, anchored/aliased scalars are refused, the writer re-reads immediately
    before the atomic rename and refuses on drift, and an advisory lock closes the
    last-writer-wins window; concurrent edits on one workspace keep both values.

v0.3.5

Choose a tag to compare

@github-actions github-actions released this 24 Oct 03:49
c69e6b7

Added

  • docs/START-HERE.md — the map each reader was missing (#260) — the dogfood run
    (2026-10-19/20) broke at five points the guide never covered: editing the wrong YAML file,
    editing without an editor, being cut by a platform cap a green validate never warned about,
    deploying by PR from the wrong directory, and links[].role: identity (the one line worth
    +20 structured-data points, documented nowhere). Three routes (person · contributor · agent),
    each with the mistake that route actually makes, plus the USER-GUIDE sections they read next:
    the seven-step cycle of one change (§0.5), the first edit without an editor, the links[]
    role rule, platform caps at edit time (§6), and deploy by PR with the .pascorc discovery
    trap (§7). The issue's proposed §4.1/§8.3 were already taken (golden rule, multi-workspace),
    so the new material sits at §4.4 and inside §7 — an addressed deviation, not a silent one.
    Published as a page (site-config.mjs), so the same Markdown is the only copy, and CONTRIBUTING
    ground rule 8 makes "the doc line" part of every dogfood finding.
  • Derived publication truth — pasco status now says whether the artifact is the one
    the SSOT produces (SPEC §20.1, #242)
    — the 2026-10-19 dogfood found every gate green
    while the published profile was not verified in the terms a user cares about: a green
    score over an unpublished artifact, a green check over a field the platform truncates,
    and a declared-but-ignored variant. Each surface with a ledger record or an artifact on
    disk now gets three orthogonal axes — sync (§19, unchanged), artifact
    (matches-SSOT / diverges / absent, offline, against the SSOT's current render),
    and limits (ok / over(cap), the #240 catalog). Derived on every read and stored
    nowhere (§20's own rule), folded into --completeness as coverage of publication
    (the weighted percentage never moves because a surface drifted) and into the generated
    CHECKLIST.md. Additive in --json (publication), and the one cap executor moved to
    packages/cli/src/limits.ts so check and status share it — and the planned
    edit slices (#248–#252) consume it instead of a second cap table.
  • The platform skills ship with @profile-as-code/cli (#253) — skills/ is copied
    into the package at build/pack time (files: ["dist","skills"]) and resolved from the
    package root, so pasco refine prints a real SKILL.md path on an npm install instead
    of the repo-dev layout a user does not have. The output now distinguishes no skill for
    <platform>
    from skills not shipped with this package; post reads the same tree.

Changed

  • The workspace has three top-level grains, and the scaffold now says so (#267) —
    content/ is identity (the SSOT), profiles/ is generated output, posts/ is the stream
    (seeds + per-channel projections, #264) — never identity facts. pasco init's README
    templates state the boundary for both flavors, SPEC §9's tree carries posts/, and
    docs/post-mvp.md names the rejected nestings (content/posts, content/identities,
    content/profiles). No rename, no schema change, no move of existing workspaces.
  • The recommended on-ramp now resolves for a stranger (#273) — profile-data-template
    went public (verified 2026-10-23: gh repo view ChristianLuciani/profile-data-template --json visibility → PUBLIC, HTTP 200), so its removal trigger fired: the link-check
    declaration in scripts/link-check/allowlist.json is gone (the registry is empty, the
    W-ALLOWLIST-UNUSED warning keeps it honest). The surfaces reconciled here — README.md,
    docs/USER-GUIDE.md, docs/SPEC.md §9.2, docs/STANDARDS.md (community items 4, 5, 10
    and the publication section), docs/ci-cd.md, site/index.html — state the template as
    public and cite the verification where they make the claim; check.mjs's header documents
    the mechanism. The wider class (surfaces still calling the engine repo private) is
    tracked in #293, not silently dropped. No duplication: templates/profile-data/
    stays a pointer; re-scoping to a second in-repo template would have created a second source
    of truth for zero reader benefit.
  • pasco skills:install <agent> → pasco skills:export <dir> [--force] [--list] (#254, C1) —
    the CLI no longer prints a projection command for a private studio. It copies the
    skill channels it ships into a directory the user names (a non-empty destination needs
    --force, which overlays and never deletes), and no published surface references a
    path/repo/tooling the reader cannot open (rule now in AGENTS/CONTRIBUTING). The tarball
    is asserted by verify:pack.
  • The hosted on-ramp is a declared v1 non-goal (#221), and the surfaces that still called it
    a plan are reconciled (#222)
    — §9.2's three on-ramps (A template repo · B pasco init ·
    C bring your own storage) all require a terminal, and A also an account, so a visitor with
    neither has no path; in particular none reaches a profile hosted by us.
    docs/SPEC.md §2 declares that path a v1 non-goal: the service it would need is
    multi-tenant (accounts, storage, secrets, privacy, cost) and inverts the folder-first model
    (D14), so it reopens only on its trigger — the first hosted use case, or M5 + web consumption
    live — and as a milestone of its own, not an extension of M5 (§12 keeps M5 a consumption
    renderer). docs/USER-GUIDE.md, README.md and docs/ROADMAP.md are reconciled to that
    state; the #122 tracker is closed and is no longer a deliverable.

v0.3.0

Choose a tag to compare

@github-actions github-actions released this 11 Oct 03:42
21e4cde

Added

  • Profile completeness — engine-computed checklist + percentage (SPEC §20, issue #102) —
    the schema is deliberately permissive (identity is the only required block), so a
    workspace at 5% and one at 95% produced the same green pasco validate: a passing check
    with no direction. New pure, deterministic, offline completeness(profile, policy) in
    packages/core/ (beside geoCheck, SPEC §7) scores five weighted items over signals
    already in the engine — presence (flavor-aware expected blocks), langCoverage (en/es/fr
    over every langMap), geoAnchors (associations reinforced elsewhere), evidence (sourced
    claims + registry) and deployment (share of trackable units with a ledger record) — with
    COMPLETENESS_CATALOG + DEFAULT_COMPLETENESS_POLICY. The percentage is never stored:
    only the policy is, read from .pascorc "completeness" (target / langs / items; a
    weight of 0 drops an item from the denominator), and compile() attaches the report to
    the slice so the renderer emits it verbatim rather than recomputing it. pasco status --completeness [--json] prints the checklist + percentage; --write-checklist folds the
    line into the generated CHECKLIST.md (D18 — the view is where the user looks);
    github-readme emits one deterministic line (Profile completeness: <pct>% — <n>/<m>)
    only when the slice carries a report — a hand-built slice renders byte-identical to
    before. No new required schema field: completeness is advisory (D12 preserved).

  • Publishable-surface gate — only packages/cli may omit private: true (#190; the
    surface set by #189)
    — the 11 bundle-only workspace packages are marked "private": true (they ship inside the CLI bundle, not as standalone npm packages), leaving
    @profile-as-code/cli as the only publishable manifest — but after #189 that invariant was
    convention-only. scripts/publishable-gate/ (the scripts/dep-gate /
    scripts/typecheck-gate shape: a fail-loud --check plus a fixture self-test that runs
    first) asserts every workspace manifest outside the explicit allowlist declares "private": true, so a new package (e.g. the planned packages/server) cannot silently rejoin the
    accidental-publish surface with every gate green. It reuses dep-gate's exported workspace
    walk instead of re-implementing it and runs in the pre-install block of ci.yml (fail
    fast). The gate verifies, never fixes, and its E-GATE vocabulary refuses the pathological
    shapes — non-object manifest, parser refusal — instead of crashing with a TypeError
    (#195, #199).

  • CLI — the documented pasco diff no-match tagPattern fallback is pinned by tests
    (#197)
    — docs/USER-GUIDE.md §5 documented that a workspace whose diff.tagPattern
    matches no tag falls back to the root commit, but that was verified only by hand: a change
    to packages/cli/src/commands/diff.ts could silently invalidate the doc. Two vitest cases
    now lock it with a non-default diff.tagPattern: "rel-*" fixture (so the .pascorc
    read is load-bearing, not the engine default): no matching tag → tag: null, base = the
    root commit, the post-root commits and hasChanges: true; adding rel-1 → tag: "rel-1",
    base = the tagged commit, no commits, hasChanges: false. No behaviour change.

Changed

  • SPEC §9 — the workspace is a folder; the three on-ramps are declared as peers (#200) —
    the contract and the guides treated this repository as the shape of the workspace, and
    every surface assumed the reader was standing in it. But the local path already works
    (pasco init), some users want no repo at all, and a prospective user can arrive from the
    npm catalog, a search engine, an agent or a shared link. §9 is retitled "the profile-data
    workspace (folder-first; a repo is recommended)"
    : the only hard requirement is a folder
    pasco can read by configured path whose content/*.yaml satisfies the schema, and a repo
    (GitHub, another git host, or none at all) is the recommended home. New §9.2
    declares three on-ramps as peers with their capability differences up front: A template
    (automation + history; requires account/repo) · B local pasco init (no account, no repo;
    loses automation and history) · C the same pasco init scaffold written at a path you
    choose. D14 is amended ("separate repo" → "separate workspace; a repo is the
    recommended home, not a requirement") and §3/§4/§10/§13 are reconciled; docs/USER-GUIDE.md,
    packages/cli/README.md (the npm page), README.md, templates/profile-data/README.md and
    docs/STANDARDS.md carry the same framing. No engine behaviour changes.

  • Release docs reconciled against the real v0.2.0 publish (#192) — the post-release
    surfaces still claimed the package was unpublished. Corrected against the actual sources
    (tag, GitHub Release, npm registry, a running bin): docs/STANDARDS.md item 3 (Tagged release + changelog) ❌ blocker → ✅, docs/USER-GUIDE.md, docs/ci-cd.md and
    packages/cli/README.md; and packages/cli/package.json's bin.pasco was normalized with
    npm pkg fix ("./dist/index.js" → "dist/index.js"), removing the single npm manifest
    auto-correction the v0.2.0 publish warned about.

Fixed

  • dep-gate and typecheck-gate refuse a non-object manifest instead of crashing (#198) —
    a package.json containing the JSON literal null parses without throwing, then crashed
    both gates on manifest.name with a raw TypeError instead of their declared E-GATE
    vocabulary (fail-loud, but the wrong failure shape — and the refusal class had no committed
    fixture). Both now guard the manifest shape right after the parse, pinned by a
    null-manifest fixture in each sibling self-test. The same pass closed two adjacent
    escapes found in review: tsconfig.test.json = null, and a pnpm-workspace.yaml whose
    quoted glob the reused parser refuses (whose GateError class did not match
    typecheck-gate's own catch).

v0.2.0

Choose a tag to compare

@github-actions github-actions released this 09 Oct 23:24
c1ca30c

First public release. This section covers what changed since the 2026-10-06
prep; the untagged ## [0.1.0] section below (schema v1, the core engine, the
pac → pasco rename, brand flavor, the evidence registry) is the project's
pre-release history and was never tagged or published.

Added

  • CI — offline, self-tested link gate for the Markdown corpus (#149) —
    scripts/link-check/ (checker + fixture self-test first, the scripts/dep-gate
    shape): every relative link/path in the tracked, non-hidden Markdown doc areas
    must resolve to a file or directory, every in-page anchor must match its
    destination's headings under GitHub's slug rules (the expected anchors are
    committed as github-slugger@2.0.0 output and asserted by the self-test), and
    private targets go through an allowlist that requires a reason + removal
    trigger. Offline and deterministic — external URLs are never fetched — so the
    required check cannot depend on third-party uptime. Runs after the pinned Node
    setup and before pnpm install (fail fast).

  • CI — every package type-checks its tests, and the wiring is gated (#181) —
    each package gains a tsconfig.test.json that extends its own tsconfig (single
    source for compiler options) and adds tests/**, the root *.config.ts and
    scripts/**; pnpm -r typecheck now covers the suites, so a mistyped shared
    contract (e.g. @profile-as-code/paste/conformance) fails the gate. A node-only
    scripts/typecheck-gate runs first and refuses a package whose typecheck
    script omits the test program — without it, pnpm -r typecheck silently skips
    the package and CI stays green.

  • Platform registry — per-platform metadata now has one machine-readable home (#154):
    platforms/registry.yaml carries the metadata that used to live hand-written across ≥7
    surfaces (family, deploy kind, hosts, subdomain hosts, handle hosts, status, since, issue,
    sources). pnpm gen:platforms (scripts/gen-platforms/, the D3 drift-check shape) projects
    every derived surface: packages/core/src/generated/platforms.gen.ts (the host maps + slug
    list consumed by drift.ts/types.ts, plus the derived test enumerations), the SPEC §6
    family table + count arithmetic and the README status counts between explicit
    <!-- gen:… --> … <!-- /gen --> markers. The reviewed contract is unchanged — the schema
    keeps $defs.platform.enum (D3) — and CI asserts registry ↔ enum ↔ generated agree
    (scripts/schema-check/check.py + node scripts/gen-platforms/gen.mjs --check), including
    the reverse assert: every enum member has a declared renderer or an explicit
    renderer: false ("not implemented yet"), and each renderer's deploySpec equals the
    registry deploy (D9).

  • CONTRIBUTING — escalation path and measured response latency (#148) — the
    published latency is measured, not a promise: a table over the three strata
    (n=103 merged PRs, [MI], 2026-10-08), the regenerable gh + python3
    one-liner, and the honest caveat that no external PRs exist yet so the figure
    describes maintenance cadence. Stalled = no maintainer response after 7 days;
    the escalation channel and the possible outcomes are named, and silence is not
    one of them.

  • Package READMEs + complete publication metadata (#143) — 9 new READMEs
    (D8, English: packages/markdown + the 8 renderers) with one uniform skeleton,
    since npm always packs README.md and publishing would otherwise show an empty
    package page; the 10 libraries + the CLI gain repository (+ directory),
    bugs, homepage and keywords, and the pure libraries gain
    sideEffects: false.

  • hotmart platform — schema additive v1.9, D16 operator extra #15 (49 total;
    issue #137)
    — Hotmart: digital-products storefront + memberships (the Payment Policy's
    recurring billing) + affiliate program, serving the Spanish/Portuguese-speaking markets
    (offices in Belo Horizonte, Madrid, Bogotá, Ciudad de México — [OFICIAL] footer, fetched
    2026-10-05), in the creator-monetization family. Enum
    entries in $defs.platform and the derived check.platform, ledger surface, drift host
    hotmart.com (+ HANDLE_HOSTS; the Hotmart Space profile URL shape stays [?] — no
    PLATFORM_SUBDOMAIN_HOSTS entry without a verified shape), Zod regenerated (D3). Paste
    platform (D9 — no management API documented for the profile body; developers.hotmart.com
    covers sales/products [?]): pasco build hotmart → paste → pasco status --mark.
    Fees/eligibility sourced from the official legal texts, fetched 2026-10-05: no
    monthly/annual fees ([OFICIAL] pricing); fixed fee BRL 2.49 / USD 1.00 / EUR 1.00 +
    service fee 9.9% (20% micro-transactions) ([OFICIAL] Payment Policy §5); withdrawal
    minimum BRL 2.00 + BRL 1.99 fee, 30/15-day deadlines ([OFICIAL] root FAQ #8); worldwide
    eligibility, sanctions-based exclusions only, bank account in own name in the country of
    residence after the first sale ([OFICIAL] ToU §A/§H/§2.8). EC payout rail remains
    [?]
    — carried per the #133 precedent (kofi/payhip PayPal-EC), verification item
    in the venture instance. The market-leadership framing in issue #137 was NOT carried into
    this contract text (no independent source; Hotmart's own "world's largest / Latin
    America's leader" wording lives in the skill research as attributed vendor marketing).

  • Renderer @profile-as-code/renderers-hotmart (v1.9, #137) — deterministic
    plain-text profile paste-source: exactly ONE bios row (long > medium > short), byte-stable,
    empty slice → empty artifact (skipped/unbuilt by pasco build, §15.1). Deploy spec is
    §7's manual (D9 paste platform).

  • Skill skills/hotmart/ (#137) — SKILL.md (storefront frame — "esto resuelve X", not
    tip-jar prose; no-results-promised rule per ToU §1.4) + playbook (paste flow with the
    first-paste surface-verification step) + checks.yaml (soft-cap curacional — no documented
    bio limit) + research/SOTA-2026.md (all sources fetched 2026-10-05; the fee table and
    eligibility from the official legal texts; the Space URL shape and EC payout carried
    [?] with sourcing leads).

  • M9 floor — private-first, consent-gated AI-crawler posture (SPEC §12/§14, issue #141) —
    the training-time corpus floor inverts: robots.txt on published surfaces blocks
    AI-training crawlers (CCBot / vendor AI crawlers) and user data ships without an open
    license; entering corpora or open-licensing data requires the profile owner's explicit opt-in
    (consent field — schema work deferred with Q10). Rationale: the engine is multi-tenant
    (SPEC §9), so an allow-posture decides privacy + licensing on behalf of profile owners
    (identity data entering training corpora without consent — GDPR-adjacent exposure when
    productized); the operator's own profiles may opt in — that consent is theirs to give. Inputs

    • sourced levers: docs/research/03-training-time-visibility.md (PR #135, issue #134;
      fetched 2026-10-05). No schema/code change — the consent field lands with Q10 (SPEC-first).
      docs/ROADMAP.md M9 row realigned.
  • Platform families freelance + job-boards — SPEC docs-first declaration
    (issue #138)
    — the platform families table in the SPEC (D16 block): freelance
    covers the pre-existing v1 upwork retroactively and names its backlog slice
    (freelancer, workana, fiverr — filed with the gig-storefront boundary note);
    job-boards is a backlog-only slice (infojobs, computrabajo, indeed —
    recruiter-facing CV surfaces; GEO hook = name-reputation in search results). No
    enum/schema/core/CLI changes (48 unchanged); each backlog member follows the #133
    member chain per slug when real operator demand lands, with an official eligibility
    page before any claim. Boundary notes: linkedin stays un-bucketed (hybrid),
    github-readme/github-org/researchgate stay un-declared (YAGNI), toptal is
    recorded as a reject candidate (invite-only network, [MI]).

  • Creator-monetization platform family — schema additive v1.8, D16 operator extras
    #11–#14 (48 total; issue #133)
    — buymeacoffee, liberapay, gumroad, payhip: the
    platforms where creators receive money (memberships / tips / recurrent donations /
    storefronts) alongside patreon (v1) and kofi (v1.7). The family is declared docs-first
    as creator-monetization ("Creator monetization platforms" — canonical industry term)
    in SPEC (D16/§9) and the schema descriptions — no hard schema field (zero consumer
    breakage). Enum entries in $defs.platform and the derived check.platform, ledger
    surfaces, drift hosts (buymeacoffee.com, liberapay.com, gumroad.com, payhip.com,
    with Gumroad/Liberapay subdomain profile URLs), Zod regenerated (D3). Paste platforms
    (D9 — no management API for the profile body): pasco build <platform> → paste →
    pasco status --mark. Per-platform gofundme deferred (no EC payout as organizer,
    operator decision in #133); per-platform skills remain separate follow-ups (D16 parity
    caveat) and each carries its own sourced research when authored.

  • Renderers @profile-as-code/renderers-* for the family (v1.8, #133) —
    renderers-buymeacoffee, renderers-liberapay, renderers-gumroad, renderers-payhip:
    deterministic plain-text paste-sources (exactly ONE bios row, long > medium > short),
    byte-stable, empty slice → empty artifact (skipped/unbuilt by pasco build, §15.1).
    Core fixture minimal-creator-monetization + schema-check fixture
    valid-creator-monetization.json.

  • kofi platform — schema additive v1.7, D16 operator extra #10 (44 total; issue #130) —
    Ko-fi creator page (same family as patreon): enum entries in $defs.platform and the
    derived check.platform, ledger surface, drift host ko-fi.com (+ HANDLE_HOSTS), Zod
    regenerated (D3). Paste platform (D9 — no API deploy; Ko-fi ships payment webhooks only):
    pasco build kofi → paste → `pasco status -...

Read more