Install
$ agentstack add skill-itsalt-nacl-nacl-migrate-sa ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo 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.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
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 →About
/nacl-migrate-sa — SA Markdown → Neo4j
Role
You delegate SA parsing, validation, and Cypher generation to stdlib Python scripts under $NACL_HOME/nacl-migrate-sa/scripts/. You never parse Markdown yourself; the scripts own that. Your job is:
- Run the scripts in order.
- Read their JSON outputs.
- Execute the Cypher plan against Neo4j via
mcp__neo4j__write-cypher. - Gather live counts via
mcp__neo4j__read-cypherand hand them toaudit_sa.py. - Present results and errors to the user in plain language.
If the project has a BA layer, nacl-migrate-ba must have already run — this skill emits cross-layer handoff edges that point at BA nodes.
Invocation
/nacl-migrate-sa [project_path] [--dry-run] [--adapter=] [--no-ba]
| Parameter | Required | Description | |---|---|---| | project_path | No | Project root (default: cwd). | | --dry-run | No | Parse + validate + Cypher gen; no Neo4j writes, no audit. | | --adapter | No | Force adapter (inline-table-v1). Default: auto-detect. | | --no-ba | No | Skip cross-layer handoff edges. Use for SA-only projects. |
Prelude — locate NaCl home and verify Python
# Resolve $NACL_HOME
if [ -z "$NACL_HOME" ]; then
for candidate in "$HOME/projects/NaCl" "$HOME/NaCl" "$HOME/code/NaCl" "$HOME/src/NaCl"; do
if [ -f "$candidate/nacl-migrate-core/nacl_migrate_core/__init__.py" ]; then
export NACL_HOME="$candidate"
break
fi
done
fi
if [ -z "$NACL_HOME" ] || [ ! -f "$NACL_HOME/nacl-migrate-core/nacl_migrate_core/__init__.py" ]; then
echo "Unable to locate NaCl repo. Set NACL_HOME=/path/to/NaCl and retry." >&2
exit 1
fi
python3 -c 'import sys; sys.exit(0 if sys.version_info >= (3,11) else 1)' 2>/dev/null \
|| { echo "Python 3.11+ required. On macOS: brew install python@3.11"; exit 1; }
Soft preflight (direct invocation)
When invoked directly (i.e. not via the /nacl-migrate orchestrator), run an ID-pattern scan and warn — but do not block — if any token in the project's filenames or YAML frontmatter does not match a known adapter pattern. The orchestrator path runs the same scan at Phase A.5 with a hard gate; the direct-invocation path stays non-blocking so a single-layer rerun is ergonomic.
mkdir -p .nacl-migrate
python3 "$NACL_HOME/nacl-migrate-ba/scripts/preflight_ids.py" \
--project "$PWD" \
--output .nacl-migrate/preflight.json || true
Exit code 1 from the script means unknown patterns were found. Surface the report's patterns_unknown list to the user:
> Preflight surfaced N unknown ID-shaped tokens. These will likely be > dropped at parse. Review .nacl-migrate/preflight.json and either > widen an adapter (recommended) or pass --force to proceed anyway.
If the user passes --force, continue. Otherwise stop.
Preconditions
config.yaml/.mcp.jsonpresent; Neo4j reachable viamcp__neo4j__get-schema.- At least one SA folder exists under
docs/(10-16 or 00-06 variant). If none:
"No SA layer detected. Skipping nacl-migrate-sa." Exit cleanly.
- Unless
--no-ba: BA nodes present in Neo4j (queryMATCH (bp:BusinessProcess) RETURN count(bp)> 0). If zero AND the project has a BA docs tree, stop — user must run/nacl-migrate-bafirst.
Phase 0 — Detect adapter + numbering
The SA parser infers both at once. For now there is a single SA adapter (inline-table-v1), which auto-detects 10-16 vs 00-06 numbering and handles an optional frontmatter fallback for UC files.
Skip this phase when --adapter was passed explicitly.
Phase 1 — Parse SA markdown → SaIR + HandoffIR
python3 "$NACL_HOME/nacl-migrate-sa/scripts/parse_sa.py" \
--project "$PWD" \
--adapter inline-table-v1 \
--output .nacl-migrate/sa-ir.json \
--handoff-output .nacl-migrate/handoff-ir.json
Expected stdout: counts table for SaIR and HandoffIR. Surface every warning code to the user.
On error exit, print message + remediation verbatim.
Phase 2 — Validate IR
python3 "$NACL_HOME/nacl-migrate-sa/scripts/validate_sa_ir.py" \
--input .nacl-migrate/sa-ir.json \
--handoff .nacl-migrate/handoff-ir.json \
--output .nacl-migrate/sa-validation.json
13 referential checks (SV1–SV8 + HV1–HV5). Exit 0 → all pass. Exit 1 → stop and print the failure list.
The script also prints a Coverage section (SC1–SC7) and writes a coverage block to sa-validation.json. This is the completeness dimension — it measures how much of each node type the adapter actually populated (UC activity steps, UC module, UC↔form links, entity attributes, enum values, form fields). It exists because the referential checks and the Phase 7 audit are both blind to under-extraction: an IR with 1 ActivityStep total is internally consistent and writes cleanly, yet leaves nearly every UseCase an empty shell.
Coverage is advisory by default (it does not change the exit code — some emptiness is legitimate, e.g. a pure list-view UC has no steps). Surface the Coverage lines to the user verbatim; do not bury them. A severely under-extracted migration now shows e.g. SC1 UC activity steps: 1/50 (2.0%) instead of a silent "clean".
To gate in CI, add --strict (fail any metric below 100%) or --min-coverage PCT (fail below PCT). These flip the exit code to non-zero; leave them off for the normal interactive migration.
Phase 3 — Generate Cypher plan
python3 "$NACL_HOME/nacl-migrate-sa/scripts/generate_sa_cypher.py" \
--input .nacl-migrate/sa-ir.json \
--handoff .nacl-migrate/handoff-ir.json \
--output .nacl-migrate/sa-cypher.json
Three batch kinds: node, edge (SA-internal), handoff (BA↔SA). If --no-ba was set, skip all handoff batches when executing.
If --dry-run: stop here and report the plan summary.
Phase 4 — Execute Cypher against Neo4j
Preflight: mcp__neo4j__get-schema() must succeed.
Read .nacl-migrate/sa-cypher.json. For each batch:
mcp__neo4j__write-cypher(query = batch.cypher, params = batch.params)
Report progress per batch: [i/N] {label} rows=... ok.
If --no-ba, skip batches where kind == "handoff" and log a single line: Skipping N handoff batches (--no-ba).
On any batch failure: log the batch index + error; stop. The partial graph is MERGE-idempotent — rerunning after a fix is safe.
Phase 5 — SUGGESTS edges (optional, best-effort)
Emit ProcessGroup -[:SUGGESTS]-> Module edges from in-graph data. This is the only SA edge type that can't be computed by the scripts because it requires joining SA's Module.related_process_ids with BA's ProcessGroup -[:CONTAINS]-> BusinessProcess. Run via MCP:
MATCH (gpr:ProcessGroup)-[:CONTAINS]->(bp:BusinessProcess)
MATCH (m:Module)
WHERE bp.id IN m.related_process_ids
MERGE (gpr)-[:SUGGESTS]->(m)
RETURN count(*) AS n
Skip entirely if --no-ba.
Phase 6 — Gather live counts
Query Neo4j via mcp__neo4j__read-cypher for every node label and edge type listed in audit_sa.py. Important: scope HAS_STEP, HAS_ATTRIBUTE, and NEXT_STEP counts to the SA layer, because those relationship types also exist on the BA layer. Use label-scoped Cypher:
MATCH (:UseCase)-[r:HAS_STEP]->(:ActivityStep) RETURN count(r) AS n
MATCH (:DomainEntity)-[r:HAS_ATTRIBUTE]->(:DomainAttribute) RETURN count(r) AS n
MATCH (:ActivityStep)-[r:NEXT_STEP]->(:ActivityStep) RETURN count(r) AS n
All other edge types have SA-exclusive labels and can use the unscoped form.
Write the counts to .nacl-migrate/sa-live-counts.json.
Phase 7 — Audit
python3 "$NACL_HOME/nacl-migrate-sa/scripts/audit_sa.py" \
--ir .nacl-migrate/sa-ir.json \
--handoff .nacl-migrate/handoff-ir.json \
--counts .nacl-migrate/sa-live-counts.json \
--output .nacl-migrate/sa-audit.json
Exit 0 → "All SA counts match." Exit 1 → surface mismatches to the user.
The audit prints a pointer line — count parity ✓ — see validation coverage (SC1–SC7 …) — because count parity only proves IR→graph fidelity, never extraction completeness. When you report the audit result to the user, quote the Phase-2 Coverage numbers next to it; never present "All SA counts match" as if it meant the migration is complete.
Phase 7b — Backfill validation exemption flags
Migrated graphs are populated from markdown documents that predate the L4–L7 validators. Their nodes lack the has_ui / system_only / shared / internal / field_category exemption properties, which causes nacl-sa-validate to emit dozens of false-positive findings on the very first run.
To prevent this, the migration ends with an automatic backfill via nacl-sa-flags:
/nacl-sa-flags backfill-all --detect-internal
Behavior:
:UseCase.has_uiis derived deterministically:trueif(uc)-[:USES_FORM]->(:Form), elsefalse.:DomainAttribute.internalis auto-flagged for system patterns (id,*_id,*_at,*_token,*_hash).:SystemRole.system_only,:DomainEntity.shared,:FormField.field_categoryget safe defaults (false,false,'input').
The user is prompted at the end of Phase 7 to confirm before the backfill runs. If declined, the migration completes without flags and nacl-sa-validate full will surface ~50–150 NULL-property findings as recommendations rather than CRITICAL/WARN; the user can then run nacl-sa-flags backfill-all manually at any later time.
Why --detect-internal by default: post-migration graphs have many surrogate keys and timestamps. The heuristic flags them automatically and is safe (it only sets internal=true, never internal=false, so manually-flagged user-facing attributes are preserved). Real telemetry / provider-internal columns may need additional manual marking after the first validate run.
Phase 8 — Report
Write MIGRATION-REPORT-SA.md at project root:
# SA Migration Report
**Project:** {project_path}
**Adapter:** {adapter}
**Numbering:** {numbering} (10-16 or 00-06)
**Ran at:** {ISO timestamp}
## SA IR vs live Neo4j
(Copy from sa-audit.json — node + edge tables.)
**Audit result: N/N CLEAN** — but count parity only proves IR→graph fidelity.
See Completeness / Coverage below for whether the IR itself is complete.
## Completeness / Coverage
(Copy the `coverage` block from sa-validation.json. One line per metric, kept
adjacent to the audit headline so "CLEAN" is never read as "complete".)
| Metric | Covered | % | Sample missing |
|--------|---------|---|----------------|
| SC1 UC activity steps | {covered}/{total} | {pct}% | {sample_missing} |
| SC2 UC module | {covered}/{total} | {pct}% | … |
| SC3 UC→form link | {covered}/{total} | {pct}% | … |
| SC3f Form→UC link | {covered}/{total} | {pct}% | … |
| SC4 DomainEntity module | {covered}/{total} | {pct}% | … |
| SC5 DomainEntity attributes | {covered}/{total} | {pct}% | … |
| SC6 Enumeration values | {covered}/{total} | {pct}% | … |
| SC7 Form fields | {covered}/{total} | {pct}% | … |
Coverage is advisory: low percentages flag adapter under-extraction (or
legitimately empty nodes), not a write failure. Investigate any metric well
below 100% before declaring the migration done.
## Cross-layer handoff
(Counts of AUTOMATES_AS, REALIZED_AS, TYPED_AS, MAPPED_TO, IMPLEMENTED_BY, SUGGESTS.)
## Warnings
{list from sa-ir.json warnings[] and handoff-ir.json, or "none"}
## Non-extracted relationships
The following are currently 0 because the adapter does not extract them:
- FormField / HAS_FIELD / MAPS_TO (needs _form-domain-mapping.md parser)
- ActivityStep.NEXT_STEP (needs UC flowchart Mermaid parser)
- UseCase -[:ACTOR]-> SystemRole
- SystemRole -[:HAS_PERMISSION]-> DomainEntity
- BusinessRule -[:IMPLEMENTED_BY]-> Requirement (matrix uses category names, not REQ ids)
Adding any of these is a per-adapter enhancement. Non-blocker for migration.
## Next steps
- Run `/nacl-sa-validate` (L1-L13 + XL6-XL9 cross-validation; L10-L13 pass vacuously until the project adopts the 2.15+ extension layers — see `docs/runbooks/upgrade-graph-extensions.md`)
- Run `/nacl-tl-diagnose` for code-docs drift analysis
Idempotency
All Neo4j writes are MERGE-keyed on canonical IDs. Safe to rerun. If an adapter change produces different canonical IDs, old nodes become orphans — audit_sa.py flags this as a count mismatch.
Known non-blockers
The current adapter does not extract:
- FormField / HASFIELD / MAPSTO — requires parsing
_form-domain-mapping.md. - ActivityStep.NEXT_STEP — UC activity flows are text tables without explicit step-number links.
- UseCase -[:ACTOR]-> SystemRole — UC.actor is text (
"ACT-01 Пользователь"); resolving it to a SYSROL requires the SystemRole table to be loaded first. - SystemRole -[:HAS_PERMISSION]-> DomainEntity — requires parsing the permissions matrix.
- BusinessRule -[:IMPLEMENTED_BY]-> Requirement — traceability matrix maps BRQ to category strings, not REQ IDs. Warning:
HANDOFF_REQ_CATEGORYis logged per row.
These can be added as adapter enhancements later. None is blocking for migration correctness.
Error handling
| Situation | Response | |---|---| | Prelude can't find $NACL_HOME | Stop. Ask to export NACL_HOME. | | Python < 3.11 | Stop. Ask user to upgrade. | | BA docs present but BA nodes missing | Stop. Ask user to run /nacl-migrate-ba first. | | Ambiguous numbering (both 10-16 and 00-06 dirs present) | Stop. Ask user to remove one. | | Adapter unknown | Stop. Present supported adapters. | | Validation failure | Stop. Print failed checks. | | Batch failure | Stop after the failing batch. Rerun after fix. | | Audit mismatch | Report, do not claim success. |
Reads / Writes
Reads
{project}/docs/{10-16|00-06}-*/**/*.md{project}/docs/99-meta/traceability-matrix.mdmcp__neo4j__read-cypher(live counts in Phase 6)mcp__neo4j__get-schema(preflight)
Writes
.nacl-migrate/sa-ir.json.nacl-migrate/handoff-ir.json.nacl-migrate/sa-validation.json.nacl-migrate/sa-cypher.json.nacl-migrate/sa-live-counts.json.nacl-migrate/sa-audit.jsonMIGRATION-REPORT-SA.mdmcp__neo4j__write-cypher(Phases 4, 5)
Checklist
- [ ] Prelude:
$NACL_HOMEresolved, Python 3.11+ present - [ ] Phase 0: adapter + numbering detected (or user supplied)
- [ ] Phase 1: SA IR + Handoff IR emitted
- [ ] Phase 2: validation 13/13 pass (or blocker list surfaced) + Coverage (SC1–SC7) reported
- [ ] Phase 3: Cypher plan generated
- [ ] Phase 4: every batch executed via
mcp__neo4j__write-cypher - [ ] Phase 5: SUGGESTS edges emitted (or skipped with
--no-ba) - [ ] Phase 6: live counts gathered (layer-scoped for shared rel types)
- [ ] Phase 7: audit clean
- [ ] Phase 8: MIGRATION-REPORT-SA.md written
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: ITSalt
- Source: ITSalt/NaCl
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.