You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
After #537's resolve-frontmatter.sh refactor, every skill's Frontmatter resolution section still has the agent compose the YAML frontmatter from the script's JSON output plus per-skill literals (provenance.skill, provenance.isInteractive, optional model, optional extensions). The agent is responsible for parsing the JSON, mapping JSON fields to YAML keys, enforcing field-omission rules, composing the YAML in the correct field order, and quoting values correctly. This works, but it leaves a non-trivial composition surface in the agent. Schema changes require coordinated updates to prose at every site, and there's room for subtle mis-formatting (pr: null instead of omission; field reordering; mis-quoted ticket_ref: '#537').
Context
The script currently emits a JSON object with these keys (some optional): branch, commit, baseSha, pr, ticket_id, ticket_ref, platform, timestamp, run_id. Each calling skill is expected to translate that JSON into a YAML block via the partial-included instruction _partials/frontmatter-via-script.md plus per-skill bullets.
The next logical reduction is to have the script emit the complete YAML block — --- delimiters included — so the agent simply prepends the output to its artifact body. Field ordering, quoting, omission, and per-section grouping all become the script's responsibility, owned and tested in one place.
Issue #588 (DRY via markdown partials) was closed in favor of the approach in this ticket: once the agent stops composing YAML, the partials are unnecessary.
Solution
Extend resolve-frontmatter.sh so it emits a complete YAML frontmatter block (delimiters included) by default. Keep the existing JSON output as an explicit --format json escape hatch for the small number of bespoke sites whose composition needs exceed what a flag-driven CLI can express cleanly.
CLI surface
resolve-frontmatter.sh
--skill NAME required for YAML mode; sets provenance.skill
--interactive BOOL required for YAML mode (true|false); sets provenance.isInteractive
--model ID optional; sets provenance.model
--extra KEY=VALUE repeatable; scalar extension field
--extra-list KEY=v1,v2,... repeatable; list-of-scalars extension field
--override KEY=VALUE repeatable; replaces a script-resolved value (empty value = force-omit)
--format yaml|json default: yaml
Output structure (YAML mode)
The block is composed in three tiers, each in its prescribed order:
provenance: block — skill, timestamp, baseSha (omit if unresolved), isInteractive, optional model. CamelCase keys per existing convention.
Canonical top-level fields in schema order — ticket_id, ticket_ref, branch, commit, pr, run_id. Snake_case keys per existing convention. Optional fields are omitted (no null, no empty strings).
Extension fields in caller-supplied (--extra / --extra-list) insertion order.
The script auto-quotes values containing YAML-special characters (e.g., # in ticket_ref, leading : in URLs). Lists emit in flow form: commits: [abc1234, def5678].
Override semantics
--override KEY=VALUE replaces the script's auto-resolved value for KEY. Empty value (--override run_id=) forces omission. Used by create-devlog to suppress the breadcrumb-derived run_id when no --run-id arg was supplied.
Bespoke exceptions
Two sites use --format json and compose YAML themselves:
refine-plan — computes the provenance: block from case branches on the input artifact's existing provenance (refinedBy, iteration, conditional preservation). The logic doesn't fit a flag-driven CLI without inventing a templating sub-language.
wrap-up (deferred-findings) — emits tickets_created as a list-of-objects extension ([{ id, items }]), which has no clean expression via --extra / --extra-list.
These exceptions are documented in artifact-conventions.md as a deliberate pattern, and each bespoke skill carries a one-sentence rationale at its frontmatter section.
Caller-side changes
For each in-scope skill or subagent:
Replace the existing partial-include and per-skill YAML-composition bullets with a single Bash invocation sentence.
Move the --extra / --extra-list arguments out of skill prose into the invocation line.
Inline ## Output format YAML frontmatter examples in skills and subagents are removed or replaced with a single reference to the canonical example in _data/artifact-conventions.md — schema documentation lives in one place.
The _partials/frontmatter-via-script.md partial is removed.
Acceptance criteria
resolve-frontmatter.sh accepts --skill, --interactive, --model, --extra KEY=VALUE (repeatable), --extra-list KEY=v1,v2,... (repeatable), --override KEY=VALUE (repeatable), and --format yaml|json (default yaml), and emits a complete YAML frontmatter block including --- delimiters in YAML mode.
The script enforces correct field order (provenance: block first, then canonical top-level fields in schema order, then extensions in insertion order) and omission rules (no pr: null, etc.).
The script auto-quotes values containing YAML-special characters; lists emit in flow form.
--override KEY= (empty value) forces field omission, overriding the script's auto-resolution.
--format json preserves the current JSON output behavior unchanged, including the existing field set and omission rules.
Shellspec tests cover: required-field presence in YAML mode, optional-field omission rules, --override force-omit, --extra / --extra-list insertion order, quoting of YAML-special characters, and preservation of existing JSON-mode behavior.
Each in-scope skill's Frontmatter resolution section is reduced to a single invocation sentence — no per-skill YAML composition prose remains.
Inline ## Output format YAML frontmatter examples in skills and subagents are removed or replaced with a single reference to the canonical example in _data/artifact-conventions.md.
_partials/frontmatter-via-script.md is removed.
refine-plan and wrap-up (deferred-findings) use --format json and compose YAML themselves; each carries a one-sentence rationale at its frontmatter section, and artifact-conventions.md documents the bespoke-exception pattern in one place.
No remaining inline YAML-composition prose in any artifact-writing skill's Frontmatter section apart from the two documented bespoke exceptions.
nmr --filter agents check passes (Prettier + tests + linting) after the refactor.
A representative end-to-end check across skill / subagent / orchestrated-subagent confirms the installed agent prepends the script's output without further composition.
New or modified behavior in this change is covered by tests.
Documentation, help text, and usage examples are updated to reflect the new CLI surface; references to anything removed (the partial, inline YAML examples) are deleted.
Problem
After #537's
resolve-frontmatter.shrefactor, every skill's Frontmatter resolution section still has the agent compose the YAML frontmatter from the script's JSON output plus per-skill literals (provenance.skill,provenance.isInteractive, optionalmodel, optional extensions). The agent is responsible for parsing the JSON, mapping JSON fields to YAML keys, enforcing field-omission rules, composing the YAML in the correct field order, and quoting values correctly. This works, but it leaves a non-trivial composition surface in the agent. Schema changes require coordinated updates to prose at every site, and there's room for subtle mis-formatting (pr: nullinstead of omission; field reordering; mis-quotedticket_ref: '#537').Context
The script currently emits a JSON object with these keys (some optional):
branch,commit,baseSha,pr,ticket_id,ticket_ref,platform,timestamp,run_id. Each calling skill is expected to translate that JSON into a YAML block via the partial-included instruction_partials/frontmatter-via-script.mdplus per-skill bullets.The next logical reduction is to have the script emit the complete YAML block —
---delimiters included — so the agent simply prepends the output to its artifact body. Field ordering, quoting, omission, and per-section grouping all become the script's responsibility, owned and tested in one place.Issue #588 (DRY via markdown partials) was closed in favor of the approach in this ticket: once the agent stops composing YAML, the partials are unnecessary.
Solution
Extend
resolve-frontmatter.shso it emits a complete YAML frontmatter block (delimiters included) by default. Keep the existing JSON output as an explicit--format jsonescape hatch for the small number of bespoke sites whose composition needs exceed what a flag-driven CLI can express cleanly.CLI surface
Output structure (YAML mode)
The block is composed in three tiers, each in its prescribed order:
provenance:block —skill,timestamp,baseSha(omit if unresolved),isInteractive, optionalmodel. CamelCase keys per existing convention.ticket_id,ticket_ref,branch,commit,pr,run_id. Snake_case keys per existing convention. Optional fields are omitted (nonull, no empty strings).--extra/--extra-list) insertion order.The script auto-quotes values containing YAML-special characters (e.g.,
#inticket_ref, leading:in URLs). Lists emit in flow form:commits: [abc1234, def5678].Override semantics
--override KEY=VALUEreplaces the script's auto-resolved value forKEY. Empty value (--override run_id=) forces omission. Used bycreate-devlogto suppress the breadcrumb-derivedrun_idwhen no--run-idarg was supplied.Bespoke exceptions
Two sites use
--format jsonand compose YAML themselves:refine-plan— computes theprovenance:block from case branches on the input artifact's existing provenance (refinedBy,iteration, conditional preservation). The logic doesn't fit a flag-driven CLI without inventing a templating sub-language.wrap-up(deferred-findings) — emitstickets_createdas a list-of-objects extension ([{ id, items }]), which has no clean expression via--extra/--extra-list.These exceptions are documented in
artifact-conventions.mdas a deliberate pattern, and each bespoke skill carries a one-sentence rationale at its frontmatter section.Caller-side changes
For each in-scope skill or subagent:
--extra/--extra-listarguments out of skill prose into the invocation line.Inline
## Output formatYAML frontmatter examples in skills and subagents are removed or replaced with a single reference to the canonical example in_data/artifact-conventions.md— schema documentation lives in one place.The
_partials/frontmatter-via-script.mdpartial is removed.Acceptance criteria
resolve-frontmatter.shaccepts--skill,--interactive,--model,--extra KEY=VALUE(repeatable),--extra-list KEY=v1,v2,...(repeatable),--override KEY=VALUE(repeatable), and--format yaml|json(defaultyaml), and emits a complete YAML frontmatter block including---delimiters in YAML mode.provenance:block first, then canonical top-level fields in schema order, then extensions in insertion order) and omission rules (nopr: null, etc.).--override KEY=(empty value) forces field omission, overriding the script's auto-resolution.--format jsonpreserves the current JSON output behavior unchanged, including the existing field set and omission rules.--overrideforce-omit,--extra/--extra-listinsertion order, quoting of YAML-special characters, and preservation of existing JSON-mode behavior.## Output formatYAML frontmatter examples in skills and subagents are removed or replaced with a single reference to the canonical example in_data/artifact-conventions.md._partials/frontmatter-via-script.mdis removed.refine-planandwrap-up(deferred-findings) use--format jsonand compose YAML themselves; each carries a one-sentence rationale at its frontmatter section, andartifact-conventions.mddocuments the bespoke-exception pattern in one place.nmr --filter agents checkpasses (Prettier + tests + linting) after the refactor.