( commands: Command[], contextWindowTokens?: number, )
| 69 | const MIN_DESC_LENGTH = 20 |
| 70 | |
| 71 | export function formatCommandsWithinBudget( |
| 72 | commands: Command[], |
| 73 | contextWindowTokens?: number, |
| 74 | ): string { |
| 75 | if (commands.length === 0) return '' |
| 76 | |
| 77 | const budget = getCharBudget(contextWindowTokens) |
| 78 | |
| 79 | // Try full descriptions first |
| 80 | const fullEntries = commands.map(cmd => ({ |
| 81 | cmd, |
| 82 | full: formatCommandDescription(cmd), |
| 83 | })) |
| 84 | // join('\n') produces N-1 newlines for N entries |
| 85 | const fullTotal = |
| 86 | fullEntries.reduce((sum, e) => sum + stringWidth(e.full), 0) + |
| 87 | (fullEntries.length - 1) |
| 88 | |
| 89 | if (fullTotal <= budget) { |
| 90 | return fullEntries.map(e => e.full).join('\n') |
| 91 | } |
| 92 | |
| 93 | // Partition into bundled (never truncated) and rest |
| 94 | const bundledIndices = new Set<number>() |
| 95 | const restCommands: Command[] = [] |
| 96 | for (let i = 0; i < commands.length; i++) { |
| 97 | const cmd = commands[i]! |
| 98 | if (cmd.type === 'prompt' && cmd.source === 'bundled') { |
| 99 | bundledIndices.add(i) |
| 100 | } else { |
| 101 | restCommands.push(cmd) |
| 102 | } |
| 103 | } |
| 104 | |
| 105 | // Compute space used by bundled skills (full descriptions, always preserved) |
| 106 | const bundledChars = fullEntries.reduce( |
| 107 | (sum, e, i) => |
| 108 | bundledIndices.has(i) ? sum + stringWidth(e.full) + 1 : sum, |
| 109 | 0, |
| 110 | ) |
| 111 | const remainingBudget = budget - bundledChars |
| 112 | |
| 113 | // Calculate max description length for non-bundled commands |
| 114 | if (restCommands.length === 0) { |
| 115 | return fullEntries.map(e => e.full).join('\n') |
| 116 | } |
| 117 | |
| 118 | const restNameOverhead = |
| 119 | restCommands.reduce((sum, cmd) => sum + stringWidth(cmd.name) + 4, 0) + |
| 120 | (restCommands.length - 1) |
| 121 | const availableForDescs = remainingBudget - restNameOverhead |
| 122 | const maxDescLen = Math.floor(availableForDescs / restCommands.length) |
| 123 | |
| 124 | if (maxDescLen < MIN_DESC_LENGTH) { |
| 125 | // Extreme case: non-bundled go names-only, bundled keep descriptions |
| 126 | if (isInternalBuild()) { |
| 127 | logEvent('ncode_skill_descriptions_truncated', { |
| 128 | skill_count: commands.length, |
no test coverage detected