Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 41 additions & 8 deletions src/helpers/coverage.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,24 @@ function collectValuesByPath(data, path) {
}
}
deepCollect(data)
// If name-based search found nothing (def is used as array items or
// additionalProperties values, not as a named key), fall back to all
// string values in the data. Covers permissionRule, builtinAction, etc.
if (values.length === 0) {
function collectAllStrings(current) {
if (typeof current === 'string') {
values.push(current)
return
}
if (!current || typeof current !== 'object') return
if (Array.isArray(current)) {
for (const item of current) collectAllStrings(item)
return
}
for (const val of Object.values(current)) collectAllStrings(val)
}
collectAllStrings(data)
}
return values
}

Expand Down Expand Up @@ -226,10 +244,25 @@ function walkProperties(schema, currentPath = '') {
if (defs && typeof defs === 'object' && !Array.isArray(defs)) {
for (const [defName, defSchema] of Object.entries(defs)) {
if (defSchema && typeof defSchema === 'object') {
const defPath = `#${defsKey}/${defName}`
// Emit the def itself if it is a leaf schema (enum/pattern/type
// directly on the def, not via child properties). This covers
// reusable string-enum defs like context or builtinAction.
if (
Array.isArray(defSchema.enum) ||
typeof defSchema.pattern === 'string' ||
(defSchema.type && !defSchema.properties)
) {
results.push({
path: defPath,
name: defName,
propSchema: /** @type {Record<string, unknown>} */ (defSchema),
})
}
results.push(
...walkProperties(
/** @type {Record<string, unknown>} */ (defSchema),
`#${defsKey}/${defName}`,
defPath,
),
)
}
Expand Down Expand Up @@ -367,7 +400,7 @@ export function checkDescriptionCoverage(schema) {
status: missing.length === 0 ? 'pass' : 'fail',
totalProperties: nonDefProps.length,
missingCount: missing.length,
missing: missing.slice(0, 20).map((p) => p.path),
missing: missing.map((p) => p.path),
}
}

Expand Down Expand Up @@ -438,7 +471,7 @@ export function checkEnumCoverage(schema, positiveTests, negativeTests) {
issues.push({
path: ePath,
type: 'positive_uncovered',
values: uncovered.slice(0, 10),
values: uncovered,
testedFiles,
})
}
Expand All @@ -457,7 +490,7 @@ export function checkEnumCoverage(schema, positiveTests, negativeTests) {
return {
status: issues.length === 0 ? 'pass' : 'fail',
totalEnums: enums.length,
issues: issues.slice(0, 20),
issues: issues,
}
}

Expand Down Expand Up @@ -536,7 +569,7 @@ export function checkPatternCoverage(schema, positiveTests, negativeTests) {
return {
status: issues.length === 0 ? 'pass' : 'fail',
totalPatterns: patterns.length,
issues: issues.slice(0, 20),
issues: issues,
}
}

Expand Down Expand Up @@ -588,7 +621,7 @@ export function checkRequiredCoverage(schema, negativeTests) {
status: issues.length === 0 ? 'pass' : 'warn',
totalRequiredGroups: requiredGroups.length,
note: 'Heuristic: name-based matching, not path-aware',
uncovered: issues.slice(0, 20),
uncovered: issues,
}
}

Expand Down Expand Up @@ -649,7 +682,7 @@ export function checkDefaultCoverage(schema, positiveTests) {
return {
status: issues.length === 0 ? 'pass' : 'fail',
totalDefaults: defaults.length,
issues: issues.slice(0, 20),
issues: issues,
}
}

Expand Down Expand Up @@ -820,7 +853,7 @@ export function checkNegativeIsolation(schema, negativeTests) {
status: multiViolationFiles.length === 0 ? 'pass' : 'warn',
totalNegativeTests: negativeTests.size,
note: 'Heuristic — all checks match by property name, not JSON path. When the same name (e.g., "source", "type") appears at different schema depths with different constraints, violations may be misattributed. For each flagged file, verify that reported violation types reflect intentional test inputs at the correct nesting level, not collisions between unrelated schema depths',
multiViolationFiles: multiViolationFiles.slice(0, 20),
multiViolationFiles: multiViolationFiles,
}
}

Expand Down
16 changes: 16 additions & 0 deletions src/negative_test/claude-code-keybindings/duplicate-context.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"bindings": [
{
"bindings": {
"ctrl+c": "app:interrupt"
},
"context": "Global"
},
{
"bindings": {
"ctrl+d": "app:exit"
},
"context": "Global"
}
]
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
{
"autoUpdatesChannel": "beta",
"defaultShell": "zsh",
"disableAutoMode": "enabled",
"disableDeepLinkRegistration": "enabled",
"editorMode": "emacs",
"effortLevel": "extreme",
"env": {
"ANTHROPIC_BEDROCK_SERVICE_TIER": "turbo",
Expand All @@ -12,11 +14,13 @@
"CLAUDE_BASH_MAINTAIN_PROJECT_WORKING_DIR": "yes",
"CLAUDE_CODE_ACCESSIBILITY": "enable",
"CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD": "on",
"CLAUDE_CODE_ALT_SCREEN_FULL_REPAINT": "yes",
"CLAUDE_CODE_ATTRIBUTION_HEADER": "yes",
"CLAUDE_CODE_AUTO_CONNECT_IDE": "1",
"CLAUDE_CODE_DEBUG_LOG_LEVEL": "trace",
"CLAUDE_CODE_DISABLE_1M_CONTEXT": "yes",
"CLAUDE_CODE_DISABLE_ADAPTIVE_THINKING": "true",
"CLAUDE_CODE_DISABLE_AGENT_VIEW": "true",
"CLAUDE_CODE_DISABLE_ALTERNATE_SCREEN": "yes",
"CLAUDE_CODE_DISABLE_ATTACHMENTS": "off",
"CLAUDE_CODE_DISABLE_AUTO_MEMORY": "yes",
Expand Down Expand Up @@ -55,6 +59,7 @@
"CLAUDE_CODE_IDE_SKIP_AUTO_INSTALL": "yes",
"CLAUDE_CODE_IDE_SKIP_VALID_CHECK": "true",
"CLAUDE_CODE_MCP_ALLOWLIST_ENV": "all",
"CLAUDE_CODE_NATIVE_CURSOR": "enable",
"CLAUDE_CODE_NEW_INIT": "start",
"CLAUDE_CODE_NO_FLICKER": "maybe",
"CLAUDE_CODE_OPUS_4_6_FAST_MODE_OVERRIDE": "yes",
Expand Down Expand Up @@ -94,6 +99,7 @@
"disableAutoMode": "enabled",
"disableBypassPermissionsMode": "enabled"
},
"preferredNotifChannel": "slack",
"skillOverrides": {
"test-skill": "invalid-mode"
},
Expand All @@ -104,6 +110,9 @@
"teammateMode": "split",
"tui": "mini",
"viewMode": "list",
"voice": {
"mode": "continuous"
},
"worktree": {
"baseRef": "invalid-ref",
"bgIsolation": "isolated"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"allowedChannelPlugins": ["internal-notifier@corp"],
"cleanupPeriodDays": "thirty",
"enableAllProjectMcpServers": 1,
"fastMode": "yes",
Expand Down
4 changes: 4 additions & 0 deletions src/schema-validation.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,10 @@
{
"schema": "claude-code-settings.json",
"strict": true
},
{
"schema": "claude-code-keybindings.json",
"strict": true
}
],
"catalogEntryNoLintNameOrDescription": [
Expand Down
57 changes: 51 additions & 6 deletions src/schemas/json/claude-code-keybindings.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@
"DiffDialog",
"ModelPicker",
"Select",
"Plugin"
"Plugin",
"Scroll",
"Doctor"
]
},
"keystrokePattern": {
Expand Down Expand Up @@ -72,21 +74,29 @@
"enum": [
"app:interrupt",
"app:exit",
"app:redraw",
"app:toggleBrief",
"app:toggleTodos",
"app:toggleTranscript",
"app:toggleTeammatePreview",
"history:search",
"history:previous",
"history:next",
"chat:cancel",
"chat:clearInput",
"chat:clearScreen",
"chat:cycleMode",
"chat:fastMode",
"chat:imagePaste",
"chat:killAgents",
"chat:modelPicker",
"chat:thinkingToggle",
"chat:newline",
"chat:stash",
"chat:submit",
"chat:thinkingToggle",
"chat:undo",
"chat:externalEditor",
"chat:stash",
"chat:imagePaste",
"voice:pushToTalk",
"autocomplete:accept",
"autocomplete:dismiss",
"autocomplete:previous",
Expand All @@ -98,6 +108,7 @@
"confirm:nextField",
"confirm:previousField",
"confirm:cycleMode",
"confirm:toggle",
"confirm:toggleExplanation",
"tabs:next",
"tabs:previous",
Expand All @@ -106,16 +117,20 @@
"historySearch:next",
"historySearch:accept",
"historySearch:cancel",
"historySearch:cycleScope",
"historySearch:execute",
"task:background",
"theme:toggleSyntaxHighlighting",
"theme:editCustom",
"help:dismiss",
"attachments:next",
"attachments:previous",
"attachments:remove",
"attachments:exit",
"footer:next",
"footer:previous",
"footer:up",
"footer:down",
"footer:openSelected",
"footer:clearSelection",
"messageSelector:up",
Expand All @@ -132,15 +147,44 @@
"diff:nextFile",
"modelPicker:decreaseEffort",
"modelPicker:increaseEffort",
"modelPicker:setAsDefault",
"select:next",
"select:previous",
"select:pageUp",
"select:pageDown",
"select:first",
"select:last",
"select:accept",
"select:cancel",
"plugin:toggle",
"plugin:install",
"plugin:favorite",
"permission:toggleDebug",
"settings:close",
"settings:periodDay",
"settings:periodWeek",
"settings:retry",
"settings:search",
"settings:retry"
"settings:sortByTokens",
"doctor:fix",
"scroll:lineUp",
"scroll:lineDown",
"scroll:pageUp",
"scroll:pageDown",
"scroll:top",
"scroll:bottom",
"scroll:halfPageUp",
"scroll:halfPageDown",
"scroll:fullPageUp",
"scroll:fullPageDown",
"selection:copy",
"selection:clear",
"selection:extendLeft",
"selection:extendRight",
"selection:extendUp",
"selection:extendDown",
"selection:extendLineStart",
"selection:extendLineEnd"
]
},
"commandBinding": {
Expand All @@ -166,7 +210,8 @@
"type": "array",
"items": {
"$ref": "#/$defs/keybindingBlock"
}
},
"uniqueItemProperties": ["context"]
}
},
"required": ["bindings"],
Expand Down
Loading
Loading