Claude Cursor Skill

skmtc-debug

Diagnose failures in SKMTC sessions — no output, wrong output, error messages, bundle freshness, parseIssues, "Registered definition mismatch", ref cycles, "Module not found" in generated code, or any other broken behavior. Applies across both CLI usage and generator authoring co

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download skmtc-skmtc-deno_docs_skills_skmtc-debug-e3abffc.zip · 16 KB
skmtc/skmtc 19 0 forks Apache-2.0 Updated 7d ago
Part of skmtc/skmtc — 12 skills

Install

skills CLI npx skills add https://github.com/skmtc/skmtc/tree/main/deno/docs/skills/skmtc-debug
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install skmtc-skmtc@llmmart
Git git clone https://github.com/skmtc/skmtc.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole skmtc/skmtc collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

SKMTC debugging

This skill guides diagnosis of SKMTC failures. The defining feature is its epistemic stance: gather evidence before proposing fixes.

1. The verification-first stance

When debugging SKMTC, verify before stating.

  • The manifest is the canonical record of what happened in the last run. Read it before assuming behavior.
  • The code is the canonical record of what runs. Read it before trusting docstrings.
  • Docstrings, comments, and training-data priors are not evidence. Drift is real (see §2).
  • Reproduce the failure before proposing a fix. "Try X" without reproduction is guess-and-check, not debugging.

This stance is the load-bearing reason this skill exists separately from skmtc-cli and skmtc-generator. Those skills encourage proposing solutions from operational principles; this one requires gathering observable evidence first.

2. The five facts that override default LLM intuitions

Same five as in the other skills. One debug-relevant note added:

  1. No plugin registry, no dependency graph, no topological sort.
  2. Render does not run Prettier or Biome. Output is unformatted.
  3. Generator source code is the customization surface.
  4. OasSchema is a union type, not a class hierarchy.
  5. Same-named wrapper. insertNormalizedModel exists on both GenerateContext (takes explicit destinationPath) and the projection-base wrappers (fill destinationPath from settings.exportPath). Same name, different signatures.

Drift between docstrings and code is real. Docstrings and type comments can lag behind code reorganizations or removals. When a docstring or comment disagrees with what the function body actually does, the code is canonical. Any claim sourced from docstring prose should be verified against the function body.

3. Diagnostic paths by symptom

The lookup table. Before proposing a cause, find the symptom and walk the listed investigation steps in order.

Symptom First step If clean, next step
No output for operation X Check manifest.results for X's per-operation status Check isSupported predicate; check client.json skip/include
Wrong output (compiles) Read the generator's toString() template Compare against the stock generator's pattern; check insertOperation returns
Wrong output (doesn't compile) Run skmtc generate --typecheck; read TS errors Trace TS errors back to the generator source producing the offending line
parseIssue at level: 'error' Read the issue's location Walk to that path in the OpenAPI doc; check schema validity
INVALID_DEPENDENCY_REF Find the upstream INVALID_SCHEMA Fix the upstream schema; dependent issues should heal
Registered definition mismatch: 'X' in 'Y' Read the two generatorKey values from the error Clone one generator and disambiguate toIdentifier
Bundle freshness warning Compare deno.json#imports to imports in worker.ts Run skmtc bundle <project>
Max lookups reached The ref chain exceeds 10 hops Inspect the schema for circular refs or chains > 10
Module not found in generated code Read the unresolved import path in the generated file Either implement the consumer-side path, or clone the generator and change the import target
Orphaned/stale generated files on disk (output from a since-removed generator, a renamed export) Compare on-disk tree to manifest.files; a normal generate only prunes files the next run replaces skmtc clean <project> --dry-run to preview, then skmtc clean <project> for a full reset, then re-generate
No matching export … for import "X" (bundle time) Peer-dep version skew Run skmtc doctor --json; check project-core-pin/<project>
ConfigValidationError Stale manifest schema Upgrade CLI; the manifest auto-rewrites on next generate
Per-generator enrichments arrive as {} in the worker The installed CLI is pinned to old @skmtc/cli / @skmtc/core Delete ~/.deno/bin/.skmtc/deno.lock; reinstall with --reload
An enrichment customization doesn't land jq '.enrichmentWarnings' .settings/manifest.json — routing is the literal path + lowercase method (never operationId), the refName for models, under a main variant key Fix the flagged key (the message names the nearest match); skmtc doctor shows the same between runs
An item is error and the log shows a Valibot message (Invalid type: Expected …) — the manifest carries only the status, keyed by generator, item and variant A value has the wrong type for the generator's enrichments.ts schema, or a _generator / _stack value reached a generator whose umbrella declares that scope v.undefined() Fix the value; or declare the scope on every generator that will see it
"Raw mode is not supported on the current process.stdin" Ink command run in non-TTY Add --json flag; ported commands auto-degrade

For unrecognized symptoms: read manifest.json, then read the relevant source file, then ask the user for the exact error message verbatim (paraphrased error messages lose diagnostic signal).

4. Reading the manifest

The manifest at <root>/.skmtc/<project>/.settings/manifest.json is the canonical record of every decision the engine made in the last run. Read it immediately after the run you want to diagnose — the next generate/dev cycle overwrites it.

Top-level shape

{
  deploymentId: string         // identifies the run
  traceId, spanId: string      // log correlation
  region?: string
  startAt, endAt: number       // unix-ms; (endAt - startAt) = wall time
  files: Record<string, {      // every file actually written
    lines: number
    characters: number
    destinationPath: string    // resolved output path
  }>
  previews: Record<…, Preview> // UI-facing preview entries per Projection
  mappings?: Record<…, Mapping>
  results: ResultsItem         // per-(generator × item) outcome
  parseIssues: ParseIssue[]    // always present; empty array = no issues
}

results — what worked and what didn't

results is a deeply nested record keyed by trace → span → "generate" → generator package id → identifier:

{
  "trace-1778185255674": {
    "span-1778185255674": {
      "generate": {
        "@skmtc/gen-shadcn-form": {
          "get_Applicants":        "notSupported",
          "get_ApplicantById":     "success",
          "post_CreateApplicant":  "error"
        },
        "@skmtc/gen-zod": {
          "ApplicantModel": "success"
        }
      }
    }
  }
}

Each leaf is a ResultType:

Value Meaning
success Generator ran and produced output for this item
warning Output produced, with a recoverable issue logged
error Generator threw or returned failure; output may be missing or partial
skipped Item was matched but deliberately skipped (e.g., by client.json filters)
notSupported Generator's isSupported returned false — expected for items outside the generator's scope

Diagnostic workflow against the manifest

  1. "It generated nothing" — open results. If every leaf is notSupported, no generator's isSupported matched any operation/model. Check the schema actually has the operations expected and that the right generators are installed.

  2. "It generated less than expected" — grep the results subtree for the generator in question. Find which identifiers came back notSupported/skipped vs success. Identifier format is <protocol>_<operationId> for operations (query_…, mutation_…, get_…, post_…) and the model name for models.

  3. "A specific output is missing" — check files first. If the destinationPath isn't there, find the corresponding identifier in results. error means the generator failed; notSupported means the engine never reached it.

  4. "Cost / size accounting" — files has lines and characters per output. (endAt - startAt) is wall-clock duration.

jq queries for slicing

M=<root>/.skmtc/<project>/.settings/manifest.json

# Count by status across all generators in the most recent run:
jq '[.. | strings] | group_by(.) | map({status: .[0], n: length})' "$M"

# All non-success identifiers under a specific generator:
jq '.results[][].generate["@skmtc/gen-shadcn-form"]
    | to_entries | map(select(.value != "success"))' "$M"

# Files written by output subdirectory:
jq '.files | to_entries | group_by(.value.destinationPath | split("/")[1])
    | map({dir: .[0].value.destinationPath, n: length})' "$M"

# parseIssues at level "error":
jq '.parseIssues // [] | map(select(.level == "error"))' "$M"

Full manifest schema reference: reference/manifest-format.md.

5. Understanding parseIssues

The two-tier error model in Parse:

Tier 1: per-item isolation

Every per-item parse runs inside tryParseAt (core/context/tryParseAt.ts). A throw becomes a ParseIssue at level: 'error', and the item is dropped from the output map. Siblings continue.

Tier 2: cascade pruning

ParseContext maintains #refConsumers (who pointed at this ref) and #refErrors (which refs failed). At end-of-parse, removeErroredItems deletes every consumer of every failed ref, generating INVALID_DEPENDENCY_REF issues for the pruned consumers.

Implication: a single root-cause INVALID_SCHEMA can produce many INVALID_DEPENDENCY_REF issues elsewhere. The diagnostic move is to find the upstream INVALID_SCHEMA and fix it; the INVALID_DEPENDENCY_REF downstream issues typically resolve on their own.

Cascade pruning is one hop deep by current design — transitive dependents of pruned items may fail later (at generate time) with Ref "..." not found errors. Treat that as a hint that an even-more- upstream schema is broken.

Issue types you'll see

  • INVALID_SCHEMA — top-level schema parse failure
  • INVALID_DEPENDENCY_REF — cascade-pruned consumer of a failed ref
  • MISSING_OBJECT_TYPE — schema has properties but no type: 'object'; SKMTC inferred object (warning)
  • MISSING_ARRAY_TYPE — has items but no type: 'array' (warning)
  • MISSING_STRING_TYPE / MISSING_BOOLEAN_TYPE — similar fallback inferences (warning)
  • UNEXPECTED_PROPERTY — extra key in a schema position (warning)

Full reference: reference/error-codes.md.

6. Common failure scenarios with diagnostic paths

Scenario A: No output for an operation

Symptom: skmtc generate reports success but a specific operation produced no files.

  1. Open manifest.json. Find the per-operation result for the missing operation in manifest.results[traceId][spanId].generate[generatorId][identifier].
  2. Branches:
    • 'notSupported': The generator's isSupported predicate rejected this operation. Check the predicate in gen-<name>/src/mod.ts.
    • 'skipped': A filter in client.json (skip or include) is excluding it. Check client.json#settings.skip and .include.
    • 'success' but no file: The generator's transform returned content (which is discarded) instead of calling register or insertOperation. Read the generator source.
    • 'error': Read the error message in the manifest (or stderr from the run). The generator's constructor or toString threw.
  3. If the result is missing entirely (operation not present in manifest.results): the operation was pruned at parse time (look for INVALID_SCHEMA / INVALID_DEPENDENCY_REF in parseIssues at the operation's path).

Scenario B: Wrong output (compiles)

Symptom: Generated TS compiles but has incorrect semantics.

  1. Identify the offending file and the offending fragment.
  2. Read the generator's toString() template. Is the right Projection being instantiated? Is the right schema being read? (operation.toRequestBody, operation.toSuccessResponse, schema.resolve())
  3. Is the right peer Projection being referenced? Check insertOperation(Other, op).toName() calls — the returned name is what the template should embed.
  4. Did the constructor's side effects (register, insertNormalizedModel) run? Look for them in the constructor — if they're in toString(), that's wrong (mutation in toString is an anti-pattern).
  5. If the generator is stock and the output is consistently wrong: clone it and inspect the source. If a cloned generator: edit it.

Scenario C: Wrong output (doesn't compile)

Symptom: Generated TS has type errors.

  1. Run skmtc generate <project> --typecheck. The CLI returns diagnostics scoped to this run's files.
  2. Map each TS error back to the generator source that produced the offending line. Common patterns:
    • "Module not found": The generator produced a path the consumer hasn't implemented. Check the generator's register({ imports: ... }) calls — the consumer must provide the named module at the generated path, or the generator should be cloned and the import target changed. For a package name (@acme/sdk/models), the moduleName in settings.packages is not declared in that package's package.json (name, or an exports entry for a subpath). A render-time throw that names settings.packages (has no moduleName, under no package root) is a config fault, not a generator fault: docs/using/how-to/generate-into-multiple-packages.md.
    • Type mismatch between schema and validator: The schema → DSL conversion produced a Zod (or other) schema with different shape than the TS type. Usually the form / hook generator and the type / validator generator disagree on the input — check that they're using insertNormalizedModel consistently for the same schema.
    • Missing properties on a type: The schema is optional / nullable in a way the generator didn't account for. Read the OAS schema for the affected property.

Scenario D: Bundle freshness warning

Symptom: Strict-mode generate refuses with Error: bundle.js is out of sync with deno.json — add: … (exit 2).

  1. deno.json#imports and worker.ts declared different generator sets. Either was hand-edited without rebundling.
  2. Remediation: skmtc bundle <project> (rebuilds worker.ts from deno.json#imports).
  3. If worker.ts was edited by hand: the bundle has unrecorded changes; reset by regenerating. Hand-edits to worker.ts are not supported.
  4. Diagnostic: skmtc doctor --json surfaces this as project-bundle/<project>.

Scenario E: Registered definition mismatch

Symptom: Error: Registered definition mismatch: 'X' in file 'Y'. Cached key 'A' does not match new key 'B'.

A second form names options instead of keys: Cached options {...} do not match new options {...}. Fold options into toIdentifierName. The same (name, exportPath) was reached twice with different caller options, and the peer's toIdentifierName ignores them. Fix the peer (fold the options its output depends on into the name), not the caller.

  1. Two generators (or two callers within one generator) are producing the same identifier at the same exportPath.
  2. Read the two generatorKey values from the error. They identify the colliding generators. The 4-segment OAS format is generatorId|path|method|variant; GQL is generatorId|rootKind|fieldName|variant. If the only segment that differs is variant, this is the variants-aware case (Scenario G below); follow that branch instead.
  3. Branches:
    • Both are stock generators: Clone one and change its toIdentifier to disambiguate.
    • One is yours: Your toIdentifier is computing the same name as a peer. Make it more specific (verb prefix, kind suffix, etc.).
  4. The error is raised by OasOperationDriver.affirmDefinition — the cache key uniqueness invariant is enforced strictly for Driver-path insertions. (The insertNormalizedModel fallback-name path does not enforce; see #SKM-47.)

Scenario F: Engine throws "must include a 'main' variant"

Symptom: Error: [<generator-id>] Enrichments for '<METHOD> <path>' must include a 'main' variant. Found variants: customer, location.

  1. The consumer's client.json declares variant keys at enrichments[<gen-id>][<path>][<method>] (or [<rootKind>][<fieldName>] for GraphQL) without 'main' among them.
  2. The engine refuses to dispatch because every variants-aware path defaults to 'main' — silently inventing it would mask the misconfiguration.
  3. Fix: open client.json and either:
    • Add "main": {} (or "main": { ... }) to the variants record, OR
    • Remove the non-'main' variants and inline their content as the operation-level enrichment, OR
    • If you want the consumer to opt out of 'main', declare it anyway and add (path, method, "main") to skip.
  4. Where it's thrown: core/helpers/toVariantList.ts, invoked from GenerateContext.#runOasOperationGenerator and #runGqlOperationGenerator. Pinning test: core/context/GenerateContext.variants.test.ts → "declared variants without main throws at engine dispatch".

Scenario G: Driver throws "Cannot insert variant 'X'"

Symptom: Error: [<peer-gen-id>] Cannot insert variant '<name>' for '<METHOD> <path>' — peer has no enrichments configured. Only 'main' is permitted. or Available variants: main, customer.

  1. A variants-aware generator is calling context.insertOperation({ projection: Peer, operation, variant: 'X' }) where 'X' isn't declared in the PEER's enrichment block. The Driver's assertPeerVariantExists guard fires before the Projection is even constructed.
  2. Almost always the auto-inherit-variant anti-pattern (see skmtc-generator skill §8) — the caller's source has this.insertOperation(Peer, op, { variant: this.settings.variant }) against a variants-unaware peer.
  3. Fix in the caller's source:
    • If the peer is variants-unaware (most peers are): this.insertOperation(Peer, op) — drop the { variant }. The Driver defaults to 'main'; both variants of the caller share the peer's single Definition.
    • If the peer is variants-aware AND the caller genuinely wants a per-variant peer Definition: the peer's client.json enrichment must declare that variant before the call will succeed. Either add the declaration or remove the threading.
  4. Where it's thrown: core/dsl/operation/oas/OasOperationDriver.ts (and the GQL counterpart) → assertPeerVariantExists. Pinning tests: core/dsl/operation/oas/OasOperationDriver.test.ts → "Variant validation".

Scenario H: TypeError: this.context.X is not a function (workspace fallback to JSR)

Symptom: a runtime exception like TypeError: this.context.insertNormalizedModel is not a function (any context method) during skmtc generate, while bundle.js visibly contains a similar-but-differently-spelled method (insertNormalisedModel vs insertNormalizedModel, toRefName vs getRefName).

Cause: two @skmtc/core versions in one bundle — a workspace member silently fell back to the JSR-published version:

  1. @skmtc/worker pins @skmtc/core with an exact version (for example @skmtc/core@0.4.0).
  2. The local workspace member declares a different version (for example 0.4.4).
  3. Deno's workspace resolution rejects the mismatch and silently fetches the worker's exact-pinned core from JSR. The bundle then contains one GenerateContext from the worker's core and another from the generators' core; at runtime this.context is the wrong one.

Diagnostic path:

grep -i "Workspace member" .skmtc/<project>/.settings/error-logs.txt

The fallback emits Warning: Workspace member '@skmtc/core@X' was not used because it did not match '@skmtc/core@Y' — and it surfaces ONLY in error-logs.txt: bundle doesn't print it, generate doesn't mention it, doctor doesn't currently flag it. The log file is the authoritative diagnostic.

Fix: align the worker's expected @skmtc/core version with the workspace — upgrade the worker to a ranged pin (^0.4) or pin the workspace member to the worker's exact version. One core copy in the bundle → the method exists at runtime.

7. Anti-patterns specific to debugging

The defaults to override when in debug mode:

Don't propose code changes before reproducing the failure

❌ "Try changing toIdentifier — that might fix it."
✅ "Let's reproduce first. Run `skmtc generate <project> --json` and
   share the output."

"Try X" without reproduction is guess-and-check, not debugging. Each attempt costs a generate cycle.

Don't trust docstrings as authoritative

Docstrings and comments can lag behind code changes. Drift between docs and code is real. Verify against the function body, not the comments.

Don't extrapolate behavior from training data

This codebase has specific quirks that other codegen tools don't share:

  • No Prettier in the pipeline
  • OasSchema as a union, not a class hierarchy
  • Two spellings of insertNormali[sz]edModel
  • Worker permissions: net: false, run: false

Verify each claim against the source.

Don't assume the bug is in the generator

The failure may be in:

  • client.json (wrong path, wrong enrichment shape, wrong include/ skip)
  • The OpenAPI schema itself (malformed, missing $ref target)
  • A stale bundle (worker.ts ↔ deno.json drift)
  • A version mismatch (peer-pin between @skmtc/core and a generator)
  • The consumer-side code the generated output imports against
  • The user's setup (Deno version, JSR_URL, lockfile staleness)

Walk the diagnostic paths in §3 before deciding.

Don't restart from scratch unless symptoms warrant it

"Clean install" / "delete .skmtc and redo" should not be the first move. If specific symptoms suggest workspace corruption (manifest fails to parse, bundle.js is malformed, deno.json is invalid JSON), then targeted recreation makes sense. Otherwise, diagnose specifically.

Don't suggest --verbose or console.log before checking the manifest

The manifest already has structured diagnostic data per item. Reading it is faster than instrumenting the generator. Use jq queries from §4.

Don't paraphrase error messages

When asking the user about an error, request the exact verbatim text. Paraphrased messages lose the discriminator information that maps to the diagnostic path in §3.

8. When to escalate

Clone a stock generator for inspection

If the bug is in stock generator behavior (e.g., a gen-shadcn-form output is wrong), cloning brings the source local where it can be read and modified. Once cloned, the diagnostic shifts: now it's a generator-authoring problem (skmtc-generator skill takes over).

Surface to the friction log

If the diagnosis revealed a pattern (a confusing error message, a missing API helper, a frequently-misunderstood invariant), the skmtc-retro skill should capture it as a friction-log entry. Don't let an interesting diagnostic insight evaporate.

Suggest a SKMTC code change

If the bug is in @skmtc/core or @skmtc/cli (not in a generator), propose the fix as a PR or GitHub issue. Distinguish between:

  • Fix in cloned generator — immediate, local, ships in the consumer's repo
  • Fix in core — slower, upstream, affects all projects

Choosing the wrong level produces friction. Generator-shape bugs typically belong in the generator; engine-shape bugs belong in core.

9. Boundary with other skills

  • skmtc-cli: hand off when the diagnosis has identified a CLI / configuration fix (e.g., "you need to update client.json basePath"). The skmtc-cli skill guides applying the fix.
  • skmtc-generator: hand off when the diagnosis has identified a fix in generator source. The skmtc-generator skill guides the source edit.
  • skmtc-retro: end-of-session. Debug sessions often surface retro-worthy observations — patterns of confusing error messages, missing diagnostic surfaces, recurring failure modes.

The transition: this skill is active while the LLM doesn't yet know what's wrong. Once a root cause is identified, the appropriate "doing" skill helps with the fix.

10. Cross-references

Files (skmtc)
  • CLAUDE.md 344 B
    <claude-mem-context>
    # Recent Activity
    
    <!-- This section is auto-generated by claude-mem. Edit content outside the tags. -->
    
    ### May 12, 2026
    
    | ID | Time | T | Title | Read |
    |----|------|---|-------|------|
    | #20769 | 11:43 AM | 🟣 | SKMTC debugging skill created with verification-first diagnostic approach | ~580 |
    </claude-mem-context>
  • design.md 14 KB
    # skmtc-debug skill outline
    
    > Plan for a proposed new skill — diagnose failures in SKMTC
    > sessions. Applies across CLI and generator-authoring contexts.
    
    ## Status: proposed
    
    This skill does not yet exist. The argument for adding it:
    
    Debugging requires a fundamentally different epistemic stance than
    building. When *doing* SKMTC work, the LLM should propose solutions
    from operational principles. When *debugging*, the LLM should
    **verify before stating** — read the manifest, check parseIssues
    against actual files, reproduce the failure before suggesting fixes.
    
    Folding debugging into `skmtc-cli` or `skmtc-generator` means the
    LLM's default posture during failures is wrong. A separate skill with
    "verify-first" priors is the lowest-cost way to flip that posture
    automatically when symptoms suggest something is broken.
    
    ## Purpose
    
    Diagnose failures in SKMTC sessions:
    
    - No output produced for an operation that was expected to produce some
    - Wrong output (compiles but is incorrect, or doesn't compile)
    - Error messages the user doesn't understand
    - Bundle freshness or worker setup issues
    - Cascading parseIssues from a single bad schema
    - "Registered definition mismatch" collisions between generators
    
    The skill's job is to **lead the LLM through evidence-gathering**
    before solution-proposing.
    
    ## Audience
    
    Anyone with broken SKMTC behavior — users debugging configuration,
    authors debugging generators, integrators debugging CI failures. The
    defining characteristic is the *symptom*, not the role. When something
    is broken, this skill takes priority over the user/author skills.
    
    ## Triggers
    
    Intent phrases that should load this skill:
    
    - "why isn't my generator working"
    - "no output for X" / "generation produced nothing for X"
    - "wrong output" / "the generated code is wrong"
    - "this error message" (when accompanied by an error)
    - "generation failed"
    - "manifest says X"
    - "bundle is stale" / "bundle freshness"
    - "parseIssue" / "INVALID_SCHEMA" / "INVALID_DEPENDENCY_REF"
    - "this doesn't compile" (in the context of generated output)
    - "Registered definition mismatch"
    - "Module not found" (in generated code)
    - "Max lookups reached" (ref cycle)
    
    Should NOT auto-load on:
    
    - "install a generator" → `skmtc-cli`
    - "write a generator" → `skmtc-generator`
    - "let's retro" → `skmtc-retro`
    
    ## Scope boundary
    
    ### In skill (operational, just-in-time content)
    
    - **The verification-first stance** — the foundational epistemic
      principle. Read before assert. Reproduce before propose. Check the
      manifest, not the docs, for run state.
    - **Diagnostic paths by symptom** — table mapping common failure
      symptoms to ordered investigation steps
    - **Reading the manifest** — what each field means, how to interpret
      per-operation results
    - **Understanding parseIssues** — types, severities, what cascade
      pruning implies
    - **The cascade pruning model** — why a single bad schema can produce
      many `INVALID_DEPENDENCY_REF` issues elsewhere
    - **The five common failure scenarios** with reproducible diagnostic
      paths:
      - "No output for operation X"
      - "Wrong output (semantic bug)"
      - "Generated code doesn't compile"
      - "Bundle freshness warning"
      - "Same-name collision (Registered definition mismatch)"
    - **Anti-patterns specific to debugging** — defaults to override:
      don't propose fixes without reproducing; don't extrapolate from
      training data; don't read docstrings as ground truth
    - **When to escalate** — clone a stock generator for inspection;
      surface to the friction log; suggest a SKMTC code change
    
    ### Deferred to docs
    
    - Full error code reference → `reference/error-codes.md`
    - Full manifest format → `reference/manifest-format.md`
    - How to fix bugs *in generator code* (once located) → `skmtc-generator`
    - How to fix bugs *in CLI configuration* (once located) → `skmtc-cli`
    - Debug-related tutorials and recipes (when written) → `using/how-to/debug-failing-generation.md`
    
    ### Boundary with adjacent skills
    
    - **skmtc-cli**: takes over when symptoms suggest failure. Debug owns
      diagnosis; cli takes over once the user knows what to change in
      config.
    - **skmtc-generator**: same — debug owns diagnosis. Once the bug is
      located in generator source, generator skill helps with the fix.
    - **skmtc-retro**: end-of-session. If debugging surfaced a SKMTC-level
      pattern worth recording, retro captures it.
    
    The boundary heuristic: **this skill is active while the LLM doesn't
    yet know what's wrong**. Once a root cause is identified, the
    appropriate "doing" skill (cli or generator) helps with the fix.
    
    ## Outline structure
    
    The actual `SKILL.md` should have approximately these sections:
    
    ### 1. The verification-first stance
    
    The foundational principle. A short paragraph stating:
    
    > When debugging SKMTC, **verify before stating**. The manifest is
    > the canonical record of what happened. The code is the canonical
    > record of what runs. Docstrings, comments, and training-data priors
    > are not evidence. Read the manifest first. Reproduce the failure
    > before proposing a fix. Trust observed behavior over assumed behavior.
    
    This sets the epistemic stance for everything below.
    
    ### 2. The five facts that override default LLM intuitions
    
    Same five as in the other skills (duplicated from `llms.md`), with
    one extra debug-relevant fact called out:
    
    > **Drift between docstrings and code is real.** Docstrings and
    > type comments can lag behind code changes. When a docstring and
    > the code disagree, the code is canonical.
    
    ### 3. Diagnostic paths by symptom
    
    A table that the LLM consults *before* proposing causes:
    
    | Symptom | First step | If that's clean, next step |
    |---|---|---|
    | No output for operation X | Check manifest's per-operation result for X | Check `isSupported` predicate; check `skip`/`include` |
    | Wrong output (compiles) | Read the generator's `toString()` template | Compare against the expected pattern in the stock generator |
    | Wrong output (doesn't compile) | Run `--typecheck`; read TS errors | Trace TS errors back to the generator source |
    | `parseIssue` at `level: 'error'` | Read the issue's `location` | Walk to that path in the OpenAPI doc; check schema validity |
    | `INVALID_DEPENDENCY_REF` | Find the upstream `INVALID_SCHEMA` | Fix the upstream schema; the dependent should heal |
    | `Registered definition mismatch` | Find the two `generatorKey`s | One generator's `toIdentifier` is colliding; clone and disambiguate |
    | Bundle freshness warning | Compare `deno.json#imports` to `worker.ts` | Run `skmtc bundle <project>` |
    | `Max lookups reached` | The ref chain exceeds 10 hops | Inspect the schema for circular refs |
    
    ### 4. Reading the manifest
    
    The canonical reference is `manifest.parseIssues` and the per-operation
    results map. Key fields to interpret:
    
    - `parseIssues[]` — each with `level`, `type`, `location`, `message`
    - `results[generatorId][operationOrRefName]` —
      `'success' | 'notSupported' | 'skipped' | 'error'`
    - Exit code derivation — fatal parseIssue or typecheck failure → 1
    
    Cross-reference to `reference/manifest-format.md` for the full schema.
    
    ### 5. Understanding parseIssues
    
    The two-tier error model:
    
    - Per-item: a single bad schema becomes one `INVALID_SCHEMA` issue;
      the item is dropped from output but siblings continue
    - Cross-ref: a `$ref` to the dropped item triggers `INVALID_DEPENDENCY_REF`
      on every consumer, and those consumers are pruned
    
    Implication: a single root-cause schema bug can produce many issues.
    The diagnostic path is to find the *upstream* `INVALID_SCHEMA` and fix
    it; the downstream `INVALID_DEPENDENCY_REF` issues typically resolve on
    their own.
    
    Cross-reference to `reference/error-codes.md` for the full list of
    issue types.
    
    ### 6. Common failure scenarios (with diagnostic paths)
    
    Each scenario gets a short playbook — what to check, in order, with
    the typical root cause:
    
    #### Scenario A: No output for an operation
    
    - Check the manifest: was the generator's `transform` called for this
      operation? (Look at the per-operation result.)
    - If `'notSupported'`: the generator's `isSupported` rejected the
      operation. Check the predicate.
    - If `'skipped'`: a filter in `client.json` (`skip` or `include`) is
      excluding it.
    - If `'success'` but no file: the generator's transform may have
      returned content (which is discarded) instead of registering.
    - If `'error'`: read the error message in the manifest.
    
    #### Scenario B: Wrong output (compiles)
    
    - Read the generator's `toString()` template. Is the right Projection
      being instantiated? The right schema being read?
    - Is the right peer Projection being referenced? Check
      `insertOperation(Other, op).toName()` calls.
    - Did the constructor's side effects (`register`, `insertNormalizedModel`)
      run? They're in the constructor, not `toString()`.
    
    #### Scenario C: Wrong output (doesn't compile)
    
    - Run `skmtc generate <project> --typecheck`. TS errors are scoped to
      this run's files.
    - Map each TS error back to the generator source that produced the
      offending line. Common patterns:
      - "Module not found": stock generator produced a path the consumer
        hasn't implemented; check `register({ imports: ... })`
      - Type mismatch: schema → DSL conversion produced a Zod schema with
        different shape than the TS type; check `insertNormalizedModel`
        consistency between the two generators
    
    #### Scenario D: Bundle freshness warning
    
    - `deno.json#imports` and `worker.ts` declared different generator
      sets.
    - Run `skmtc bundle <project>` (rebuilds `worker.ts` from `deno.json`).
    - If `worker.ts` was hand-edited, the bundle has unrecorded changes;
      reset by regenerating.
    
    #### Scenario E: Registered definition mismatch
    
    - Two generators (or two callers within one generator) produce the
      same identifier at the same `exportPath`.
    - Read the two `generatorKey` values from the error message.
    - Disambiguate by editing one generator's `toIdentifier` (typically
      the cloned one).
    
    ### 7. Anti-patterns specific to debugging
    
    The defaults to override when debugging:
    
    - **Don't propose code changes before reproducing the failure.** "Try
      X" without reproducing is guess-and-check, not debugging.
    - **Don't read docstrings or comments as authoritative.** Docstrings
      drift; the code is canonical.
    - **Don't extrapolate behavior from training data.** This codebase has
      specific quirks (no Prettier, `OasSchema` union). Verify each claim
      against the source.
    - **Don't assume the bug is in the generator.** It may be in
      `client.json`, in the OpenAPI schema, in a stale bundle, or in
      consumer-side code the generator imports against.
    - **Don't restart from scratch** ("clean install" / "delete .skmtc and
      redo") **unless** the symptoms specifically suggest workspace
      corruption.
    - **Don't suggest "run with --verbose"** or **"add console.log"**
      before checking the manifest. The manifest already has structured
      diagnostic data.
    
    ### 8. When to escalate
    
    - **Clone a stock generator for inspection** — if the bug is in stock
      generator behavior, cloning brings the source local where you can
      read and modify it.
    - **Surface to the friction log** — if the diagnosis revealed a
      pattern (e.g., a confusing error message, a missing API helper),
      the retro skill should capture it.
    - **Suggest a SKMTC code change** — if the bug is in `@skmtc/core` or
      `@skmtc/cli` itself, propose the fix as a PR or issue. Distinguish
      from "fix in cloned generator" (immediate, local) vs "fix in core"
      (slower, upstream).
    
    ### 9. Cross-references
    
    - `using/how-to/debug-failing-generation.md` (or
      `using/how-to/debug-failing-generation.md` if extending)
    - `reference/manifest-format.md`
    - `reference/error-codes.md`
    - `concepts/error-handling-philosophy.md`
    - `concepts/refs-and-resolution.md` (for ref-cycle issues)
    - `llms.md`'s "Verification protocol" section
    
    ## Open design questions
    
    ### Should this be its own skill or a section in cli/generator?
    
    The argument for a separate skill: the epistemic stance differs.
    Loading the debug skill auto-flips the LLM's posture from
    propose-solutions to verify-first.
    
    The argument against: three skills is more than two, and the boundary
    between "I'm authoring and stuck" vs "I'm debugging" is fuzzy.
    
    Recommendation in this outline: separate skill. The cost of the third
    skill is small (one more file, one more set of triggers); the value of
    flipping epistemic stance is large enough to justify it.
    
    ### Should debug have its own slash command?
    
    Symmetry with `skmtc-retro` suggests yes — `/skmtc-debug` for explicit
    invocation, with the skill also auto-loading on symptom triggers. The
    command would be useful when the user knows they're starting a debug
    session and wants to invoke the stance deliberately.
    
    ### Symptom-driven vs intent-driven triggers
    
    The trigger list above mixes symptom phrases ("no output", "wrong
    output") with error-message phrases ("Registered definition
    mismatch"). This is intentional — both work as triggers.
    
    The risk: if intent matching fires too eagerly on these phrases, the
    debug skill loads when the user actually wanted cli or generator. The
    mitigation is the description's wording — be precise about *failures*
    (not just questions or curiosities).
    
    ### How does debug interact with the manifest reader?
    
    The skill heavily relies on the manifest. If the manifest format
    changes, the skill needs to update. Consider: should the skill embed
    a reference to specific manifest fields, or defer entirely to
    `reference/manifest-format.md`?
    
    Right answer: embed the *most-used* fields (`parseIssues`, `results`)
    inline; defer the full schema to the reference doc. Same pattern as
    other skills.
    
    ### When does the debug skill produce a friction-log entry?
    
    Many debug sessions surface SKMTC-level patterns (confusing error
    messages, missing diagnostic surfaces, recurring failure modes).
    Currently the user invokes `skmtc-retro` after debugging; the retro
    captures observations.
    
    Open question: should the debug skill *itself* prompt for retro
    capture before ending? Could be: "before we close out, is there
    anything worth logging?" This couples debug to retro in a useful way.
    
    ### Should debug produce structured output?
    
    For agents (not humans), structured output may be valuable. The
    manifest is already structured; a debug skill could produce
    "diagnostic reports" in a consistent format. Defer until there's
    demand.
    
  • SKILL.md 25.9 KB
    ---
    name: skmtc-debug
    version: 0.2.1
    description: |
      Diagnose failures in SKMTC sessions — no output, wrong output, error
      messages, bundle freshness, parseIssues, "Registered definition
      mismatch", ref cycles, "Module not found" in generated code, or any
      other broken behavior. Applies across both CLI usage and generator
      authoring contexts.
    
      Use this skill when the user asks "why isn't my generator working",
      "no output for X", "wrong output", "what does this error mean",
      "manifest says X", "bundle is stale", "INVALID_SCHEMA",
      "INVALID_DEPENDENCY_REF", "Registered definition mismatch", "Module
      not found" (in generated code), "ConfigValidationError", or reports
      any other SKMTC failure.
    
      This skill encodes a **verify-first epistemic stance** — read the
      manifest, check parseIssues, reproduce the failure before proposing
      fixes. Distinct from `skmtc-cli` and `skmtc-generator` which guide
      *doing*; this skill guides *diagnosing*.
    allowed-tools:
      - Bash
      - Read
      - Glob
      - Grep
      - Write
      - Edit
    metadata:
      internal: true
    ---
    
    # SKMTC debugging
    
    This skill guides diagnosis of SKMTC failures. The defining feature is
    its **epistemic stance**: gather evidence before proposing fixes.
    
    ## 1. The verification-first stance
    
    When debugging SKMTC, **verify before stating**.
    
    - The **manifest** is the canonical record of what happened in the
      last run. Read it before assuming behavior.
    - The **code** is the canonical record of what runs. Read it before
      trusting docstrings.
    - **Docstrings, comments, and training-data priors are not evidence.**
      Drift is real (see §2).
    - **Reproduce** the failure before proposing a fix. "Try X" without
      reproduction is guess-and-check, not debugging.
    
    This stance is the load-bearing reason this skill exists separately
    from `skmtc-cli` and `skmtc-generator`. Those skills encourage
    proposing solutions from operational principles; this one requires
    gathering observable evidence first.
    
    ## 2. The five facts that override default LLM intuitions
    
    Same five as in the other skills. One debug-relevant note added:
    
    1. **No plugin registry, no dependency graph, no topological sort.**
    2. **Render does not run Prettier or Biome.** Output is unformatted.
    3. **Generator source code is the customization surface.**
    4. **`OasSchema` is a union type, not a class hierarchy.**
    5. **Same-named wrapper.** `insertNormalizedModel` exists on both
       `GenerateContext` (takes explicit `destinationPath`) and the
       projection-base wrappers (fill `destinationPath` from
       `settings.exportPath`). Same name, different signatures.
    
    **Drift between docstrings and code is real.** Docstrings and
    type comments can lag behind code reorganizations or removals.
    When a docstring or comment disagrees with what the function body
    actually does, the code is canonical. Any claim sourced from
    docstring prose should be verified against the function body.
    
    ## 3. Diagnostic paths by symptom
    
    The lookup table. Before proposing a cause, find the symptom and walk
    the listed investigation steps in order.
    
    | Symptom | First step | If clean, next step |
    |---|---|---|
    | No output for operation X | Check `manifest.results` for X's per-operation status | Check `isSupported` predicate; check `client.json` `skip`/`include` |
    | Wrong output (compiles) | Read the generator's `toString()` template | Compare against the stock generator's pattern; check `insertOperation` returns |
    | Wrong output (doesn't compile) | Run `skmtc generate --typecheck`; read TS errors | Trace TS errors back to the generator source producing the offending line |
    | `parseIssue` at `level: 'error'` | Read the issue's `location` | Walk to that path in the OpenAPI doc; check schema validity |
    | `INVALID_DEPENDENCY_REF` | Find the upstream `INVALID_SCHEMA` | Fix the upstream schema; dependent issues should heal |
    | `Registered definition mismatch: 'X' in 'Y'` | Read the two `generatorKey` values from the error | Clone one generator and disambiguate `toIdentifier` |
    | Bundle freshness warning | Compare `deno.json#imports` to imports in `worker.ts` | Run `skmtc bundle <project>` |
    | `Max lookups reached` | The ref chain exceeds 10 hops | Inspect the schema for circular refs or chains > 10 |
    | Module not found in generated code | Read the unresolved import path in the generated file | Either implement the consumer-side path, or clone the generator and change the import target |
    | Orphaned/stale generated files on disk (output from a since-removed generator, a renamed export) | Compare on-disk tree to `manifest.files`; a normal `generate` only prunes files the *next* run replaces | `skmtc clean <project> --dry-run` to preview, then `skmtc clean <project>` for a full reset, then re-`generate` |
    | `No matching export … for import "X"` (bundle time) | Peer-dep version skew | Run `skmtc doctor --json`; check `project-core-pin/<project>` |
    | `ConfigValidationError` | Stale manifest schema | Upgrade CLI; the manifest auto-rewrites on next generate |
    | Per-generator enrichments arrive as `{}` in the worker | The installed CLI is pinned to old `@skmtc/cli` / `@skmtc/core` | Delete `~/.deno/bin/.skmtc/deno.lock`; reinstall with `--reload` |
    | An enrichment customization doesn't land | `jq '.enrichmentWarnings' .settings/manifest.json` — routing is the literal path + lowercase method (never `operationId`), the refName for models, under a `main` variant key | Fix the flagged key (the message names the nearest match); `skmtc doctor` shows the same between runs |
    | An item is `error` and the log shows a Valibot message (`Invalid type: Expected …`) — the manifest carries only the status, keyed by generator, item and variant | A value has the wrong type for the generator's `enrichments.ts` schema, or a `_generator` / `_stack` value reached a generator whose umbrella declares that scope `v.undefined()` | Fix the value; or declare the scope on every generator that will see it |
    | "Raw mode is not supported on the current process.stdin" | Ink command run in non-TTY | Add `--json` flag; ported commands auto-degrade |
    
    For unrecognized symptoms: read `manifest.json`, then read the
    relevant source file, then ask the user for the exact error message
    verbatim (paraphrased error messages lose diagnostic signal).
    
    ## 4. Reading the manifest
    
    The manifest at `<root>/.skmtc/<project>/.settings/manifest.json` is
    the canonical record of every decision the engine made in the last
    run. Read it **immediately after the run you want to diagnose** —
    the next `generate`/`dev` cycle overwrites it.
    
    ### Top-level shape
    
    ```ts
    {
      deploymentId: string         // identifies the run
      traceId, spanId: string      // log correlation
      region?: string
      startAt, endAt: number       // unix-ms; (endAt - startAt) = wall time
      files: Record<string, {      // every file actually written
        lines: number
        characters: number
        destinationPath: string    // resolved output path
      }>
      previews: Record<…, Preview> // UI-facing preview entries per Projection
      mappings?: Record<…, Mapping>
      results: ResultsItem         // per-(generator × item) outcome
      parseIssues: ParseIssue[]    // always present; empty array = no issues
    }
    ```
    
    ### `results` — what worked and what didn't
    
    `results` is a **deeply nested record** keyed by trace → span →
    `"generate"` → generator package id → identifier:
    
    ```jsonc
    {
      "trace-1778185255674": {
        "span-1778185255674": {
          "generate": {
            "@skmtc/gen-shadcn-form": {
              "get_Applicants":        "notSupported",
              "get_ApplicantById":     "success",
              "post_CreateApplicant":  "error"
            },
            "@skmtc/gen-zod": {
              "ApplicantModel": "success"
            }
          }
        }
      }
    }
    ```
    
    Each leaf is a `ResultType`:
    
    | Value | Meaning |
    |---|---|
    | `success` | Generator ran and produced output for this item |
    | `warning` | Output produced, with a recoverable issue logged |
    | `error` | Generator threw or returned failure; output may be missing or partial |
    | `skipped` | Item was matched but deliberately skipped (e.g., by `client.json` filters) |
    | `notSupported` | Generator's `isSupported` returned false — *expected* for items outside the generator's scope |
    
    ### Diagnostic workflow against the manifest
    
    1. **"It generated nothing"** — open `results`. If every leaf is
       `notSupported`, no generator's `isSupported` matched any
       operation/model. Check the schema actually has the operations
       expected and that the right generators are installed.
    
    2. **"It generated less than expected"** — grep the `results` subtree
       for the generator in question. Find which identifiers came back
       `notSupported`/`skipped` vs `success`. Identifier format is
       `<protocol>_<operationId>` for operations (`query_…`, `mutation_…`,
       `get_…`, `post_…`) and the model name for models.
    
    3. **"A specific output is missing"** — check `files` first. If the
       `destinationPath` isn't there, find the corresponding identifier in
       `results`. `error` means the generator failed; `notSupported`
       means the engine never reached it.
    
    4. **"Cost / size accounting"** — `files` has `lines` and `characters`
       per output. `(endAt - startAt)` is wall-clock duration.
    
    ### jq queries for slicing
    
    ```bash
    M=<root>/.skmtc/<project>/.settings/manifest.json
    
    # Count by status across all generators in the most recent run:
    jq '[.. | strings] | group_by(.) | map({status: .[0], n: length})' "$M"
    
    # All non-success identifiers under a specific generator:
    jq '.results[][].generate["@skmtc/gen-shadcn-form"]
        | to_entries | map(select(.value != "success"))' "$M"
    
    # Files written by output subdirectory:
    jq '.files | to_entries | group_by(.value.destinationPath | split("/")[1])
        | map({dir: .[0].value.destinationPath, n: length})' "$M"
    
    # parseIssues at level "error":
    jq '.parseIssues // [] | map(select(.level == "error"))' "$M"
    ```
    
    Full manifest schema reference: [`reference/manifest-format.md`](../../reference/manifest-format.md).
    
    ## 5. Understanding parseIssues
    
    The two-tier error model in Parse:
    
    ### Tier 1: per-item isolation
    
    Every per-item parse runs inside `tryParseAt`
    (`core/context/tryParseAt.ts`). A throw becomes a `ParseIssue` at
    `level: 'error'`, and the item is dropped from the output map.
    Siblings continue.
    
    ### Tier 2: cascade pruning
    
    `ParseContext` maintains `#refConsumers` (who pointed at this ref)
    and `#refErrors` (which refs failed). At end-of-parse,
    `removeErroredItems` deletes every consumer of every failed ref,
    generating `INVALID_DEPENDENCY_REF` issues for the pruned consumers.
    
    **Implication:** a single root-cause `INVALID_SCHEMA` can produce
    many `INVALID_DEPENDENCY_REF` issues elsewhere. The diagnostic move
    is to find the *upstream* `INVALID_SCHEMA` and fix it; the
    `INVALID_DEPENDENCY_REF` downstream issues typically resolve on
    their own.
    
    Cascade pruning is **one hop deep** by current design — transitive
    dependents of pruned items may fail later (at generate time) with
    `Ref "..." not found` errors. Treat that as a hint that an even-more-
    upstream schema is broken.
    
    ### Issue types you'll see
    
    - `INVALID_SCHEMA` — top-level schema parse failure
    - `INVALID_DEPENDENCY_REF` — cascade-pruned consumer of a failed ref
    - `MISSING_OBJECT_TYPE` — schema has `properties` but no `type:
      'object'`; SKMTC inferred object (warning)
    - `MISSING_ARRAY_TYPE` — has `items` but no `type: 'array'` (warning)
    - `MISSING_STRING_TYPE` / `MISSING_BOOLEAN_TYPE` — similar fallback
      inferences (warning)
    - `UNEXPECTED_PROPERTY` — extra key in a schema position (warning)
    
    Full reference: [`reference/error-codes.md`](../../reference/error-codes.md).
    
    ## 6. Common failure scenarios with diagnostic paths
    
    ### Scenario A: No output for an operation
    
    **Symptom:** `skmtc generate` reports success but a specific
    operation produced no files.
    
    1. Open `manifest.json`. Find the per-operation result for the
       missing operation in `manifest.results[traceId][spanId].generate[generatorId][identifier]`.
    2. Branches:
       - **`'notSupported'`**: The generator's `isSupported` predicate
         rejected this operation. Check the predicate in
         `gen-<name>/src/mod.ts`.
       - **`'skipped'`**: A filter in `client.json` (`skip` or `include`)
         is excluding it. Check `client.json#settings.skip` and `.include`.
       - **`'success'` but no file**: The generator's `transform`
         returned content (which is discarded) instead of calling
         `register` or `insertOperation`. Read the generator source.
       - **`'error'`**: Read the error message in the manifest (or
         stderr from the run). The generator's constructor or `toString`
         threw.
    3. If the result is missing entirely (operation not present in
       `manifest.results`): the operation was pruned at parse time (look
       for `INVALID_SCHEMA` / `INVALID_DEPENDENCY_REF` in `parseIssues`
       at the operation's path).
    
    ### Scenario B: Wrong output (compiles)
    
    **Symptom:** Generated TS compiles but has incorrect semantics.
    
    1. Identify the offending file and the offending fragment.
    2. Read the generator's `toString()` template. Is the right
       Projection being instantiated? Is the right schema being read?
       (`operation.toRequestBody`, `operation.toSuccessResponse`,
       `schema.resolve()`)
    3. Is the right peer Projection being referenced? Check
       `insertOperation(Other, op).toName()` calls — the returned name
       is what the template should embed.
    4. Did the constructor's side effects (`register`,
       `insertNormalizedModel`) run? Look for them in the constructor —
       if they're in `toString()`, that's wrong (mutation in `toString`
       is an anti-pattern).
    5. If the generator is stock and the output is consistently wrong:
       clone it and inspect the source. If a cloned generator: edit it.
    
    ### Scenario C: Wrong output (doesn't compile)
    
    **Symptom:** Generated TS has type errors.
    
    1. Run `skmtc generate <project> --typecheck`. The CLI returns
       diagnostics scoped to this run's files.
    2. Map each TS error back to the generator source that produced the
       offending line. Common patterns:
       - **"Module not found"**: The generator produced a path the
         consumer hasn't implemented. Check the generator's `register({
         imports: ... })` calls — the consumer must provide the named
         module at the generated path, or the generator should be cloned
         and the import target changed. For a package name
         (`@acme/sdk/models`), the `moduleName` in `settings.packages` is
         not declared in that package's `package.json` (`name`, or an
         `exports` entry for a subpath). A render-time throw that names
         `settings.packages` (`has no moduleName`, `under no package
         root`) is a config fault, not a generator fault:
         `docs/using/how-to/generate-into-multiple-packages.md`.
       - **Type mismatch between schema and validator**: The schema → DSL
         conversion produced a Zod (or other) schema with different
         shape than the TS type. Usually the form / hook generator and
         the type / validator generator disagree on the input — check
         that they're using `insertNormalizedModel` consistently for the
         same schema.
       - **Missing properties on a type**: The schema is `optional` /
         `nullable` in a way the generator didn't account for. Read the
         OAS schema for the affected property.
    
    ### Scenario D: Bundle freshness warning
    
    **Symptom:** Strict-mode `generate` refuses with
    `Error: bundle.js is out of sync with deno.json — add: …` (exit 2).
    
    1. `deno.json#imports` and `worker.ts` declared different generator
       sets. Either was hand-edited without rebundling.
    2. Remediation: `skmtc bundle <project>` (rebuilds `worker.ts` from
       `deno.json#imports`).
    3. If `worker.ts` was edited by hand: the bundle has unrecorded
       changes; reset by regenerating. Hand-edits to `worker.ts` are
       not supported.
    4. Diagnostic: `skmtc doctor --json` surfaces this as
       `project-bundle/<project>`.
    
    ### Scenario E: Registered definition mismatch
    
    **Symptom:** `Error: Registered definition mismatch: 'X' in file
    'Y'. Cached key 'A' does not match new key 'B'`.
    
    A second form names options instead of keys: `Cached options {...}
    do not match new options {...}. Fold options into toIdentifierName.`
    The same `(name, exportPath)` was reached twice with different caller
    options, and the peer's `toIdentifierName` ignores them. Fix the peer
    (fold the options its output depends on into the name), not the
    caller.
    
    1. Two generators (or two callers within one generator) are
       producing the same identifier at the same `exportPath`.
    2. Read the two `generatorKey` values from the error. They identify
       the colliding generators. The 4-segment OAS format is
       `generatorId|path|method|variant`; GQL is
       `generatorId|rootKind|fieldName|variant`. **If the only segment
       that differs is `variant`**, this is the variants-aware case
       (Scenario G below); follow that branch instead.
    3. Branches:
       - **Both are stock generators**: Clone one and change its
         `toIdentifier` to disambiguate.
       - **One is yours**: Your `toIdentifier` is computing the same name
         as a peer. Make it more specific (verb prefix, kind suffix,
         etc.).
    4. The error is raised by `OasOperationDriver.affirmDefinition` —
       the cache key uniqueness invariant is enforced strictly for
       Driver-path insertions. (The `insertNormalizedModel`
       fallback-name path does *not* enforce; see `#SKM-47`.)
    
    ### Scenario F: Engine throws "must include a 'main' variant"
    
    **Symptom:** `Error: [<generator-id>] Enrichments for '<METHOD> <path>'
    must include a 'main' variant. Found variants: customer, location.`
    
    1. The consumer's `client.json` declares variant keys at
       `enrichments[<gen-id>][<path>][<method>]` (or
       `[<rootKind>][<fieldName>]` for GraphQL) without `'main'` among
       them.
    2. The engine refuses to dispatch because every variants-aware path
       defaults to `'main'` — silently inventing it would mask the
       misconfiguration.
    3. Fix: open `client.json` and either:
       - Add `"main": {}` (or `"main": { ... }`) to the variants record, OR
       - Remove the non-`'main'` variants and inline their content as
         the operation-level enrichment, OR
       - If you want the consumer to opt out of `'main'`, declare it
         anyway and add `(path, method, "main")` to `skip`.
    4. Where it's thrown: `core/helpers/toVariantList.ts`, invoked from
       `GenerateContext.#runOasOperationGenerator` and
       `#runGqlOperationGenerator`. Pinning test:
       `core/context/GenerateContext.variants.test.ts` → "declared
       variants without `main` throws at engine dispatch".
    
    ### Scenario G: Driver throws "Cannot insert variant 'X'"
    
    **Symptom:** `Error: [<peer-gen-id>] Cannot insert variant '<name>'
    for '<METHOD> <path>' — peer has no enrichments configured. Only
    'main' is permitted.` or `Available variants: main, customer.`
    
    1. A variants-aware generator is calling
       `context.insertOperation({ projection: Peer, operation, variant:
       'X' })` where `'X'` isn't declared in the PEER's enrichment
       block. The Driver's `assertPeerVariantExists` guard fires before
       the Projection is even constructed.
    2. Almost always the auto-inherit-variant anti-pattern (see
       `skmtc-generator` skill §8) — the caller's source has
       `this.insertOperation(Peer, op, { variant: this.settings.variant })`
       against a variants-unaware peer.
    3. Fix in the caller's source:
       - If the peer is variants-unaware (most peers are):
         `this.insertOperation(Peer, op)` — drop the `{ variant }`. The
         Driver defaults to `'main'`; both variants of the caller share
         the peer's single Definition.
       - If the peer is variants-aware AND the caller genuinely wants a
         per-variant peer Definition: the peer's `client.json`
         enrichment must declare that variant before the call will
         succeed. Either add the declaration or remove the threading.
    4. Where it's thrown:
       `core/dsl/operation/oas/OasOperationDriver.ts` (and the GQL
       counterpart) → `assertPeerVariantExists`. Pinning tests:
       `core/dsl/operation/oas/OasOperationDriver.test.ts` →
       "Variant validation".
    
    ### Scenario H: `TypeError: this.context.X is not a function` (workspace fallback to JSR)
    
    **Symptom:** a runtime exception like `TypeError:
    this.context.insertNormalizedModel is not a function` (any context
    method) during `skmtc generate`, while `bundle.js` visibly contains a
    similar-but-differently-spelled method (`insertNormalisedModel` vs
    `insertNormalizedModel`, `toRefName` vs `getRefName`).
    
    **Cause:** two `@skmtc/core` versions in one bundle — a workspace
    member silently fell back to the JSR-published version:
    
    1. `@skmtc/worker` pins `@skmtc/core` with an exact version (for
       example `@skmtc/core@0.4.0`).
    2. The local workspace member declares a different version (for
       example `0.4.4`).
    3. Deno's workspace resolution rejects the mismatch and silently
       fetches the worker's exact-pinned core from JSR. The bundle then
       contains one `GenerateContext` from the worker's core and another
       from the generators' core; at runtime `this.context` is the wrong
       one.
    
    **Diagnostic path:**
    
    ```bash
    grep -i "Workspace member" .skmtc/<project>/.settings/error-logs.txt
    ```
    
    The fallback emits `Warning: Workspace member '@skmtc/core@X' was not
    used because it did not match '@skmtc/core@Y'` — and it surfaces ONLY
    in `error-logs.txt`: bundle doesn't print it, generate doesn't
    mention it, `doctor` doesn't currently flag it. The log file is the
    authoritative diagnostic.
    
    **Fix:** align the worker's expected `@skmtc/core` version with the
    workspace — upgrade the worker to a ranged pin (`^0.4`) or pin the
    workspace member to the worker's exact version. One core copy in the
    bundle → the method exists at runtime.
    
    ## 7. Anti-patterns specific to debugging
    
    The defaults to override when in debug mode:
    
    ### Don't propose code changes before reproducing the failure
    
    ```
    ❌ "Try changing toIdentifier — that might fix it."
    ✅ "Let's reproduce first. Run `skmtc generate <project> --json` and
       share the output."
    ```
    
    "Try X" without reproduction is guess-and-check, not debugging. Each
    attempt costs a generate cycle.
    
    ### Don't trust docstrings as authoritative
    
    Docstrings and comments can lag behind code changes. Drift
    between docs and code is real. **Verify against the function
    body, not the comments.**
    
    ### Don't extrapolate behavior from training data
    
    This codebase has specific quirks that other codegen tools don't
    share:
    
    - No Prettier in the pipeline
    - `OasSchema` as a union, not a class hierarchy
    - Two spellings of `insertNormali[sz]edModel`
    - Worker permissions: `net: false`, `run: false`
    
    Verify each claim against the source.
    
    ### Don't assume the bug is in the generator
    
    The failure may be in:
    
    - `client.json` (wrong path, wrong enrichment shape, wrong `include`/
      `skip`)
    - The OpenAPI schema itself (malformed, missing `$ref` target)
    - A stale bundle (`worker.ts` ↔ `deno.json` drift)
    - A version mismatch (peer-pin between `@skmtc/core` and a generator)
    - The consumer-side code the generated output imports against
    - The user's setup (Deno version, JSR_URL, lockfile staleness)
    
    Walk the diagnostic paths in §3 before deciding.
    
    ### Don't restart from scratch unless symptoms warrant it
    
    "Clean install" / "delete `.skmtc` and redo" should not be the first
    move. If specific symptoms suggest workspace corruption (manifest
    fails to parse, bundle.js is malformed, `deno.json` is invalid JSON),
    then targeted recreation makes sense. Otherwise, diagnose specifically.
    
    ### Don't suggest `--verbose` or `console.log` before checking the manifest
    
    The manifest already has structured diagnostic data per item. Reading
    it is faster than instrumenting the generator. Use `jq` queries from §4.
    
    ### Don't paraphrase error messages
    
    When asking the user about an error, request the **exact verbatim
    text**. Paraphrased messages lose the discriminator information that
    maps to the diagnostic path in §3.
    
    ## 8. When to escalate
    
    ### Clone a stock generator for inspection
    
    If the bug is in stock generator behavior (e.g., a `gen-shadcn-form`
    output is wrong), cloning brings the source local where it can be
    read and modified. Once cloned, the diagnostic shifts: now it's a
    generator-authoring problem (`skmtc-generator` skill takes over).
    
    ### Surface to the friction log
    
    If the diagnosis revealed a pattern (a confusing error message, a
    missing API helper, a frequently-misunderstood invariant), the
    `skmtc-retro` skill should capture it as a friction-log entry. Don't
    let an interesting diagnostic insight evaporate.
    
    ### Suggest a SKMTC code change
    
    If the bug is in `@skmtc/core` or `@skmtc/cli` (not in a generator),
    propose the fix as a PR or GitHub issue. Distinguish between:
    
    - **Fix in cloned generator** — immediate, local, ships in the
      consumer's repo
    - **Fix in core** — slower, upstream, affects all projects
    
    Choosing the wrong level produces friction. Generator-shape bugs
    typically belong in the generator; engine-shape bugs belong in core.
    
    ## 9. Boundary with other skills
    
    - **skmtc-cli**: hand off when the diagnosis has identified a CLI /
      configuration fix (e.g., "you need to update client.json
      `basePath`"). The `skmtc-cli` skill guides applying the fix.
    - **skmtc-generator**: hand off when the diagnosis has identified a
      fix in generator source. The `skmtc-generator` skill guides the
      source edit.
    - **skmtc-retro**: end-of-session. Debug sessions often surface
      retro-worthy observations — patterns of confusing error messages,
      missing diagnostic surfaces, recurring failure modes.
    
    The transition: this skill is active *while the LLM doesn't yet know
    what's wrong*. Once a root cause is identified, the appropriate
    "doing" skill helps with the fix.
    
    ## 10. Cross-references
    
    - Verification protocol (canonical): [`llms.md`](../../llms.md#verification-protocol)
    - Manifest format reference: [`reference/manifest-format.md`](../../reference/manifest-format.md)
    - Parse-issue type reference: [`reference/error-codes.md`](../../reference/error-codes.md)
    - Error-handling philosophy: [`concepts/error-handling-philosophy.md`](../../concepts/error-handling-philosophy.md)
    - Ref resolution mechanics: [`concepts/refs-and-resolution.md`](../../concepts/refs-and-resolution.md)
    - How-to: [`using/how-to/debug-failing-generation.md`](../../using/how-to/debug-failing-generation.md)
    - Friction log (where new diagnostic patterns should be recorded): [`friction-log/`](../../friction-log/)
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related