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
Install
npx skills add https://github.com/skmtc/skmtc/tree/main/deno/docs/skills/skmtc-debug
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install skmtc-skmtc@llmmart
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:
- No plugin registry, no dependency graph, no topological sort.
- Render does not run Prettier or Biome. Output is unformatted.
- Generator source code is the customization surface.
OasSchemais a union type, not a class hierarchy.- Same-named wrapper.
insertNormalizedModelexists on bothGenerateContext(takes explicitdestinationPath) and the projection-base wrappers (filldestinationPathfromsettings.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
"It generated nothing" — open
results. If every leaf isnotSupported, no generator'sisSupportedmatched any operation/model. Check the schema actually has the operations expected and that the right generators are installed."It generated less than expected" — grep the
resultssubtree for the generator in question. Find which identifiers came backnotSupported/skippedvssuccess. Identifier format is<protocol>_<operationId>for operations (query_…,mutation_…,get_…,post_…) and the model name for models."A specific output is missing" — check
filesfirst. If thedestinationPathisn't there, find the corresponding identifier inresults.errormeans the generator failed;notSupportedmeans the engine never reached it."Cost / size accounting" —
fileshaslinesandcharactersper 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 failureINVALID_DEPENDENCY_REF— cascade-pruned consumer of a failed refMISSING_OBJECT_TYPE— schema haspropertiesbut notype: 'object'; SKMTC inferred object (warning)MISSING_ARRAY_TYPE— hasitemsbut notype: '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.
- Open
manifest.json. Find the per-operation result for the missing operation inmanifest.results[traceId][spanId].generate[generatorId][identifier]. - Branches:
'notSupported': The generator'sisSupportedpredicate rejected this operation. Check the predicate ingen-<name>/src/mod.ts.'skipped': A filter inclient.json(skiporinclude) is excluding it. Checkclient.json#settings.skipand.include.'success'but no file: The generator'stransformreturned content (which is discarded) instead of callingregisterorinsertOperation. Read the generator source.'error': Read the error message in the manifest (or stderr from the run). The generator's constructor ortoStringthrew.
- If the result is missing entirely (operation not present in
manifest.results): the operation was pruned at parse time (look forINVALID_SCHEMA/INVALID_DEPENDENCY_REFinparseIssuesat the operation's path).
Scenario B: Wrong output (compiles)
Symptom: Generated TS compiles but has incorrect semantics.
- Identify the offending file and the offending fragment.
- Read the generator's
toString()template. Is the right Projection being instantiated? Is the right schema being read? (operation.toRequestBody,operation.toSuccessResponse,schema.resolve()) - Is the right peer Projection being referenced? Check
insertOperation(Other, op).toName()calls — the returned name is what the template should embed. - Did the constructor's side effects (
register,insertNormalizedModel) run? Look for them in the constructor — if they're intoString(), that's wrong (mutation intoStringis an anti-pattern). - 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.
- Run
skmtc generate <project> --typecheck. The CLI returns diagnostics 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": 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), themoduleNameinsettings.packagesis not declared in that package'spackage.json(name, or anexportsentry for a subpath). A render-time throw that namessettings.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
insertNormalizedModelconsistently for the same schema. - Missing properties on a type: The schema is
optional/nullablein a way the generator didn't account for. Read the OAS schema for the affected property.
- "Module not found": The generator produced a path the
consumer hasn't implemented. Check the generator's
Scenario D: Bundle freshness warning
Symptom: Strict-mode generate refuses with
Error: bundle.js is out of sync with deno.json — add: … (exit 2).
deno.json#importsandworker.tsdeclared different generator sets. Either was hand-edited without rebundling.- Remediation:
skmtc bundle <project>(rebuildsworker.tsfromdeno.json#imports). - If
worker.tswas edited by hand: the bundle has unrecorded changes; reset by regenerating. Hand-edits toworker.tsare not supported. - Diagnostic:
skmtc doctor --jsonsurfaces this asproject-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.
- Two generators (or two callers within one generator) are
producing the same identifier at the same
exportPath. - Read the two
generatorKeyvalues from the error. They identify the colliding generators. The 4-segment OAS format isgeneratorId|path|method|variant; GQL isgeneratorId|rootKind|fieldName|variant. If the only segment that differs isvariant, this is the variants-aware case (Scenario G below); follow that branch instead. - Branches:
- Both are stock generators: Clone one and change its
toIdentifierto disambiguate. - One is yours: Your
toIdentifieris computing the same name as a peer. Make it more specific (verb prefix, kind suffix, etc.).
- Both are stock generators: Clone one and change its
- The error is raised by
OasOperationDriver.affirmDefinition— the cache key uniqueness invariant is enforced strictly for Driver-path insertions. (TheinsertNormalizedModelfallback-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.
- The consumer's
client.jsondeclares variant keys atenrichments[<gen-id>][<path>][<method>](or[<rootKind>][<fieldName>]for GraphQL) without'main'among them. - The engine refuses to dispatch because every variants-aware path
defaults to
'main'— silently inventing it would mask the misconfiguration. - Fix: open
client.jsonand 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")toskip.
- Add
- Where it's thrown:
core/helpers/toVariantList.ts, invoked fromGenerateContext.#runOasOperationGeneratorand#runGqlOperationGenerator. Pinning test:core/context/GenerateContext.variants.test.ts→ "declared variants withoutmainthrows 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.
- 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'sassertPeerVariantExistsguard fires before the Projection is even constructed. - Almost always the auto-inherit-variant anti-pattern (see
skmtc-generatorskill §8) — the caller's source hasthis.insertOperation(Peer, op, { variant: this.settings.variant })against a variants-unaware peer. - 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.jsonenrichment must declare that variant before the call will succeed. Either add the declaration or remove the threading.
- If the peer is variants-unaware (most peers are):
- 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:
@skmtc/workerpins@skmtc/corewith an exact version (for example@skmtc/core@0.4.0).- The local workspace member declares a different version (for
example
0.4.4). - Deno's workspace resolution rejects the mismatch and silently
fetches the worker's exact-pinned core from JSR. The bundle then
contains one
GenerateContextfrom the worker's core and another from the generators' core; at runtimethis.contextis 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
OasSchemaas 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, wronginclude/skip)- The OpenAPI schema itself (malformed, missing
$reftarget) - A stale bundle (
worker.ts↔deno.jsondrift) - A version mismatch (peer-pin between
@skmtc/coreand 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"). Theskmtc-cliskill guides applying the fix. - skmtc-generator: hand off when the diagnosis has identified a
fix in generator source. The
skmtc-generatorskill 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 - Manifest format reference:
reference/manifest-format.md - Parse-issue type reference:
reference/error-codes.md - Error-handling philosophy:
concepts/error-handling-philosophy.md - Ref resolution mechanics:
concepts/refs-and-resolution.md - How-to:
using/how-to/debug-failing-generation.md - Friction log (where new diagnostic patterns should be recorded):
friction-log/
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.
Reviews (0)
No reviews yet.
No comments yet.