feat: add additionalProperties support for skills#223
Conversation
- Add additionalProperties JSONB column to skills and skill_versions tables - Parse Claude Code-specific frontmatter fields (model, hooks, context, etc.) into additionalProperties - Thread additionalProperties through upload, diff, and deploy pipelines - Add updateSkillAdditionalProperty change proposal type with per-key granularity - Add conflict detection, validation, and applier for the new CP type - Render additionalProperties in ClaudeDeployer SKILL.md output (not Copilot/Cursor) - Add frontend display in review original/result tabs #206 Co-Authored-By: Claude Opus 4.6 <[email protected]>
…lifecycle Extract Claude Code additional fields (model, user-invocable, hooks, etc.) in parseSkillDirectory and serialize them back into SKILL.md in the create applier, so new skills created via `diff add` preserve these properties end-to-end. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Cover additionalProperties in uploadSkill create/update/content-identity paths, expand parseSkillDirectory to all 7 Claude Code fields, and add context/agent assertions to parseSkillMd. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Display key-value pairs (e.g. "disableModelInvocation: true") instead of bare values when rendering additionalProperties changes in the diff command. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…conflict detector The ConflictDetector type requires 3 parameters but the function was only accepting 2, causing a TypeScript build error. Also narrow cp2 type after the runtime type guard to satisfy TypeScript's type checker. Split multi-expect tests into one-expect-per-test to satisfy the backend-tests-redaction standard. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Render skill additional properties (argument-hint, hooks, model, etc.) in the frontmatter UI with deterministic ordering and kebab-case keys, matching the ClaudeDeployer output format. Complex nested values like hooks render in YAML-like indented format while simple values stay inline. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Prepend the property key (e.g. "user-invocable") to values displayed in the review changes diff for updateSkillAdditionalProperty proposals, so reviewers see "user-invocable: false → true" instead of just "false → true". Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Greptile SummaryThis PR adds full end-to-end support for skill Key design decisions are well-executed:
One notable issue found:
Changes also include:
Confidence Score: 4/5
Important Files Changed
Sequence DiagramsequenceDiagram
participant CLI as CLI (SkillDiffStrategy)
participant API as API (SkillChangeProposalValidator)
participant Applier as SkillChangeProposalApplier
participant DB as Database (JSONB)
CLI->>CLI: parseSkillMd(serverContent)<br/>→ additionalProperties[camelKey] = canonicalJsonStringify(raw)
CLI->>CLI: parseSkillMd(localContent)<br/>→ additionalProperties[camelKey] = canonicalJsonStringify(raw)
CLI->>CLI: diff: oldValue='null'|jsonStr, newValue=jsonStr|''
CLI->>API: submit updateSkillAdditionalProperty<br/>{ targetId, oldValue, newValue }
API->>DB: getLatestSkillVersion(skillId)
DB-->>API: skillVersion.additionalProperties[key] = rawValue
API->>API: currentValue = canonicalJsonStringify(rawValue ?? null)
API->>API: verify: expectedOld === currentValue
API->>Applier: applyChangeProposal(source, proposal)
alt newValue === ''
Applier->>Applier: delete additionalProperties[key]
else
Applier->>Applier: additionalProperties[key] = JSON.parse(newValue)
end
Applier->>DB: save updated SkillVersion
Reviews (21): Last reviewed commit: "Fix formatting issues" | Re-trigger Greptile |
apps/cli/src/application/useCases/diffStrategies/SkillDiffStrategy.ts
Outdated
Show resolved
Hide resolved
packages/coding-agent/src/infra/repositories/claude/ClaudeDeployer.ts
Outdated
Show resolved
Hide resolved
…isArray branch Fix serverValue fallback from ?? '' to ?? 'null' to match validator and DB expectations, preventing ChangeProposalPayloadMismatchError for missing server properties. Remove redundant Array.isArray check in formatEntryValue since typeof value === 'object' already covers arrays. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
buildSkillMdContent was missing additionalProperties when reconstructing the SKILL.md frontmatter, causing the Raw tab and copy-to-clipboard to omit fields like argument-hint, user-invocable, model, etc. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…posals SaveSkillVersionUsecase was not passing additionalProperties to addSkillVersion or updateSkill, causing all additional properties to be silently dropped whenever a change proposal was accepted and applied. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…o fix outdated mismatch The validator was comparing against Skill.additionalProperties while the CLI diffs against the deployed SKILL.md (rendered from SkillVersion). When these were out of sync, all additionalProperties submissions failed with "skill is outdated". Now uses SkillVersion as the source of truth, falling back to Skill only when no version exists. Also includes mismatch details in the CLI error message for easier debugging. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…tch in additional properties JSON.stringify does not guarantee key ordering, causing "oldValue does not match" errors when comparing nested objects (e.g. hooks) serialized from different sources (YAML parsing vs PostgreSQL JSONB vs API response). Replace with canonicalJsonStringify that recursively sorts keys before serialization. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…oposals The skill detail page fetches data by slug, but only the by-ID cache was invalidated after accepting change proposals, causing stale frontmatter display. #206 Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Identifies 6 issues including a high-severity non-canonical JSON comparison bug in uploadSkill.usecase.ts that causes false version bumps. https://claude.ai/code/session_01Vp2EoraDJfuhpR2sv3QWti
Move pure functions (deepSortKeys, canonicalJsonStringify, camelToKebab, sortAdditionalPropertiesKeys) and constants (CLAUDE_CODE_ADDITIONAL_FIELDS, CLAUDE_CODE_ADDITIONAL_FIELDS_ORDER, CAMEL_TO_YAML_KEY) into @packmind/types so both frontend and backend can import from a single source of truth. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Remove local deepSortKeys, canonicalJsonStringify, CLAUDE_CODE_ADDITIONAL_FIELDS, CLAUDE_CODE_ADDITIONAL_FIELDS_ORDER, and sortAdditionalPropertiesKeys. Import and re-export from @packmind/types to maintain backward compatibility. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
When adding a new property, oldValue is 'null' (JSON sentinel). Because 'null' is truthy, the CLI displayed a misleading '- model: null' line. Filter out the 'null' sentinel to show only meaningful diff lines. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…ops logic Replace local ADDITIONAL_FIELDS_ORDER, camelToKebab, and sortAdditionalPropertiesEntries with imports from @packmind/types. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…tants Replace local camelToKebab, CAMEL_TO_YAML_KEY, and ADDITIONAL_FIELDS_ORDER with imports from @packmind/types. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…zation Replace local deepSortKeys, canonicalJsonStringify, and serializeSkillMetadata with canonicalJsonStringify from @packmind/types. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…h error handling Unprotected JSON.parse calls could throw opaque SyntaxErrors on malformed payloads. Introduce ChangeProposalPayloadParseError for clear diagnostics. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…andler Replace inline additional property diff logic with the extracted utility that was created but not yet wired in production code. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
… @packmind/types Extract toYamlLike utility into a shared module with unit tests so it can be reused and is properly covered. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
- Change parsedMetadata from Record<string, unknown> to Record<string, string> to match expected metadata type Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
|
@greptile update your review |
…lFrontmatterInfo Export the existing isDeepValue function from @packmind/types instead of duplicating it in the frontend component. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…ingify for YAML frontmatter JSON.stringify produces quoted JSON notation instead of proper YAML values in the generated SKILL.md frontmatter for additional properties. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…llMdContent Replace inline sorting logic with the shared sortAdditionalPropertiesKeys utility to avoid duplicating the ordering implementation. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…alues Cover the oldValue: 'null' and newValue: 'null' code paths that handle property addition from null and property removal to null. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
|
@greptile update your review |
… targetId to kebab-case in diffs - Replace duplicate camelToKebab implementations with shared utility from @packmind/types - Use CAMEL_TO_YAML_KEY fallback map for correct YAML key resolution - Add null/undefined guard in formatYamlScalar to produce bare YAML null - Convert camelCase targetId to kebab-case in CLI and frontend diff displays Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Simple arrays (all primitives) like argument-hint now render as `argument-hint: ['issue-number']` instead of block style with `- ` entries. Complex arrays containing objects still use block style. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
- Register effort in CLAUDE_CODE_ADDITIONAL_FIELDS and field order - Add tests for parsing, rendering, and mapping of effort property Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
|
I also noticed that Cursor supports the |
…rsor deployers - Extract YAML formatting functions to shared YamlFrontmatterUtils - Add COPILOT_ADDITIONAL_FIELDS (argument-hint, user-invocable, disable-model-invocation) - Add CURSOR_ADDITIONAL_FIELDS (disable-model-invocation) - Add filterAdditionalProperties utility function - Add tests for both deployers and filter utility Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…skillVersion.files support Fix two rendering inconsistencies in SingleFileDeployer (used by GitlabDuoDeployer): - Render allowedTools as kebab-case `allowed-tools` in YAML frontmatter - Check skillVersion.files first (with isBase64, skillFileId, skillFilePermissions), falling back to skillFilesMap, matching Claude/Copilot/Cursor deployer behavior Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
|
… skillAdditionalProperties tests Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…ployer escapeSingleQuotes crashes with "value.replace is not a function" when metadata contains non-string values (e.g. booleans from additionalProperties). Wrap the value with String() before calling replace. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…t case Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
|



Explanation
Add full support for skill
additionalProperties(e.g.,user-invocable,model,context) across the stack — from parsing and storage through CLI diffing, change proposals, and frontend display.Relates to #206
Type of Change
Affected Components
types,skills,playbookTesting
Test Details:
extractProposalDiffValues.spec.tscovering all proposal type branches (scalar, additional property with key prefix, collection update/add/delete, unknown)TODO List
Reviewer Notes
"user-invocable: false"→"user-invocable: true") instead of just"false"→"true"extractProposalDiffValuesfunction has a dedicated branch forupdateSkillAdditionalPropertythat readstargetId(the property key) from the payload🤖 Generated with Claude Code