nacl-tl-fix
SkillDocs & knowledgeSpec-first bug fixing with automatic documentation sync. Auto-detects affected UC/TECH from problem description. Classifies fix level (L0/L1/L2/L3), updates docs BEFORE code. Use when: fix bug, resolve error, debug issue, something broke, or the user says "/nacl-tl-fix" followed by a problem description.
Available today. Use it from your connected AI after setup.
No other account needed.
Add ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.
Then ask your AI: use the nacl-tl-fix skill
What this skill tells your AI
The instructions your AI receives, as published by itsalt/nacl in nacl-tl-fix/SKILL.md and read by ahel’s review.
Use with /goal
Wrap with: /nacl-goal fix:<BUG-NNN> (tier S)
This skill is a good fit for autonomous /goal loops because bug fix progress is graph-verifiable: the regression test transitions RED→GREEN in a way the check script can observe, and the fix commit appears in the task's graph evidence. The wrapper composes a completion condition that the regression test is GREEN, a PR is open, and no new test failures appear relative to the pre-fix baseline.
Auto-retry behavior: any existing retry inside this skill is preserved; /goal loops between retries, not inside them.
Check script: nacl-goal/checks/fix.sh
Refusals: see nacl-goal/refusal-catalog.md for the gates this wrapper guards.
Background: docs/guides/goal-command.md
TeamLead Spec-First Bug Fix Skill
CRITICAL: Follow ALL 8 Steps (across two phases)
You MUST execute every step of the workflow below, in order, without skipping. Do NOT jump straight to fixing code. Do NOT skip triage, context loading, or gap-check. The full workflow is: TRIAGE → CONTEXT → GAP-CHECK → DEFINE BEHAVIOR → FIX DOCS → FIX CODE → VALIDATE → REPORT. Skipping steps leads to regressions. This has been proven empirically.
The eight steps run across two cognitive phases (see "Two-Phase Architecture"
below): Steps 1–5 (DIAGNOSE & SPEC) are delegated to the diagnostician agent
(opus); Steps 6–8 (EXECUTE) run inline in this skill (sonnet). The phase boundary
does not relax the "all steps mandatory" rule — it routes each step to the right
model tier.
Two-Phase Architecture: diagnose (opus) → execute (sonnet)
nacl-tl-fix is an orchestrator over two cognitive tiers, not a monolith:
- Phase A — DIAGNOSE & SPEC (Steps 1–5). Delegated to the
diagnosticianagent (opus, high effort). The high-reasoning half: graph impact traversal, gap-check, L0/L1/L2/L3 classification, correct-behavior definition, and authoring the corrected spec/docs. The diagnostician writes specs and graph nodes but never production code and never commits — preserving the firewall that the spec author ≠ the code author. - USER GATE (L2 / L3-spec-gap). Presented by this skill (the orchestrator
runs in the interactive context; the diagnostician sub-agent cannot talk to the
user). In autonomous
/nacl-goalmode the gate auto-resolves exactly as before. - Phase B — EXECUTE (Steps 6–8). Runs inline in this skill (sonnet, the
developer tier). This is the honest-execution core: capture baseline →
RED-first regression test (itself delegated to a separate test-author sub-agent
at 6d; infrastructure fixes take the verification-based Path C instead) →
apply fix → six-status determination → impact survey → report. This same
core is what
nacl-tl-dev --continue(TECH),nacl-tl-dev-be/fe --continue,nacl-tl-reopened, andnacl-tl-hotfixdelegate into — they are thin wrappers over Phase B, not peers.
Why this split
The diagnostic half is the work the framework defines as strategist-tier
("misclassified triage wastes entire cycles", docs/agents.md). Running it on
sonnet under-powered the single most important guardrail in this skill — the
L3-feature classification. The execution half is code generation from a now-complete
spec, which sonnet handles at opus quality within 2–3% (docs/agents.md, developer
rationale). The seam falls after Step 5 so Phase B receives a COMPLETE spec —
restoring the developer-tier premise of "codegen from complete specifications".
A skill executes in one model context, so the only way to change tier mid-skill is to delegate a phase to a sub-agent. This seam mirrors the existing 6d test-author seam.
Phase A delegation (how the orchestrator invokes the diagnostician)
At invocation, after parsing flags, this skill delegates Phase A:
Agent: diagnostician
Prompt: Load /nacl-tl-fix Phase A (Steps 1–5) for this bug. Execute TRIAGE,
CONTEXT LOAD, GAP-CHECK + classification, DEFINE CORRECT BEHAVIOR, and
(L2/L3 only) FIX DOCS. Write doc/spec/graph changes to the working tree
but DO NOT COMMIT and DO NOT touch production code. Return the fix-plan
artifact below.
Inputs: <verbatim user problem description>, flags (--uc, --dry-run,
--treat-as-l3-spec-gap), config.yaml graph section, goal-context env
vars if present.
The diagnostician returns the fix-plan artifact:
classification: L0 | L1 | L2 | L3-spec-gap | L3-feature
exit_reason: null | "L3-feature" # if L3-feature, orchestrator prints the
# Step 3 routing report and EXITS — no code.
affected:
ucs: [UC-###, ...] # union of Step 1 Stage 2 + Stage 3
domain_entity: { id, name }
module: ...
code_files: [...]
docs: [...]
tasks: [...]
root_cause: "..."
correct_behavior: # Step 4
current: "..."
expected: "..."
unchanged: ["..."]
decision: # Step 4/5 — L2 / L3-spec-gap ONLY (null for L0/L1)
title: "..." # one line: what was decided about the behavior
chosen: "..." # = correct_behavior.expected
rationale: "..." # = root_cause + why expected is correct [REQUIRED]
alternatives_considered: ["keep current (rejected: <root_cause>)"]
context: "..." # = correct_behavior.current
justifies_ucs: [UC-###, ...] # = affected.ucs (artifacts this decision shapes)
supersedes: null | "DEC-NNN" # if this reverses an earlier decision
# The Decision is the graph-native "why" record. It is written as part of the
# Phase B spec-update commit (Step 6 / 7) so the 6.SF detector counts it as a
# spec mutation. nacl-sa-validate L9 refuses a structural change with no Decision.
docs_changed: # Step 5 — written to working tree, UNCOMMITTED
- { path: ..., diff: ... } # or graph-mutation descriptor
# empty for L0/L1
gapcheck_attestation: # L1 ONLY — the on-record proof that docs are
# already current (there is nothing to change).
# null for L0/L2/L3. See Step 5 (L1 branch) + 6.SF.
affected_docs: [...] # docs compared against code in Step 3
verdict: "no-drift" # docs describe the correct behavior
checked_by: "diagnostician"
gate_payload: # what to show at the USER GATE (L2/L3); null otherwise
docs_to_change: [...]
doc_diffs: ...
code_fix_plan: "..."
impact_targets: # seeds Step 7.5 data-flow survey
read_paths: [{ uc, file }]
write_paths: [{ uc, file }]
refresh_paths: [...]
source_of_truth: code | user_data
impact_unverified: false # true if Neo4j was unavailable (Step 1 flag)
This skill (Phase B) consumes the artifact, presents the USER GATE if
gate_payload is non-null, then proceeds to Step 6 — committing the spec-update
FIRST (which satisfies the 6.SF spec-first prerequisite by construction, since the
diagnostician left docs uncommitted) and then the code fix.
Fallback (no Agent tool / Codex runtime): if sub-agent delegation is
unavailable, this skill executes Phase A inline on its own model. This is the
behavior of the skills-for-codex variant, which is intentionally left monolithic.
Contract
This skill's output contract is consumed by downstream skills. If any of the following changes, every consumer below MUST be audited and updated in the same release (the absence of this section is what let the 0.10.0→0.10.1 status-vocab change ship without auditing consumers):
Output contract:
- Six-status vocabulary:
PASS | BLOCKED | UNVERIFIED | NO_INFRA | RUNNER_BROKEN | REGRESSION - Step 8 report header strings (see Step 8 table)
- The authoritative
Status:line - The regression-test seam block (
Tests > Regression test,Tests > RED→GREEN), including the Path C valueverification: <path>that orchestrators map toverify-GREEN:<path>evidence
Downstream consumers:
nacl-tl-dev-be(--continuedelegates here)nacl-tl-dev-fe(--continuedelegates here)nacl-tl-reopenednacl-tl-hotfix
This restructure (diagnose/execute split) changes the internal flow only.
Steps 7–8 (status machine + report) are unchanged, so the output contract is
preserved and the consumers above need no change. The ## Contract section is a
documentation discipline, not a runtime mechanism.
Routing — When /nacl-tl-fix vs /nacl-tl-intake
/nacl-tl-fix handles one bug whose classification is unambiguous (existing UC is broken, error message names a known surface). Step 1 now traverses the graph from the DomainEntity the bug touches and lists every UC that consumes or produces it — so the TRIAGE table will show all neighbours of a shared catalog / table, not just the one whose name keyword-matched. Review those neighbours before approving the fix.
If the bug's surface is ambiguous (could be a feature, could be a bug, or you cannot guess which DomainEntity it touches), run /nacl-tl-intake first — intake's graph-backed bug-vs-feature disambiguation will surface the affected entity and route to /nacl-tl-fix with the impact scope already named. Routing through intake is mandatory only when the entity cannot be identified by hand; for single-surface bugs, direct invocation is still preferred.
Your Role
You are a senior developer and specification maintainer who fixes bugs using the spec-first approach. You do NOT just fix code — you ensure that documentation and code remain synchronized. Every fix follows the principle: specification is the source of truth; code follows the spec.
When you run the full skill you act as the orchestrator: you delegate the
diagnose-and-spec half (Phase A, Steps 1–5) to the diagnostician sub-agent
(opus), present the USER GATE, then execute the honest-execution core (Phase B,
Steps 6–8) yourself (sonnet). When the diagnostician runs Phase A it adopts the
diagnostic role described under each of Steps 1–5; the role text below applies to
both, scoped by phase.
Key Principle: Spec-First
WRONG (code-first): Find bug → Fix code → Forget docs → Next session reads stale docs → Regression
RIGHT (spec-first): Find bug → Read docs → Check for drift → Fix docs → Fix code to match → Validate
The spec-first approach is supported by:
- Kiro bugfix specs (AWS 2025): Define Current → Expected → Unchanged BEFORE coding
- TDD: Write failing test (= spec) first, then fix code
- GitHub Spec Kit: "Specification is the durable thing, code is the flexible thing"
- Thoughtworks 2025: Separate design and implementation phases
Spec-First Prerequisite (Strict-Only) — W10 binding
No code change ships while its spec is stale. The required spec-first evidence depends on the fix level:
- L2 / L3-spec-gap — the docs do change, so the evidence is a spec-update commit that precedes the first code-fix commit.
- L1 — by definition docs are already current and there is nothing to change,
so the evidence is a gap-check attestation (the Step 3 result recorded to
.tl/status.json/.tl/changelog.md, timestamped before code — see Step 5 L1 branch). A spec-update commit is NOT required for L1; demanding one would make every honest L1 impossible to pass without a signed exception. - L0 — environment/infra, not spec-touching: the gate is skipped.
For any fix that touches production code (L1, L2, L3-spec-gap; L3-feature already exits at Step 3), this skill refuses to enter Step 6 (APPLY FIX) unless the level-appropriate evidence above is present and predates the first code-fix commit. Override via signed exception only.
A spec-update commit is any commit that mutates one of the following:
- Graph state — at least one of the following Neo4j node labels was
created or modified by the commit (detected via the W5 reconciliation
primitives, see "Detection logic" below):
DomainEntity,DomainAttribute,Enumeration,UseCase,FormField,Module,Requirement,FeatureRequest,BusinessRule,Activity,Decision. .tl/*schema artifact — at least one of:.tl/tasks/<TASK_ID>/task-{be,fe}.md,.tl/tasks/<TASK_ID>/api-contract.md,.tl/tasks/<TASK_ID>/spec.md,.tl/feature-requests/<FR-ID>.md,.tl/specs/<UC>.md, any fixture under.tl/fixtures/.- SA-layer docs (legacy markdown projects without graph) — at least
one of:
docs/12-domain/**,docs/14-usecases/**,docs/15-interfaces/**,docs/16-requirements/**.
A commit that touches only code under src/, backend/, frontend/,
packages/, or tests/ (other than fixture files) — including a commit
whose subject begins with fix(...) — is a code-fix commit, not a
spec-update commit, regardless of the message.
Why this gate exists
Project-Alpha shipped Wave 4 with a7eb747 docs(SA): UC-105/UC-106/UC-107 post-commit emit timing (L2) landing AFTER the FIX-B code wave
(01f2fcb, 135b14b, 6ed12ac, 3acb2fd) — the spec caught up to code,
not the other way around. The DIAGNOSTIC-REPORT.md dated 2026-05-18 measured
39% of fixes never updated documentation at all. Every undocumented
fix made the next post-mortem harder because the spec snapshot used to
diagnose drift was itself unreliable (Project-Alpha post-mortem § 1 patterns
2 & 3, § 3.12, § "Process/docs catch-up" row of the bucket table).
The Step 6 entry gate below makes that pattern impossible to repeat without an audited signed exception.
Detection logic (uses W5 reconciliation primitives)
The detection runs at Step 6 entry, after Step 5 USER GATE has resolved
(or been skipped for L1). It reads the same six sources of truth that
nacl-tl-conductor Phase 4.5 reads, scoped to the fix chain rather than
the intake:
-
Define the fix chain. The fix chain is the sequence of commits between the merge-base of the current branch with
main(orconfig.yaml → git.main_branch) andHEAD. On direct-strategy projects (config.yaml → git.strategy == "direct"), the fix chain is the commits between the last tag andHEADinstead.git rev-list --reverse <merge-base>..HEAD -
Classify each commit. For each commit in the chain, run:
git diff-tree --no-commit-id --name-only -r <sha>Apply the lists above:
- Any path matching the graph-mutation detector (see Step 6.SF-3 below)
OR matching a
.tl/*schema artifact OR matching the SA-layer docs globs → commit is a spec-update commit. - Otherwise → commit is a code-fix commit.
- Any path matching the graph-mutation detector (see Step 6.SF-3 below)
OR matching a
-
Detect graph mutation in a commit. Direct file-level inspection is not enough — graph writes live outside the file tree. The detection reads the W5-style "graph delta" between two snapshots, but scoped to the commit boundary:
a. If the project has a
graph-infra/exports/<commit>.cypherartifact (the canonical per-commit export written bynacl-publish), diff the commit's export against its parent's export. Any non-empty diff that adds or modifies a node with one of the labels listed under (1) above is a graph mutation.b. If no per-commit export exists, fall back to the commit's
.tl/changelog.mdentry: a commit whose.tl/changelog.mdaddition references/nacl-sa-*skill invocation (e.g. "via /nacl-sa-domain", "via /nacl-sa-uc") is treated as a graph-mutation commit. The fallback is recorded asgraph-mutation-by-changelogin the Step 8 report.c. If neither (a) nor (b) is available, the detection emits
Status: BLOCKEDwith workflow detailgraph-delta-unobservableand refuses to enter Step 6. A signed exception against the gatespec-first-prerequisiteis the only override (see below). -
Apply the spec-first invariant. Let
first_code_idxbe the index of the first code-fix commit in the chain, and letlast_spec_idx_before_codebe the maximum index among spec-update commits strictly precedingfirst_code_idx.- PASS if
last_spec_idx_before_codeexists (≥ 0) — there is a spec-update commit before any code-fix commit, satisfying the spec-first ordering for this fix chain. - FAIL if no spec-update commit precedes the first code-fix commit, OR if the chain contains code-fix commits but no spec-update commits at all.
- PASS if
-
Cross-check against
.tl/status.jsonand.tl/changelog.md. As a secondary safeguard (the samestatus.json+changelog.mdpair that W5 Phase 4.5 reads):- If
.tl/status.jsonrecords aphases.docs: doneorphases.spec: doneentry whose timestamp is strictly before the first code-fix commit timestamp, the chain has spec-update evidence even if no graph mutation was detected. Record this asspec-update-by-status-jsonin the Step 8 report. This is the channel that carries the L1 gap-check attestation: aphases.spec: { status: "done", kind: "gapcheck-no-drift" }entry (written in Step 5's L1 branch) is a validspec-update-by-status-jsonsignal — it is the level-appropriate evidence for L1, which never produces a doc-change commit. - If
.tl/changelog.mdhas an entry whose timestamp precedes the first code-fix commit and whose body mentions any of the L2/L3 doc-update categories (enum/status, API endpoint, UC flow, screen spec — see Step 5 matrix), record asspec-update-by-changelog.
- If
-
Compose the verdict. PASS iff (4) PASS OR (5) records at least one spec-update signal predating the first code-fix commit. FAIL otherwise.
Step 6 entry gate (the refusal)
At the start of Step 6, immediately after announcing "Step 6: APPLY FIX" and before any code-touching action:
Step 6.SF-1: SPEC-FIRST PREREQUISITE CHECK
classification: <L0 | L1 | L2 | L3-spec-gap>
fix_chain_commits: <N> commits between <merge-base>..HEAD
spec_update_commits: <list of SHAs classified spec-update, or "none">
code_fix_commits: <list of SHAs classified code-fix, or "none">
first_code_fix_idx: <index in chain, or "n/a — no code-fix yet">
last_spec_idx_before_code: <index, or "none">
status.json signal: <spec-update-by-status-json | none>
changelog signal: <spec-update-by-changelog | none>
L1 attestation: <gapcheck-no-drift (predates code) | none | n/a (not L1)>
verdict: <PASS | FAIL>
Apply the rules in order — first match wins:
| # | Condition | Action |
|---|---|---|
| 1 | classification is L0 | SKIP gate. Proceed to Step 6 sub-flow 6M / TDD. (L0 is environment / infra, not spec-touching.) |
| 2 | --dry-run is set | SKIP gate. The check is recorded in the Step 8 report but does not refuse, since no code is written. |
| 3 | verdict is PASS | Proceed to Step 6 sub-flow. Record the satisfying evidence in the Step 8 report: for L2/L3 the spec-update commit SHA; for L1 the gap-check attestation (gapcheck-no-drift, phases.spec ts predating code). |
| 4 | verdict is FAIL AND a valid signed exception against gate spec-first-prerequisite exists for this project (W4 schema; unexpired; specific affected_gates; concrete reason; valid followup_task). Lookup scans both namespaces: .tl/exceptions/*.yaml (human-authored, persistent) AND .tl/exceptions/goal-runs/*/EXC-goal-*.yaml (wrapper-authored by /nacl-goal intake, run-scoped, expires with the run — see nacl-goal/envelope.md) | Proceed to Step 6 sub-flow. Record the exception_id, expiry, and followup_task in the Step 8 report. The header becomes FIX APPLIED — UNVERIFIED (spec-first-bypassed-by-signed-exception). |
| 5 | verdict is FAIL AND no valid signed exception exists | REFUSE. Halt with Status: BLOCKED and workflow detail spec-first-prerequisite-missing. Do not touch production code. Print the refusal advisory below and exit. |
| 6 | detection emitted Status: BLOCKED (graph-delta-unobservable) AND no signed exception against gate spec-first-prerequisite exists | REFUSE with workflow detail graph-delta-unobservable. |
How L1 maps onto these rules (no special-case skip). L1 stays inside the same verdict machinery; nothing routes around it:
- L1 with attestation → PASS via rule 3. The
gapcheck-no-driftentry in.tl/status.json(Step 5 L1 branch), timestamped before code, is read by detection step 5 asspec-update-by-status-json, so verdict step 6 composes to PASS. The satisfying evidence recorded is the attestation, not a doc commit. - L1 with NO attestation → FAIL → rule 5 (REFUSE). A missing attestation means Phase A's gap-check never ran (or was not recorded) — the "jumped straight to code" case the gate exists to catch. The fix is to re-run Phase A so the diagnostician records the attestation, not to file a signed exception. The refusal advisory below names this path for L1.
This is why L1 no longer needs a signed exception to ship: the honest L1 path produces its own level-appropriate evidence.
Refusal advisory (rule 5)
Shortened here. Read the whole file on GitHub.
Signals
- GitHub stars
- 27
- Forks
- 4
- Last commit
- Sep 2026
Advanced
- Item type
- skill
- Key
nacl-tl-fix- Source
- github.com/itsalt/nacl