Imported from alvis/.agents (
plugins/specification/skills/sync-spec/SKILL.md). Install upstream withnpx skills add alvis/.agents --skill sync-spec. Copyright stays with the author.
Sync Spec
Safely coordinate three copies of a Notion-backed specification: an immutable recorded base, the work-local authored copy, and a fresh remote staging pull. specification:sync-notion owns transport. specification:mdc owns the MDC body grammar; another dialect uses an explicitly selected body_author for semantic body changes. Detect changes by comparing the specification content directly (byte-for-byte, or via git diff), disregarding only the volatile Notion last_edited_time line for semantic equality. Approvals bind to the approved specification content, not to any hash.
Boundaries
materializeobtains a fresh remote view, then creates or refreshes only the requested page tree under.state/works/<work-id>/spec/when the base/local/remote decision permits it.completeis a publication gate. It reconciles the authored copy with a fresh remote view, verifies stage-specific approval, delegates guarded transport, verification-pulls, refreshes the work-local materialization and immutable receipt, and reports dependent work that needs revalidation.- External specifications never derive version-controlled specification files. In PRs and tracked documents, cite their canonical external URL only—never
.state, a mirror, an absolute path, orfile://. - Only the main agent writes the external authority,
.state/**, rootREADME.md, ordocs/**. Subagents return proposals and evidence. - Never derive or rename an MDC filename. Select files by stable
ref:and the transport relationship report. - Never call
notion-syncdirectly, treat the selected mirror as an authoring surface, overwrite a fixed receipt, or infer that cached mirror bytes are the latest remote bytes. - Never treat a metadata-only edit (only the volatile
last_edited_timeline differs) as a contract change, and never approve, plan, or review against content you have not compared directly against the recorded base.
Inputs
- Required: Notion URL or page id.
- Optional: work id, exact
--mirror=<path>, mode (defaultmaterialize), lowercase capability slug, and a verified creation receipt for an explicitly created new page. - Required for every Notion operation: an explicit absolute
--transport-profile=<file>, or an active-state destination mapping that names one absolute profile file, its last verified exact-byte SHA-256, and logical profile name. The mapping selects a file only;sync-notionrevalidates its current bytes/executable on every invocation. Never infer a profile path from a logical name, mirror, workspace, or origin receipt. - Required for completion that applies authored content: explicit
--body-author=<plugin:skill>in canonical capability form. Materialization that only preserves pulled bytes may omit it. Resolve the selector once, never infer a default, and require every nested call plus existing creation/materialization receipt to match it exactly. - Completion only:
--stage=specification|implementationis required. Specification stage requires explicit specification approval of the final specification content. Implementation stage requires a clean implementation review that was performed against that exact final content, confirmed by direct comparison. - Prerequisites: injected Essential state contract, resolved active work, the strict destination/team profile required by
sync-notion, andNOTION_TOKEN.
Workflow
- Read the absolute injected
state.mdcontract before artifact writes. If unavailable, stop artifact writes and report the missing contract. A direct main-agent run follows Essential's work-stream establishment lifecycle: preserve an explicit user--work-idoverride; otherwise treat resolver matches as candidates, reuse one only when its charter owns the requested outcome, select a collision-free identity when none does, and rerun the resolver with that identity. Onwork_id_required, select contextually and rerun without asking the user to approve an identifier. A delegated run receives the resolved id/root or returns the resolver payload to the main agent without writing. Resolve:work_spec_root = <active>/.state/works/<work-id>/spec;mirror_rootfrom explicit input, active state, or an immutable receipt;transport_profile_filefrom explicit input or the exact validated active-state mapping described above;body_authorfrom the explicit argument only when this chain may apply authored body bytes, withselection_source: explicit_argument(ordelegated_callerwhen already bound by the parent);receipt_root = <work-dir>/artifacts/spec-sync;- immutable receipts at
materializations/<base-id>.jsonand base snapshots atbases/<base-id>/, where<base-id>is a stable identifier derived from the full accepted byte set of the base — all per-unit identities and their revisions together (sanitized for filesystem use) — never the root page's observed revision alone, which collides when a child page or layout changes while the root revision is unchanged and would let a later materialization overwrite or compare against a stale base. Every receipt is JSON with these required top-level fields:base_idequal to its filename stem,canonical_url, a non-emptyobserved_external_revisionsstring map, UTCcreated_at, and a non-empty lexically sortedcontent_manifest. Each manifest entry is exactly the work-relative POSIXpath, lowercase SHA-256sha256, and integerbytesfor one regular file. The manifest enumerates every file in bothbases/<base-id>/andspec/; both trees must match it byte-for-byte and contain no symlinks. Record the selected body-author identity when applicable. A matching path orbase_idwithout this complete byte proof is invalid evidence. Validatebody_authoragainst canonical<plugin>:<skill>identity and retain it separately from the transport profile. A missing or changed selector before semantic body mutation returnsstatus: refused,next_action: select_body_author; it is nevertransport_unverified. Creation, materialization, completion, and derivation receipts record the selected capability and selection source when present; nested calls compare them before authoring.goal.mdalone points to the accepted base, local copy, and current receipt; the main agent owns that update.state.mdrecords sync status and links togoal.mdwithout restating anchors. Require the real work/mirror targets to be ignored and untracked in their owning VCS workspaces; otherwise returnrequires_ignorewith the exact ignore file. Require the mapped logical name to equal the selected profile'snameand pass the absolute file explicitly to every transport call. A missing, ambiguous, moved, or changed mapping returnstransport_unverified; never fall back toPATHor a conventional file location.
- Normalize the Notion id only for identity comparison and require 32 hex characters after dash removal. Resolve the selected recursive page set by returned
ref:/relationship data, never filename shape. Load references/concurrent-edit-matrix.md. Reject malformed/ambiguous carriers and duplicate identities/paths/logical unit ids. Use the three-copy rules for every later decision. - Before either mode decides anything, invoke
Skill(sync-notion)innotion-to-localmode with the exact mirror root and--transport-profile=<transport_profile_file>into a unique remote staging directory. Verify full requested coverage, stable identity, and revision evidence. Store the exact pulled bytes and observed revision as immutable evidence for later direct comparison. Do not refresh the selected mirror or work copy yet. - In
materializemode, compare the current authored tree L and fresh remote staging R with immutable base B. Report operationalstatusseparately fromclassificationandnext_action. Before declaringmetadata_only, compare every unit directly and require identical carrier kind, stable identity, logical id, path, and semantic content; onlyobserved_revisionand the uniquely allowedlast_edited_timeline may differ. A stable-identity, logical-id, or carrier-kind shift is invalid evidence and returnsstatus: refused,classification: invalid_evidence, andnext_action: repair_evidence. A verified path/layout rename with identities intact isstructural_change, invalidates dependent evidence, and is never metadata-only:- absent L/B establishes the first base by atomic staging and promotion and returns
status: success,classification: initial, andnext_action: none; - absent B with existing L returns
status: refused,classification: baseline_required, andnext_action: establish_baseline, and preserves L, except that a verified new-page creation receipt may establish the initial base only when it preserves pre-create L, proves creation authorization and the stable identity/parent transition, and records post-create approval plus exact verification evidence for R; atomically promote that verified R as initial L/B; - clean L plus unchanged R returns
status: success,classification: unchanged, andnext_action: none; - semantic B/L/R equality with a remote-only metadata change that passes the structured-unit restriction above (only the
last_edited_timeline and observed revision differ) returnsstatus: success,classification: metadata_only, andnext_action: none, atomically refreshes the exact remote bytes and revision, and creates a new immutable base/receipt without invalidating approval, plan, code, or review; - clean L plus a remote structural change atomically promotes the fully staged/verified R tree, creates a new base/receipt, and returns
status: success,classification: structural_change, andnext_action: revalidate; it invalidates approval, plan, code, and review even when the content is otherwise equal; - clean L plus changed R atomically refreshes L/mirror, creates a new base/receipt, and returns
status: success,classification: remote_only, andnext_action: revalidatewhen plan, review, or implementation evidence exists; - dirty L plus unchanged R returns
status: success,classification: local_only, andnext_action: none, and preserves L; - dirty L plus a semantic or structural remote change returns
status: success,classification: materialization_conflict, andnext_action: resolve_conflict, preserves every canonical byte, and reports B/L/R paths and manifest differences. Stage and verify the complete selected tree before any atomic promotion; retain rollback bytes until both promoted manifests verify.
- absent L/B establishes the first base by atomic staging and promotion and returns
- In
completemode, require a valid stage and compare B/L/R before canonical writes. Missing B isstatus: refused,classification: baseline_required, andnext_action: establish_baseline; unchanged and converged content need no push; local-only content may proceed through its content-approval stage gate; remote-only or structural change isstatus: successwithnext_action: revalidateand no push; concurrent content requires an explicit three-way merge. Workers may return conflict packets/proposals only. The main agent/user owns choices, andKeep Bothrequires explicit approval of the synthesized final content. AnySkipleaves that pair's local, mirror, and remote bytes untouched and forbids a push. A concurrent relationship atstage=implementationreturnsstatus: success,classification: concurrent, andnext_action: specification_reconciliationwith B/L/R evidence and proposals; it must not apply or push merged content at implementation stage. The source owner must author the selected merge, complete it throughstage=specification, verification-pull it, establish a new immutable base/receipt, and materialize that base before any plan or implementation resumes. - Freeze each selected pair's final proposal. For
stage=specification, require explicit specification approval of the exact final specification content. Forstage=implementation, require the clean implementation review to have been performed against that exact final content. Any semantic edit after the gate invalidates it; a declared metadata-only refresh (only thelast_edited_timeline differs) does not, but must refresh the exact base evidence. Apply approved authored changes only to a staged mirror copy through the exact capability bound asbody_author. Pass only the approved staged body and exact path, and require the selector to match the parent and receipt. - Immediately before each outbound operation, use
Skill(sync-notion)to re-fetch/re-diff the exact remote revision and content, passing the same exact--transport-profilefile and mirror root. Abort and restart on any content or revision change, so the base/revision evidence can be refreshed. Require the pinned transport to prove conditional-update support and record that condition. If a valid profile declares it unavailable, propagatestatus: refused, preserve the observed B/L/R classification, setnext_action: provide_conditional_transport, and leave Notion plus canonical L/mirror bytes unchanged. A malformed, mismatched, moved, or unproven profile remainstransport_unverified. Push only a fully resolved pair, then perform an independent verification pull. - Only after verified identity/content may canonical L/mirror state advance. Create a new immutable base directory and receipt keyed by
<base-id>; never rewrite an earlier base or receipt. A partial remote write ispartialwith exact recovery evidence, not success. Atomically refreshspec/, record the observed revision and exact content in the base-id-keyed receipt, and return a main-agent reconciliation delta forgoal.md. Do not create or update version-controlled specification files; the external authority remains the canonical contract. - Enumerate locally registered Git worktrees and jj workspaces. For readable open work on the same source id whose recorded content changed, keep
status: success, setnext_action: revalidate, and return workspace/work/ state paths; list external anchors and unknown/remote dependents separately. Never edit another main agent's state. - Return every final path created or materially rewritten as
generated_files. Each writer followsessential:references/output-manifest.mdfor work Markdown it creates or rewrites. The main agent applies thegoal.md/state.mdreconciliation delta.
Verification
- B is immutable, L is the authored copy, and R came from a fresh staging pull.
- The recorded base stores stable identities, the observed revision, and the full accepted bytes; comparison was performed directly against those bytes.
- Existing local bytes changed only in an allowed matrix row; conflict,
baseline_required, remote-only, and skipped outcomes did not push. - The approval/review was performed against the final specification content, and the immediate remote recheck matched the exact comparison revision and content.
- Every successful publication has a verification pull, refreshed
spec/, and a new base/receipt keyed by<base-id>; no fixedmaterialization.jsonwas overwritten and no version-controlled specification derivation was produced. - Opaque body content and Notion transport stayed with their selected owners; every semantic mutation used the exact recorded
body_author.
Completion
skill: sync-spec
status: success|partial|refused|requires_ignore|transport_unverified
classification: initial|unchanged|metadata_only|local_only|remote_only|structural_change|converged|concurrent|baseline_required|materialization_conflict|invalid_evidence|not_applicable
next_action: none|revalidate|establish_baseline|resolve_conflict|specification_reconciliation|recover_partial|repair_evidence|provide_conditional_transport|select_body_author
mode: materialize|complete
stage: specification|implementation|null
work_id: '<id>'
transport_profile: {logical_name: '', profile_file: '<absolute destination-local path>', profile_file_sha256: '', verification: verified|transport_unverified}
body_author: {capability_id: '<plugin>:<skill>|null', selection_source: explicit_argument|delegated_caller|null, verification: matched|not_required|refused}
outputs:
transport_mirror: '<absolute selected path or null>'
work_spec_root: '<absolute active-workspace path>'
base_snapshot: '<absolute immutable path or null>'
materialization_receipt: '<absolute base-id-keyed path or null>'
notion_root: {id: '<32hex>', path: '<notion-sync-owned path>'}
comparison:
classification: initial|unchanged|metadata_only|local_only|remote_only|structural_change|converged|concurrent|baseline_required|materialization_conflict|invalid_evidence|not_applicable
base: {revision: ''}
local: {revision: ''}
remote: {revision: ''}
base_id: '<stable base identifier or empty>'
publication: {approved: false, reviewed_spec_matches_final: false, remote_rechecked: false, required_capability: conditional_update|conditional_create|null, conditional_write: false, pushed: false, verified: false}
reconciliation: {required: false, reason: null, conflict_packet_paths: [], invalidated: []}
provenance: {path: '', embedded_output_paths: [], self_hash_external: ''}
revalidation: {local_registered: [], external_receipt_anchors: [], unknown_or_remote_dependents: []}
generated_files: []
issues: []
Use these deterministic top-level mappings: missing base → status: refused, classification: baseline_required, next_action: establish_baseline; dirty materialization with remote semantic/structural change → status: success, classification: materialization_conflict, next_action: resolve_conflict; implementation-stage concurrent change → status: success, classification: concurrent, next_action: specification_reconciliation; remote-only semantic/structural change or a success that invalidates dependents → status: success, matching classification, next_action: revalidate; ambiguous/partial remote mutation → status: partial, next_action: recover_partial; policy/precondition failure → status: refused, classification: invalid_evidence|not_applicable, and next_action: repair_evidence when repairable. Verified absence of the conditional capability required for publication → status: refused, preserve the observed B/L/R classification, and next_action: provide_conditional_transport, with no remote or canonical-local mutation. An absent or mismatched transport profile/mapping propagates transport_unverified; missing mirror ignore coverage propagates requires_ignore. A missing or changed body-author selector before semantic mutation returns status: refused, preserves the observed classification, and sets next_action: select_body_author without changing body or transport bytes.