Creates or scaffolds a new ysonet IGenerator gadget from verified technique code. It checks uniqueness, gathers missing requirements and credits, classifies facets, systematically attempts the maximum plausible formatter and serializer set, implements real formatter and bridge behavior, registers the source, adds focused and runtime-effect tests, updates the public catalogs, and runs Debug and FULL verification. Use when the user asks to add, create, or scaffold a gadget and expects implementation. Use ysonet-dev-create-plan when the requested deliverable is only a plan. Not for plugins or small edits to an existing gadget.
Build a truthful, working gadget end to end. Never weaken a test, invent a
credit, or register one gadget name while returning another gadget's payload.
Quality bar
Quality comes before a quick "it generates bytes". The gadget is done when it
is right and complete, not when the first formatter works.
Take the durable route. When a better implementation lasts longer and makes
the next gadget easier to add, do that one, even when it is more work.
Do not stop at the minimum. Every plausible serializer, every variant, every
option, every IGenerator member, tests, docs, help, and catalogs are part of
the gadget, not extras.
Fix root causes. A special case, a copy-paste of another generator, or a
"for now" patch is acceptable only when a hard constraint blocks the proper
fix, and then it needs a dev-kitchen/todo/ note stating the proper fix.
Use the existing helpers, base classes, and test patterns. If a shared helper
is genuinely wrong for this gadget, improve the shared helper instead of
bypassing it, so later gadgets benefit.
Never trade correctness or test integrity for a green tick or a faster
finish. An unproven cell is unproven, not "supported".
If quality work needs more scope or a maintainer decision, ask. Do not
silently ship a narrower gadget.
Integrity gate
A registered gadget must implement its stated technique. Automatic generation
tests only prove that it returns bytes or text; they do not prove that the named
chain is present.
Do not use an existing leaf gadget as the completed body of a different
gadget.
Do not add the csproj entry, public documentation row, or supported status
until at least one real formatter branch works.
If the technique source or a material design fact is unavailable, ask for it.
If the user explicitly wants a draft, keep it unregistered under
dev-kitchen/dirty/ and label it incomplete. Do not make the product or tests
claim that the gadget exists.
List only formatter and variant combinations that the real implementation
produces. Assert a proven framework limitation as an expected failure; never
hide it with a skip or substitute payload.
Workflow
1. Load the current contract
Read:
CLAUDE.md, the shared memory index and its listed files;
gadget, test, build, and documentation sections of
docs/ARCHITECTURE.md;
the complete closest generators, their helpers, and their focused tests; and
assets/GadgetGenerator.template.cs before using the template.
Resolve references/ and assets/ paths relative to this skill's directory.
Treat documentation as a map and the current source as authoritative.
2. Check uniqueness before editing
Search the architecture gadget table, docs/gadgets-and-plugins.md, and
ysonet/Generators/ for the same primitive, sink, carrier, or target behavior.
Compare variants as well as class names. If the proposal substantially overlaps
an existing gadget, show the evidence and get the maintainer's decision before
creating a second public gadget.
3. Establish the evidence
Answer repository-checkable questions from source first. Ask only for material
facts that cannot be discovered, such as intended scope, original research
credit, or unavailable technique code. Record:
the source or write-up for the technique;
original researchers and the ysonet implementer;
target-side types, assemblies, runtime versions, and patched limitations;
the meaning of -c, options, variants, and the formatter candidate matrix;
whether it consumes an upstream payload and in which formatter; and
the observable runtime effect and safe test-owned sink.
Do not guess. Preserve an unknown as unknown instead of choosing convenient
metadata.
4. Classify the gadget
Use $ysonet-categorize-gadget to derive Facets() for the gadget and any
variant overrides from the verified evidence. If named-skill invocation is not
available, read and follow
.claude/skills/ysonet-categorize-gadget/SKILL.md directly. Use only the
vocabulary in ysonet/Generators/Base/IGenerator.cs. Omit
WithInputs(...) when CommandInput() derives the correct input. Keep an
unproven axis uncategorized, and never mix it with a real value on the same
axis.
Decide the runtime version axis here too, and do it last, after the runtime
effect test in step 8 has actually fired the payload. Every runtime-gated gadget
or variant must name at least one evidence-backed WORKING VERSION before it is
finished:
identify what target property the version describes: normally the framework
the target process runs on, but for a compile-time compatibility gate it is
the target application's TargetFrameworkAttribute. Never use ysonet's build
merely because it is easy to read.
first test the current/latest candidate. Run FULL and read the Runtime: line
and the version report at the end of the execution matrix; a gadget that fired
but declares nothing is listed there by name.
if the effect does not fire on the latest candidate and the difference is a
runtime-version gate, do not use that version as the ceiling and do not finish
with vague compatibility. Reproduce on older supported target versions
(installed runtime or target-app stamp, as applicable) until at least one
fires, then use the highest verified working version as the ceiling. Record
the latest tested non-working version and the limitation in the gadget
documentation or AdditionalInfo().
the FLOOR is the documented introduction of the types the chain needs. Default
RuntimeVersion.NetFx40 (the CLR v4 generation this tool targets);
NetFx45 when the chain goes through System.Security.Claims, WIF, or
Comparer<T>.Create. Microsoft documentation is acceptable evidence for a
floor.
then PROBE the floor instead of leaving it at the default, because the default
is an assumption rather than a measurement. Cheap static check first: a NORMAL
run prints LEGACY floor candidates, the gadgets whose generated payload names
no 4.x assembly version. If yours is on that list, add a row to
LegacyClrRows in ysonet.Tests/Tiers/LegacyClrTier.cs (a private module uses the
RunPrivateLegacyRows hook) and run ysonet.Tests.exe --legacy, which fires
the real payload on CLR 2 in 2.0 / 3.0 / 3.5 lanes. Lower the floor only on an
observed EFFECT there, never on a clean deserialize. Record BOTH bounds, or say
explicitly in the plan and the gadget comment that the floor was not measured.
A measured negative is a result too: keep its classified reason (usually "the
payload names a 4.x assembly version and the strict readers bind it verbatim")
in AdditionalInfo(), not in a facet value.
use a single token when only one target version is verified. Use
.WithVersions(RuntimeVersion.Range(floor, ceiling)) only when the evidence
supports that contiguous span. Repeat the declaration in every variant
FacetOverride (an override replaces the whole set; a standing test fails the
build when one variant declares versions and another does not).
leave unspecified when the gadget's real gate is not a runtime version at
all (an OS patch, a library version, a config switch). That gate belongs in
AdditionalInfo(). If the gate is a runtime version but no working version has
been established, the gadget remains unverified and unfinished; never guess.
5. Implement the real chain
Copy assets/GadgetGenerator.template.cs to the normal generator location only
when enough evidence exists to implement the real chain. Replace the name token,
resolve every TODO, and delete optional template sections that do not apply.
Match the closest generator's structure.
Keep the whole payload in the gadget's own file. This is a hard rule, and
ysonet/Generators/README.md is the contract:
Payload templates, target type names, member names and the ORDER they are
written in, and any surrogate shape (declared as a nested type in the generator
class) all live in Generators/<Name>Generator.cs. Changing what the gadget
emits must mean changing one file.
Never put them in a helper, and never create a shared "payload builder" for
several gadgets. A helper may only hold mechanics that name no gadget, taking
the names and shapes as arguments: the type-name swaps
(MessagePackTypelessTypeSwap, SharpSerializerTypeSwap), SerializersHelper,
the minifiers, the escapers.
Reuse the base class instead of copying plumbing:
GenericGenerator.Serialize for BinaryFormatter / SoapFormatter /
NetDataContractSerializer / LosFormatter, and
GenericGenerator.HandWritten.cs for a hand written document or your own bytes
(FinishHandWrittenPayload covers minify plus the -t self-test, and
RequireCommandInput, RawInputOption, EscapeForJson,
EscapeForXmlAttribute, IsFormatter, IsMessagePackTypeless,
IsMessagePackLz4, UnsupportedFormatter cover the rest). If one of those is
genuinely wrong for this gadget, improve the shared member; do not fork it.
The only allowed dependency on another gadget is reusing it as the INNER
payload through GenerateInner, declared with GadgetTags.Bridged or
GadgetTags.Hosted.
Test what generation cannot prove. A bait-and-switch payload (a surrogate plus
a type-name swap) still generates when the swap silently fails, so assert the
target names in the emitted bytes, as
MessagePackTypelessCarriesTargetTypeNames does.
Take the operator's input as typed. Unless the user asked for validation, the only
check is "not empty" (RequireCommandInput): no path, URL, scheme, host, extension,
character-set, or assembly-identity check. What the value means is the TARGET's
decision, ysonet never opens or resolves it, and a refusal only blocks the research.
Teach the proven form in the option help, one line of AdditionalInfo(), and the
docs; use Debugging.ShowNote for a hint, never a refusal. Refuse only what cannot
be EMITTED. Escaping, the --minify corruption guard, and gadget scope are separate
and still apply. Contract: ysonet/Generators/README.md, "Operator input: document
it, do not police it".
Accept -t by default, including when the effect leaves this machine - a callout is
not damage. Refuse only when the self-test would damage or compromise the OPERATOR's
machine (destroy their data, run their code, hand their unparsed bytes to native
code), decided per VARIANT and before anything is constructed. A denial-of-service
payload never deserializes in the ysonet process: route it through
SelfTestNeedsChildProcess / IsolatedSelfTest, and refuse only where that child
cannot run it. Contract: ysonet/Generators/README.md, "-t (self-test) policy".
For a DENIAL-OF-SERVICE gadget, read ysonet/Generators/README.md, "Denial of
service: one facet, and everything it turns on", and follow it in full. Declaring
PayloadKind.DenialOfService is the single switch that arms the acknowledgement, the
bulk exclusions, the test-tier exclusions and the interactive preview, so never add a
second mechanism beside it. The parts that are NOT automatic are the ones to get
right: -t through a child process with PayloadReader.CanRead covering every
advertised formatter, one line printed before the child is terminated, never a row in
any fire list, a short AdditionalInfo(), and per-formatter effect evidence produced
by an operator-run harness that requires the TARGET'S OWN exception rather than
merely a dead child. The acknowledgement is required on every surface and worded for
each: the editor names the SETTING, never the command-line flag, and interactive use
must not imply it.
Write the gadget to be READ. It is research material: a human and an AI must be
able to understand the technique from this one file. Nothing is hidden and
nothing is obfuscated.
Keep each payload a whole, readable document in a verbatim string (@"..."),
with the target type names spelled out, so a reader can copy it straight into
the testing arena (ysonet/Helpers/TestingArena/TestingArenaHome.cs) or a
scratch project and have it work.
Never obfuscate, encode, or compress a payload in source: no base64 blob or
byte array standing in for a readable document, no string assembled from
fragments or char codes, no reflection used to avoid naming a type that can be
named, no one document split across methods. When the WIRE format genuinely
needs encoding or compression, build it from readable source at generation time
and state in a comment what the bytes are.
Use the real target and member names and technique-derived variable names.
Comment the WHY - the sink, why the property order or member set matters, the
target-side condition, what would silently break - not the syntax.
Prefer straightforward code over a compact trick. Reflection, dynamic code or
metaprogramming only when the technique requires it, and then explained.
The Release binary's string encryption (ysonet/obfuscar.xml) is an antivirus
measure on one shipped executable. It never justifies writing obscure source.
Walk every fillable member ysonet/Generators/Base/IGenerator.cs exposes and
give each an evidence-backed value or a deliberate default. Do not leave a
member at an empty or placeholder default because it was not considered. Confirm
each against the verified evidence:
Name(): derived from the class name without the Generator suffix; override
only when the derived public name is wrong.
Generate: the real chain for every advertised formatter.
Finders(): verified original researcher names.
Contributors(): this tool's implementer; omit when identical to Finders().
Credit(): never override; the base class composes it.
AdditionalInfo(): concise purpose, target assemblies and versions, CVEs, and
public references.
Labels(): only the GadgetTags constants that apply (Independent,
Bridged, Subclass, Hosted, GetterChain, OnDeserialized,
SecondOrderDeserialization, NotInGAC, Hidden); never a guessed string.
Hosted payload check (do this before writing the file). Ask: does Generate()
serialize a type this generator defines (its own *Marshal class or a real
framework type)? If NO, and it hands another generator's object to Serialize(),
then it is a hosted payload: put the file in Generators/HostedPayloads/, keep the
namespace ysonet.Generators, and tag it GadgetTags.Hosted. If YES, it stays in
Generators/ and must NOT carry GadgetTags.Hosted, even when it nests another
gadget's payload inside its own type (that is GadgetTags.Bridged territory).
Having a Variants() list or a var/variant option is irrelevant to this choice.
See ysonet/Generators/HostedPayloads/README.md.
SupportedFormatters(): the maximum verified union from step 6. On a gadget with
more than one variant, annotate each token with the number of variants that
formatter carries: "BinaryFormatter (2)", and a bare name when only one variant
supports it. The count is per formatter, so a gadget can read
"BinaryFormatter (3)", "SoapFormatter (2)". It is display-only (every consumer
splits on the first space), and it is the only place the public catalog reveals
that the gadget has variants at all, so a multi-variant gadget without it reads as
single-variant. Mirror the same token in docs/gadgets-and-plugins.md and the
docs/ARCHITECTURE.md formatter column.
SupportedBridgedFormatter() and BridgedPayload: only for a bridge consumer
(step 8).
Options(): every real option with clear parsing and sane defaults.
CommandInput(): the real meaning of -c; keep the ShellCommand default
only when evidence proves it.
Variants(): distinct payload shapes with .Without(...), .Input, and
.WithFacets(...) as needed (step 7).
Facets(): the broad discovery axes derived in step 4.
Honor inputArgs.Minify and inputArgs.Test on every advertised path.
Return the output shape the formatter uses, normally byte[] for the base
binary serializers and string for text serializers.
Throw a clear exception on bad input. Never call Environment.Exit.
Keep target dependencies separate from dependencies used only to generate the
payload.
Run a focused generation check before registration. If the real branch is not
ready, use the integrity gate and leave it unregistered.
6. Expand formatter support systematically
Follow references/formatter-expansion.md in full. Build the candidate set
from current source rather than from memory: Formatters, every live
SupportedFormatters() declaration, all SerializersHelper partials, and
dedicated or inline serializer implementations. The public set is currently
broader than either the constants or SerializersHelper.ShowAll().
Perform this audit even when the request or an implementation plan names no
additional formatter, lists only one formatter, or omits serializer work
entirely. Plan silence does not authorize a narrow implementation.
Attempt every plausible candidate against the named technique. Try the real
graph first, then a serializer-specific representation of that same chain when
the serializer needs a different shape. Compare the closest working generator
before concluding that a direct serialization failure is fundamental.
Maintain an evidence matrix with supported, proven impossible,
product-excluded, or not yet proven for every formatter and variant. Do not
stop after the first working formatter. Advertise only supported cells; an
exploration helper printing output is not proof. If investigation is blocked,
report the unproven cells and the next experiment instead of calling them
impossible.
7. Handle variants correctly
SupportedFormatters() is the gadget-wide union. For a formatter that only one
variant cannot produce:
add .Without(Formatters.X) to that GadgetVariant;
call GuardVariantFormatter(variant_number, formatter) near the start of
Generate(); and
add or update the expected-failure assertion in the full matrix when the
limitation is fundamental.
Use GadgetVariant.Input and .WithFacets(...) when a variant's input or
capability differs. Do not combine facts from different variants.
8. Handle bridge consumers correctly
A gadget is a bridge consumer only when it can wrap another gadget's serialized
payload. Define all of these together:
include GadgetTags.Bridged in Labels();
return the one accepted inner formatter from
SupportedBridgedFormatter(); and
consume BridgedPayload when it is set, otherwise build the gadget's genuine
default inner payload.
Confirm the incoming runtime type from the producer's real return value instead
of assuming it. PayloadRunner.GenerateGadget serializes each producer in the
next consumer's bridged formatter and passes the result forward.
9. Register only after the gate passes
Add the new generator source to the old-style ysonet/ysonet.csproj<Compile> items only after a real branch generates. Match the path form used
by the surrounding MSBuild entries; use forward slashes for documentation
references.
Discovery is reflection-based, but an unlisted source file is not compiled.
10. Add complete coverage
Read the current test helpers and the nearest comparable gadget tests before
editing ysonet.Tests/Tests.cs.
EveryGadgetGeneratesAPayload automatically checks the first formatter in
the normal tier.
GadgetFullMatrixGenerates automatically covers every advertised formatter,
variant, and minify state. Add a stable expected-failure assertion only for a
proven impossible cell.
Prove each advertised formatter with its actual deserializer and a safe
assertion. Do not rely on ShowAll, TestAll, or a caught inputArgs.Test
exception as the sole evidence that the named chain survives round-trip.
Add the gadget's observable runtime effect to
PayloadsFireIntoTestSinks, using a marker file, loopback listener, temp
directory, self-closing C# fixture, or another test-owned sink.
Add focused assertions for new option parsing, command input, variant
behavior, bridge behavior, minification, exact output, or error handling that
the matrices do not prove.
For a bridge consumer, automatic chain generation covers only a representative
output formatter. Add focused end-to-end firing and any formatter-specific
bridge coverage needed by the new behavior.
Follow .claude/memory/testing.md for file locations and antivirus resilience.
Reuse the repository's current test-artifact helpers rather than hardcoding a
temp or machine path.
A TODO comment or empty test stub is not coverage. If a runtime effect truly
cannot be exercised on the current machine, assert the expected limitation or
use a capability-gated test with a clear reason, following the existing suite.
11. Audit metadata and public surfaces
Use $ysonet-audit-gadget-metadata after implementation. If named-skill
invocation is not available, read and follow
.claude/skills/ysonet-audit-gadget-metadata/SKILL.md directly. Fix
evidence-backed drift across facets, variants, labels, help, tests, and
documentation.
Update:
the gadget table and related counts/details in docs/ARCHITECTURE.md;
docs/gadgets-and-plugins.md; and
credits or reference documentation when the new technique adds entries there.
Do not document an unregistered draft as supported.
12. Verify in a loop
Keep the first test loop specific to the gadget. Restore and compile without
starting the post-build runner:
Run only the new gadget's focused generation/deserialization assertions,
affected formatter/variant/option/minify/error cases, and safe runtime-effect
trigger. Fix root causes and repeat this narrow set until the real payload
triggers and every gadget-specific check passes. During that focused gate,
smoke:
one real generation for every materially different branch; and
the interactive module editor entry and category result.
If the gadget's only runtime effect is an outbound UNC/SMB callback, it needs the
opt-in OOB tier, and an automated run never touches a UNC path on the public
endpoint. Use the maintainer's self-hosted interactsh server when they have one.
When they do not, STOP AND ASK before the first UNC touch: state that the touch
goes to a public third-party endpoint and that Windows sends authentication
material when it opens the SMB session, then let them decide. Their explicit
approval covers only the runs discussed and only this gadget. Never point
YSONET_INTERACTSH_SERVER at a public endpoint yourself to make the gate pass.
Without approval the check stays a named skip and the runtime effect is reported
as UNVERIFIED; do not call the gadget finished on generation evidence alone.
Only after that gate is green, run the normal Debug tests and then the FULL
suite as the final regression gate:
msbuild ysonet.sln -p:Configuration=Debug -v:minimal -nologo
cd ysonet/bin/Debug
ysonet.Tests.exe --full
Run the standalone executable from its output directory so bundled assemblies
resolve. If that route is not suitable, set YSONET_FULL_TESTS=1 for the final
Debug build. If FULL exposes an issue, fix it, rerun the affected focused
checks, and repeat the final regression gate. The final tested source state must
end with a green FULL run.
Report any environment-specific skip or blocker honestly.
Final checks
The technique and credits are supported by evidence.
No existing gadget already covers the same behavior without approval.
Every live serializer family was considered and every plausible candidate
has an evidence-backed status for each variant.
Every advertised formatter and variant builds the named real chain.
Every fillable IGenerator member (name, finders, contributors,
additional info, labels, supported formatters, options, command input,
variants, facets, and bridge members) has an evidence-backed value or an
intentional default; none left at an unconsidered empty placeholder.
Facets, command input, labels, variants, and target requirements agree.
Every runtime-gated gadget or variant names at least one verified working
version. If latest failed, the highest verified working version is the
ceiling and the latest tested non-working version is documented.
WithVersions uses a single token for one established version or an
evidence-backed contiguous range, repeated in every variant override; a
non-runtime gate is deliberately left unspecified and documented.
The FLOOR was probed, not assumed: the static LEGACY floor candidates
report was read, and either a LEGACY row measured the payload on CLR 2 or
the report says plainly that the floor was not measured.
Bridge metadata and BridgedPayload behavior are complete when applicable.
The old-style csproj entry is present only for the finished source.
Focused generation, deserialization, behavior, and runtime-effect coverage passed first.
Public catalogs and help surfaces include the finished gadget.
Reflection and interactive smokes passed before the repository regression gate.
Debug tests passed and the final tested source state ends with a green FULL run.
No test was weakened, no fake placeholder was registered, and no version,
commit, or push action was taken without the required approval.
1---2name: ysonet-dev-create-gadget3description: Creates or scaffolds a new ysonet IGenerator gadget from verified technique code. It checks uniqueness, gathers missing requirements and credits, classifies facets, systematically attempts the maximum plausible formatter and serializer set, implements real formatter and bridge behavior, registers the source, adds focused and runtime-effect tests, updates the public catalogs, and runs Debug and FULL verification. Use when the user asks to add, create, or scaffold a gadget and expects implementation. Use ysonet-dev-create-plan when the requested deliverable is only a plan. Not for plugins or small edits to an existing gadget.4---56# Create a ysonet gadget78Build a truthful, working gadget end to end. Never weaken a test, invent a9credit, or register one gadget name while returning another gadget's payload.1011## Quality bar1213Quality comes before a quick "it generates bytes". The gadget is done when it14is right and complete, not when the first formatter works.1516- Take the durable route. When a better implementation lasts longer and makes17 the next gadget easier to add, do that one, even when it is more work.18- Do not stop at the minimum. Every plausible serializer, every variant, every19 option, every `IGenerator` member, tests, docs, help, and catalogs are part of20 the gadget, not extras.21- Fix root causes. A special case, a copy-paste of another generator, or a22 "for now" patch is acceptable only when a hard constraint blocks the proper23 fix, and then it needs a `dev-kitchen/todo/` note stating the proper fix.24- Use the existing helpers, base classes, and test patterns. If a shared helper25 is genuinely wrong for this gadget, improve the shared helper instead of26 bypassing it, so later gadgets benefit.27- Never trade correctness or test integrity for a green tick or a faster28 finish. An unproven cell is unproven, not "supported".29- If quality work needs more scope or a maintainer decision, ask. Do not30 silently ship a narrower gadget.3132## Integrity gate3334A registered gadget must implement its stated technique. Automatic generation35tests only prove that it returns bytes or text; they do not prove that the named36chain is present.3738- Do not use an existing leaf gadget as the completed body of a different39 gadget.40- Do not add the csproj entry, public documentation row, or supported status41 until at least one real formatter branch works.42- If the technique source or a material design fact is unavailable, ask for it.43 If the user explicitly wants a draft, keep it unregistered under44 `dev-kitchen/dirty/` and label it incomplete. Do not make the product or tests45 claim that the gadget exists.46- List only formatter and variant combinations that the real implementation47 produces. Assert a proven framework limitation as an expected failure; never48 hide it with a skip or substitute payload.4950## Workflow5152### 1. Load the current contract5354Read:5556- `CLAUDE.md`, the shared memory index and its listed files;57- gadget, test, build, and documentation sections of58 `docs/ARCHITECTURE.md`;59- `CONTRIBUTING.md`;60- `references/interview-questions.md`;61- `references/formatter-expansion.md`;62- `.claude/skills/ysonet-dev-create-plan/references/making-a-gadget.md`;63- the complete closest generators, their helpers, and their focused tests; and64- `assets/GadgetGenerator.template.cs` before using the template.6566Resolve `references/` and `assets/` paths relative to this skill's directory.67Treat documentation as a map and the current source as authoritative.6869### 2. Check uniqueness before editing7071Search the architecture gadget table, `docs/gadgets-and-plugins.md`, and72`ysonet/Generators/` for the same primitive, sink, carrier, or target behavior.73Compare variants as well as class names. If the proposal substantially overlaps74an existing gadget, show the evidence and get the maintainer's decision before75creating a second public gadget.7677### 3. Establish the evidence7879Answer repository-checkable questions from source first. Ask only for material80facts that cannot be discovered, such as intended scope, original research81credit, or unavailable technique code. Record:8283- the source or write-up for the technique;84- original researchers and the ysonet implementer;85- target-side types, assemblies, runtime versions, and patched limitations;86- the meaning of `-c`, options, variants, and the formatter candidate matrix;87- whether it consumes an upstream payload and in which formatter; and88- the observable runtime effect and safe test-owned sink.8990Do not guess. Preserve an unknown as unknown instead of choosing convenient91metadata.9293### 4. Classify the gadget9495Use `$ysonet-categorize-gadget` to derive `Facets()` for the gadget and any96variant overrides from the verified evidence. If named-skill invocation is not97available, read and follow98`.claude/skills/ysonet-categorize-gadget/SKILL.md` directly. Use only the99vocabulary in `ysonet/Generators/Base/IGenerator.cs`. Omit100`WithInputs(...)` when `CommandInput()` derives the correct input. Keep an101unproven axis `uncategorized`, and never mix it with a real value on the same102axis.103104Decide the runtime version axis here too, and do it last, after the runtime105effect test in step 8 has actually fired the payload. Every runtime-gated gadget106or variant must name at least one evidence-backed WORKING VERSION before it is107finished:108109- identify what target property the version describes: normally the framework110 the target process runs on, but for a compile-time compatibility gate it is111 the target application's `TargetFrameworkAttribute`. Never use ysonet's build112 merely because it is easy to read.113- first test the current/latest candidate. Run FULL and read the `Runtime:` line114 and the version report at the end of the execution matrix; a gadget that fired115 but declares nothing is listed there by name.116- if the effect does not fire on the latest candidate and the difference is a117 runtime-version gate, do not use that version as the ceiling and do not finish118 with vague compatibility. Reproduce on older supported target versions119 (installed runtime or target-app stamp, as applicable) until at least one120 fires, then use the highest verified working version as the ceiling. Record121 the latest tested non-working version and the limitation in the gadget122 documentation or `AdditionalInfo()`.123- the FLOOR is the documented introduction of the types the chain needs. Default124 `RuntimeVersion.NetFx40` (the CLR v4 generation this tool targets);125 `NetFx45` when the chain goes through `System.Security.Claims`, WIF, or126 `Comparer<T>.Create`. Microsoft documentation is acceptable evidence for a127 floor.128- then PROBE the floor instead of leaving it at the default, because the default129 is an assumption rather than a measurement. Cheap static check first: a NORMAL130 run prints `LEGACY floor candidates`, the gadgets whose generated payload names131 no 4.x assembly version. If yours is on that list, add a row to132 `LegacyClrRows` in `ysonet.Tests/Tiers/LegacyClrTier.cs` (a private module uses the133 `RunPrivateLegacyRows` hook) and run `ysonet.Tests.exe --legacy`, which fires134 the real payload on CLR 2 in 2.0 / 3.0 / 3.5 lanes. Lower the floor only on an135 observed EFFECT there, never on a clean deserialize. Record BOTH bounds, or say136 explicitly in the plan and the gadget comment that the floor was not measured.137 A measured negative is a result too: keep its classified reason (usually "the138 payload names a 4.x assembly version and the strict readers bind it verbatim")139 in `AdditionalInfo()`, not in a facet value.140- use a single token when only one target version is verified. Use141 `.WithVersions(RuntimeVersion.Range(floor, ceiling))` only when the evidence142 supports that contiguous span. Repeat the declaration in every variant143 `FacetOverride` (an override replaces the whole set; a standing test fails the144 build when one variant declares versions and another does not).145- leave `unspecified` when the gadget's real gate is not a runtime version at146 all (an OS patch, a library version, a config switch). That gate belongs in147 `AdditionalInfo()`. If the gate is a runtime version but no working version has148 been established, the gadget remains unverified and unfinished; never guess.149150### 5. Implement the real chain151152Copy `assets/GadgetGenerator.template.cs` to the normal generator location only153when enough evidence exists to implement the real chain. Replace the name token,154resolve every TODO, and delete optional template sections that do not apply.155Match the closest generator's structure.156157Keep the whole payload in the gadget's own file. This is a hard rule, and158`ysonet/Generators/README.md` is the contract:159160- Payload templates, target type names, member names and the ORDER they are161 written in, and any surrogate shape (declared as a nested type in the generator162 class) all live in `Generators/<Name>Generator.cs`. Changing what the gadget163 emits must mean changing one file.164- Never put them in a helper, and never create a shared "payload builder" for165 several gadgets. A helper may only hold mechanics that name no gadget, taking166 the names and shapes as arguments: the type-name swaps167 (`MessagePackTypelessTypeSwap`, `SharpSerializerTypeSwap`), `SerializersHelper`,168 the minifiers, the escapers.169- Reuse the base class instead of copying plumbing:170 `GenericGenerator.Serialize` for BinaryFormatter / SoapFormatter /171 NetDataContractSerializer / LosFormatter, and172 `GenericGenerator.HandWritten.cs` for a hand written document or your own bytes173 (`FinishHandWrittenPayload` covers minify plus the `-t` self-test, and174 `RequireCommandInput`, `RawInputOption`, `EscapeForJson`,175 `EscapeForXmlAttribute`, `IsFormatter`, `IsMessagePackTypeless`,176 `IsMessagePackLz4`, `UnsupportedFormatter` cover the rest). If one of those is177 genuinely wrong for this gadget, improve the shared member; do not fork it.178- The only allowed dependency on another gadget is reusing it as the INNER179 payload through `GenerateInner`, declared with `GadgetTags.Bridged` or180 `GadgetTags.Hosted`.181- Test what generation cannot prove. A bait-and-switch payload (a surrogate plus182 a type-name swap) still generates when the swap silently fails, so assert the183 target names in the emitted bytes, as184 `MessagePackTypelessCarriesTargetTypeNames` does.185186Take the operator's input as typed. Unless the user asked for validation, the only187check is "not empty" (`RequireCommandInput`): no path, URL, scheme, host, extension,188character-set, or assembly-identity check. What the value means is the TARGET's189decision, ysonet never opens or resolves it, and a refusal only blocks the research.190Teach the proven form in the option help, one line of `AdditionalInfo()`, and the191docs; use `Debugging.ShowNote` for a hint, never a refusal. Refuse only what cannot192be EMITTED. Escaping, the `--minify` corruption guard, and gadget scope are separate193and still apply. Contract: `ysonet/Generators/README.md`, "Operator input: document194it, do not police it".195196Accept `-t` by default, including when the effect leaves this machine - a callout is197not damage. Refuse only when the self-test would damage or compromise the OPERATOR's198machine (destroy their data, run their code, hand their unparsed bytes to native199code), decided per VARIANT and before anything is constructed. A denial-of-service200payload never deserializes in the ysonet process: route it through201`SelfTestNeedsChildProcess` / `IsolatedSelfTest`, and refuse only where that child202cannot run it. Contract: `ysonet/Generators/README.md`, "`-t` (self-test) policy".203204For a DENIAL-OF-SERVICE gadget, read `ysonet/Generators/README.md`, "Denial of205service: one facet, and everything it turns on", and follow it in full. Declaring206`PayloadKind.DenialOfService` is the single switch that arms the acknowledgement, the207bulk exclusions, the test-tier exclusions and the interactive preview, so never add a208second mechanism beside it. The parts that are NOT automatic are the ones to get209right: `-t` through a child process with `PayloadReader.CanRead` covering every210advertised formatter, one line printed before the child is terminated, never a row in211any fire list, a short `AdditionalInfo()`, and per-formatter effect evidence produced212by an operator-run harness that requires the TARGET'S OWN exception rather than213merely a dead child. The acknowledgement is required on every surface and worded for214each: the editor names the SETTING, never the command-line flag, and interactive use215must not imply it.216217Write the gadget to be READ. It is research material: a human and an AI must be218able to understand the technique from this one file. Nothing is hidden and219nothing is obfuscated.220221- Keep each payload a whole, readable document in a verbatim string (`@"..."`),222 with the target type names spelled out, so a reader can copy it straight into223 the testing arena (`ysonet/Helpers/TestingArena/TestingArenaHome.cs`) or a224 scratch project and have it work.225- Never obfuscate, encode, or compress a payload in source: no base64 blob or226 byte array standing in for a readable document, no string assembled from227 fragments or `char` codes, no reflection used to avoid naming a type that can be228 named, no one document split across methods. When the WIRE format genuinely229 needs encoding or compression, build it from readable source at generation time230 and state in a comment what the bytes are.231- Use the real target and member names and technique-derived variable names.232 Comment the WHY - the sink, why the property order or member set matters, the233 target-side condition, what would silently break - not the syntax.234- Prefer straightforward code over a compact trick. Reflection, dynamic code or235 metaprogramming only when the technique requires it, and then explained.236- The Release binary's string encryption (`ysonet/obfuscar.xml`) is an antivirus237 measure on one shipped executable. It never justifies writing obscure source.238239Walk every fillable member `ysonet/Generators/Base/IGenerator.cs` exposes and240give each an evidence-backed value or a deliberate default. Do not leave a241member at an empty or placeholder default because it was not considered. Confirm242each against the verified evidence:243244- `Name()`: derived from the class name without the `Generator` suffix; override245 only when the derived public name is wrong.246- `Generate`: the real chain for every advertised formatter.247- `Finders()`: verified original researcher names.248- `Contributors()`: this tool's implementer; omit when identical to `Finders()`.249- `Credit()`: never override; the base class composes it.250- `AdditionalInfo()`: concise purpose, target assemblies and versions, CVEs, and251 public references.252- `Labels()`: only the `GadgetTags` constants that apply (`Independent`,253 `Bridged`, `Subclass`, `Hosted`, `GetterChain`, `OnDeserialized`,254 `SecondOrderDeserialization`, `NotInGAC`, `Hidden`); never a guessed string.255- Hosted payload check (do this before writing the file). Ask: does `Generate()`256 serialize a type this generator defines (its own `*Marshal` class or a real257 framework type)? If NO, and it hands another generator's object to `Serialize()`,258 then it is a hosted payload: put the file in `Generators/HostedPayloads/`, keep the259 namespace `ysonet.Generators`, and tag it `GadgetTags.Hosted`. If YES, it stays in260 `Generators/` and must NOT carry `GadgetTags.Hosted`, even when it nests another261 gadget's payload inside its own type (that is `GadgetTags.Bridged` territory).262 Having a `Variants()` list or a `var/variant` option is irrelevant to this choice.263 See `ysonet/Generators/HostedPayloads/README.md`.264- `SupportedFormatters()`: the maximum verified union from step 6. On a gadget with265 more than one variant, annotate each token with the number of variants that266 formatter carries: `"BinaryFormatter (2)"`, and a bare name when only one variant267 supports it. The count is per formatter, so a gadget can read268 `"BinaryFormatter (3)", "SoapFormatter (2)"`. It is display-only (every consumer269 splits on the first space), and it is the only place the public catalog reveals270 that the gadget has variants at all, so a multi-variant gadget without it reads as271 single-variant. Mirror the same token in `docs/gadgets-and-plugins.md` and the272 `docs/ARCHITECTURE.md` formatter column.273- `SupportedBridgedFormatter()` and `BridgedPayload`: only for a bridge consumer274 (step 8).275- `Options()`: every real option with clear parsing and sane defaults.276- `CommandInput()`: the real meaning of `-c`; keep the `ShellCommand` default277 only when evidence proves it.278- `Variants()`: distinct payload shapes with `.Without(...)`, `.Input`, and279 `.WithFacets(...)` as needed (step 7).280- `Facets()`: the broad discovery axes derived in step 4.281282- Honor `inputArgs.Minify` and `inputArgs.Test` on every advertised path.283- Return the output shape the formatter uses, normally `byte[]` for the base284 binary serializers and `string` for text serializers.285- Throw a clear exception on bad input. Never call `Environment.Exit`.286- Keep target dependencies separate from dependencies used only to generate the287 payload.288289Run a focused generation check before registration. If the real branch is not290ready, use the integrity gate and leave it unregistered.291292### 6. Expand formatter support systematically293294Follow `references/formatter-expansion.md` in full. Build the candidate set295from current source rather than from memory: `Formatters`, every live296`SupportedFormatters()` declaration, all `SerializersHelper` partials, and297dedicated or inline serializer implementations. The public set is currently298broader than either the constants or `SerializersHelper.ShowAll()`.299300Perform this audit even when the request or an implementation plan names no301additional formatter, lists only one formatter, or omits serializer work302entirely. Plan silence does not authorize a narrow implementation.303304Attempt every plausible candidate against the named technique. Try the real305graph first, then a serializer-specific representation of that same chain when306the serializer needs a different shape. Compare the closest working generator307before concluding that a direct serialization failure is fundamental.308309Maintain an evidence matrix with `supported`, `proven impossible`,310`product-excluded`, or `not yet proven` for every formatter and variant. Do not311stop after the first working formatter. Advertise only supported cells; an312exploration helper printing output is not proof. If investigation is blocked,313report the unproven cells and the next experiment instead of calling them314impossible.315316### 7. Handle variants correctly317318`SupportedFormatters()` is the gadget-wide union. For a formatter that only one319variant cannot produce:3203211. add `.Without(Formatters.X)` to that `GadgetVariant`;3222. call `GuardVariantFormatter(variant_number, formatter)` near the start of323 `Generate()`; and3243. add or update the expected-failure assertion in the full matrix when the325 limitation is fundamental.326327Use `GadgetVariant.Input` and `.WithFacets(...)` when a variant's input or328capability differs. Do not combine facts from different variants.329330### 8. Handle bridge consumers correctly331332A gadget is a bridge consumer only when it can wrap another gadget's serialized333payload. Define all of these together:334335- include `GadgetTags.Bridged` in `Labels()`;336- return the one accepted inner formatter from337 `SupportedBridgedFormatter()`; and338- consume `BridgedPayload` when it is set, otherwise build the gadget's genuine339 default inner payload.340341Confirm the incoming runtime type from the producer's real return value instead342of assuming it. `PayloadRunner.GenerateGadget` serializes each producer in the343next consumer's bridged formatter and passes the result forward.344345### 9. Register only after the gate passes346347Add the new generator source to the old-style `ysonet/ysonet.csproj`348`<Compile>` items only after a real branch generates. Match the path form used349by the surrounding MSBuild entries; use forward slashes for documentation350references.351352Discovery is reflection-based, but an unlisted source file is not compiled.353354### 10. Add complete coverage355356Read the current test helpers and the nearest comparable gadget tests before357editing `ysonet.Tests/Tests.cs`.358359- `EveryGadgetGeneratesAPayload` automatically checks the first formatter in360 the normal tier.361- `GadgetFullMatrixGenerates` automatically covers every advertised formatter,362 variant, and minify state. Add a stable expected-failure assertion only for a363 proven impossible cell.364- Prove each advertised formatter with its actual deserializer and a safe365 assertion. Do not rely on `ShowAll`, `TestAll`, or a caught `inputArgs.Test`366 exception as the sole evidence that the named chain survives round-trip.367- Add the gadget's observable runtime effect to368 `PayloadsFireIntoTestSinks`, using a marker file, loopback listener, temp369 directory, self-closing C# fixture, or another test-owned sink.370- Add focused assertions for new option parsing, command input, variant371 behavior, bridge behavior, minification, exact output, or error handling that372 the matrices do not prove.373- For a bridge consumer, automatic chain generation covers only a representative374 output formatter. Add focused end-to-end firing and any formatter-specific375 bridge coverage needed by the new behavior.376- Follow `.claude/memory/testing.md` for file locations and antivirus resilience.377 Reuse the repository's current test-artifact helpers rather than hardcoding a378 temp or machine path.379380A TODO comment or empty test stub is not coverage. If a runtime effect truly381cannot be exercised on the current machine, assert the expected limitation or382use a capability-gated test with a clear reason, following the existing suite.383384### 11. Audit metadata and public surfaces385386Use `$ysonet-audit-gadget-metadata` after implementation. If named-skill387invocation is not available, read and follow388`.claude/skills/ysonet-audit-gadget-metadata/SKILL.md` directly. Fix389evidence-backed drift across facets, variants, labels, help, tests, and390documentation.391392Update:393394- the gadget table and related counts/details in `docs/ARCHITECTURE.md`;395- `docs/gadgets-and-plugins.md`; and396- credits or reference documentation when the new technique adds entries there.397398Do not document an unregistered draft as supported.399400### 12. Verify in a loop401402Keep the first test loop specific to the gadget. Restore and compile without403starting the post-build runner:404405```text406nuget restore ysonet.sln407msbuild ysonet.sln -p:Configuration=Debug -p:RunYsonetTests=false -v:minimal -nologo408```409410Run only the new gadget's focused generation/deserialization assertions,411affected formatter/variant/option/minify/error cases, and safe runtime-effect412trigger. Fix root causes and repeat this narrow set until the real payload413triggers and every gadget-specific check passes. During that focused gate,414smoke:415416- `ysonet/bin/Debug/ysonet.exe --list gadgets`;417- `ysonet/bin/Debug/ysonet.exe --list formatters -g <Name>`;418- one real generation for every materially different branch; and419- the interactive module editor entry and category result.420421If the gadget's only runtime effect is an outbound UNC/SMB callback, it needs the422opt-in OOB tier, and an automated run never touches a UNC path on the public423endpoint. Use the maintainer's self-hosted interactsh server when they have one.424When they do not, STOP AND ASK before the first UNC touch: state that the touch425goes to a public third-party endpoint and that Windows sends authentication426material when it opens the SMB session, then let them decide. Their explicit427approval covers only the runs discussed and only this gadget. Never point428`YSONET_INTERACTSH_SERVER` at a public endpoint yourself to make the gate pass.429Without approval the check stays a named skip and the runtime effect is reported430as UNVERIFIED; do not call the gadget finished on generation evidence alone.431432Only after that gate is green, run the normal Debug tests and then the FULL433suite as the final regression gate:434435```text436msbuild ysonet.sln -p:Configuration=Debug -v:minimal -nologo437cd ysonet/bin/Debug438ysonet.Tests.exe --full439```440441Run the standalone executable from its output directory so bundled assemblies442resolve. If that route is not suitable, set `YSONET_FULL_TESTS=1` for the final443Debug build. If FULL exposes an issue, fix it, rerun the affected focused444checks, and repeat the final regression gate. The final tested source state must445end with a green FULL run.446447Report any environment-specific skip or blocker honestly.448449## Final checks450451- [ ] The technique and credits are supported by evidence.452- [ ] No existing gadget already covers the same behavior without approval.453- [ ] Every live serializer family was considered and every plausible candidate454 has an evidence-backed status for each variant.455- [ ] Every advertised formatter and variant builds the named real chain.456- [ ] Every fillable `IGenerator` member (name, finders, contributors,457 additional info, labels, supported formatters, options, command input,458 variants, facets, and bridge members) has an evidence-backed value or an459 intentional default; none left at an unconsidered empty placeholder.460- [ ] Facets, command input, labels, variants, and target requirements agree.461- [ ] Every runtime-gated gadget or variant names at least one verified working462 version. If latest failed, the highest verified working version is the463 ceiling and the latest tested non-working version is documented.464- [ ] `WithVersions` uses a single token for one established version or an465 evidence-backed contiguous range, repeated in every variant override; a466 non-runtime gate is deliberately left `unspecified` and documented.467- [ ] The FLOOR was probed, not assumed: the static `LEGACY floor candidates`468 report was read, and either a LEGACY row measured the payload on CLR 2 or469 the report says plainly that the floor was not measured.470- [ ] Bridge metadata and `BridgedPayload` behavior are complete when applicable.471- [ ] The old-style csproj entry is present only for the finished source.472- [ ] Focused generation, deserialization, behavior, and runtime-effect coverage passed first.473- [ ] Public catalogs and help surfaces include the finished gadget.474- [ ] Reflection and interactive smokes passed before the repository regression gate.475- [ ] Debug tests passed and the final tested source state ends with a green FULL run.476- [ ] No test was weakened, no fake placeholder was registered, and no version,477 commit, or push action was taken without the required approval.
Run npx skillmds@latest add irsdl/ysonet-dev-create-gadget in your terminal (requires Node.js), paste this page's agent-chat prompt into Claude, Cursor, or any MCP-connected agent, or download the SKILL.md file and copy it into your agent's skills directory.
Creates or scaffolds a new ysonet IGenerator gadget from verified technique code. It checks uniqueness, gathers missing requirements and credits, classifies facets, systematically attempts the maximum plausible formatter and serializer set, implements real formatter and bridge behavior, registers the source, adds focused and runtime-effect tests, updates the public catalogs, and runs Debug and FULL verification. Use when the user asks to add, create, or scaffold a gadget and expects implementation. Use ysonet-dev-create-plan when the requested deliverable is only a plan. Not for plugins or small edits to an existing gadget. It is listed under Coding & Dev Tools on SkillMD.
This skill has not completed SkillMD's automated safety review yet. SkillMD never runs a skill's scripts for you; review the SKILL.md before installing.
This skill is tagged as working with Claude Code, Claude.ai, OpenAI Codex. SKILL.md is an open format, so most agents that read a skills directory can load it too.
Yes. Installing skills from SkillMD is free, and the skill stays under its author's original license.
irsdl (@irsdl) published this skill. Their other Agent Skills are listed on their SkillMD profile.