Clickup
Skill SashaMarchuk/claude-plugins/plugins/clickup/skills/clickup
Sasha Marchuk's Claude Code plugins — opinionated tooling for ticket management, automation, and everyday engineering workflows. Marketplace install: /plugin marketplace add SashaMarchuk/claude-plugins
npx -y skills add SashaMarchuk/claude-plugins --skill clickupAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
ClickUp ticket creation, modification, and workspace management with enforced quality standards. Creates consistent tickets using Connextra user stories, evidence-only descriptions, fuzzy list aliases, first-name teammate resolution, bug-keyword type inference, priority-keyword inference, duplicate detection, idempotent create, and preview-and-edit confirmation. Includes a two-step onboarding wizard, persistent user config + memory files, and a stale-config reminder. Use when the user types /clickup, /clickup --auto, /clickup --onboard, /clickup --memory, /clickup --status, /clickup --workspace, or says "create a ticket", "add to backlog", "put in ClickUp", "make a task", "file a bug in ClickUp", "create a ClickUp task", or references a ClickUp list, task, or workflow.
SKILL.md
27.7 KB, as published. Nobody here has run it
/clickup
Universal skill for creating and managing ClickUp tickets. Enforces consistent title + description conventions so every teammate writes the same way. Onboarding builds a personal config; memory captures learned preferences.
Step 1: Parse $ARGUMENTS
| Flag | Mode | Details |
|---|---|---|
| (none) | Interactive ticket create | references/modes.md#default |
--auto | Silent create with defaults | references/modes.md#auto |
--onboard | Full wizard (identity + workspace) | references/modes.md#onboard |
--onboard identity | Re-run shared identity wizard only | references/modes.md#onboard-identity |
--onboard workspace | Re-run clickup-local wizard only | references/modes.md#onboard-workspace |
--memory [add|list|remove|clear] | Manage learned patterns | references/modes.md#memory |
--status | Config health check (both files + connection) | references/modes.md#status |
--connect | Investigate transports, pick one, remember it | references/modes.md#connect |
--connect show | Read-only connection view (alias for status block) | references/modes.md#connect |
--workspace | Switch active ClickUp workspace | references/modes.md#workspace |
--reload | Reconcile config.lists with workspace state | references/modes.md#reload |
--reload --mode=incremental | Force incremental even on large diff | references/modes.md#reload |
--reload --mode=full | Force route to onboard-workspace with archive carry-forward | references/modes.md#reload |
Precedence on conflict: --onboard > --status > --connect > --memory > --workspace > --reload > --auto > default. Flag arguments are space-separated (--onboard identity, not --onboard=identity). Positional args after flags are the ticket-seed text.
Transport is never hardcoded. Every ClickUp side-effect goes through a named capability operation (op.*) whose per-transport realization (ClickUp MCP / clickup-cli / REST) lives ONLY in references/connection.md. Which transport runs is whatever config.connection resolves to (Step 2.5). This file never names a transport primitive.
--onboard --auto is REJECTED at parse time. If $ARGUMENTS contains BOTH --onboard AND --auto (in any order, with or without a sub-arg like --onboard identity), HALT BEFORE any pre-flight step with the one-liner:
refusing --onboard --auto: onboarding must be interactive (AskUserQuestion required). Run `/clickup --onboard` alone first, then retry `/clickup --auto "<seed>"`.
Rationale: --onboard requires AskUserQuestion rounds (see references/modes.md → onboard-identity Step 2/3/4/7); --auto explicitly bans interactive prompts ("silent create with defaults"). The combined invocation has no valid execution — either AskUserQuestion opens (violating --auto) or onboarding silently skips (violating --onboard precedence). An LLM resolves this differently each run; the only safe behaviour is parse-time refusal. Closes PLG-clickup-F5.
--reload --auto is REJECTED at parse time. If $ARGUMENTS contains BOTH --reload AND --auto (in any order), HALT BEFORE any pre-flight step with the one-liner:
refusing --reload --auto: list-reconciliation requires interactive confirm. Run `/clickup:reload` alone.
Rationale: reload's diff is presented in a confirm card ([1] Apply / [2] Cancel / [3] Pick aliases for new lists first). --auto bans interactive prompts. Silently rewriting the user's lists[] + aliases is a data-loss vector. Mirrors the parse-time refuse for --onboard --auto immediately above.
--connect --auto is REJECTED at parse time. If $ARGUMENTS contains BOTH --connect AND --auto (in any order), HALT BEFORE any pre-flight step with the one-liner:
refusing --connect --auto: choosing a transport requires interactive investigation (AskUserQuestion required). Run `/clickup:connect` alone first, then retry `/clickup --auto "<seed>"`.
Rationale: ## connect investigates all three transports and the user must CHOOSE one (AskUserQuestion) — the choice is never silent. --auto bans interactive prompts. The combined invocation has no valid execution; the only safe behaviour is a parse-time refuse. Mirrors --onboard --auto / --reload --auto.
Seed-text 4 KB cap (pre-extract truncation). Any seed text — pasted transcript, previous-turn context carried forward, positional args — longer than 4096 bytes (UTF-8 encoded) is truncated BEFORE the extract step. Truncation point: the nearest sentence boundary (period, question mark, exclamation mark, or newline) at or before the 4096-byte mark. If no sentence boundary exists in the first 4096 bytes, fall back to the nearest whitespace; if none, hard-cut at 4096 bytes. After truncation, show the user an explicit banner:
[SEED-TRUNCATED: <N> bytes dropped at sentence boundary]
Where <N> is the byte count of the dropped tail. The banner is load-bearing — the operator must know that downstream extraction saw only the truncated prefix, not the full paste. Rationale: the evidence-only rule is the only fence against attacker-controlled bulk paste; a 4 KB cap bounds the blast radius of a pasted tracking pixel / prompt injection / @mention flood without degrading legitimate ticket-creation flows (most seed texts are well under 4 KB). Closes PLG-clickup-F14.
Step 2: Pre-flight (every invocation, in order)
- Read shared identity from
~/.claude/shared/identity.json. If missing oronboarding_complete != true:- In
--automode: HALT with "identity missing — run/clickup --onboardfirst" (don't drag user into interactive onboarding mid-auto). - In interactive mode: redirect to
--onboard identitywith one-line explanation; carry the original request as ticket seed.
- In
- Read clickup config from
~/.claude/clickup/config.json. If missing oronboarding_complete != true:- In
--automode: HALT with "config missing — run/clickup --onboard workspacefirst". - In interactive mode: redirect to
--onboard workspace; carry the ticket seed.
- In
Step 2.5 — Resolve connection (which transport to use). Read config.connection. This runs for EVERY ClickUp-touching mode (default create, --auto, --reload, --workspace, --onboard workspace — anything that calls an op.*). If connection is absent OR connection.configured != true (connection has never been investigated — the gate, see references/config-schema.md → connection):
- In
--automode: HALT with "connection not configured — run/clickup:connectfirst" (never drag--autointoAskUserQuestion; mirrors the identity/config auto-halts above). No write, no data touched. - In interactive mode: redirect to
references/modes.md#connectwith a one-line explanation; carry the ticket seed forward (same pattern as the identity/workspace redirects). Never create or mutate a ticket without an investigated, chosen connection on disk — and never silently pick a transport. - In
--statusmode: report "connection: not configured" and do NOT detour.
When configured == true, run the resolver in references/connection.md → Resolver: build the available list (preference first, then fallback_order), bind ACTIVE for the whole invocation, and surface a banner if a fallback is used instead of the preferred transport. The resolved transport is what Step 6's probe and every later op.* dispatch through.
-
Validate schemaVersion — both files must have integer
schemaVersion≤ the version this skill understands (currently2; readers accept{1, 2}during the v1 → v2 back-compat window — seereferences/config-schema.md→ Shared-contract constants). Writers always emit2, and/clickup:connect/--onboardupgrade a v1 file to2on mutation, so a v2 file written by this skill must NOT be treated as "higher version". On a genuinely higher version (> 2): refuse to write, degrade to read-only with a banner. On corrupt JSON: quarantine to<file>.corrupt-<epoch>and re-onboard. -
Read memory from
~/.claude/clickup/memory.md. Apply rules. If any rule is unused >60 days or applied >20 times, prepend a one-line review banner: "💡 N memory rules may be stale — run /clickup --memory list". -
Check config freshness. If
config.updated_at> 30 days ago, prepend: "💡 Config is 30+ days old — run /clickup --onboard to refresh". Non-blocking. -
Verify the resolved transport's auth with
op.probe(). Runop.probe()against the transport bound in Step 2.5 (the realization per transport — MCP /clickup-cli/ REST — lives inreferences/connection.md→ Realization; this file never names it). Classify the return via named dispatch — DO NOT collapse into a single "fail" bucket:auth-ok— probe returns a usable result (a non-empty workspace tree, orclickup-cli auth checkexit 0, or a 200 from the REST user endpoint). Proceed.auth-fail— HTTP 401 / 403, "not authenticated" / "invalid token" / "disconnected" / any explicit credential-rejection. Try the next transport infallback_order(per the resolver); if none reachesauth-ok, HALT with the per-transport re-auth fix hint fromreferences/connection.md→ Fix hints (e.g. the MCP re-auth step,clickup-cli setup, or the REST token hint).retryable-network— timeout, connection refused, DNS failure, HTTP 5xx, or the transport process unreachable. Retry ONCE after 2s backoff; on second failure try the next transport infallback_order, else HALT with "ClickUp unreachable (rc=retryable-network). Check network/transport, then retry /clickup." Do NOT escalate toauth-fail— a transient network error is not a credential problem.other— any unclassified error (malformed response, unexpected shape). HALT with the raw error and rc=other; never silently proceed.
Never fabricate a success URL. The probe MUST fire on EVERY invocation — do not skip under context pressure. If you cannot name the resolved transport's probe call or classify its return in your own words, you have not verified auth.
-
Re-validate teammates lazily. If any
teammates[].last_validated_at> 7 days (ornull), silently fetch workspace members; diff against identity; surface significant changes (removed users, renames) as a banner. Updates go to~/.claude/shared/identity.jsonvia the atomic helper inreferences/config-schema.md.
Step 3: Route by flag
Load the referenced section from references/modes.md before acting. Each mode has its own deterministic flow.
Core rules (apply in EVERY mode that creates or edits a ticket)
Full rules + worked examples in references/ticket-format.md. Enforce:
Title — imperative verb + subject + qualifier. English. ≤80 chars. No [Bug] / [Feature] / [Task] prefixes, no list-name prefixes, no ticket numbers. Must pass the test: "To complete this ticket, I need to ___." Generate with a pre-translate buffer of ≤72 chars to leave room for EN expansion; regenerate (drop adjectives/qualifiers) rather than truncate mid-word.
Description — English. Always open with the Connextra line:
As a [beneficiary role], I want [goal], so that [benefit].
Omit the line entirely if the beneficiary role is not extractable from source. Role = who benefits, not who requested. The requester goes in the optional "Requested by" section.
Evidence-only — never invent acceptance criteria, metrics, stakeholders, timelines, impact statements, or business-value boilerplate.
No field duplication — never restate assignee, tag, priority, status, dates in the body. Those live in ClickUp's native fields.
Optional sections — render ONLY when source provides content: Context, Proposed Solution, Acceptance Criteria, Open Questions, References, Requested by. If nothing extractable beyond the user story, the description is just the Connextra line.
Defaults (enforced unless user overrides in preview)
| Field | Default | Override signal |
|---|---|---|
| Language | English | none — always EN |
| Priority | normal | urgent/ASAP/P0/burning → urgent; "high priority"/P1 → high; "low priority"/P3 → low. Resolved via the 4-tier precedence in references/config-schema.md → "Application order": (1) explicit CLI flag > (2) keyword-in-turn > (3) memory rule > (4) default. Keyword-in-turn WINS over memory — e.g. memory rule "Daria = P1" + source "low priority typo for Daria" → priority=low. |
| Status | backlog | only if user explicitly names another status |
| Task type | task | bug signals: bug, broken, fails, failing, regression, crash, 500, error, doesn't work, not working → propose bug, confirm in preview |
| Tag | none | source names one, or memory rule applies |
| Dates | none | never inferred |
| Custom fields | skipped | only if user explicitly asks |
Resolution rules
Assignee (dual-key resolver, teammates live in shared identity.json)
The roster lives in ~/.claude/shared/identity.json under teammates[]. /g-event reads the same file — changes here are seen there.
Homoglyph-collision gate (runs before every silent single-match AND before every zero-match upsert): compute the UTS #39 skeleton on the RAW typed input, BEFORE the zero-width / BOM strip in step 1 (unicodedata.normalize("NFKC", raw).casefold() + confusables-map transform). Order is load-bearing: if the strip runs first, a BOM-prefixed record like Misha collapses to Misha and skeleton-matches an existing Misha as identical bytes — the gate would never fire even though the distinct-record signal was the very BOM the strip just erased. Compute the skeleton BEFORE the strip, and compare BOTH the skeleton AND the raw byte-string to every existing teammate's skeleton+raw. If the skeleton matches an EXISTING teammate AND raw byte-strings differ (i.e. visually identical but distinct records), FORCE disambiguation — never silent-match. This gate ALSO runs on the zero-match upsert path (step 7 below) BEFORE a new teammate is written: compute the skeleton of the typed local-part AND the full email AND compare against every existing teammates[].email skeleton; on collision, FORCE disambiguation between the existing and proposed record — do NOT silent-upsert. Legitimate pure-script names (all-Cyrillic, all-Latin) never trigger this — no skeleton collision with anyone else. This precedence is load-bearing and overrides any "silent-allow" rule elsewhere.
- FIRST: compute raw-skeleton for the homoglyph gate above (RAW bytes, PRE-strip). THEN: NFC-normalize the typed name; strip leading/trailing whitespace (
re.sub(r"[\s]+", "", ...)— ASCII + zero-width + BOM); strip emoji. Usestr.casefold()(NOT.lower()) for all case-insensitive comparisons (handles Turkish İ/i; German ß/ss correctly). Order matters: skeleton-on-raw BEFORE strip — if this order is violated, the BOM-prefix attack in the gate prose above slips through. - First pass — casefold match against
teammates[].latin_alias. (ASCII common case — most hits land here.) - Second pass — casefold + NFC match against
teammates[].first_name. (Cyrillic users typing their own name.) - Third pass — casefold match on
teammates[].email(when user typed an email). - Single match → fill silently.
- Multiple matches → prompt disambiguation (show full names + emails).
- Zero matches → freeform prompt for full email. Validate against
^[^@\s"'\\<>]+@[^@\s"'\\<>]+\.[^@\s"'\\<>]+$AND reject any domain with non-ASCII characters (IDNA mixed-script attack defense) AND reject any domain OR any domain-label that begins withxn--(IDN punycode rejection —xn--pple-43d.comis pure ASCII but unpacks toаpple.comwith Cyrillicа, so the non-ASCII check alone is bypassable). THEN — BEFORE the upsert — run the homoglyph gate defined above on the zero-match path: compute the skeleton of the typed local-part AND the full email AND compare against every existingteammates[].emailskeleton; on collision (raw bytes differ but skeletons match — e.g. Cyrillic-local-partrа[email protected]vs existing Latin-local-part[email protected]), FORCE disambiguation between the existing and the proposed record. Do NOT silent-upsert. On failure, re-prompt with the reason. On valid email that passes ALL gates (regex + non-ASCII +xn--+ skeleton-collision), upsert intoteammates[]withsources: ["manual"]+last_validated_at: nullvia the atomic write helper. A later MCP refresh will enrich withexternal_ids.clickup+full_name. - Re-validation guard: before assigning, check
teammate.active == true. Missingactivefield is treated asfalse— a teammate record with noactivekey is BLOCKED from mention/assign resolution until explicitly activated by/clickupworkspace-sync. This is load-bearing: JSundefined == trueisfalse(safe) and PythonNone == TrueisFalse(safe), but a prose-only contract could otherwise be read as "treat missing as truthy". The explicit rule is: noactive: trueliteral present → blocked, no exceptions. Cross-references the v2 schema addition atreferences/config-schema.md→teammates[].activefield rule, which pins the same default on-read for v1 → v2 migration. On deactivated user OR missing-active user, block; force re-prompt / re-onboard. Closes PLG-clickup-F13. - In
--auto: if ambiguous or deactivated AND no memory rule resolves unambiguously → refuse with one-line reason.
List (alias → fuzzy hierarchy)
- Match user-named list (case-insensitive) against
config.lists[].aliases. - Alias hit → resolve to stored
list_id; verify still exists and not archived viaop.get_list. If renamed, update alias silently. Ifarchived: trueon the stored record, refuse with"list <name> archived — re-onboard or pick a different list". If theidis not present in theop.get_listresponse and the stored record does NOT havearchived: true, refuse with"list not found — run /clickup:reload to reconcile (id may have been deleted) or re-onboard". If renamed (id present, name differs), update the storednamesilently — alias resolution is by id, not name. - No alias hit → call
op.get_hierarchy, fuzzy-match top 3 candidates, surface for user confirm. - In
--auto: if no alias hit AND no single high-confidence fuzzy match → refuse.
Duplicate detection (before create)
- Search open tickets in target list via
op.find_tasks(include_closed=false). - Pinned similarity metric — deterministic under identical inputs. Compute the Jaccard coefficient on casefolded, NFKC-normalised word tokens between the candidate title and each open ticket's title:
- Tokenise: split on Unicode whitespace + ASCII punctuation
[\s\.,;:!?\(\)\[\]\{\}"'-/\]+`. Drop empty tokens. - Normalise each token:
unicodedata.normalize("NFKC", tok).casefold(). (Casefold — NOT.lower()— handles Turkish İ/i and German ß correctly; NFKC collapses compatibility variants like fullwidth digits.) - Stopword removal (fixed list, documented — do NOT expand arbitrarily):
{"a","an","and","are","as","at","be","by","for","from","has","have","in","is","it","its","of","on","or","that","the","to","was","were","will","with"}. English-only; source-language keyword compare at step 5 handles other languages. - Build two sets
A(candidate tokens) andB(existing-ticket tokens) from the remaining tokens. - Jaccard =
|A ∩ B| / |A ∪ B|. If|A ∪ B| == 0(both titles empty after stopword removal), overlap = 0. This metric is pinned so two runs on the same inputs always agree on whether the 70% / 89.5% / 90% bands trigger.
- Tokenise: split on Unicode whitespace + ASCII punctuation
- Interactive mode: surface top 3 at Jaccard
>= 0.70overlap. User pickscreate anyway/link to existing/cancel. --automode: only block at Jaccard>= 0.895overlap (pinned threshold — deliberately just below the 0.90 boundary to catch near-identical titles that LLM-generated phrasing drift would otherwise slip past). Below that, proceed silently. Above, HALT with "possible duplicate: <url> (Jaccard=<value>, threshold 0.895)".- Also compare source-language keywords (before translation) to catch cross-language dupes. Same metric (Jaccard on casefolded-NFKC tokens); stopword set is English-only, so non-English token sets skip stopword removal (the information content of e.g. "і", "та" is minimal but they'd dilute the set; if this becomes a problem in practice, add a per-language stopword map under config — do NOT silently expand the English list).
Idempotency (retry safety)
-
Generate UUID idempotency key per invocation. Must be UUIDv4. Pin the regex gate below on the generated key AND on any externally-provided UUID (e.g. any future
/clickup --retry <uuid>flag or task-id parse path) BEFORE any filesystem operation:^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$This is load-bearing: it rejects path-traversal payloads like
../../etc/passwd, empty strings, shell-metachar injection, and non-v4 UUIDs (v1/v3/v5) in one check. A value that fails this regex MUST NOT be used to compose any path under~/.claude/clickup/drafts/— HALT with "invalid UUID — refusing to compose draft path". Today the UUID is LLM-generated so traversal is not immediately exploitable, but this gate is the load-bearing defense for any future feature that accepts a user-provided UUID (command-line retry flag, crash-recovery path, etc.). -
Before calling
op.create_task, write draft to~/.claude/clickup/drafts/<uuid>.json— ONLY after the UUID passed the regex gate above. -
Include the key as a marker in the ticket description (hidden HTML comment:
<!-- ck:<uuid> -->) so retries can find partial successes under any transport. -
The moment
op.create_taskreturns, record the newtask_id+urlinto the draft (api_response/task_url). This is the transport-independent retry anchor — it does not depend on the marker surviving in the stored description (which is unverified forclickup-cli— seereferences/connection.md→ VERIFY-AT-IMPL). -
A create error is "outcome unknown", never "failed". On timeout / 5xx / dropped connection, the task may exist. Before ANY retry OR fallback create — including a create on a different transport per the resolver's
fallback_order— runop.find_by_marker(uuid, list_id)on the transport about to be used. If the marker (or the draft-recorded id) is found, adopt that task and return its url; do NOT create a duplicate. This closes the cross-transport double-create window.
--auto safety net (refuse conditions)
Refuse creation with a one-line reason when any of these hold:
- No source context ≥40 chars in current or previous turn (thin-context refusal)
- Assignee missing AND no memory rule resolves it
- List ambiguous (no alias hit AND no single high-confidence fuzzy match)
- Resolved assignee is deactivated
- Connection not configured (
config.connection.configured != true) — HALT with "connection not configured — run/clickup:connectfirst" (per Step 2.5; never silently pick a transport in--auto). - Capability gap with no silent fix — the ticket needs a field the resolved transport can't set (e.g.
task_type=bugonclickup-cli) AND no more-capable transport is available to borrow ANDconnection.auto_borrow != true.--autodoes NOT borrow across transports silently (the unattended user can't see the hop). Refuse with the one-liner inreferences/connection.md→ Degradation tier 3. (Interactive mode instead surfaces the borrow in the previewDegrades:row.)
The spirit of --auto is "save with whatever exists." If what exists is too little to produce a non-garbage ticket, it's better to refuse than to fabricate.
Preview + edit (interactive mode only)
Render compact draft in a monospace block:
Title: <title>
List: <list name> (<alias>)
Assignee: <full name>
Priority: <priority>
Status: <status>
Type: <task|bug|...>
Tag: <tag or "none">
Degrades: <only if a capability borrow/degradation applies, e.g. "clickup-cli can't set task_type=bug → will borrow mcp for this create">
The Degrades: line is rendered ONLY when the resolved transport cannot natively satisfy a requested field (see references/connection.md → Degradation) — it makes any cross-transport borrow visible before the create. Omit the line entirely when the active transport satisfies everything.
Offer: [1] Confirm & create [2] Edit field(s) [3] Cancel.
Edit: multi-select — user picks one or more fields; skill re-prompts only those. Mutations persist in a draft object (do NOT regenerate the preview from source — that would silently revert prior edits).
After any edit, redraw the preview and repeat. Cancel deletes the draft snapshot.
Files (user state, OUTSIDE the plugin dir — survives /plugin update)
~/.claude/shared/identity.json— SHARED with/g-event. User profile + teammate roster (first_name, latin_alias, full_name, email, external_ids, active, sources, last_validated_at). Both skills read and append.~/.claude/clickup/config.json— clickup-local. Workspace, lists + aliases, defaults, behavior flags, and theconnectionblock (chosen transport + fallback order + probe cache +rest_token_refPOINTER — never a token value). Nouserorteammateshere. Seereferences/config-schema.md→connection.~/.claude/clickup/memory.md— learned patterns + corrections (markdown, human-editable).~/.claude/clickup/drafts/— per-invocation idempotency snapshots (also hold the post-createtask_id/urlretry anchor).
Secrets: a ClickUp token (for REST or referenced from clickup-cli) is NEVER written to any file, draft, snapshot, status output, or chat — config.connection.rest_token_ref holds only a pointer (env:<NAME> or cli-config). See references/connection.md → Secret handling.
All JSON writes use atomic tmp + fsync + os.replace under fcntl.flock on a sentinel file. The canonical identity.json lock is ~/.claude/shared/identity.json.lock (NO leading dot — sibling of identity.json, not a dotfile). This path is the cross-plugin contract shared with /g-event; any deviation breaks mutual exclusion. See the reference helper in references/config-schema.md. Readers preserve unknown keys on rewrite (forward-compat with /g-event fields this plugin doesn't know about).
Schemas + examples in references/config-schema.md.
See also
references/connection.md— transports, theop.*capability contract, per-transport realization, capability/degradation matrix, the resolver, and secret handling. The only file that names a transport primitive.references/modes.md— detailed flow for every mode (including## connect)references/ticket-format.md— title + description rules with examples and anti-patternsreferences/config-schema.md— config.json (incl. theconnectionblock) and memory.md formats