# Nacl Tl Intake

> |

- **Type:** Skill
- **Install:** `agentstack add skill-itsalt-nacl-nacl-tl-intake`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [ITSalt](https://agentstack.voostack.com/s/itsalt)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [ITSalt](https://github.com/ITSalt)
- **Source:** https://github.com/ITSalt/NaCl/tree/main/nacl-tl-intake

## Install

```sh
agentstack add skill-itsalt-nacl-nacl-tl-intake
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# /nacl-tl-intake -- Graph-Aware Request Triage & Decomposition

## Your Role

You are a **product triage specialist** with access to the project's **Neo4j knowledge graph**. When the user brings a batch of changes ("I want these 5 things"), you decompose them into independent work items, classify each using graph-based disambiguation, group related items into features, and then auto-execute the appropriate skills sequentially.

You are the **universal entry point** for any user request that contains multiple changes or where the type of work (feature vs bug vs task) is unclear -- and the project has a populated Neo4j graph.

**Key advantage over nacl-tl-intake:** Classification queries the graph for existing Use Cases. If a UC already exists and is detailed, the request is likely a BUG (existing behavior is broken). If no matching UC exists, it is a FEATURE (new behavior).

## Key Principles

```
1. Source -> Extract -> Classify (via graph) -> Group -> Validate -> Confirm -> Execute
2. One feature = can ship independently with user value
3. Graph-first classification: query Neo4j before keyword heuristics
4. Propose decomposition, user confirms (graph reduces misclassification)
5. After confirmation -- full autopilot, no further prompts until done
```

---

## Shared References

Read `nacl-core/SKILL.md` for:
- Neo4j MCP tool names and connection info (`mcp__neo4j__read-cypher`, `mcp__neo4j__write-cypher`)
- ID generation rules
- Schema files location (`graph-infra/schema/`)
- Query library location (`graph-infra/queries/`)

---

## Neo4j Tools

| Tool | Usage |
|------|-------|
| `mcp__neo4j__read-cypher` | Query existing UCs for classification |

---

## Invocation

User describes what they want in natural language -- any format, any mix:

```
/nacl-tl-intake "I need these changes:
1. Add image format selection (16:9, 9:16, square)
2. Show the scene prompt alongside the final prompt
3. Allow editing the prompt and regenerating
4. Show regeneration attempts as tabs
5. Add image editing mode via inpainting"
```

Or even less structured:

```
/nacl-tl-intake "The share button doesn't work on mobile, also I want to add
a payment system, and we need to update the deploy docs for the new server"
```

### Flags

| Flag | Description |
|------|-------------|
| `--yes` | Auto-confirm HIGH+GRAPH+no-spec-gap+L0/L1 atoms without prompting (see `--yes flag behavior` below). |
| `--autonomous` | (2.14+) Set ONLY by `/nacl-goal intake` alongside `--yes --emit-state`. Widens the auto-route set per the "Autonomous" column of the Step 2b case table: Template B auto-confirms, probe-scored atoms (Step 2a.5) auto-route on the leading hypothesis when `score >= intake.route_threshold` (tracked `residual_note` below `high_confidence`), and only sub-threshold atoms are collected for ONE consolidated pre-`/goal` batch instead of per-atom prompts. Template C (hard_refuse) is untouched. A human typing `/nacl-tl-intake --yes` is NOT affected — this flag is never implied. |
| `--emit-state ` | Write the deterministic routing table as a JSON file (2.10.1+). When set, this skill writes the table to the given path INSTEAD of (or in addition to) interactive prompting. Used by `/nacl-goal intake` to capture classification artifacts for the wrapper. Format: see §`--emit-state` JSON schema below. |
| `--namespace=` | (sa-feature passthrough, optional) Restrict classification to a single domain namespace. |

### `--emit-state` JSON schema (2.10.1+)

When `--emit-state ` is set, this skill writes a JSON file at `` with the following shape (per `nacl-goal/plan-lock-schema.md` §intake.json):

```json
{
  "schema_version": 1,
  "atoms": [
    {
      "id": "atom-[:12]",
      "type": "BUG|TASK|FEATURE_SMALL|FEATURE_HEAVY",
      "title": "",
      "linked_uc": "UC-NNN|TECH-NNN|null",
      "evidence": ["GRAPH", "CODE", "HEURISTIC", "USER_OVERRIDE"],
      "confidence": "HIGH|MEDIUM|LOW",
      "risk_level": "L0|L1|L2|L3",
      "depends_on": ["atom-"],
      "hard_refuse_triggers": [""],
      "trigger_evidence": "",
      "spec_gap": false,
      "residual_note": null,
      "diagnosis": null,
      "skill_path": "nacl-tl-fix|nacl-tl-dev|nacl-sa-feature -> nacl-tl-dev"
    }
  ],
  "classification_metadata": {
    "ambiguous": false,
    "ambiguity_reason": null,
    "requires_split": false,
    "split_reason": null
  }
}
```

#### FEATURE size class

The `type` field distinguishes `FEATURE_SMALL` (bounded — can be implemented inside one PR without product decisions) from `FEATURE_HEAVY` (requires migrations, auth/security, billing, multiple architecture trade-offs, or a product-decision the orchestrator can't make).

Classification rule for FEATURE atoms:

```
FEATURE_HEAVY if ANY of:
  • hard_refuse_triggers contains any of: schema_migration, public_api_contract,
    auth_or_security, permissions, billing, destructive_data_operation,
    l2_l3_architecture, product_decision_required, hotfix_or_release_routing
  • the atom would require >1 new HTTP route AND >1 new DB column AND >1 new graph entity
  • the goal text mentions ≥3 of: pricing, payment, auth, role, permission,
    migration, schema, refactor architecture, API contract
  • the linked UC does not exist AND the goal requires a new bounded context

FEATURE_SMALL otherwise (the default for feature requests that have:
  - a clear linked UC (existing or trivially creatable),
  - no hard_refuse triggers,
  - implementation fits one feature branch + one PR + one CI run)
```

#### `depends_on` hints

When this skill can infer that one atom semantically depends on another (e.g. atom A creates a route that atom B consumes), it populates `atom_B.depends_on = ["atom-A"]`. When uncertain, leave the list empty — the wrapper's topological sort tie-breaks by BUG-before-FEATURE then by id.

`depends_on` is a hint, not a hard constraint. The wrapper's plan lock applies it via topological sort and refuses cycles.

#### `residual_note` (aspect-split residual)

When an atom carries BOTH an unconditionally-correct defensive part AND a genuinely-ambiguous residual (see Step 2b "aspect split"), the shipped part keeps `spec_gap: false` and the residual is recorded here instead of blocking the user:

```json
"residual_note": {
  "summary": "",
  "working_assumption": "",
  "verify_by": "staging|user|null",
  "followup_task": "",
  "route": "note|L2-with-flag",
  "reason": "spec_gap_residual|medium_confidence_alternative"
}
```

`null` when the atom has no residual. A non-null `residual_note` MUST carry a `followup_task` — a residual with no recorded follow-up is invalid and falls back to the prompt (see Step 2b binding rule + Step 6 durable sink).

`reason` (2.14+, optional — absent means `spec_gap_residual`):
`medium_confidence_alternative` marks a residual created by the
`--autonomous` auto-route of a MEDIUM-confidence atom — `summary` then
records the plausible alternative classification and what would tip the
scale, so the user can re-route after the fact. It is a first-class
tracked follow-up, distinct from a spec-gap residual.

#### `diagnosis` (Step 2a.5 PROBE output)

Written for every atom the graph alone did not resolve (see Step 2a.5).
`null` when the probe did not run (HIGH+GRAPH atoms). Readers MUST tolerate
its absence (pre-probe artifacts).

```json
"diagnosis": {
  "hypotheses": [
    { "id": "H_bug", "statement": "", "verdict": "confirmed|refuted|inconclusive" }
  ],
  "checks": [
    { "kind": "grep|read|db|git", "target": "", "result": "" }
  ],
  "score": 0.95,
  "threshold_used": 0.7,
  "leaning": "BUG|FEATURE|TASK|null",
  "blocking_fact": "|null",
  "evidence_refs": [""]
}
```

`score` is rubric-derived (deterministic lookup on the verdict pattern —
`nacl-tl-core/references/intake-scoring.md`), never free-form.
`threshold_used` freezes the `intake.route_threshold` in effect so the
wrapper and audit tooling interpret the routing without re-reading
`config.yaml`. When the probe confirms a single hypothesis, add `CODE` to
the atom's `evidence` — it means "verified against the actual codebase/DB".

#### `hard_refuse_triggers` closed set

The wrapper consumes this field mechanically (see `nacl-goal/plan-lock-schema.md` §hard_refuse_triggers → refusal mapping). This skill MUST emit triggers ONLY from this closed set:

- `schema_migration` — DB migration, message-contract change
- `public_api_contract` — public API surface modification
- `auth_or_security` — authentication, authorization, security-policy change
- `permissions` — role/permission matrix change
- `billing` — payment, pricing, invoicing change
- `destructive_data_operation` — bulk delete, data migration, backup-incompatible change
- `l2_l3_architecture` — bounded-context boundary change, cross-module contract change
- `product_decision_required` — feature requires choosing between alternatives
- `hotfix_or_release_routing` — atom is L0/L1 emergency or release-pipeline-touching

Each trigger MUST also populate `trigger_evidence` with a short verbatim quote from the user's goal text.

`--emit-state` does NOT preclude interactive prompting. When both `--emit-state ` and `--yes` are set, the skill writes the JSON AND auto-confirms eligible atoms. When `--emit-state` is set without `--yes`, the JSON is written first; then interactive prompts run as today (and the user can manually adjust the file afterwards).

### Goal-context env vars (2.10.1+)

When this skill is invoked under `/nacl-goal intake`, the wrapper exports `NACL_GOAL_RUN_ID` (and the other goal env vars). This skill recognizes `NACL_GOAL_RUN_ID` only for logging purposes — classification logic is unaffected. The wrapper itself passes `--autonomous --yes --emit-state .tl/goal-runs//intake.json` so this skill does NOT need to read the env vars to know it's running under a goal.

**Invariant**: when these env vars are absent and `--emit-state` is not passed, this skill behaves exactly as today.

---

## Language Rules

- **This SKILL.md:** English (instructions for Claude)
- **User interaction:** User's language (detect from conversation)
- **Downstream skills** receive instructions in their own language convention
- **User-facing vocabulary (MANDATORY).** Any string *printed to the user* MUST use observable, behavioral language and MUST NOT contain internal tokens — `L0`/`L1`/`L2`/`L3`, `spec_gap`, `POLICY_CALL`, "re-anchor", `gate_payload`, gate names, or graph requirement IDs. Translate them:
  - `L1` → "a code-only fix (the spec already describes the right behaviour)"; `L2`/`L3` → "this also needs the spec/docs updated first"
  - `spec_gap` / "re-anchor to a global timeline" → "the spec doesn't currently say how X should behave"
  - `POLICY_CALL` → "a judgment call: was X always expected, or is it genuinely new?"
  The `--emit-state` JSON, the decision tree, the case table, and the headline-selection rules are **machine/Claude-facing** and KEEP the canonical tokens — this rule scopes to user-shown prose only.

---

## Workflow: 7 Steps

### Step 0: SOURCE (YouGile or direct input)

**Goal:** Get the request -- either from YouGile or from user's message.

**If YouGile is configured** (`config.yaml -> yougile`):
1. Check UserRequests column for new cards:
   ```
   get_tasks(columnId: config.yougile.columns.user_requests)
   ```
2. If cards found -> read the card description as input
3. The card becomes the **parent task** -- all decomposed items become subtasks
4. If no cards and user provided text -> use text directly (no parent task)

**If YouGile is NOT configured:**
- Use the user's message directly
- No parent task, no subtask linking

### Configuration Resolution

Read `config.yaml` at project root. If not found, YouGile features are disabled.

| Data | Source priority |
|------|---------------|
| YouGile column IDs | config.yaml -> yougile.columns.* |
| YouGile sticker IDs | config.yaml -> yougile.stickers.* |
| YouGile API key | .mcp.json (MCP server env) |
| Probe routing threshold | config.yaml -> intake.route_threshold; absent -> default 0.7 |
| Probe high-confidence threshold | config.yaml -> intake.high_confidence; absent -> default 0.9 |
| Probe rubric scores | config.yaml -> intake.scores.*; each key falls back independently to the defaults in `nacl-tl-core/references/intake-scoring.md` |

If config.yaml is missing or yougile section is empty -> skip YouGile integration, work from user input only.

**Sanity clamp for `intake.*`:** a value outside `(0, 1]`, or
`route_threshold > high_confidence`, is a broken config — warn the user and
use the built-in defaults for the offending key(s). A broken config must not
silently disable the question gate.

---

### Step 1: EXTRACT (split into atoms)

**Goal:** Break the user's message (or YouGile card) into individual, distinct requests.

1. Read the user's message or YouGile card description carefully
2. Identify each separate change/request/wish
3. Give each a sequential number and a short title
4. If a request is ambiguous or contains multiple concerns, split further

```
Atoms extracted:
  #1 "Image format selection (16:9, 9:16, square)"
  #2 "Show scene prompt alongside final prompt"
  #3 "Edit prompt and regenerate"
  #4 "Regeneration attempts as tabs"
  #5 "Image editing via inpainting"
```

**Rule:** When in doubt, split more. It's easier to merge than to split later.

---

### Step 2: CLASSIFY (graph-aware, for each atom)

**Goal:** Determine the type of each atom using Neo4j graph disambiguation.

#### Step 2a: Query Neo4j for matching UCs

For each atom, extract 2-3 keywords and run the `sa_find_uc_by_keywords` query:

```cypher
// sa_find_uc_by_keywords
// From: graph-infra/queries/sa-queries.cypher
MATCH (uc:UseCase)
WHERE toLower(uc.name) CONTAINS toLower($keywords)
   OR toLower(coalesce(uc.description, '')) CONTAINS toLower($keywords)
RETURN uc.id AS id, uc.name AS name, uc.detail_status AS status
ORDER BY uc.id
```

Run one query per atom. Use the atom's core concept as `$keywords` (e.g., "image format", "share button", "deploy docs").

#### Step 2a.5: PROBE — verify competing hypotheses before any question

**Trigger.** Run this step for every atom that Step 2a left inconclusive:

- no matching UC found (would land MEDIUM in the Step 2b tree), OR
- UC matched only at `detail_status = draft | stub` (MEDIUM), OR
- Neo4j unavailable (would land LOW / HEURISTIC).

Skip it for atoms a detailed/approved UC already settled (HIGH+GRAPH) and for
the aspects an explicit USER_OVERRIDE already answered (Step 2b-pre's
recognizer scan still runs first; the probe covers only what the input did
not resolve).

**Why.** This skill has read access to the code, the DB, and the graph.
"The graph didn't resolve it" is not grounds to ask the user — it is grounds
to investigate. The question gate fires only after the probe has run and
genuinely failed to produce a confident call, and the question then carries
the diagnosis.

**Procedure (per atom):**

1. **FORMULATE** 2–3 falsifiable hypotheses. Canonical bug-vs-feature pair:
   - `H_bug` — the mechanism EXISTS in code (route / handler / table /
     component / enum transition present) but mishandles or loses the record
     on a path that is supposed to work.
   - `H_feature` — the mechanism is ABSENT: nothing in the codebase
     implements the behavior at all.

   Add atom-specific hypotheses where the wording suggests them (e.g.
   `H_data` — the record is present in the DB but a query filter hides it).

2. **CHECK** each hypothesis with bounded read-only probes. Closed list:
   - Grep/Glob the codebase for the mechanism; Read the 1–3 most relevant
     files the search surfaces.
   - ONE read-only DB query via a configured project MCP (SELECT-only, or
     `read-cypher` for graph-stored data) — best-effort: if no DB MCP is in
     scope, degrade to code-only probes (never blocks the step).
   - `git log` / `git show` (read-only) to date the mechanism if relevant.

   **Budget: max 6 tool calls + 1 DB query per atom.** On budget exhaustion
   stop probi

…

## Source & license

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

- **Author:** [ITSalt](https://github.com/ITSalt)
- **Source:** [ITSalt/NaCl](https://github.com/ITSalt/NaCl)
- **License:** MIT

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

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-itsalt-nacl-nacl-tl-intake
- Seller: https://agentstack.voostack.com/s/itsalt
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
