AgentStack
SKILL verified MIT Self-run

Nacl Tl Plan

skill-itsalt-nacl-nacl-tl-plan · by ITSalt

|

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

Install

$ agentstack add skill-itsalt-nacl-nacl-tl-plan

✓ 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.

Are you the author of Nacl Tl Plan? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

/nacl-tl-plan -- Development Planning from Neo4j Graph

Purpose

Graph-powered replacement for /nacl-tl-plan. Reads the SA specification from Neo4j (modules, use cases, entities, dependencies) via Cypher queries and generates self-sufficient task files for dev agents (nacl-tl-dev-be, nacl-tl-dev-fe).

Critical difference from nacl-tl-plan:

| Aspect | nacl-tl-plan | nacl-tl-plan | |--------|---------|---------------| | Data source | ~70 markdown files in docs/ | Neo4j graph | | Tokens per UC | ~150K (read all docs) | ~550 (~50 query + ~500 response) | | Retrieval method | Read files sequentially | 1 Cypher query per UC | | Task file format | Standard .tl/tasks/ | IDENTICAL (dev agents unchanged) |

Shared references: nacl-core/SKILL.md


Neo4j Tools

| Tool | Usage | |------|-------| | mcp__neo4j__read-cypher | Read SA graph (modules, UCs, entities, deps) | | mcp__neo4j__write-cypher | Create Wave and Task nodes in the TL layer | | mcp__neo4j__get-schema | Introspect current graph schema before planning |


Invocation

/nacl-tl-plan [options]

Parameters

| Parameter | Values | Description | |-----------|--------|-------------| | scope | full (default) | Plan all UCs from the graph | | | module: | Plan only UCs in a specific module | | | uc:, | Plan only specific UCs | | --feature | FR-NNN | Plan only UCs from a feature request. Resolves the UC list and new/modified split from the graph ((:FeatureRequest)-[:INCLUDES_UC]->), falling back to .tl/feature-requests/FR-NNN.md only if the node is absent (Step 1.5b). | | wave-start | 0 (default) | Starting wave number (for incremental planning) | | --overwrite | (flag) | Destroy ALL existing Task/Wave nodes and re-plan from scratch. Default is incremental (Step 1.5b); use this only for an intentional clean rebuild. |


Workflow Overview

Phase 1: READ SA GRAPH
  |
  +-- Modules, UseCases, DomainEntities, Dependencies
  +-- Priorities, SystemRoles
  +-- External Contracts Gate (W6, Step 1.6) — BLOCKED if missing/stub
  |
Phase 2: WAVE PLANNING
  |
  +-- Topological sort by DEPENDS_ON + priority
  +-- Wave 0: TECH tasks (infra)
  +-- Wave 1+: UC-BE before UC-FE, independent UCs in parallel
  +-- Create Wave / Task nodes in Neo4j
  |
Phase 3: TASK GENERATION
  |
  +-- For each UC: run sa_uc_full_context($ucId)
  +-- Map query result to 8 task files
  +-- Write files to .tl/tasks/UC###/
  |
Phase 4: MASTER PLAN
  |
  +-- Update master-plan.md
  +-- Update status.json
  +-- Update changelog.md

Phase 1: Read SA Graph

Step 1.1: Pre-flight -- verify graph has SA data

// Pre-flight: count SA-layer nodes
MATCH (n)
WHERE n:Module OR n:UseCase OR n:DomainEntity OR n:Form OR n:Requirement OR n:SystemRole
RETURN labels(n)[0] AS label, count(n) AS count
ORDER BY label

If result is empty or all counts are 0:

  1. STOP -- planning is impossible without SA data in the graph.
  2. Suggest user runs /nacl-sa-architect or /nacl-sa-domain first.

Step 1.2: Get all modules with their UCs and entities

// All modules with UC and entity counts
MATCH (m:Module)
OPTIONAL MATCH (m)-[:CONTAINS_UC]->(uc:UseCase)
OPTIONAL MATCH (m)-[:CONTAINS_ENTITY]->(de:DomainEntity)
RETURN m.id AS module_id, m.name AS module_name,
       count(DISTINCT uc) AS uc_count,
       count(DISTINCT de) AS entity_count
ORDER BY m.id

Step 1.3: Get all UCs with priorities and dependencies

// sa_uc_dependencies -- all UCs with their DEPENDS_ON edges
MATCH (uc:UseCase)
OPTIONAL MATCH (uc)-[:DEPENDS_ON]->(dep:UseCase)
OPTIONAL MATCH (m:Module)-[:CONTAINS_UC]->(uc)
RETURN uc.id AS uc_id, uc.name AS uc_name,
       uc.priority AS priority,
       m.id AS module_id, m.name AS module_name,
       collect(dep.id) AS depends_on
ORDER BY uc.priority DESC, uc.id

Step 1.4: Get complete domain model overview

// Domain entities with attribute count and relationships
MATCH (de:DomainEntity)
OPTIONAL MATCH (de)-[:HAS_ATTRIBUTE]->(da:DomainAttribute)
OPTIONAL MATCH (de)-[rel:RELATES_TO]->(de2:DomainEntity)
RETURN de.id AS entity_id, de.name AS entity_name,
       count(DISTINCT da) AS attr_count,
       collect(DISTINCT {target: de2.name, type: rel.rel_type, card: rel.cardinality}) AS relationships
ORDER BY de.id

Step 1.5: Check for existing TL-layer data (incremental planning)

// Count existing Task and Wave nodes
MATCH (n)
WHERE n:Task OR n:Wave
RETURN labels(n)[0] AS label, count(n) AS count

If Tasks/Waves already exist, the default is INCREMENTAL re-planning, not overwrite. A full overwrite destroys in-progress dev state and re-bakes every snapshot; it happens only when the user explicitly passes --overwrite. Otherwise run Step 1.5b to find the narrow set of UCs that actually changed since the last plan, and regenerate only those.

Step 1.5b: Detect which UCs need re-planning (idempotency)

A Task's .tl/tasks/UC###/*.md files embed a point-in-time snapshot of the SA graph (field types, role permissions, enum values — see "Self-Sufficiency"). That snapshot goes stale when its source UC changes. Two drift-confirmed signals identify the stale set precisely — no markdown diffing, no whole-layer nuke. Both require evidence of actual drift; neither fires on a UC that is already current.

Signal 1 — version drift (a Task baked from an older spec than its UC now carries):

// mcp__neo4j__read-cypher
// GUARD planned_from_version IS NOT NULL: a Task with no baseline (project upgraded to
// Фаза 0 but gap-closure baseline not yet run) is NOT treated as drifted here — that
// would over-flag every task on day one and reset in-progress work. Such a real change
// is still caught by Signal 2 (set by sa-feature step 3g / tl-fix L2-L3).
MATCH (uc:UseCase)-[:GENERATES]->(t:Task)
WHERE t.planned_from_version IS NOT NULL
  AND coalesce(uc.spec_version, 0) > t.planned_from_version
RETURN DISTINCT uc.id AS uc_id, uc.spec_version AS current_version,
       t.planned_from_version AS planned_version, 'spec-drift' AS reason

Signal 2 — explicit stale stamp (set by the write-skills at change time; catches changes even without a version bump, e.g. some tl-fix paths):

// mcp__neo4j__read-cypher
MATCH (uc:UseCase)
WHERE coalesce(uc.review_status,'current') = 'stale'
   OR EXISTS { (uc)-[:GENERATES]->(t:Task) WHERE coalesce(t.review_status,'current')='stale' }
RETURN DISTINCT uc.id AS uc_id, uc.stale_origin AS origin

> Do NOT add a third "FR flagged modified" auto-detection arm keyed only on > INCLUDES_UC {kind:'modified'} + status='spec-complete'. spec-complete is a > sticky state — an FR that was never advanced keeps that edge forever, so such an > arm re-flags its UCs on every run even after they're current, regenerating > already-current tasks and resetting in-progress work (the exact churn the Signal-1 > guard prevents). sa-feature step 3g always bumps spec_version AND stamps stale > when it processes an FR, so Signals 1+2 already catch every real FR-driven change. > INCLUDES_UC {kind} is consumed for explicit --feature FR-NNN scoping only > (below), never for auto drift detection.

Incremental algorithm (default when tasks exist):

  1. stale_set = UCs from Signal 1 ∪ Signal 2 (both drift-confirmed).
  2. new_set = UCs from INCLUDES_UC {kind:'new'} (or in-scope UCs) with no GENERATES edge yet.
  3. Regenerate task files only for stale_set ∪ new_set. UCs not in either set are left untouched — their tasks and dev state survive.
  4. Use MERGE (t:Task {id: $taskId}) (Step 2.4) so re-running is idempotent at the node level: the same UC###-BE/UC###-FE ids are updated in place, never duplicated.
  5. On each successful regeneration, stamp planned_from_version and clear the staleness flag (Step 2.4).
  6. If stale_set ∪ new_set is empty, report "plan is current — nothing to regenerate" and stop.

First Фаза-0 plan on an existing project — baseline, don't regenerate. If Tasks exist but none has planned_from_version (project just upgraded), do NOT treat them as drifted. Baseline them once so future drift is detectable, without resetting any in-progress work:

// mcp__neo4j__write-cypher — one-time baseline (idempotent; only touches null ones)
MATCH (uc:UseCase)-[:GENERATES]->(t:Task)
WHERE t.planned_from_version IS NULL
SET t.planned_from_version = coalesce(uc.spec_version, 0)

After baselining, only a real spec_version bump (or a review_status='stale' stamp from nacl-sa-feature/nacl-tl-fix) marks a task for regeneration.

--feature FR-NNN: resolve the UC list from the graph, not the markdown file — read (fr:FeatureRequest {id:$frId})-[r:INCLUDES_UC]->(uc:UseCase) and use r.kind to split new vs modified. This finally consumes the INCLUDES_UC{kind} edges that nacl-sa-feature writes (previously written but never read). Fall back to the markdown UC list only if the FR node is absent.

// mcp__neo4j__read-cypher — resolve --feature scope from the graph
MATCH (fr:FeatureRequest {id:$frId})-[r:INCLUDES_UC]->(uc:UseCase)
RETURN uc.id AS uc_id, r.kind AS kind
ORDER BY uc.id

> Wave numbers for the regenerated/new tasks are NOT assigned by hand. Once you have > decided the feature's task list and dependency edges, hand them to the deterministic > planner in assign mode with waveStart = max(existing Wave.number) + 1 (Step 2.1). > The tool returns the global wave per task; feed those into Step 2.4. This is the path a > mature project almost always takes — keep it on the tool, not on reasoning.

Step 1.6: External Contracts Gate (W6)

Purpose. For every UC in planning scope, refuse to generate a task when the UC references an external provider/protocol whose .tl/external-contracts/.md is absent. This is a consumer-side read of the artifact written by nacl-sa-architect during its External Contracts phase (W6 plan brief, declared primary-owner exception). Strict-only — there is no inline --skip-external-contract flag.

Why this check exists. 13 of ~60 postmortem signals across two NaCl projects were external-API / wire-protocol gaps (kie.ai in both projects, TUS upload, base_url divergence, reverse-proxy URL scheme, ffmpeg/ffprobe runtime — see docs/retrospectives/project-beta-runtime-baseline.md §§ A1–A9, B1–B7). Local tests passed; the product did not work. The nacl-tl-sync Wire-Evidence Gate (W2) already downgrades sync to UNVERIFIED when wire-evidence is absent. W6 makes the artifact concrete upstream so the gate has something to point at.

1.6.1: Discover external dependencies
// mcp__neo4j__read-cypher
// Per-UC external-contract requirements via graph
MATCH (uc:UseCase)
WHERE uc.id IN $uc_ids
OPTIONAL MATCH (uc)-[:REQUIRES_EXTERNAL]->(ec:ExternalContract)
OPTIONAL MATCH (m:Module)-[:CONTAINS_UC]->(uc)
OPTIONAL MATCH (m)-[:DEPENDS_ON_EXTERNAL]->(mec:ExternalContract)
RETURN uc.id AS uc_id,
       collect(DISTINCT {id: ec.id, name: ec.name, kind: ec.kind,
                         file_path: ec.file_path}) AS uc_direct,
       collect(DISTINCT {id: mec.id, name: mec.name, kind: mec.kind,
                         file_path: mec.file_path}) AS via_module

The union of uc_direct and via_module is the set of external contracts the UC's tasks must reference at generation time.

1.6.2: File-system existence and stub check

For each ExternalContract row returned above, verify the file referenced by ec.file_path:

  1. File absent on disk → record external-contract-missing for `(uc_id,

contract_id)`.

  1. File present but required sections empty / "TBD" / stub → record

external-contract-stub. The required sections are 1–8 and 10–11 of the template (.tl/external-contracts/_template.md). Section 9 is required when ec.kind == 'provider'. Section 7 must be filled OR explicitly marked N/A — no file URLs.

Both conditions are blockers; the only override is a signed exception under the W4 schema (no inline flag).

1.6.3: Example check logic (pseudocode)
violations = []
for uc in scope:
  required_contracts = graph_query(uc.id)  # Step 1.6.1
  for contract in required_contracts:
    if not file_exists(contract.file_path):
      violations.append({
        uc_id: uc.id, contract_id: contract.id,
        contract_name: contract.name, contract_kind: contract.kind,
        reason: "external-contract-missing",
        expected_path: contract.file_path,
        remedy: "Run /nacl-sa-architect External Contracts phase OR file " +
                "signed exception under W4 schema."
      })
      continue
    sections = parse_required_sections(contract.file_path)
    missing_required = []
    for section in [1,2,3,4,5,6,7,8,10,11]:
      if not sections[section].filled_non_stub:
        missing_required.append(section)
    if contract.kind == 'provider' and not sections[9].filled_non_stub:
      missing_required.append(9)
    if missing_required:
      violations.append({
        uc_id: uc.id, contract_id: contract.id,
        contract_name: contract.name,
        reason: "external-contract-stub",
        missing_sections: missing_required,
        expected_path: contract.file_path,
        remedy: "Complete sections " + missing_required + " of " +
                contract.file_path + " OR file signed exception (W4)."
      })

if violations:
  # Cluster violations per UC. Surface the full list.
  for v in violations:
    log(v.uc_id, v.contract_name, v.reason, v.expected_path)
  emit_headline("PLAN HALTED — EXTERNAL_CONTRACT_MISSING")
  emit_status("BLOCKED")
  exit_without_writing_anything()

The example above is a sketch. The skill's implementation MUST:

  • Surface every violation (do NOT short-circuit after the first one — the

operator needs the complete list to either author the missing contracts or to scope a signed exception).

  • Refuse to write any TL node, any Wave node, any task file, or any

.tl/status.json / .tl/master-plan.md / .tl/changelog.md entry until the gate passes OR a signed exception covering every violation is on disk.

  • Emit Status: BLOCKED workflow detail external-contract-missing (when

the file is absent) or external-contract-stub (when sections are unfilled). See Output Summary below for the full headline / status contract.

1.6.4: Worked example — UC-300 referencing kie.ai
graph: UC-300 -[:REQUIRES_EXTERNAL]-> (ExternalContract {id: 'ext-kie',
        name: 'kie.ai', kind: 'provider',
        file_path: '.tl/external-contracts/kie.md'})

case A: .tl/external-contracts/kie.md exists, all required sections filled
  → gate PASSES; UC-300 task generation proceeds.

case B: .tl/external-contracts/kie.md absent
  → gate FAILS with external-contract-missing.
  → headline: PLAN HALTED — EXTERNAL_CONTRACT_MISSING
  → status:   BLOCKED
  → remedy:   /nacl-sa-architect External Contracts phase, or signed
              exception under W4 schema.

case C: file exists but Section 9 (Model namespace) is "TBD"
  → gate FAILS with external-contract-stub; missing_sections: [9].
  → headline: PLAN HALTED — EXTERNAL_CONTRACT_STUB
  → status:   BLOCKED

The same flow applies for any protocol (TUS, SSE, etc.) — the kind property on ExternalContract toggles the Section-9 requirement only.

1.6.5: Strict-only language

There is no inline --skip-external-contract flag. There is no gate_mode: legacy carve-out. The project_kind: prototype config does NOT relax this gate; prototypes are the PR/CI carve-out, not the contract carve-out. The only override is a signed exception under the W4 schema covering every violation by (uc_id, contract_id) tuple.


Phase 2: Wave Planning

Step 2.1: Compute the wave plan (deterministic — both planning paths)

Do not assign waves by hand — a missed dependency edge silently produces an FE-

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.