Skip to content

os g skill — no scaffolder exists for the extension primitive ADR-0063 names #11025

Description

@os-elon

Follow-up to #10359 · Blocked-by: #10359 (its PR removes the agent generator and makes os g agent point here in prose; this card adds the scaffolder that prose says does not exist yet).

The maintainer's ruling on #10359 was Option A — retire the agent generator and add a skill generator, with explicit latitude: "The implementer is permitted to split the skill-generator half into its own card if that scopes better; the retirement half proceeds either way." This is that split. The retirement half is PR #11028; this is the second half, filed rather than dropped.

Placeholder spelling. GitHub's body sanitizer strips short angle-bracket fragments, including inside code fences and inline code — measured on the first revision of this very issue, where three of them vanished silently. Placeholders below are therefore spelled in CAPS (NAME, TYPE).

Why it was split off rather than done in the same PR

Adding a seventh GENERATORS entry looks mechanical and is not. Four things have to be decided first, and each of them is a decision rather than a template:

1. The generated filename does not match the loader's filePatterns

DEFAULT_METADATA_TYPE_REGISTRY (packages/spec/src/kernel/metadata-plugin.zod.ts) gives every metadata type a filePatterns shaped **/*.TYPE.ts, and skill is no exception:

{ type: 'skill', label: 'AI Skill', filePatterns: ['**/*.skill.ts', '**/*.skill.yml'], … allowRuntimeCreate: true, … }

But runMetadataGeneration in packages/cli/src/commands/generate.ts writes ${toSnakeCase(name)}.ts — no type infix. So os g skill lead-qualification would produce src/skills/lead_qualification.ts, which matches noskill file pattern, while real projects name theirs NAME.skill.ts (examples/app-crm/src/objects/account.object.ts, …/views/lead.view.ts, and so on for every type).

That is not a cosmetic mismatch on this type: skill is allowRuntimeCreate: true and is discovered from the filesystem, so a scaffold the pattern misses would validate, publish and never load — the exact silent-strip failure #10359 exists to close, re-entering through the fix for it. Getting it right means deciding one of:

  • give the harness a per-generator filename hook and use it for skill only (a divergence that needs a reason), or
  • change the harness convention to NAME.TYPE.ts for all seven types — which is arguably what the registry has said all along, and which changes every existing generator's output plus the docs and examples that show it.

The second option is a real repo-wide question and should not ride in on a skill template.

2. tools is required, with no default

SkillSchema declares tools: z.array(…) with no.optional() and no .default(), so a template must emit something. [] is the obvious placeholder and is not obviously right: ADR-0064 is entirely about tool scoping to an agent, and it records actions_executor's empty tools: [] plus a resolver fall-through as "the hole". That fall-through has since been closed (SkillSchema's own docblock now says an agent's tool set is the union of its surface-compatible skills' tools, with no global fall-through), but which default a scaffolder should teach — [], a commented example, or a wildcard family like action_* — is an authoring-guidance decision, not a fill-in-the-blank.

3. surface has to be chosen, and it is lint-checked

surface: 'ask' | 'build' | 'both' defaults to 'ask' in the schema, and ADR-0063 §3 binds a skill to an agent whose surface it matches, enforced at load. A scaffold either emits the key (teaching the author it exists, and picking for them) or omits it (silently taking 'ask'). Both are defensible; the choice belongs in this card.

4. The obvious copy source for the template is itself wrong

The @example blocks on SkillSchema and defineSkill (packages/spec/src/ai/skill.zod.ts) both pass triggerPhrases, which is a retiredKey() tombstone that rejects on parse. Verified at origin/main @ f334d662e:

$ node -e "…defineSkill({ name:'case_management', …, triggerPhrases:[…] })"
THREW: [ { "expected": "never", "code": "invalid_type", "path": [ "triggerPhrases" ] …

Filed separately as #11026 — but whoever writes the template must not copy from there.

Scope

  • packages/cli/src/commands/generate.ts — a skill entry (or whatever Add metamodel interfaces for ObjectQL/ObjectUI contract #1 decides), plus the filename question.
  • content/docs/deployment/cli.mdx — the Available-types table, the os g … example block, and the retirement Callout PR fix(cli): retire the agent generator — os g agent now names ADR-0063 and points at skills #11028 added, whose last sentence ("There is no os g skill scaffolder yet, so write that file by hand for now") becomes false the moment this lands.
  • packages/cli/README.md — the "Available generate types" roster and the same sentence.
  • packages/cli/src/commands/generate.ts's RETIRED_GENERATORS.agent.detail — the refusal tells the author os g skill does not exist; that line has to move with this card.
  • Tests + a changeset.

⛔ Repo-root skills/** is a governed surface and is not in scope — that directory is the published authoring-skill catalog (SKILL.md files for coding agents), a different thing from defineSkill agent skills. See content/docs/ai/skills.mdx for the two-meanings note.

Premise re-check commands

git grep -n "defaultDir" origin/main -- packages/cli/src/commands/generate.ts
git grep -n "type: 'skill'" origin/main -- packages/spec/src/kernel/metadata-plugin.zod.ts
ls examples/app-crm/src/objects # the naming convention real projects use

Generated by Claude Code

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions