| name | nacl-core |
| description | Shared references, templates, and utilities for all nacl-* skills.
Not directly invocable — provides Neo4j connection conventions,
schema references, ID format rules, and Excalidraw standards.
|
Graph Core — Shared References
This skill is NOT invocable by users. It provides shared references, templates, and utilities for all nacl-* skills.
Neo4j Connection
All graph skills use Neo4j MCP tools:
mcp__neo4j__read-cypher — read-only queries
mcp__neo4j__write-cypher — create/update/delete
mcp__neo4j__get-schema — introspect schema
Connection is managed by the MCP server (configured in .mcp.json at project root).
Skills do NOT pass connection strings — they just call MCP tools.
Liveness probe and start/fix tool: $(nacl-home)/nacl-core/scripts/graph-doctor.mjs
(node "$(nacl-home)/nacl-core/scripts/graph-doctor.mjs" [--fix]) —
reports NACL_GRAPH_DOCTOR: status=UP|DOWN|NOT_NACL, and with --fix starts
the local container / relaunches the remote sidecar. Use it in remediation
instead of raw docker commands.
Graph Config Resolution (execute at skill start)
Every graph skill MUST read config.yaml at the start to resolve graph settings.
Steps:
- Resolve
$NACL_PROJECT_ROOT (see Project Root Resolution below) and read config.yaml from there
- Extract the
graph section
- If
graph section missing or config.yaml absent → use all defaults
- Store resolved values for use in error messages and board paths:
$neo4j_bolt_port = graph.neo4j_bolt_port || 3587
$neo4j_http_port = graph.neo4j_http_port || 3574
$neo4j_password = graph.neo4j_password || "neo4j_graph_dev"
$excalidraw_port = graph.excalidraw_port || 3580
$boards_dir = graph.boards_dir || "graph-infra/boards"
$container_prefix = graph.container_prefix || project.name || "graph"
Configuration Resolution Table
| Data | Source priority |
|---|
| Neo4j Bolt port | graph.neo4j_bolt_port > fallback 3587 |
| Neo4j HTTP port | graph.neo4j_http_port > fallback 3574 |
| Neo4j password | graph.neo4j_password > fallback "neo4j_graph_dev" |
| Excalidraw port | graph.excalidraw_port > fallback 3580 |
| Boards directory | graph.boards_dir > fallback "graph-infra/boards" |
| Container prefix | graph.container_prefix > project.name > fallback "graph" |
Important:
- Do NOT pass connection strings to MCP tools — the MCP server handles this
- These values are ONLY for: error messages, Docker commands, board file paths, Excalidraw URLs
- When showing error messages, always use resolved values:
bolt://localhost:{$neo4j_bolt_port}
Project Root Resolution (worktree-safe)
Claude Code Desktop parallel sessions run in linked git worktrees
(<project>/.claude/worktrees/...). Configuration and infrastructure live at
the MAIN checkout root, not the worktree root. Resolve the root before reading
config.yaml or composing docker commands:
COMMON=$(git rev-parse --path-format=absolute --git-common-dir 2>/dev/null)
if [ -n "$COMMON" ]; then NACL_PROJECT_ROOT=$(dirname "$COMMON"); else NACL_PROJECT_ROOT=$(pwd); fi
Rule of thumb: configuration and infra (config.yaml, graph-infra/,
.mcp.json, .env) resolve against $NACL_PROJECT_ROOT; versioned content
(boards, docs, source code) resolves against the checkout you are working in.
The JS authority for this resolution is resolveProjectRoot in
$(nacl-home)/nacl-core/scripts/graph-doctor.mjs, pinned by its test.
Graph-Down HALT (canonical)
When a graph skill HALTs because Neo4j is unreachable, it MUST use the
canonical message below, preceded by the marker comment
<!-- nacl-graph-halt -->. The lint gate scripts/check-graph-halt-snippet.sh
enforces byte-identical copies across skills — edit here first, then re-sync
the copies.
Full form (blockquote HALT sites):
Neo4j is not reachable at bolt://localhost:{$neo4j_bolt_port}.
Tell me "start the graph" and I will run node "$(nacl-home)/nacl-core/scripts/graph-doctor.mjs" --fix via Bash (works in Claude Code Desktop and CLI).
Or start it yourself from the project root (main checkout, not a worktree):
- local mode:
docker compose -f graph-infra/docker-compose.yml up -d --- if Docker Desktop is not running, open the Docker Desktop app first
- remote mode: relaunch the sidecar
~/.nacl/sidecar/<project_scope>.sh (Windows: %USERPROFILE%\.nacl\sidecar\<project_scope>.cmd)
The line after the block is the per-skill tail — one of:
> This skill requires Neo4j --- cannot proceed without it. (default)
> Cannot proceed with sync. (nacl-ba-sync)
Compact form (table cells and ERROR: print strings — from-board, render, publish):
Neo4j is not reachable at bolt://localhost:{$neo4j_bolt_port}. Tell me "start the graph" (I will run node "$(nacl-home)/nacl-core/scripts/graph-doctor.mjs" --fix) or start it from the project root: docker compose -f graph-infra/docker-compose.yml up -d (local) / ~/.nacl/sidecar/<project_scope>.sh (remote).
Concurrent Sessions (Desktop parallel sessions / worktrees)
Parallel Claude Code sessions of one project share ONE graph (one local
container or one remote scope). The database layer is safe (Neo4j per-write
locking), but semantic lost-updates are possible when two sessions run the
same writing BA/SA skill over the same nodes. Known limitation:
- Run at most one WRITING BA/SA skill at a time per project. Read-only skills
and validators are safe in parallel.
- TL task claiming in remote mode is protected by the claim-lock
(
$(nacl-home)/nacl-core/scripts/claim-task.mjs).
- Board files under
graph-infra/boards/ are versioned per-checkout: two
worktrees syncing the same board into one graph will fight — run
/nacl-ba-sync from the main checkout only.
Schema Reference
Schema files: graph-infra/schema/
ba-schema.cypher — BA layer (13 node types: ProcessGroup, BusinessProcess, WorkflowStep, BusinessEntity, EntityAttribute, EntityState, BusinessRole, BusinessRule, GlossaryTerm, SystemContext, Stakeholder, ExternalEntity, DataFlow)
sa-schema.cypher — SA layer (12 node types)
tl-schema.cypher — TL layer (3 node types)
Task.verification_evidence — Taxonomy
Task.verification_evidence is a string property on TL Task nodes that records how a task was verified. It is read by nacl-tl-release (Step 2 pre-merge gate and the final report Evidence-level column) and must be written by every skill that advances a Task to a terminal status. Leaving it NULL on a done task causes the release skill to surface a "Verification gap" — that is a data-integrity failure, not normal output.
Values
| Value | When to write | Written by |
|---|
test-GREEN:<artifact_path> | PASS + regression test ran RED→GREEN. <artifact_path> is a repo-relative path to the test file (e.g. apps/web/src/__tests__/funnel.spec.ts) or to the task's regression-test record (.tl/tasks/<TASK_ID>/regression-test.md). | conductor Phase 3, tl-full Phase 7, tl-fix terminal step, tl-deliver, tl-hotfix |
verify-GREEN:<artifact_path> | Workflow-B (infrastructure) PASS: the documented verification command re-ran cleanly after the change and all expected resources were confirmed. <artifact_path> is a repo-relative path to the committed verification record (.tl/tasks/<TASK_ID>/verification.md, written by nacl-tl-dev step B.3.5). Derived from the sub-skill report line Regression test: verification: <path>. | conductor Phase 3, tl-full Phase 1 (TECH tasks) |
test-UNVERIFIED | Code applied but RED→GREEN not confirmed (no regression test, or sub-skill returned UNVERIFIED). Paired with t.status = 'verified-pending'. | Same writers as above |
no-test | Legacy — no current producer. Was: PASS under the explicit user NO-TEST override; the flag was REMOVED in W4-blocking-release, so no skill writes this value anymore. Readers keep parsing it for graphs written before W4. | (none since W4) |
null (unset) | Reserved for t.status ∈ {'failed', 'blocked'} — those tasks are excluded from release scope, so an evidence string would be misleading. | n/a |
Format rules
- Single string, no JSON.
test-GREEN and verify-GREEN payloads after : are forward-slash repo-relative paths; no quoting, no scheme.
- For
test-GREEN, use the test file path when one exists, otherwise the .tl/tasks/<TASK_ID>/regression-test.md artifact path.
- For
verify-GREEN, the payload is always the .tl/tasks/<TASK_ID>/verification.md record path.
test-UNVERIFIED and no-test carry no payload — exactly those literal strings.
Reader contract
nacl-tl-release parses verification_evidence like this:
- Prefix
test-GREEN: → Evidence level = test-GREEN, path extracted for the report column.
- Prefix
verify-GREEN: → Evidence level = verify-GREEN, path extracted for the report column. NOT a verification gap.
- Literal
test-UNVERIFIED → Evidence level = test-UNVERIFIED.
- Literal
no-test → Evidence level = no-test (legacy graphs only).
NULL / empty / unrecognised → Evidence level = unknown → "Verification gap" footer.
Sources: nacl-tl-release/SKILL.md § Output → "Per-UC evidence-level values".
Writer obligation
Every skill in this list MUST set verification_evidence in the same Cypher statement that sets t.status to a terminal value:
nacl-tl-conductor — Phase 3 graph-write block (PASS / UNVERIFIED writes).
nacl-tl-full — Phase 7 final aggregation graph-write.
nacl-tl-fix — terminal graph-write after fix verified.
nacl-tl-deliver — stamping after delivery validation.
nacl-tl-hotfix — hotfix Task node graph-write.
Skills that only read terminal status (nacl-tl-release, nacl-tl-deploy, nacl-tl-reconcile) do NOT write this field. They may, however, gate on it: if a writer leaves it NULL, an orchestrator gate (e.g. conductor Phase 4) MUST HALT and surface the contract violation.
Graph Skills Registry
BA Layer (14 skills)
| Skill | Purpose |
|---|
| nacl-ba-context | System boundaries → Neo4j |
| nacl-ba-process | Business process map → Neo4j |
| nacl-ba-workflow | Activity diagrams → Neo4j |
| nacl-ba-entities | Entity catalog → Neo4j |
| nacl-ba-roles | Business roles → Neo4j |
| nacl-ba-glossary | Glossary → Neo4j |
| nacl-ba-rules | Business rules → Neo4j |
| nacl-ba-validate | L1-L8 + XL1-XL5 validation |
| nacl-ba-handoff | BA→SA traceability |
| nacl-ba-full | Full BA orchestrator (10 phases) |
| nacl-ba-from-board | Board orchestrator (import/sync/enrich/validate/handoff) |
| nacl-ba-import-doc | Document → Excalidraw |
| nacl-ba-analyze | Board completeness analysis |
| nacl-ba-sync | Board → Neo4j sync |
SA Layer (9 skills)
| Skill | Purpose |
|---|
| nacl-sa-architect | Module decomposition |
| nacl-sa-domain | Domain model |
| nacl-sa-roles | Role model + permissions |
| nacl-sa-uc | UC registry + detailing |
| nacl-sa-ui | UI architecture |
| nacl-sa-validate | L1-L13 + XL6-XL9 validation |
| nacl-sa-feature | Incremental feature specification |
| nacl-sa-finalize | Statistics, ADR, readiness |
| nacl-sa-full | Full SA orchestrator (10 phases) |
TL Layer (7 skills)
| Skill | Purpose |
|---|
| nacl-tl-plan | Task planning from graph |
| nacl-tl-intake | Graph-aware triage |
| nacl-tl-status | Status + SA coverage |
| nacl-tl-next | Next task + SA context |
| nacl-tl-full | Full lifecycle orchestrator |
| nacl-tl-conductor | Batch process manager |
| nacl-tl-hotfix | Emergency hotfix to main |
Output (2 skills)
| Skill | Purpose |
|---|
| nacl-render | Graph → Markdown + Excalidraw |
| nacl-publish | Graph → Docmost |
Query library: graph-infra/queries/
ba-queries.cypher, sa-queries.cypher, handoff-queries.cypher, validation-queries.cypher, tl-queries.cypher
Excalidraw File-Based Workflow
Board files stored in: {$boards_dir}/*.excalidraw
(where $boards_dir is resolved from config.yaml → graph.boards_dir, default: graph-infra/boards)
AI reads/writes .excalidraw JSON directly (no MCP server needed).
Excalidraw JSON Format
{
"type": "excalidraw",
"version": 2,
"elements": [...],
"appState": {"viewBackgroundColor": "#ffffff"}
}
Element Types Used
| BA Concept | Excalidraw Type | backgroundColor | customData.nodeType |
|---|
| WorkflowStep (бизнес-функция) | rectangle | #e8f5e9 (green) | WorkflowStep |
| WorkflowStep (автоматизируется) | rectangle | #e3f2fd (blue) | WorkflowStep |
| Decision | diamond | #fff3e0 (orange) | Decision |
| BusinessEntity / Document | rectangle | #f3e5f5 (purple) | BusinessEntity |
| BusinessRole (swimlane label) | rectangle | #fafafa (grey) | BusinessRole |
| Annotation / Note | text | — | Annotation |
| Connection | arrow | — | — |
Color Coding for Confidence
| Confidence | strokeColor | Meaning |
|---|
| High (confirmed) | #2e7d32 (green) | Clearly identified in source |
| Medium (assumption) | #f57f17 (amber) | AI inferred from context |
| Low (needs info) | #c62828 (red) | Missing information |
customData Structure
Every shape element MUST have customData:
{
"customData": {
"nodeId": "BP-001-S03",
"nodeType": "WorkflowStep",
"confidence": "high",
"sourceDoc": "process.docx",
"sourcePage": 3,
"synced": false
}
}
Excalidraw Element Template (rectangle with text)
To create a labeled rectangle, you need TWO elements:
- The shape (rectangle/diamond) with
boundElements pointing to text
- The text element with
containerId pointing back to shape
{
"id": "rect-001",
"type": "rectangle",
"x": 100, "y": 200,
"width": 200, "height": 60,
"strokeColor": "#2e7d32",
"backgroundColor": "#e3f2fd",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"seed": 12345,
"version": 1,
"versionNonce": 1,
"isDeleted": false,
"groupIds": [],
"frameId": null,
"boundElements": [{"id": "text-001", "type": "text"}],
"updated": 1,
"link": null,
"locked": false,
"customData": {
"nodeId": "BP-001-S03",
"nodeType": "WorkflowStep",
"confidence": "high",
"synced": false
}
}
Layout Guidelines
- Swimlanes: horizontal bands, 200px height each
- Steps: left-to-right flow, 220px spacing
- Documents: right column, 50px margin from steps
- Decisions: centered between alternative paths
- Arrows: use startBinding/endBinding for connected arrows
Board Meta Sidecar (<board>.meta.json)
Every .excalidraw board file may have a companion sidecar file at {$boards_dir}/<board>.meta.json. This file is the source of truth for "when was this board last generated from the graph" and "when was it last synced back". The NaCl Analyst Tool (analyst-tool/) reads the sidecar to display sync status in its sidebar tree — without the sidecar the tool cannot determine freshness. Skills are the canonical writers of this file; the tool only reads it (and may write optimistically as a fallback when a skill run fails partway through).
Schema
{
"lastGeneratedAt": "2026-05-03T18:42:00.000Z",
"lastGeneratedBy": "nacl-render",
"lastSyncedAt": "2026-05-03T19:00:00.000Z",
"lastSyncStatus": "ok",
"lastSyncRunId": "r-95f71b58",
"contentHashAtLastSync": "sha256:<64-hex-chars>"
}
All six fields are nullable (null when not yet set). Dates are ISO-8601 UTC strings.
Field Semantics
| Field | Type | Writer | Null means |
|---|
lastGeneratedAt | ISO-8601 string | null | nacl-render | board was never generated from graph |
lastGeneratedBy | string | null | nacl-render | board was never generated from graph |
lastSyncedAt | ISO-8601 string | null | nacl-ba-sync (success only) | board was never synced to graph |
lastSyncStatus | "ok" | "failed" | null | nacl-ba-sync | no sync has been attempted |
lastSyncRunId | string | null | nacl-ba-sync | invoked directly (not via analyst-tool), or no run ID available |
contentHashAtLastSync | "sha256:<hex>" | null | nacl-render and nacl-ba-sync (success only) | no render or sync has completed |
Who Writes What
| Action | Fields written |
|---|
| Render from graph (success) | lastGeneratedAt, lastGeneratedBy, contentHashAtLastSync |
| Sync to graph (success) | lastSyncedAt, lastSyncStatus = "ok", lastSyncRunId, contentHashAtLastSync |
| Sync to graph (failure) | lastSyncStatus = "failed", lastSyncRunId |
| Analyst-tool save | nothing — the tool does not write meta; hasUnsyncedEdits is derived at read time from mtime + recomputed hash |
Content Hash Algorithm
contentHashAtLastSync is a SHA-256 hash of a normalized representation of the board scene. The algorithm (canonical TypeScript implementation: analyst-tool/server/src/services/meta.ts, function computeBoardHash):
- Extract three top-level fields from the
.excalidraw JSON: elements (array), appState (object), files (object).
- Normalize
appState: keep only viewBackgroundColor and gridSize; all other keys are dropped. Missing keys default to null.
- Normalize each element in
elements:
- Strip the following volatile per-element keys:
version, versionNonce, seed, updated.
- Sort the remaining keys alphabetically (ascending).
- Produce a new object with only the sorted, non-volatile keys.
- Assemble the normalized scene object:
{
"elements": [ ...normalized elements... ],
"appState": { "viewBackgroundColor": <value or null>, "gridSize": <value or null> },
"files": <files object or {}>
}
- Serialize with
JSON.stringify (no extra whitespace, no key sorting at the top level — the top-level key order is elements, appState, files as shown above).
- Hash the UTF-8 bytes of the JSON string with SHA-256.
- Prefix the lowercase hex digest with
sha256:.
Result format: "sha256:<64 lowercase hex characters>".
Any re-implementation (Python, shell, etc.) must produce the same byte sequence as the TypeScript reference for identical input. The key sort in step 3 is per-element, not recursive — nested objects inside element properties are not sorted.
Atomic Write Requirement
To prevent the analyst-tool fs-watcher from reading a half-written file, the sidecar must be written atomically:
- Write the new JSON content to
<board>.meta.json.tmp (same directory as the board).
- Rename
<board>.meta.json.tmp → <board>.meta.json.
On POSIX filesystems rename(2) is atomic. On Windows the tool is expected to run on macOS/Linux so this guarantee holds.
Merge Rule
A skill never blindly overwrites the entire sidecar. Before writing, it reads the existing sidecar (or uses all-null defaults if missing), merges only the fields it owns (per the "Who Writes What" table above), and writes the merged result. This ensures nacl-render does not destroy lastSyncedAt set by a previous nacl-ba-sync run, and vice versa.
Analyst Tool Integration
The meta sidecar is also read by the local NaCl Analyst Tool (analyst-tool/), which uses it to display sync status in the sidebar tree. The tool writes meta optimistically as a fallback in case a skill run fails partway, but the skills themselves are the canonical writers per the table above.
Project Initialization
The /nacl-init skill creates or updates CLAUDE.md, config.yaml, and graph infrastructure for a project. Re-running /nacl-init on an existing project is idempotent and triggers automatic migration of legacy infrastructure (removing stale excalidraw containers, creating missing graph-infra/boards/, injecting missing project.id / project.name fields) — see nacl-init/SKILL.md § Auto-migrate Legacy Artefacts for details.
Project Registry (~/.nacl/projects.json)
The NaCl Analyst Tool discovers projects through a per-user registry at ~/.nacl/projects.json (override via NACL_HOME env var — see docs/configuration.md). The registry is written only by nacl-init (Step 2d); the analyst-tool and all other skills only read it. The canonical TypeScript types (ProjectRecord, ProjectRegistry) and the atomic-write implementation live in analyst-tool/server/src/services/project-registry.ts. Every entry has the fields id, name, root, createdAt, and lastUsed; the top-level object carries version: 1 and activeProjectId. Skills must never write to this file directly — invoke /nacl-init to register or refresh a project.
ID Generation Rules
| Layer | Format | Example | Counter |
|---|
| BA Process Group | GPR-NN | GPR-01 | Global sequential |
| BA Process | BP-NNN | BP-001 | Global sequential |
| BA Workflow Step | {BP}-S{NN} | BP-001-S03 | Per-process |
| BA Entity | OBJ-NNN | OBJ-001 | Global sequential |
| BA Entity Attribute | {OBJ}-A{NN} | OBJ-001-A01 | Per-entity |
| BA Entity State | {OBJ}-ST{NN} | OBJ-001-ST01 | Per-entity |
| BA Role | ROL-NN | ROL-01 | Global sequential |
| BA Rule | BRQ-NNN | BRQ-001 | Global sequential |
| BA Glossary | GLO-NNN | GLO-001 | Global sequential |
| System Context | SYS-NNN | SYS-001 | Global sequential |
| Stakeholder | STK-NN | STK-01 | Global sequential |
| External Entity | EXT-NN | EXT-01 | Global sequential |
| Data Flow | DFL-NNN | DFL-001 | Global sequential |
| Decision (provenance) | DEC-NNN | DEC-001 | Global sequential |
| Screen | SCR-{PascalName} | SCR-ResultViewer | Name-based |
| Screen State | SCRST-{Screen}-{State} | SCRST-ResultViewer-Loading | Per-screen, name-based |
| Screen Event | SCREV-{Screen}-{Event} | SCREV-ResultViewer-OnRetry | Per-screen, name-based |
| Screen Transition | SCRTR-{Screen}-NNN | SCRTR-ResultViewer-001 | Per-screen sequential |
| Screen Effect | SCREF-{Screen}-NNN | SCREF-ResultViewer-001 | Per-screen sequential |
| Analytics Event | ANEV-{Name} | ANEV-ResultViewed | Name-based |
| Behavior Slice | SLC-{NNN}-{PascalName} | SLC-006-HappyPath | Per-UC, name-based (latin) |
| Domain Error | ERR-{UPPER_SNAKE_CODE} | ERR-PROMO_NOT_FOUND | Code-based (latin) |
| Error Presentation | ERRP-{CODE}-{PascalName} | ERRP-PROMO_NOT_FOUND-Inline | Per-error, kind/context-based |
| Cache Policy | CACHE-{PascalName} | CACHE-ResultMediaIndexedDb | Name-based (latin) |
| Degradation Rule | DEG-{NNN}-{PascalName} | DEG-006-OfflineRestore | Per-UC, name-based (latin) |
Screen state machine ids (SCR-* family) belong to the SA screen state
machine written by nacl-sa-ui state-machine (labels :Screen,
:ScreenState, :ScreenEvent, :Transition (reified), :ScreenEffect,
:AnalyticsEvent). {Screen} in child ids is the PascalName part of the
Screen id without the SCR- prefix. See graph-infra/schema/sa-schema.cypher
§ 3-bis and the sa_screen_machine named query.
Behavior Slice (SLC-{NNN}-{PascalName}) is the graph-native acceptance
scenario (label :Slice), written by nacl-sa-uc slices. {NNN} is the
parent UC number (UC-006 → 006) — the infix makes repeated scenario names
(every UC has a HappyPath) unique and enables scoped filtering by
STARTS WITH 'SLC-NNN-'. {PascalName} derives from a latin short scenario
name (kebab/snake/space → PascalCase); the display name property may stay
in the project language. See graph-infra/schema/sa-schema.cypher § 3-ter
and the sa_uc_slices named query.
Domain Error (ERR-{UPPER_SNAKE_CODE}) is the transport-independent
domain-error record (label :DomainError), written by nacl-sa-uc errors
(and by nacl-tl-fix when an L2/L3 fix uncovers a missing error branch).
{UPPER_SNAKE_CODE} equals the code property — the join key to the API
envelope and the codebase; codes are domain-prefixed (PROMO_NOT_FOUND,
never bare NOT_FOUND) so module catalogs stay collision-free. The catalog
is module-scoped ((:Module)-[:HAS_ERROR]->): the same error is shared by
endpoints of different UCs — MERGE by id, never duplicate per UC.
Error Presentation (ERRP-{CODE}-{PascalName}) is one user-facing
presentation of that error (label :ErrorPresentation); {PascalName}
derives from the presentation kind/context by the same latin-PascalName rule
as Screen states. See graph-infra/schema/sa-schema.cypher § 3-quater and
the sa_uc_errors named query.
Cache Policy (CACHE-{PascalName}) is the caching policy of one server
data surface (label :CachePolicy), written by nacl-sa-uc resilience (and
by nacl-tl-fix when an L2/L3 fix uncovers a missing cache/invalidation
policy). {PascalName} derives from the cached surface + storage by the
latin-PascalName rule (CACHE-ResultMediaIndexedDb, CACHE-QuotaMemory).
The catalog is module-scoped ((:Module)-[:HAS_CACHE]->): real caches are
shared infrastructure of a bounded context — MERGE by id, never duplicate
per UC. Degradation Rule (DEG-{NNN}-{PascalName}) is one UC's
degradation behavior (label :DegradationRule); {NNN} is the parent UC
number — the infix enables scoped filtering by STARTS WITH 'DEG-NNN-',
exactly like SLC. See graph-infra/schema/sa-schema.cypher § 3-quinquies
and the sa_uc_resilience named query.
Decision (DEC-NNN) is the graph-native provenance record (label :Decision).
Written by nacl-sa-feature (feature changes), nacl-tl-fix (L2/L3 fixes), and
nacl-sa-finalize (architecture ADRs + backfill). It is NOT a Requirement —
keeping it a distinct label avoids polluting the L3 "every UC needs a Requirement"
check and the planner's requirement queries. See graph-infra/schema/sa-schema.cypher
§ Decision properties and the sa_decisions_for_node / sa_timeline_of_why queries.
Canonical ID format (single authority). Padding is apoc.text.lpad(toString(n), width, '0')
— a true left-pad that never truncates. The JS authority is $(nacl-home)/nacl-core/scripts/nacl-ids.mjs
(node $(nacl-home)/nacl-core/scripts/nacl-ids.mjs <kind> <next_int> [parentId]), pinned by
$(nacl-home)/nacl-core/scripts/nacl-ids.test.mjs; use it when formatting an id OUTSIDE Cypher. In-Cypher,
use apoc.text.lpad (identical output). Never use right('0…'+toString(n), width) — it
truncates the high digits at n≥10^width (right('00100',2) → '00'), the bug that once made
nacl-ba-sync diverge from nacl-ba-process/-entities/-roles.
To get next available ID:
// Example: next BusinessProcess ID — canonical lpad form (== nacl-ids.mjs)
MATCH (bp:BusinessProcess)
WITH max(toInteger(replace(bp.id, 'BP-', ''))) AS maxNum
RETURN 'BP-' + apoc.text.lpad(toString(coalesce(maxNum, 0) + 1), 3, '0') AS nextId
Skill Modifier Conventions
Skills accept modifiers — arguments that change behavior. See Skill Modifiers Reference for the full catalog.
Three Paradigms
| Paradigm | When to Use | Syntax | Example |
|---|
| Mode (positional) | 2-5 mutually exclusive workflow branches | /skill MODE | /nacl-sa-domain IMPORT_BA |
| Subcommand | 3+ distinct operations under one namespace | /skill command [args] | /nacl-ba-from-board sync |
| Flag | Optional behavioral modifiers | /skill [target] --flag | /nacl-tl-ship --deploy |
Standard Flag Families
Reuse these before inventing new flags:
| Family | Flags | Purpose |
|---|
| Task type filter | --be, --fe, --tech | Filter by development type |
| Workflow filter | --review, --sync, --qa | Filter by workflow phase |
| Scope | --wave N, --feature FR-NNN, --task UC###, --all | Limit scope |
| Skip | --skip-{phase} | Skip a named workflow phase |
| User gates | --yes (skip), --confirm (add) | Control confirmation prompts |
| Safety | --dry-run, --force, --force-push | Risk-level control |
| Auto-chain | --deploy, --auto-ship | Chain into next skill |
| Version | --major, --minor, --patch | SemVer bump type |
| Output | --compact, --list, --final, --output <path> | Control output format |
Naming Rules for New Modifiers
- Flags: kebab-case with double dashes:
--skip-verify, --dry-run
- Mode values: UPPER_CASE for CRUD branches (
FULL, CREATE, MODIFY), lowercase for utility modes (full, module)
- Subcommands: lowercase:
sync, verify, stories
- Identifiers: match layer format:
UC028, FR-001, TECH003