AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Nacl Publish

skill-itsalt-nacl-nacl-publish · by ITSalt

|

No reviews yet
0 installs
38 views
0.0% view→install

Install

$ agentstack add skill-itsalt-nacl-nacl-publish

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-itsalt-nacl-nacl-publish)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
2mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Nacl Publish? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

/nacl-publish -- Граф -> Docmost + Excalidraw Boards

Назначение

Публикация данных из Neo4j-графа во внешние форматы:

  • Docmost -- генерация markdown из графа (через nacl-render md) и публикация страниц в Docmost
  • Excalidraw Boards -- генерация визуальных бордов из графа и привязка к страницам Docmost
Neo4j Graph
    |
    ├── nacl-render md ──> Markdown ──> Docmost Pages     (/nacl-publish docmost)
    |
    ├── nacl-render excalidraw ──> .excalidraw files       (/nacl-publish boards)
    |
    └── Excalidraw links ──> Docmost Pages updated          (/nacl-publish boards-link)

Dependencies

  • nacl-core/SKILL.md -- shared Neo4j connection, schema, ID rules
  • nacl-render/SKILL.md -- md and excalidraw rendering logic
  • Neo4j MCP: mcp__neo4j__read-cypher
  • Docmost MCP: mcp__docmost__create_page, mcp__docmost__update_page, mcp__docmost__list_spaces, mcp__docmost__get_page, mcp__docmost__list_pages, mcp__docmost__search

Config Resolution

| Параметр | Источник (по приоритету) | Fallback | |----------|--------------------------|----------| | Docmost API URL | config.yaml → docmost.api_url | Использовать env-настройки Docmost MCP | | space_id (graph scope) | config.yaml → docmost.spaces.graph.space_id > docmost.spaces.sa.space_id > manifest space_id | Спросить пользователя через mcp__docmost__list_spaces | | root_page_id (graph scope) | config.yaml → docmost.spaces.graph.root_page_id > docmost.spaces.sa.root_page_id > manifest root_page_id | Создать новую корневую страницу | | Boards directory | config.yaml → graph.boards_dir | graph-infra/boards | | Neo4j Bolt port | config.yaml → graph.neo4j_bolt_port | 3587 |

Принцип: config.yaml — первый источник для адресации Docmost. Manifest (.docmost-sync.json) отвечает только за page-level sync state (page IDs, content hashes, last_updated). Если в проекте нет отдельного spaces.graph — fallback на spaces.sa, поскольку graph и SA публикуют один и тот же набор страниц.

Invocation

/nacl-publish  [args]

Commands Overview

| Command | Description | Status | |---------|-------------|--------| | docmost | Full publish: generate md from graph, create/update all pages | Implemented | | docmost-incremental | Publish only nodes changed since last sync | Implemented | | docmost-preview | Preview one page in terminal (no publish) | Implemented | | boards | Generate all Excalidraw boards from graph | Implemented | | boards-link | Add board links/embeds to Docmost pages | Implemented | | full | Complete pipeline: docmost + boards + boards-link | Implemented |


Manifest: .docmost-sync.json

Located at project root (next to graph-infra/). This file tracks synchronization state between the graph and Docmost.

Structure

{
  "project": "project-name",
  "space_id": "019cd479-...",
  "root_page_id": "019cd6de-...",
  "last_sync": "2026-03-20T14:30:00Z",
  "pages": {
    "DE-Order": {
      "page_id": "019cd6f0-...",
      "parent_page_id": "019cd6df-33de-...",
      "content_hash": "sha256:a1b2c3d4...",
      "last_updated": "2026-03-20T14:30:00Z",
      "source_type": "entity",
      "source_id": "DE-Order"
    },
    "UC-101": {
      "page_id": "019cd6f1-...",
      "parent_page_id": "019cd6df-4348-...",
      "content_hash": "sha256:e5f6a7b8...",
      "last_updated": "2026-03-20T14:30:00Z",
      "source_type": "uc",
      "source_id": "UC-101"
    }
  },
  "sections": {
    "Архитектура": "019cd6df-2475-...",
    "Domain Model": "019cd6df-33de-...",
    "Use Cases": "019cd6df-4348-...",
    "Интерфейсы": "019cd6df-5512-...",
    "Трассировка": "019cd6df-6623-...",
    "Роли и права": "019cd6df-7734-..."
  }
}

Fields

| Field | Description | |-------|-------------| | project | Project name (from graph or user input) | | space_id | Cached Docmost space ID. Source of truth: config.yaml → docmost.spaces.graph.space_id (fallback spaces.sa.space_id). Manifest stores it for offline reference; config.yaml wins on conflict. | | root_page_id | Cached root page ID. Source of truth: config.yaml → docmost.spaces.graph.root_page_id (fallback spaces.sa.root_page_id). Manifest stores it for offline reference; config.yaml wins on conflict. | | last_sync | ISO 8601 timestamp of the last full or incremental sync | | pages | Map: logical page key -> {page_id, parent_page_id, content_hash, last_updated, source_type, source_id} | | sections | Map: section name -> Docmost page ID (section pages serve as parents) |

Content Hash

Compute SHA-256 of the generated markdown content (trimmed trailing whitespace per line):

echo "$CONTENT" | sed 's/[[:space:]]*$//' | shasum -a 256 | cut -d' ' -f1

Page Structure in Docmost

The hierarchy mirrors the project's artifact types, organized by modules:

{Project}/
├── Архитектура/
│   ├── Context Map
│   └── Модули
├── Domain Model/
│   ├── {Entity1}
│   ├── {Entity2}
│   └── ...
├── Use Cases/
│   ├── UC Index
│   ├── {UC-101}
│   ├── {UC-102}
│   └── ...
├── Интерфейсы/
│   ├── {Form1}
│   ├── {Form2}
│   └── ...
├── Трассировка/
│   └── BA -> SA Matrix
└── Роли и права/
    └── Permission Matrix

Section-to-Graph Mapping

| Section | Graph Source | nacl-render command | |---------|-------------|---------------------| | Context Map | Module nodes + DEPENDS_ON edges | md domain-model (module overview) | | Domain Model / {Entity} | DomainEntity + attrs + rels | md entity | | UC Index | All UseCase nodes | md uc-index | | Use Cases / {UC} | UseCase + steps + forms | md uc | | Интерфейсы / {Form} | Form + fields + mapping | md form | | Трассировка | BA->SA handoff edges | md traceability | | Роли и права | SystemRole + Permission nodes | Custom query (see below) |


Pre-flight Checks

Before any Docmost command, verify:

  1. Docmost MCP available? Call mcp__docmost__list_spaces.
  • If fails -> ERROR: Docmost MCP not available. Check that the MCP server is connected.
  1. Neo4j available? Call mcp__neo4j__read-cypher with RETURN 1.
  • If fails -> ERROR: Neo4j not available. Check config.yaml → graph.neo4j_bolt_port (default: 3587) and ensure Docker is running.
  1. Graph has data? Query:

``cypher MATCH (n) WITH labels(n) AS lbls, count(*) AS cnt UNWIND lbls AS lbl RETURN lbl, sum(cnt) AS total ORDER BY lbl ``

  • If empty -> WARNING: Graph is empty. Run /nacl-ba-from-board or seed data first.
  1. Manifest exists? Read .docmost-sync.json.
  • If missing and command is not docmost (full) -> suggest running /nacl-publish docmost first.

Pre-publish reconciliation gate

nacl-publish writes externally-visible artifacts (Docmost pages, Excalidraw boards). Publishing inconsistent state to Docmost makes the drift visible to stakeholders and harder to retract than an internal .tl/ artifact. This gate is mandatory before any Docmost write (full docmost, docmost-incremental, and boards-link commands). Preview-only commands (docmost-preview) are exempt.

The gate compares the live graph (the source publish is about to derive from) against .tl/changelog.md (the record of what has been shipped). If these two disagree, the publish is refused.

Live graph reads only — no .cypher export fallback. Exports are stale by definition the moment the next graph mutation lands, and a publish run that consumed an export would push stale pages to Docmost. If the graph container is unreachable, the gate emits Status: BLOCKED with workflow detail graph_unavailable and the publish refuses. The fix is to bring the graph container up (docker compose up -d from graph-infra/), not to swap in an export. Operators who must publish under an unreachable graph file a W4 signed exception against gate graph-stale — the exception does NOT re-enable export fallback; it accepts that the publish ran against unreconciled state.

Step 1: Reach the live graph

Use the project-resolved Bolt endpoint:

RETURN 1 AS ok

On failure: HALT.

HALT — graph_unavailable (pre-publish reconciliation).

The live Neo4j graph is unreachable. A stale .cypher export is
NOT an acceptable substitute — publishing it to Docmost would
push out-of-date pages to stakeholders.

Resolution: bring the project graph container up and rerun, or
file a signed exception (.tl/exceptions/) against gate
`graph-stale`.

Status: BLOCKED (workflow detail: graph_unavailable)

Step 2: Read graph state and changelog

// Single round-trip read of the comparison surface:
OPTIONAL MATCH (fr:FeatureRequest)
WITH collect({ id: fr.id, release_tag: fr.release_tag }) AS fr_list
OPTIONAL MATCH (uc:UseCase)
WITH fr_list, collect(uc.id) AS uc_list
OPTIONAL MATCH (t:Task)
WITH fr_list, uc_list, collect({
  id: t.id, release_tag: t.release_tag,
  evidence: coalesce(t.verification_evidence, '')
}) AS task_list
RETURN fr_list, uc_list, task_list

Read .tl/changelog.md and parse the most recent released section: every FR-NNN / UC-NNN mentioned, the release tag header, and any release-status.json cross-reference.

Step 3: Cross-checks (publish-scope subset)

Reuses pairs from the W5 reconciliation taxonomy but scoped to the publish target:

| Pair | Sources | Assertion | |---|---|---| | P-P1 | .tl/changelog.md released FR list vs graph FeatureRequest | every released FR-NNN in the latest changelog section exists as FeatureRequest {id: 'FR-NNN'} in the live graph. | | P-P2 | .tl/changelog.md released UC list vs graph UseCase | every released UC-NNN in the latest changelog section exists as UseCase {id: 'UC-NNN'}. | | P-P3 | .tl/release-status.json.release_tag vs graph release_tag properties | if the JSON release tag is non-null, the live graph carries that tag on ≥1 FeatureRequest or Task node. (Skip if .tl/release-status.json is absent — the gate does not require it; absence is logged.) |

An assertion that fails under no active signed exception is a hard refusal.

Step 4: Refuse on disagreement

If any of P-P1 … P-P3 fails, refuse the publish:

REFUSED — changelog and live graph disagree.

nacl-publish writes externally-visible Docmost pages and refuses
to publish stale or contradictory state.

P-P1  changelog.md vs live graph FeatureRequest
      .tl/changelog.md (section "0.18.0 ...") references FR-007;
      live graph has NO FeatureRequest {id: 'FR-007'}.

Active signed exceptions against `graph-stale`: none.

Resolution options:
  [1] Run /nacl-tl-conductor (or /nacl-sa-feature FR-007) to
      reconcile graph state with the changelog claim.
  [2] If the changelog is correct and the graph is genuinely
      stale, file a signed exception against `graph-stale` and
      rerun.

Status: BLOCKED (workflow detail: publish-drift)

Step 5: Record reconciliation evidence

On PASS (or PASS-under-exception), write the publish-side reconciliation evidence:

.tl/reconciliation/-publish.json

Same schema as the conductor's reconciliation artifact (see /home/project-owner/projects/NaCl/.tl/reconciliation/ _template.json) but with sources_checked scoped to the publish subset and terminal_status recorded against the publish gate specifically.

Only on terminal_status == VERIFIED does the publish proceed to the actual Docmost write steps.


Command: /nacl-publish docmost

Full publish -- generate markdown from graph for every artifact and create/update all pages in Docmost.

Workflow

Step 1: Init/Load     Step 2: Create        Step 3: Generate &      Step 4: Save
── manifest ──── ->  ── section pages ── ->  ── publish pages ── ->  ── manifest ──

Step 1: Resolve Docmost target, then Initialize or Load Manifest

Step 1a: Read config.yaml → docmost (first source for space_id / root_page_id):

  1. Try docmost.spaces.graph.space_id and docmost.spaces.graph.root_page_id.
  2. If spaces.graph is missing, fall back to docmost.spaces.sa.space_id / spaces.sa.root_page_id (graph and SA publish into the same Docmost area in most projects).
  3. If both are empty -> mark space_id and root_page_id as "needs prompt".

Step 1b: If .docmost-sync.json does NOT exist (first run):

  1. If space_id resolved from config.yaml:
  • Validate by calling mcp__docmost__list_spaces -- confirm the space still exists.
  • Use the resolved space_id directly. Do not prompt the user.
  1. Otherwise ask the user:
  • space_id -- which Docmost space to publish into? (list spaces via mcp__docmost__list_spaces)
  • After the user chooses, suggest writing the value to config.yaml → docmost.spaces.graph.space_id so the next run is non-interactive.
  1. If root_page_id resolved from config.yaml:
  • Use it as-is, do not create a new root page.
  1. Otherwise create a new root page:

`` mcp__docmost__create_page( title: "{project} -- Спецификация", content: "# {project}\n\nАвтоматически сгенерированная спецификация из графа знаний.", spaceId: "{space_id}" ) ` Save returned id as rootpageid and **suggest writing it to config.yaml → docmost.spaces.graph.rootpageid`**.

  1. Initialize manifest with empty pages and sections. Persist space_id / root_page_id into the manifest as cache, but config.yaml remains the source of truth on subsequent runs.

Step 1c: If .docmost-sync.json EXISTS:

  1. Read manifest.
  2. Reconcile space_id / root_page_id:
  • If config.yaml has values -> use them. If they differ from manifest, log a warning and update manifest to match config.yaml (manifest is a cache, not the source of truth).
  • If config.yaml is empty -> fall back to manifest values.
  1. Validate space_id by calling mcp__docmost__list_spaces -- confirm the space still exists.
  2. Continue with existing section pages from sections map.

Step 2: Create Section Pages

For each section in the hierarchy, create a parent page if not already in sections:

| Section | Title | Parent | |---------|-------|--------| | Архитектура | Архитектура | rootpageid | | Domain Model | Domain Model | rootpageid | | Use Cases | Use Cases | rootpageid | | Интерфейсы | Интерфейсы | rootpageid | | Трассировка | Трассировка | rootpageid | | Роли и права | Роли и права | rootpageid |

For each section not in sections map:

mcp__docmost__create_page(
  title: "{section title}",
  content: "# {section title}\n\nРаздел спецификации.",
  spaceId: "{space_id}",
  parentPageId: "{root_page_id}"
)

Save returned id to sections[title].

Order of creation: Архитектура, Domain Model, Use Cases, Интерфейсы, Трассировка, Роли и права. Sequential calls (Docmost does not guarantee ordering with parallel requests).

Step 3: Generate Content and Publish Pages

Process artifacts in this order:

3a: Architecture -- Context Map
  1. Use nacl-render md domain-model logic to generate the full domain model overview.
  2. Also generate a module list page:

Query modules: ``cypher MATCH (m:Module) OPTIONAL MATCH (m)-[:CONTAINS_ENTITY]->(de:DomainEntity) OPTIONAL MATCH (m)-[:CONTAINS_UC]->(uc:UseCase) RETURN m.id AS id, m.name AS name, m.description AS description, count(DISTINCT de) AS entity_count, count(DISTINCT uc) AS uc_count ORDER BY m.id ``

Generate markdown: ```markdown # Модули

| ID | Модуль | Описание | Сущностей | Use Cases | |----|--------|----------|-----------|-----------| | {id} | {name} | {description} | {entitycount} | {uccount} | ```

  1. Publish to section Архитектура:
  • Page "Context Map" -- create_page or update_page under sections["Архитектура"]
  • Page "Модули" -- under sections["Архитектура"]
3b: Domain Model -- Entities
  1. Query all DomainEntities:

```cypher MATCH (de:DomainEntity) RETURN de.id AS id, de.name AS name

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.