nacl-tl-fix

SkillDocs & knowledge

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

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 diagnostician agent (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-goal mode 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, and nacl-tl-hotfix delegate 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 value verification: <path> that orchestrators map to verify-GREEN:<path> evidence

Downstream consumers:

  • nacl-tl-dev-be (--continue delegates here)
  • nacl-tl-dev-fe (--continue delegates here)
  • nacl-tl-reopened
  • nacl-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:

  1. 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.
  2. .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/.
  3. 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:

  1. Define the fix chain. The fix chain is the sequence of commits between the merge-base of the current branch with main (or config.yaml → git.main_branch) and HEAD. On direct-strategy projects (config.yaml → git.strategy == "direct"), the fix chain is the commits between the last tag and HEAD instead.

    git rev-list --reverse <merge-base>..HEAD
    
  2. 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.
  3. 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>.cypher artifact (the canonical per-commit export written by nacl-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.md entry: a commit whose .tl/changelog.md addition 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 as graph-mutation-by-changelog in the Step 8 report.

    c. If neither (a) nor (b) is available, the detection emits Status: BLOCKED with workflow detail graph-delta-unobservable and refuses to enter Step 6. A signed exception against the gate spec-first-prerequisite is the only override (see below).

  4. Apply the spec-first invariant. Let first_code_idx be the index of the first code-fix commit in the chain, and let last_spec_idx_before_code be the maximum index among spec-update commits strictly preceding first_code_idx.

    • PASS if last_spec_idx_before_code exists (≥ 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.
  5. Cross-check against .tl/status.json and .tl/changelog.md. As a secondary safeguard (the same status.json + changelog.md pair that W5 Phase 4.5 reads):

    • If .tl/status.json records a phases.docs: done or phases.spec: done entry 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 as spec-update-by-status-json in the Step 8 report. This is the channel that carries the L1 gap-check attestation: a phases.spec: { status: "done", kind: "gapcheck-no-drift" } entry (written in Step 5's L1 branch) is a valid spec-update-by-status-json signal — it is the level-appropriate evidence for L1, which never produces a doc-change commit.
    • If .tl/changelog.md has 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 as spec-update-by-changelog.
  6. 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:

#ConditionAction
1classification is L0SKIP gate. Proceed to Step 6 sub-flow 6M / TDD. (L0 is environment / infra, not spec-touching.)
2--dry-run is setSKIP gate. The check is recorded in the Step 8 report but does not refuse, since no code is written.
3verdict is PASSProceed 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).
4verdict 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).
5verdict is FAIL AND no valid signed exception existsREFUSE. Halt with Status: BLOCKED and workflow detail spec-first-prerequisite-missing. Do not touch production code. Print the refusal advisory below and exit.
6detection emitted Status: BLOCKED (graph-delta-unobservable) AND no signed exception against gate spec-first-prerequisite existsREFUSE 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-drift entry in .tl/status.json (Step 5 L1 branch), timestamped before code, is read by detection step 5 as spec-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