Skip to content

feat: introduce skills management feature in supercode-cli:#226

Merged
yashdev9274 merged 1 commit into
mainfrom
supercode-cli
Jul 23, 2026
Merged

feat: introduce skills management feature in supercode-cli:#226
yashdev9274 merged 1 commit into
mainfrom
supercode-cli

Conversation

@yashdev9274

@yashdev9274 yashdev9274 commented Jul 23, 2026

Copy link
Copy Markdown
Owner

Description

  • Added commands for managing agent skills: add, remove, and list.
  • Implemented skill loading functionality to enhance AI interactions.
  • Updated context and documentation to include new skill management capabilities.
  • Bumped supercode-cli version to 0.1.84 to reflect these changes.

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Refactor (no functional changes)

How Has This Been Tested?

Please describe the tests that you ran to verify your changes.

  • bun test passes
  • bun run typecheck passes
  • bun run lint passes (if applicable)

Checklist:

  • My code follows the project's style guidelines
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works

Summary by CodeRabbit

  • New Features

    • Added skill management through the CLI, including installing, listing, viewing, loading, and removing skills.
    • Added /skills and /sk commands with an interactive skill picker and filtering.
    • AI conversations can now use loaded skills as context.
    • Added automatic discovery of installed and local skills.
    • Added skill management capabilities for the AI assistant.
  • Chores

    • Updated the CLI version to 0.1.84.

- Added commands for managing agent skills: add, remove, and list.
- Implemented skill loading functionality to enhance AI interactions.
- Updated context and documentation to include new skill management capabilities.
- Bumped supercode-cli version to 0.1.84 to reflect these changes.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@vercel

vercel Bot commented Jul 23, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
supercli Ready Ready Preview, Comment Jul 23, 2026 1:20pm
supercli-client Ready Ready Preview, Comment Jul 23, 2026 1:20pm
supercli-docs Ready Ready Preview, Comment Jul 23, 2026 1:20pm

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

This PR adds a reusable @super/skills package, skill installation and lock-file management, Commander and slash commands, an AI skill tool, available-skill prompt context, and chat-loop handling for loading and applying skill content.

Changes

Agent Skills

Layer / File(s) Summary
Skill storage and installation foundation
packages/skills/*
Defines skill metadata, persists skills-lock.json, installs and removes GitHub-hosted SKILL.md files, lists installed skills, and restores missing files.
CLI skill management and discovery
apps/supercode-cli/server/src/cli/commands/skill/*, apps/supercode-cli/server/src/cli/commands/slashCommands/skill.ts, apps/supercode-cli/server/src/cli/main.ts, apps/supercode-cli/server/package.json
Adds skill add, list, and remove commands plus /skills discovery, interactive selection, loading, installation, removal, and inspection.
AI skill tool and prompt exposure
apps/supercode-cli/server/src/tools/definitions/skill.ts, apps/supercode-cli/server/src/tools/registry.ts, apps/supercode-cli/server/src/cli/workspace/context.ts
Adds validated install, remove, list, info, and load actions, registers the tool, and appends installed-skill descriptions to the system prompt.
Slash loading and chat context flow
apps/supercode-cli/server/src/cli/commands/slashCommands/index.ts, apps/supercode-cli/server/src/cli/ai/chat/chat.ts
Adds skill-related slash results and combines loaded skill content with the next user message or immediately triggers an AI response.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant handleSlashCommand
  participant skillCommand
  participant chatLoop
  participant streamAIResponse
  User->>handleSlashCommand: enter a skill command
  handleSlashCommand->>skillCommand: load the selected skill
  skillCommand-->>handleSlashCommand: return skill content
  handleSlashCommand-->>chatLoop: return skill message result
  chatLoop->>streamAIResponse: send skill content with user message
Loading

Possibly related PRs

Poem

A bunny found skills in a lock-file nest,
Installed each SKILL.md snugly and best.
Slash commands hopped, the picker grew bright,
Chat carried skill wisdom into the night.
“Load, list, remove!” the rabbit sings—
AI now knows what each skill brings!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding skills management to supercode-cli.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch supercode-cli

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 Checkov (3.3.8)
apps/supercode-cli/server/package.json

Traceback (most recent call last):
File "/usr/local/bin/checkov", line 2, in
from checkov.main import Checkov
ModuleNotFoundError: No module named 'checkov'

packages/skills/package.json

Traceback (most recent call last):
File "/usr/local/bin/checkov", line 2, in
from checkov.main import Checkov
ModuleNotFoundError: No module named 'checkov'

packages/skills/tsconfig.json

Traceback (most recent call last):
File "/usr/local/bin/checkov", line 2, in
from checkov.main import Checkov
ModuleNotFoundError: No module named 'checkov'

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@yashdev9274
yashdev9274 merged commit c758528 into main Jul 23, 2026
4 of 8 checks passed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/supercode-cli/server/package.json`:
- Line 75: Move the `@super/skills` entry from devDependencies to dependencies in
the package manifest, keeping the existing workspace version unchanged so clean
published CLI installs include it for the skill add/remove/list commands.

In `@apps/supercode-cli/server/src/cli/ai/chat/chat.ts`:
- Around line 2113-2125: Clear loadedSkillContent after persisting the combined
skill message in the result.skillContent branch, matching the parallel path
before streamAIResponse runs. Keep loadedSkillName and the persisted combined
user message unchanged.

In `@apps/supercode-cli/server/src/cli/workspace/context.ts`:
- Line 182: Guard the pushSkillsSection call with hasTools so skills are
advertised only when the tool is available; leave the existing behavior
unchanged when hasTools is true.
- Line 249: Update the unavailable-skill description in the workspace context
flow to remove the `supercode skill sync` instruction and instead direct users
to reinstall using the displayed source. Preserve the existing
unavailable-on-disk context and update only the recovery guidance.

In `@apps/supercode-cli/server/src/tools/definitions/skill.ts`:
- Around line 2-3: Replace the skill context read in skill.ts with
Bun.file(...).text(). In apps/supercode-cli/server/src/cli/workspace/context.ts
lines 1-3, make pushSkillsSection asynchronous and use Bun.file(...).text(),
make buildSystemPrompt asynchronous, and await buildSystemPrompt at every
streamAIResponse caller.

In `@apps/supercode-cli/server/src/tools/registry.ts`:
- Line 97: Update the `skill` entry in the tool registry to require permission,
ensuring `install` and `remove` cannot mutate the user home directory without
approval; alternatively, split read-only skill actions from the mutating actions
and mark only the latter as permissioned.

In `@packages/skills/package.json`:
- Around line 10-14: Replace the placeholder scripts in the package.json scripts
block for build, lint, and typecheck with the repository’s actual TypeScript
type-checking and build/lint commands, ensuring Turbo validation performs real
checks for `@super/skills`; if the package is not ready for those commands, remove
the scaffolding scripts instead.

In `@packages/skills/src/lock-file.ts`:
- Around line 25-52: Update readSkillsLock to default to an empty lock only for
ENOENT, while propagating other read errors and rejecting malformed JSON or
invalid lock data. Make addSkillToLock and removeSkillFromLock concurrency-safe
by serializing mutations and rereading the latest lock within the serialized
operation, then update writeSkillsLock to atomically replace the lock file via a
temporary file and rename rather than writing directly.
- Around line 13-18: Reject path traversal through a shared strict skill-name
validator in packages/skills/src/lock-file.ts, and invoke it before getSkillDir
constructs any filesystem path so installer, removal, sync, and list flows are
protected. In apps/supercode-cli/server/src/tools/definitions/skill.ts lines
70-81, use the validated shared path helper before loading skill content. In
apps/supercode-cli/server/src/cli/workspace/context.ts lines 241-245, validate
lock-file entry names before reading their SKILL.md paths.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 77e150e3-9212-4f0e-b11a-bb8578422b57

📥 Commits

Reviewing files that changed from the base of the PR and between 01ce796 and db9675e.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (18)
  • apps/supercode-cli/server/package.json
  • apps/supercode-cli/server/src/cli/ai/chat/chat.ts
  • apps/supercode-cli/server/src/cli/commands/skill/add.ts
  • apps/supercode-cli/server/src/cli/commands/skill/index.ts
  • apps/supercode-cli/server/src/cli/commands/skill/list.ts
  • apps/supercode-cli/server/src/cli/commands/skill/remove.ts
  • apps/supercode-cli/server/src/cli/commands/slashCommands/index.ts
  • apps/supercode-cli/server/src/cli/commands/slashCommands/skill.ts
  • apps/supercode-cli/server/src/cli/main.ts
  • apps/supercode-cli/server/src/cli/workspace/context.ts
  • apps/supercode-cli/server/src/tools/definitions/skill.ts
  • apps/supercode-cli/server/src/tools/registry.ts
  • packages/skills/package.json
  • packages/skills/src/index.ts
  • packages/skills/src/installer.ts
  • packages/skills/src/lock-file.ts
  • packages/skills/src/types.ts
  • packages/skills/tsconfig.json

"@openrouter/ai-sdk-provider": "^2.9.0",
"@openrouter/sdk": "^0.12.79",
"@super/db-terminal": "workspace:*",
"@super/skills": "workspace:*",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check where `@super/skills` is declared and how the CLI is packaged/bundled.
fd -t f 'package.json' apps/supercode-cli/server --max-depth 2 --exec cat -n {}
echo "----- build/publish scripts -----"
rg -nC2 '"(build|prepublishOnly|prepack|bin)"|bun build|--compile' apps/supercode-cli/server/package.json

Repository: yashdev9274/supercli

Length of output: 6664


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "----- repo files of interest -----"
git ls-files | rg '(^|/)(apps/supercode-cli/server/package.json|apps/supercode-cli/server/src/.*skills.*|apps/supercode-cli/server/src/cli/main.ts)' || true

echo
echo "----- imports/usages of `@super/skills` -----"
rg -n "(`@super/skills`|installSkill|uninstallSkill|listInstalledSkills)" apps/supercode-cli/server/src apps/supercode-cli/server/package.json || true

echo
echo "----- bun availability/version -----"
if command -v bun >/dev/null 2>&1; then
  bun --version
else
  echo "bun not found"
fi

echo
echo "----- package workspace/config files -----"
rg -n "packages|\$workspace|`@super/skills`|`@super/db-terminal`|bun" package.json bun.lockb bun.lock apps/supercode-cli apps || true

echo
echo "----- deterministic package.json role check -----"
python3 - <<'PY'
import json, pathlib
p = pathlib.Path('apps/supercode-cli/server/package.json')
data=json.loads(p.read_text())
bin=data.get('bin')
prepublish=data.get('scripts',{}).get('prepublishOnly')
build=data.get('scripts',{}).get('build')
files=data.get('files')
deps=data.get('dependencies',{})
dev=data.get('devDependencies',{})
print('bin_entrypoint_is_dist_main_js=',bin.get('supercode')==data.get('main')=='dist/main.js')
print('prepublish_builds=',prepublish is not None and 'build' in ' '.join(prepublish))
print('publish_includes_dist=', 'dist/main.js' in files)
print('external_dependencies',list(deps.keys()))
print('/@super/skills in dependencies:', '/@super/skills' in '/'.join(deps.keys()))
print('/@super/skills in devDependencies:', '/@super/skills' in '/'.join(dev.keys()))
PY

Repository: yashdev9274/supercli

Length of output: 38539


Move @super/skills into dependencies.

The published CLI binary points to dist/main.js, and prepublishOnly runs bun run build:cli instead of bun run build:cli --prod, so the registry bundle is produced with devDependencies available. A clean published install running the CLI would not install devDependencies, leaving @super/skills imports unresolved for the skill add/remove/list commands.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/supercode-cli/server/package.json` at line 75, Move the `@super/skills`
entry from devDependencies to dependencies in the package manifest, keeping the
existing workspace version unchanged so clean published CLI installs include it
for the skill add/remove/list commands.

Comment on lines +2113 to +2125
if (result.skillContent) {
loadedSkillName = result.skillName || ""
loadedSkillContent = result.skillContent
const combinedMsg = `${result.skillContent}\n\n${result.message}`
userMessage(`[${result.skillName}]\n\n${result.skillContent}\n\n${result.message}`)
messageCount++
await addMessage(conversation.id, "user", combinedMsg)
} else {
userMessage(trimmed)
messageCount++
await addMessage(conversation.id, "user", result.message)
await trySetAutoTitle(conversation.id, result.message, messageCount)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Skill content is injected twice in the picker→type flow.

Here combinedMsg already embeds the full skill content in the persisted user message, but loadedSkillContent is left set, so the subsequent streamAIResponse (Line 2127) re-injects the same content into the system prompt via the ## Loaded Skill block at Lines 323-325. The whole SKILL.md ends up duplicated in one request (wasted context tokens, and inconsistent with the AI seeing it as both user turn and system instruction).

The parallel path at Lines 2154-2155 already clears the state before streaming; do the same here.

🐛 Clear skill state after persisting the combined message
       await addMessage(conversation.id, "user", combinedMsg)
+      // Content is now persisted in the user message; clear module state so
+      // streamAIResponse doesn't also inject it into the system prompt.
+      loadedSkillName = undefined
+      loadedSkillContent = undefined
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (result.skillContent) {
loadedSkillName = result.skillName || ""
loadedSkillContent = result.skillContent
const combinedMsg = `${result.skillContent}\n\n${result.message}`
userMessage(`[${result.skillName}]\n\n${result.skillContent}\n\n${result.message}`)
messageCount++
await addMessage(conversation.id, "user", combinedMsg)
} else {
userMessage(trimmed)
messageCount++
await addMessage(conversation.id, "user", result.message)
await trySetAutoTitle(conversation.id, result.message, messageCount)
}
if (result.skillContent) {
loadedSkillName = result.skillName || ""
loadedSkillContent = result.skillContent
const combinedMsg = `${result.skillContent}\n\n${result.message}`
userMessage(`[${result.skillName}]\n\n${result.skillContent}\n\n${result.message}`)
messageCount++
await addMessage(conversation.id, "user", combinedMsg)
// Content is now persisted in the user message; clear module state so
// streamAIResponse doesn't also inject it into the system prompt.
loadedSkillName = undefined
loadedSkillContent = undefined
} else {
userMessage(trimmed)
messageCount++
await addMessage(conversation.id, "user", result.message)
await trySetAutoTitle(conversation.id, result.message, messageCount)
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/supercode-cli/server/src/cli/ai/chat/chat.ts` around lines 2113 - 2125,
Clear loadedSkillContent after persisting the combined skill message in the
result.skillContent branch, matching the parallel path before streamAIResponse
runs. Keep loadedSkillName and the persisted combined user message unchanged.

lines.push("- When the user wraps a URL in single or double quotes, treat it as a")
lines.push(" string literal — strip the quotes before calling `url_fetch`.")
lines.push("")
pushSkillsSection(lines)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Only advertise skills when the tool is available.

pushSkillsSection runs even when hasTools is false, so those prompts can instruct the model to call an unavailable skill tool. Guard this call with hasTools.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/supercode-cli/server/src/cli/workspace/context.ts` at line 182, Guard
the pushSkillsSection call with hasTools so skills are advertised only when the
tool is available; leave the existing behavior unchanged when hasTools is true.

const match = content.match(/description:\s*(.+)/)
description = match ? match[1]!.trim() : ""
} catch {
description = "(not on disk — run `supercode skill sync`)"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Remove the unavailable skill sync recovery instruction.

The supplied CLI command set contains add/remove/list, and the tool schema has no sync action. This recovery path tells users to run a command they cannot access; instruct them to reinstall using the displayed source instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/supercode-cli/server/src/cli/workspace/context.ts` at line 249, Update
the unavailable-skill description in the workspace context flow to remove the
`supercode skill sync` instruction and instead direct users to reinstall using
the displayed source. Preserve the existing unavailable-on-disk context and
update only the recovery guidance.

Comment on lines +2 to +3
import fs from "fs/promises"
import path from "path"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== candidate files =="
git ls-files 'apps/supercode-cli/server/src/tools/definitions/skill.ts' 'apps/supercode-cli/server/src/cli/workspace/context.ts'

echo "== skill.ts outline =="
ast-grep outline apps/supercode-cli/server/src/tools/definitions/skill.ts || true

echo "== skill.ts relevant contents =="
cat -n apps/supercode-cli/server/src/tools/definitions/skill.ts | sed -n '1,220p'

echo "== context.ts outline =="
ast-grep outline apps/supercode-cli/server/src/cli/workspace/context.ts || true

echo "== context.ts relevant contents =="
cat -n apps/supercode-cli/server/src/cli/workspace/context.ts | sed -n '1,280p'

Repository: yashdev9274/supercli

Length of output: 22290


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== streamAIResponse usages =="
rg -n "buildSystemPrompt|streamAIResponse" apps/supercode-cli/server/src -g '*.ts' -g '*.tsx'

echo "== all fs node imports in server src =="
rg -n 'import .*from ["'\'']fs(/promises)?["'\'']|readFileSync|readFile\(' apps/supercode-cli/server/src -g '*.ts' -g '*.tsx' | sed -n '1,220p'

echo "== context.ts surrounding exports/users =="
cat -n apps/supercode-cli/server/src/cli/workspace/context.ts | sed -n '188,280p'

Repository: yashdev9274/supercli

Length of output: 10908


Use Bun file APIs for skill context reads.

In apps/supercode-cli/server/src/tools/definitions/skill.ts, replace the fs.readFile call with Bun.file(...).text(). In apps/supercode-cli/server/src/cli/workspace/context.ts, switch pushSkillsSection to async/Bun.file(...).text() and make buildSystemPrompt async, then update the streamAIResponse callers to await the prompt builder.

📍 Affects 2 files
  • apps/supercode-cli/server/src/tools/definitions/skill.ts#L2-L3 (this comment)
  • apps/supercode-cli/server/src/cli/workspace/context.ts#L1-L3
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/supercode-cli/server/src/tools/definitions/skill.ts` around lines 2 - 3,
Replace the skill context read in skill.ts with Bun.file(...).text(). In
apps/supercode-cli/server/src/cli/workspace/context.ts lines 1-3, make
pushSkillsSection asynchronous and use Bun.file(...).text(), make
buildSystemPrompt asynchronous, and await buildSystemPrompt at every
streamAIResponse caller.

Source: Coding guidelines

task: { category: "agent", requiresPermission: false, description: taskTool.description },
question: { category: "agent", requiresPermission: false, description: questionTool.description },
todowrite: { category: "agent", requiresPermission: false, description: todowriteTool.description },
skill: { category: "agent", requiresPermission: false, description: skillTool.description },

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Require approval for mutating skill actions.

skill is marked requiresPermission: false, but install downloads remote content and writes to the user home directory while remove recursively deletes from it. Mark the tool as permissioned, or split read-only actions from install/remove so autonomous chat flows cannot mutate local state without approval.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/supercode-cli/server/src/tools/registry.ts` at line 97, Update the
`skill` entry in the tool registry to require permission, ensuring `install` and
`remove` cannot mutate the user home directory without approval; alternatively,
split read-only skill actions from the mutating actions and mark only the latter
as permissioned.

Comment on lines +10 to +14
"scripts": {
"build": "echo 'skills build pending'",
"lint": "echo 'skills lint pending'",
"typecheck": "echo 'skills typecheck pending'"
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== git status short =="
git status --short || true

echo "== root package.json =="
cat -n packages/skills/package.json

echo "== workspace manifests =="
for f in package.json packages/*/package.json; do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    jq -c '{name, scripts, dependencies, devDependencies}' "$f"
  fi
done

echo "== TypeScript/lint config candidates =="
git ls-files | rg '(^|/)(package.json|tsconfig.*\.json|eslint\.|biome\.|prettier|vitest|jest|pnpm-lock\.yaml|package-lock\.json|yarn\.lock)$' || true

echo "== script keywords refs =="
rg -n '"(build|lint|typecheck|check|dev|test|ci|check-ts|tsc|typescript|eslint|biome)"|tsc|typescript|eslint|biome|vitest|jest' --glob '!**/node_modules/**' --glob '!**/dist/**' --glob '!**/build/**' .

echo "== workflow files =="
git ls-files | rg '(^|/)\.github/workflows/.*\.ya?ml$|(^|/)CI|(^|/)Makefile|(^|/)justfile' || true
for f in $(git ls-files | rg '(^|/)\.github/workflows/.*\.ya?ml$|(^|/)CI|(^|/)Makefile|(^|/)justfile' || true); do
  echo "--- $f ---"
  sed -n '1,220p' "$f"
done

Repository: yashdev9274/supercli

Length of output: 50378


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== git status short =="
git status --short || true

echo "== root package.json =="
cat -n package.json

echo "== skills package =="
cat -n packages/skills/package.json

echo "== package entry files =="
git ls-files packages/skills | sed -n '1,160p'

echo "== package declarations across workspace =="
python3 - <<'PY'
import json, pathlib
for path in sorted(p for p in pathlib.Path('.').rglob('package.json') if 'node_modules' not in p.parts):
    try:
        data=json.loads(path.read_text())
    except Exception as e:
        print(f"--- {path}: parse error {e} ---")
        continue
    fields={}
    for k in ("name","type","version","main","types","exports","scripts"):
        if k in data: fields[k]=data[k]
    if fields:
        print("---", path, "---")
        for k,v in fields.items():
            print(f"{k}: {v!r}")
PY

echo "== script command invocations =="
python3 - <<'PY'
import json, pathlib, re
terms = ["build","lint","typecheck","check","check-ts","test","ci","tsc","typescript","eslint","biome","prettier","vitest","jest"]
for path in sorted(p for p in pathlib.Path('.').rglob('*') if p.is_file() and 'node_modules' not in p.parts and '.git' not in p.parts and 'dist' not in p.parts and 'build' not in p.parts):
    try:
        text=path.read_text(errors='ignore')
    except Exception:
        continue
    if not any(b in text for b in terms):
        continue
    for i,line in enumerate(text.splitlines(),1):
        if any(b in line for b in terms):
            print(f"{path}:{i}:{line}")
PY

Repository: yashdev9274/supercli

Length of output: 50377


Wire the package scripts to real validation commands.

root routes build, lint, and typecheck through Turbo, so this package should define non-placeholder scripts; otherwise @super/skills passes those gates without building, linting, or type-checking. Use the repo’s TypeScript tooling for typecheck and a build pipeline for build/lint, or remove these scripts if they are still scaffolding.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/skills/package.json` around lines 10 - 14, Replace the placeholder
scripts in the package.json scripts block for build, lint, and typecheck with
the repository’s actual TypeScript type-checking and build/lint commands,
ensuring Turbo validation performs real checks for `@super/skills`; if the package
is not ready for those commands, remove the scaffolding scripts instead.

Comment on lines +13 to +18
export function getSkillDir(name: string): string {
return path.join(getSkillsDir(), name)
}

export function getSkillFilePath(name: string): string {
return path.join(getSkillDir(name), "SKILL.md")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win

Reject path traversal in skill names. A caller-controlled name reaches filesystem path construction; values such as ../... can escape the skills directory. This enables arbitrary local reads through load, writes through install, and recursive deletion through uninstall.

  • packages/skills/src/lock-file.ts#L13-L18: enforce one strict skill-name validator before constructing skill paths; this protects installer, removal, sync, and list flows.
  • apps/supercode-cli/server/src/tools/definitions/skill.ts#L70-L81: use the validated shared path helper before loading content.
  • apps/supercode-cli/server/src/cli/workspace/context.ts#L241-L245: validate lock-file entry names before reading their SKILL.md paths.
Suggested validation
+export function assertSkillName(name: string): void {
+  if (!/^[a-z0-9][a-z0-9-]*$/i.test(name)) {
+    throw new Error("Invalid skill name")
+  }
+}
+
 export function getSkillDir(name: string): string {
+  assertSkillName(name)
   return path.join(getSkillsDir(), name)
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function getSkillDir(name: string): string {
return path.join(getSkillsDir(), name)
}
export function getSkillFilePath(name: string): string {
return path.join(getSkillDir(name), "SKILL.md")
export function assertSkillName(name: string): void {
if (!/^[a-z0-9][a-z0-9-]*$/i.test(name)) {
throw new Error("Invalid skill name")
}
}
export function getSkillDir(name: string): string {
assertSkillName(name)
return path.join(getSkillsDir(), name)
}
export function getSkillFilePath(name: string): string {
return path.join(getSkillDir(name), "SKILL.md")
}
📍 Affects 3 files
  • packages/skills/src/lock-file.ts#L13-L18 (this comment)
  • apps/supercode-cli/server/src/tools/definitions/skill.ts#L70-L81
  • apps/supercode-cli/server/src/cli/workspace/context.ts#L241-L245
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/skills/src/lock-file.ts` around lines 13 - 18, Reject path traversal
through a shared strict skill-name validator in
packages/skills/src/lock-file.ts, and invoke it before getSkillDir constructs
any filesystem path so installer, removal, sync, and list flows are protected.
In apps/supercode-cli/server/src/tools/definitions/skill.ts lines 70-81, use the
validated shared path helper before loading skill content. In
apps/supercode-cli/server/src/cli/workspace/context.ts lines 241-245, validate
lock-file entry names before reading their SKILL.md paths.

Source: Linters/SAST tools

Comment on lines +25 to +52
export async function readSkillsLock(): Promise<SkillsLock> {
try {
const data = await fs.readFile(LOCK_FILE, "utf-8")
return JSON.parse(data) as SkillsLock
} catch {
return { version: 1, skills: {} }
}
}

export async function writeSkillsLock(lock: SkillsLock): Promise<void> {
await fs.mkdir(SUPERCODE_DIR, { recursive: true })
await fs.writeFile(LOCK_FILE, JSON.stringify(lock, null, 2), "utf-8")
}

export async function addSkillToLock(
name: string,
definition: SkillDefinition,
): Promise<SkillsLock> {
const lock = await readSkillsLock()
lock.skills[name] = definition
await writeSkillsLock(lock)
return lock
}

export async function removeSkillFromLock(name: string): Promise<SkillsLock> {
const lock = await readSkillsLock()
delete lock.skills[name]
await writeSkillsLock(lock)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Make lock mutations failure- and concurrency-safe.

Invalid JSON or any read error becomes an empty lock, so a following add/remove can overwrite existing metadata. Concurrent clients also overwrite each other’s snapshots, and direct writes can leave a truncated lock file. Default only on ENOENT, reject malformed locks, serialize mutations, and atomically replace the file.

🧰 Tools
🪛 ast-grep (0.44.1)

[warning] 26-26: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.readFile(LOCK_FILE, "utf-8")
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename-typescript)


[warning] 35-35: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.writeFile(LOCK_FILE, JSON.stringify(lock, null, 2), "utf-8")
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename-typescript)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/skills/src/lock-file.ts` around lines 25 - 52, Update readSkillsLock
to default to an empty lock only for ENOENT, while propagating other read errors
and rejecting malformed JSON or invalid lock data. Make addSkillToLock and
removeSkillFromLock concurrency-safe by serializing mutations and rereading the
latest lock within the serialized operation, then update writeSkillsLock to
atomically replace the lock file via a temporary file and rename rather than
writing directly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant