Compare commits
8 Commits
ccefd1751b
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
| fb08692dde | |||
| 19ac0ea563 | |||
| 2cce76f46f | |||
| 8e8e1aaa6e | |||
| 54d6aebbe5 | |||
| 2e5f6b4db0 | |||
| 57e03af2b9 | |||
| 4ed06c7e4b |
@@ -1,14 +1,24 @@
|
||||
# Handbooks
|
||||
|
||||
Instance-agnostic reference material for coding agents — the verbose, citable kind that belongs in
|
||||
a retrieval store, not in a skill.
|
||||
Instance-agnostic reference material for coding agents, in two shapes: long-form **handbooks** meant
|
||||
for a retrieval store, and short **redirect-skills** meant to be deployed into an agent's own skill
|
||||
directory. Both are authored and version-controlled here; neither is consumed directly *from* here —
|
||||
a handbook isn't live until admitted to a KB, a skill isn't live until deployed to wherever it
|
||||
actually runs.
|
||||
|
||||
## What goes here
|
||||
|
||||
- Public standards, style guides, and conventions that apply regardless of which box or project is
|
||||
running — general git operations, language style guides, CERT/MISRA-style rulebooks.
|
||||
- Long-form and specific. A handbook can be as detailed as it needs to be; nothing here is loaded
|
||||
into a model's context wholesale, so length carries no per-turn tax.
|
||||
- **Handbooks: public standards, style guides, and conventions** that apply regardless of which box
|
||||
or project is running — general git operations, language style guides, CERT/MISRA-style rulebooks.
|
||||
Long-form and specific; nothing here is loaded into a model's context wholesale, so length carries
|
||||
no per-turn tax.
|
||||
- **Redirect-skills: short, prescriptive, and specifically pointer-shaped** — their entire job is
|
||||
telling an agent to verify against a live or handbook source rather than trust its own recall,
|
||||
for domains that churn too fast for a stored answer to stay correct (`gdext-verify-live-docs` is
|
||||
the first: gdext is pre-1.0, so the skill points at `docs.rs/gdext` and the `godot-rust` book
|
||||
instead of encoding any API surface as text). **Not** a home for full procedural skills that encode
|
||||
real workflow knowledge of their own (a debugging methodology, a review checklist) — those still
|
||||
belong wherever they're consumed, authored there directly.
|
||||
|
||||
## What does not go here
|
||||
|
||||
@@ -16,40 +26,111 @@ a retrieval store, not in a skill.
|
||||
box's PR checklist — that's instance-bound and belongs in that project's own KB corpus instead
|
||||
(the `engineering` corpus, for
|
||||
[halogen](https://git.novoyuuparosk.org/mikkeli/halogen-workstation-roadmap)).
|
||||
- **Short, prescriptive procedures achievable within a context window or two.** That's a skill, not
|
||||
a handbook — it gets loaded, not retrieved. See `skills/` in the halogen-workstation-roadmap repo
|
||||
for the sibling convention.
|
||||
- **Full procedural skills that aren't redirect-shaped.** Genuine workflow knowledge — a debugging
|
||||
methodology, a review checklist, anything that *is* the content rather than pointing at it — still
|
||||
belongs where it's consumed: the shared, git-tracked `skills/` at halogen-workstation-roadmap's
|
||||
root (OpenCode, Codex, Claude Code, via `--link`), or authored directly by an agent into its own
|
||||
runtime skill directory (hermes' per-profile home isn't git-tracked at all).
|
||||
- **The retrieval store itself.** This repo holds the source-of-truth markdown a human has vouched
|
||||
for. The chunked, embedded index built from it is derived and rebuildable state — it lives beside
|
||||
whichever KB is consuming it, never committed here.
|
||||
- **The deployed copy of a skill.** This repo holds the authored source; the running copy lives
|
||||
wherever the consuming agent actually reads skills from, deployed by copy (never symlink — an
|
||||
agent may write back to its own skill files, which a symlink would corrupt at the source).
|
||||
Deployment tooling lives with the consumer, not here.
|
||||
|
||||
## Format
|
||||
|
||||
One markdown file per topic at the repo root. Each file should stand as a single admittable
|
||||
document — the same shape a human vouches for at KB admission time.
|
||||
**Handbooks:** one markdown file per topic under `handbooks/`. Each file should stand as a single
|
||||
admittable document — the same shape a human vouches for at KB admission time. (Redirect-skills live
|
||||
under `skills/` instead, with their own format — see below.)
|
||||
|
||||
Each file opens with a small flat YAML frontmatter block — `title` and `collections` only, no nested
|
||||
structure, so it stays greppable without a YAML dependency (same reasoning `index.py` itself gives
|
||||
for hand-parsing `corpora.yaml`):
|
||||
|
||||
```yaml
|
||||
---
|
||||
title: Git operations
|
||||
collections: [handbooks, dev-practice]
|
||||
---
|
||||
```
|
||||
|
||||
`title` and `collections` are the two ingest-time values that are actually about the document, so
|
||||
they live with it instead of being retyped by hand at admission time — that's what let `--collection
|
||||
dev-practice` and an actual admission of `handbooks` drift apart on 2026-08-22, silently, until it was
|
||||
noticed by inspection rather than by anything catching it.
|
||||
|
||||
`version` is deliberately **not** a frontmatter field — a file can't contain the hash of the commit
|
||||
that writes it, the same self-reference problem any `__COMMIT__` build stamp runs into. It stays a
|
||||
value computed at admission time, same as before, just scoped to the file rather than the whole repo.
|
||||
|
||||
## Ingesting a handbook
|
||||
|
||||
Markdown needs no conversion and no extraction verifier — admit it straight from this repo:
|
||||
Markdown needs no conversion and no extraction verifier — admit it straight from this repo, reading
|
||||
`title`/`collections` from the frontmatter instead of retyping them:
|
||||
|
||||
```bash
|
||||
index.py admit ~/dev/handbooks/git-operations.md --corpus engineering \
|
||||
--vouch mikkeli --title 'Git operations' --collection dev-practice \
|
||||
--version "$(git -C ~/dev/handbooks rev-parse --short HEAD)"
|
||||
F=~/dev/handbooks/handbooks/git-operations.md
|
||||
TITLE=$(sed -n '/^---$/,/^---$/{/^title:/s/^title:[[:space:]]*//p}' "$F")
|
||||
COLLECTIONS=$(sed -n '/^---$/,/^---$/{/^collections:/s/^collections:[[:space:]]*//p}' "$F" \
|
||||
| tr -d '[]' | tr ',' '\n' | sed 's/^ *//;s/ *$//')
|
||||
VERSION=$(git -C ~/dev/handbooks log -1 --format=%h -- "$F")
|
||||
|
||||
index.py admit "$F" --corpus engineering --vouch mikkeli \
|
||||
--title "$TITLE" --version "$VERSION" \
|
||||
$(for c in $COLLECTIONS; do printf -- '--collection %s ' "$c"; done)
|
||||
```
|
||||
|
||||
Without `--title` the document is titled by its filename, which is what a retrieval hit then shows.
|
||||
`--collection` is a flat cluster tag; getting it wrong costs recall and nothing else.
|
||||
⚠ **`--version` is the last commit that touched *this file*, not the repo's `HEAD`.** A whole-repo
|
||||
`HEAD` pin would bump every handbook's recorded version on any unrelated edit, even one that never
|
||||
touched this document — the per-file form is what actually answers "which revision does this chunk
|
||||
reflect."
|
||||
|
||||
⚠ **`--version` is this repo's commit, and it is the one that matters.** Re-admitting replaces a
|
||||
document outright with no version history, so the pin is the only record of which revision a chunk
|
||||
reflects.
|
||||
⚠ **Re-admitting replaces a document outright, with no version history kept.** The `--version` pin is
|
||||
the only record of which revision a chunk reflects, which is also why editing a file here does not by
|
||||
itself update the corpus — re-admission is deliberate, a fresh claim about a specific version of a
|
||||
document, not something that should happen automatically on every commit.
|
||||
|
||||
⚠ **Editing a file here does not update the corpus.** Re-admission is deliberate, because the vouch
|
||||
is a claim about a specific version of a document.
|
||||
## Redirect-skills
|
||||
|
||||
Live under `skills/<name>/SKILL.md` — the same `skills/<name>/SKILL.md` shape every consuming agent
|
||||
already expects, so a skill here copies straight across with no reshaping. Frontmatter is `name` +
|
||||
`description` (the Agent Skills standard, not this repo's `title`/`collections`): `description` is
|
||||
what a consuming agent's own trigger-matching reads to decide relevance, so it needs to name the
|
||||
concrete situation the skill applies to, not just summarize the body.
|
||||
|
||||
A redirect-skill's body should contain no cacheable domain knowledge — only what to check, where, and
|
||||
under what condition to suspect staleness over a code error. If a skill here starts accumulating API
|
||||
details "for next time," that's the sign it's stopped being a redirect and started being the exact
|
||||
stale cache it exists to avoid.
|
||||
|
||||
**Deploying one is out of scope for this repo** — copy the file into wherever the consuming agent
|
||||
reads skills from, never symlink (an agent may write back to its own skill files over time, which a
|
||||
symlink would corrupt at the vendor source). The deployment tooling itself lives with the consumer.
|
||||
|
||||
## Status
|
||||
|
||||
Started 2026-08-21. First handbook, `git-operations.md`, composed 2026-08-21 – 2026-08-22: branching,
|
||||
committing, merging vs. rebasing, reverting and undoing, remotes and collaboration, tags and releases.
|
||||
Confirmed ingestible 2026-08-22 — 30 chunks, heading paths intact, retrieved correctly.
|
||||
|
||||
Second handbook, `failure-handling.md`, composed 2026-08-22: input contract discipline, failure-
|
||||
propagation discipline. Environment/verification parity was scoped out of it deliberately — a
|
||||
different axis (how confidence is established, not how code behaves), queued as a handbook of its
|
||||
own rather than folded in.
|
||||
|
||||
Frontmatter (`title`, `collections`) added to both 2026-08-22, after `--collection dev-practice` in
|
||||
this file's own recipe was found to have drifted from `handbooks`, the value actually used at
|
||||
admission — the two ingest-time values that describe the document now live with it instead of being
|
||||
retyped by hand.
|
||||
|
||||
Scope widened 2026-08-22, superseding the same-day note above: this repo now also houses
|
||||
**redirect-skills** (`skills/<name>/SKILL.md`) — short, pointer-shaped skills whose job is verifying
|
||||
against a live or handbook source rather than caching an answer that would go stale. First one:
|
||||
`gdext-verify-live-docs`, for `gdext` (Rust bindings for Godot 4, pre-1.0, no existing skill or MCP
|
||||
tool anywhere). Full procedural skills — genuine workflow knowledge, not a pointer — still don't
|
||||
belong here; they're authored where they're consumed, same as before.
|
||||
|
||||
Handbooks moved from the repo root into `handbooks/` the same day, for symmetry with `skills/` —
|
||||
each content shape gets its own directory rather than handbooks implicitly owning the root.
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
# Failure handling
|
||||
|
||||
> Composed 2026-08-22. General practice, not tied to any one project or language. Input contract
|
||||
> discipline is composed; failure-propagation discipline is still outline.
|
||||
|
||||
## Scope
|
||||
|
||||
General discipline for how code should behave when it encounters conditions it can't cleanly
|
||||
complete against — malformed input, an unreachable dependency, an assumption that turns out false.
|
||||
Two sections: what a function's contract should say about the input space before it's written, and
|
||||
what happens once something has actually gone wrong.
|
||||
|
||||
Excluded on purpose: language-specific exception syntax, logging infrastructure choices,
|
||||
testing-framework mechanics, and environment/verification parity ("tested" vs. "tested where it
|
||||
actually runs") — that last one is close enough to earn a future handbook of its own rather than a
|
||||
section here.
|
||||
|
||||
## Input contract discipline
|
||||
|
||||
**Degenerate input is the default case to design for, not an edge case bolted on after.** Empty
|
||||
collections, duplicate entries, out-of-range values, malformed or wrong-typed data, oversized
|
||||
payloads, and concurrently-modified reads are not rare — they're what actually arrives once code
|
||||
leaves a demo. Design for that space first, and treat "obviously fine" input as the special case, not
|
||||
the other way around.
|
||||
|
||||
**Validate, sanitize, and reject are three different responses — not one blurred behavior.**
|
||||
|
||||
- **Validate** — check conformance against the contract, making no change to the input. Proceed only
|
||||
if it holds.
|
||||
- **Sanitize** — transform nonconforming input into conforming input. The transformed value is what
|
||||
proceeds, treated exactly as if the caller had sent it that way to begin with.
|
||||
- **Reject** — refuse to proceed, surfacing why (see Failure-propagation discipline for what
|
||||
"surfacing why" should actually look like).
|
||||
|
||||
**Sanitize only what's meaning-preserving; reject anything that requires guessing intent.**
|
||||
Sanitizing has a real cost: the caller's actual input is never seen again, only its cleaned-up
|
||||
substitute. That's fine when the correction is unambiguous — trimming incidental whitespace,
|
||||
normalizing Unicode form, case-folding under an already-documented case-insensitive contract. It's
|
||||
not fine when the correction requires guessing what the caller meant — clamping an out-of-range
|
||||
number to its nearest valid bound, silently truncating an oversized value, defaulting a missing
|
||||
required field. The test: *would the caller be surprised to learn what actually happened to their
|
||||
input?* If yes, reject instead of sanitizing — finding out via an error immediately is strictly
|
||||
better than finding out weeks later that a value was quietly substituted for the one actually sent.
|
||||
Silent sanitization of an out-of-range value and a guard that returns success without checking are
|
||||
the same failure shape: something couldn't proceed correctly, and produced a plausible-looking result
|
||||
instead of saying so.
|
||||
|
||||
**Validate at trust boundaries; trust your own internals.** Check input where it crosses from outside
|
||||
your control into it — user input, an external API response, a file read from disk, a message from
|
||||
another service. Code on the inside of that boundary, already known to satisfy its own contract (by
|
||||
type, by test, by the caller being code under the same control), doesn't need to re-validate what its
|
||||
caller already guaranteed — that's redundant defense that doubles maintenance cost without doubling
|
||||
safety. The boundary is where trust actually changes; that's the only place validation earns what it
|
||||
costs.
|
||||
|
||||
**Before writing the implementation, write the contract's answer for each category.** A function's
|
||||
contract should state, explicitly, what happens for: empty input, a duplicate where uniqueness was
|
||||
assumed, a value outside the expected range, a type mismatch, an oversized input, and a stale or
|
||||
concurrently-modified read. Leave any of those unanswered, and the implementation will invent an
|
||||
answer under latitude — usually the same way, by quietly doing something plausible instead of
|
||||
refusing. Writing the answer down first is what closes that gap; discovering it during implementation,
|
||||
or worse in production, is what leaves it open.
|
||||
|
||||
## Failure-propagation discipline
|
||||
|
||||
*(Not yet composed.)*
|
||||
|
||||
*(Handbook in progress — sections above stand as citable units; the rest is outline.)*
|
||||
@@ -0,0 +1,131 @@
|
||||
---
|
||||
title: Failure handling
|
||||
collections: [handbooks, dev-practice]
|
||||
---
|
||||
|
||||
# Failure handling
|
||||
|
||||
> Composed 2026-08-22. General practice, not tied to any one project or language.
|
||||
|
||||
## Scope
|
||||
|
||||
General discipline for how code should behave when it encounters conditions it can't cleanly
|
||||
complete against — malformed input, an unreachable dependency, an assumption that turns out false.
|
||||
Two sections: what a function's contract should say about the input space before it's written, and
|
||||
what happens once something has actually gone wrong.
|
||||
|
||||
Excluded on purpose: language-specific exception syntax, logging infrastructure choices,
|
||||
testing-framework mechanics, and environment/verification parity ("tested" vs. "tested where it
|
||||
actually runs") — that last one is close enough to earn a future handbook of its own rather than a
|
||||
section here.
|
||||
|
||||
## Input contract discipline
|
||||
|
||||
**Degenerate input is the default case to design for, not an edge case bolted on after.** Empty
|
||||
collections, duplicate entries, out-of-range values, malformed or wrong-typed data, oversized
|
||||
payloads, and concurrently-modified reads are not rare — they're what actually arrives once code
|
||||
leaves a demo. Design for that space first, and treat "obviously fine" input as the special case, not
|
||||
the other way around.
|
||||
|
||||
**Validate, sanitize, and reject are three different responses — not one blurred behavior.**
|
||||
|
||||
- **Validate** — check conformance against the contract, making no change to the input. Proceed only
|
||||
if it holds.
|
||||
- **Sanitize** — transform nonconforming input into conforming input. The transformed value is what
|
||||
proceeds, treated exactly as if the caller had sent it that way to begin with.
|
||||
- **Reject** — refuse to proceed, surfacing why (see Failure-propagation discipline for what
|
||||
"surfacing why" should actually look like).
|
||||
|
||||
**Sanitize only what's meaning-preserving; reject anything that requires guessing intent.**
|
||||
Sanitizing has a real cost: the caller's actual input is never seen again, only its cleaned-up
|
||||
substitute. That's fine when the correction is unambiguous — trimming incidental whitespace,
|
||||
normalizing Unicode form, case-folding under an already-documented case-insensitive contract. It's
|
||||
not fine when the correction requires guessing what the caller meant — clamping an out-of-range
|
||||
number to its nearest valid bound, silently truncating an oversized value, defaulting a missing
|
||||
required field. The test: *would the caller be surprised to learn what actually happened to their
|
||||
input?* If yes, reject instead of sanitizing — finding out via an error immediately is strictly
|
||||
better than finding out weeks later that a value was quietly substituted for the one actually sent.
|
||||
Silent sanitization of an out-of-range value and a guard that returns success without checking are
|
||||
the same failure shape: something couldn't proceed correctly, and produced a plausible-looking result
|
||||
instead of saying so.
|
||||
|
||||
**Validate at trust boundaries; trust your own internals.** Check input where it crosses from outside
|
||||
your control into it — user input, an external API response, a file read from disk, a message from
|
||||
another service. Code on the inside of that boundary, already known to satisfy its own contract (by
|
||||
type, by test, by the caller being code under the same control), doesn't need to re-validate what its
|
||||
caller already guaranteed — that's redundant defense that doubles maintenance cost without doubling
|
||||
safety. The boundary is where trust actually changes; that's the only place validation earns what it
|
||||
costs.
|
||||
|
||||
**Before writing the implementation, write the contract's answer for each category.** A function's
|
||||
contract should state, explicitly, what happens for: empty input, a duplicate where uniqueness was
|
||||
assumed, a value outside the expected range, a type mismatch, an oversized input, and a stale or
|
||||
concurrently-modified read. Leave any of those unanswered, and the implementation will invent an
|
||||
answer under latitude — usually the same way, by quietly doing something plausible instead of
|
||||
refusing. Writing the answer down first is what closes that gap; discovering it during implementation,
|
||||
or worse in production, is what leaves it open.
|
||||
|
||||
## Failure-propagation discipline
|
||||
|
||||
**Never substitute a plausible-looking artifact for a failed operation.** When something can't
|
||||
complete its contract, the correct output is a signal that it didn't — not a value that resembles
|
||||
success closely enough to pass unnoticed. A blank result where real output should be, a guard that
|
||||
reports "fine" because it couldn't read what it was checking, a stale cached value returned as if it
|
||||
were fresh: each of these is a failure wearing success's clothes, and the cost is always paid later,
|
||||
by whoever trusted the output.
|
||||
|
||||
**A check that can't verify must refuse — and "couldn't verify" must be a different state than
|
||||
"verified, and it's fine."** Collapsing both into one boolean is the mechanism behind the failure
|
||||
above: a function that returns `true` for both "the input passed validation" and "validation itself
|
||||
couldn't run" cannot be trusted by anything downstream, because the caller has no way to tell which
|
||||
one happened. Whatever the check returns, its failure-to-run and its pass result need to be
|
||||
distinguishable — a third state, a different return type, an exception path, anything that doesn't
|
||||
require the caller to assume good faith on the checker's behalf.
|
||||
|
||||
**Retry, abort, and surface are three different responses to a failure, and picking the wrong one is
|
||||
a fail-loudly violation on its own.**
|
||||
|
||||
- **Retry** only for failures that are plausibly transient and where repeating the operation doesn't
|
||||
change its meaning (a network read, not a payment). Bound it — a fixed number of attempts or a
|
||||
timeout, never unconditional — and don't let a bounded retry become an unbounded one by accident (a
|
||||
retry nested inside another retry, a caller that itself retries a function that already retries
|
||||
internally).
|
||||
- **Abort** when the failure indicates a broken invariant or a state that's unsafe to continue from —
|
||||
retrying or asking would just compound whatever's already wrong.
|
||||
- **Surface** — to a caller, a human, or an error return — when the system doesn't have enough
|
||||
information to pick correctly on its own. This is usually the right default when nothing above
|
||||
clearly applies: asking is cheaper than guessing wrong.
|
||||
|
||||
**A retry that eventually succeeds is still a failure that happened, and needs to be recorded as one,
|
||||
not discarded because the second attempt worked.** The caller downstream can get the successful
|
||||
result — that's the point of retrying — but "the first attempt failed" is real information: it's how
|
||||
a dependency's slow slide toward actually breaking becomes visible before it breaks outright, instead
|
||||
of only being visible in the moment it finally does. A retry policy that only speaks up once retries
|
||||
are exhausted throws that signal away for free.
|
||||
|
||||
**Pick one failure-signaling convention per boundary, and don't mix it with another.** Exceptions, a
|
||||
`Result`-shaped return, a sentinel value, an error code — any of these work, but only if every caller
|
||||
at that boundary can rely on which one is in play. Mixing them within the same boundary is its own
|
||||
failure mode: a caller checking a sentinel value for a function that actually raises, or catching an
|
||||
exception type a function never throws, proceeds past the real failure exactly as if it hadn't
|
||||
checked at all — the check existed, and did nothing.
|
||||
|
||||
**Anti-patterns, all variations on the same shape:**
|
||||
|
||||
- A swallowed exception (`except: pass`, an empty `catch` block) — the failure existed for one
|
||||
instruction and then didn't.
|
||||
- `continue` on a caught exception inside a loop — one bad item disappears with no record it was ever
|
||||
there, and the loop reports success for everything it actually attempted.
|
||||
- A fallback default that isn't marked as one — returning `0`, `null`, an empty collection, or a
|
||||
stale cached value in place of a failed real result, with nothing distinguishing it from a genuine
|
||||
`0`, `null`, empty collection, or fresh value.
|
||||
- A comment explaining away a shortcut instead of the code being fixed — `# TODO: handle this
|
||||
properly` sitting next to the exact silent failure it describes, left in place.
|
||||
- An unbounded retry — functionally identical to abort, except it costs resources first and never
|
||||
actually informs anyone that it gave up.
|
||||
|
||||
The same discipline shows up in test coverage: a suite that only exercises the happy path hasn't
|
||||
tested the code, it's tested the narrative about the code.
|
||||
|
||||
*(Handbook composed 2026-08-22. Both sections above stand as citable units — a chunk boundary, not
|
||||
just a heading.)*
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
title: Git operations
|
||||
collections: [handbooks, dev-practice]
|
||||
---
|
||||
|
||||
# Git operations
|
||||
|
||||
> Composed 2026-08-21 – 2026-08-22. General practice, not tied to any one project's Gitea/GitHub
|
||||
@@ -23,6 +28,9 @@ deviation from this default, and belongs in that project's own KB corpus, not he
|
||||
else), work with what's there. Never rename a repo's default branch as an incidental part of
|
||||
unrelated work.
|
||||
|
||||
**Repo name: hyphens, not underscores** (`my-project`, not `my_project`) — for new repos, same
|
||||
caveat as trunk naming: don't rename an existing one over this.
|
||||
|
||||
**Naming scheme: `type/short-description`.** A type prefix from the taxonomy below, then a
|
||||
kebab-case description — `feat/branch-naming-rules`, `fix/null-check-on-login`. No ticket ID in the
|
||||
name: a branch name should say what the change does, and if a tracker ticket exists, that reference
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
name: gdext-verify-live-docs
|
||||
description: Verify gdext (Godot Rust bindings) API calls against live docs, not recall.
|
||||
---
|
||||
|
||||
# gdext: verify against live docs, don't trust recall
|
||||
|
||||
`gdext` is pre-1.0 and under active development — breaking changes between versions are routine, not
|
||||
exceptional. Training-data recall of its API is likely to be stale in exactly the way that looks
|
||||
correct: a plausible macro invocation or trait bound that compiled against an earlier version and
|
||||
silently doesn't against the one actually in use.
|
||||
|
||||
**Before writing gdext-specific code (a `#[derive(GodotClass)]`, `#[godot_api]` block, signal
|
||||
registration, an exported property, or any other gdext macro/trait), check the version actually
|
||||
pinned in the project's `Cargo.toml`, then verify the API against:**
|
||||
|
||||
- `https://docs.rs/gdext` — pin the query to the pinned version, not `/latest`, if the two differ.
|
||||
- `https://godot-rust.github.io/book` — the official book; tutorials and patterns change with the
|
||||
crate, so prefer the version-matched section over a remembered pattern.
|
||||
|
||||
If a build fails on a gdext API call that looks like it should work, the first hypothesis is a
|
||||
version mismatch between what's remembered and what's pinned — check the docs at the pinned version
|
||||
before assuming the code is wrong.
|
||||
|
||||
Do not treat this skill as a place to cache gdext API details for next time. Anything gdext-specific
|
||||
written here would go stale the same way recall does — the whole point is redirecting to a source
|
||||
that stays current on its own.
|
||||
Reference in New Issue
Block a user