Skip to content
Merged
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import chalk from "chalk"
import { execSync } from "node:child_process"
import { theme, frame } from "src/cli/utils/tui.ts"
import { getCrispModeSync } from "src/cli/crisp/index.ts"
import type { SlashCommandResult } from "./index.ts"

export async function crispAuditCommand(argsStr: string): Promise<SlashCommandResult> {
const mode = getCrispModeSync()
const focus = argsStr.trim() || "all"
const modeLabel = mode === "off" ? "full" : mode

let fileList = ""
try {
fileList = execSync(`fd -e ts -e tsx -e js -e jsx --exclude node_modules --exclude dist --exclude .git --max-depth 6 . ${process.cwd()}`, { encoding: "utf-8" })
Comment on lines +2 to +14

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate file =="
git ls-files | grep -F 'apps/supercode-cli/server/src/cli/commands/slashCommands/crisp-audit.ts' || true

echo "== file excerpt =="
if [ -f apps/supercode-cli/server/src/cli/commands/slashCommands/crisp-audit.ts ]; then
  wc -l apps/supercode-cli/server/src/cli/commands/slashCommands/crisp-audit.ts
  cat -n apps/supercode-cli/server/src/cli/commands/slashCommands/crisp-audit.ts
fi

echo "== child_process imports/usages in server TypeScript =="
rg -n "exec(Sync|File)(Sync)?|child_process" apps/supercode-cli/server -g '*.ts' -g '*.tsx' -g '*.js' || true

echo "== deterministic shell behavior probe =="
node - <<'JS'
const { execSync, execFileSync } = require("node:child_process")
const dir = "/tmp/crisp-audit-probe; printf 'shell injection executed' > /tmp/crisp-audit-probe-payload.txt"
const safeDir = "/tmp/crisp-audit-probe-safe"
try {
  execSync(`date +%s > ${dir}/payload.txt`, { stdio: "ignore", encoding: "utf8" })
  console.log("execSync: payload exists", require("node:fs").existsSync(`${dir}/payload.txt`))
} catch (e) {
  console.log("execSync_error", e.message.trim().split("\n")[0])
}
try {
  execFileSync("date", ["+%s"], { cwd: dir, encoding: "utf8" })
  console.log("execFileSync: payload_exists=", require("node:fs").existsSync(`${dir}/payload.txt`))
} catch (e) {
  console.log("execFileSync_error", e.message.trim().split("\n")[0])
}
require("node:fs").rmSync("/tmp/crisp-audit-probe", { recursive: true, force: true })
require("node:fs").rmSync("/tmp/crisp-audit-probe-safe", { recursive: true, force: true })
JS

Repository: yashdev9274/supercli

Length of output: 5729


🌐 Web query:

Node.js child_process execSync shell command execution default shell template string path whitespace documentation

💡 Result:

The Node.js child_process.execSync function executes a command inside a shell [1][2]. Because it operates via a shell, it interprets the provided command string as a set of arguments, where spaces typically act as delimiters [3][4]. Key technical details include: 1. Shell Execution and Whitespace: When you pass a command string containing whitespace to execSync, the underlying shell (e.g., /bin/sh or cmd.exe) parses those spaces as separators between the executable and its arguments [3][4]. To include paths containing whitespace, you must wrap the path in double quotes within the command string to ensure the shell interprets the entire path as a single argument [3][4]. 2. Default Shell: By default, execSync executes commands using '/bin/sh' on Unix-like systems and process.env.ComSpec (usually cmd.exe) on Windows [2][5]. This default behavior can be overridden using the shell option in the options object [2][6]. 3. Security Warning: Because execSync processes the command string directly through a shell, it is vulnerable to shell injection [2][5]. Node.js documentation explicitly warns against passing unsanitized user input to this function, as shell metacharacters (e.g., |, &,;, $) can be used to execute arbitrary commands [2][4]. 4. Recommended Alternatives: For scenarios involving paths with spaces or when user input is involved, it is generally safer and cleaner to use child_process.execFileSync or child_process.spawnSync [7][3]. These methods allow you to pass the executable and its arguments as separate array elements, which avoids shell interpretation entirely and eliminates the need for manual quoting or escaping of paths [7][3].

Citations:


Avoid shell interpolation of the workspace path.

execSync runs through a shell, and process.cwd() is interpolated into the command string. A workspace path with spaces breaks the scan, and shell metacharacters in the path can execute commands. Use execFileSync with fixed fd arguments and the cwd option.

🧰 Tools
🪛 OpenGrep (1.26.0)

[ERROR] 14-14: Dynamic command passed to child_process.exec/execSync. Use child_process.execFile or spawn with an argument array instead.

(coderabbit.command-injection.exec-js)

🤖 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/commands/slashCommands/crisp-audit.ts`
around lines 2 - 14, Replace execSync in crispAuditCommand with execFileSync,
passing the fd options as fixed argument entries and using process.cwd() through
the cwd option instead of interpolating it into a shell command. Preserve the
existing file extensions, exclusions, depth limit, and UTF-8 output behavior.

Source: Linters/SAST tools

} catch {
fileList = "(could not list files)"
}

const lines = fileList.split("\n").filter(Boolean)
const sampleFiles = lines.slice(0, 30).join("\n")
const totalFiles = lines.length

const header =
`Audit this workspace against the Crisp simplicity ladder (${modeLabel}). focus: ${focus}\n` +
`Scan the whole tree. Rank findings biggest cut first.\n` +
`One line per finding. Format: <tag> <what to cut>. <replacement>. [path]\n\n` +
`Tags:\n` +
` delete: dead code, unused flexibility, speculative feature. Replacement: nothing.\n` +
` stdlib: hand-rolled thing the stdlib ships. Name the function.\n` +
` native: dependency or code doing what the platform already does. Name the feature.\n` +
` yagni: abstraction with one implementation, config nobody sets, layer with one caller.\n` +
` shrink: same logic, fewer lines. Show the shorter form.\n\n` +
`Prioritise: deps the stdlib or platform already ships, single-implementation interfaces, ` +
`factories with one product, wrappers that only delegate, files exporting one thing, ` +
`dead flags and config, hand-rolled stdlib.\n\n` +
`End with: net: -<N> lines, -<M> deps possible.\n` +
`If nothing to cut: "Lean already. Ship."\n\n` +
`Found ${totalFiles} source files. Sample (${Math.min(totalFiles, 30)} of ${totalFiles}):\n` +
sampleFiles

console.log(
frame(
`${chalk.hex(theme.amber).bold("Supercode Crisp Audit")} ${chalk.hex(theme.muted)(`mode: ${modeLabel} · focus: ${focus}`)}\n\n${chalk.hex(theme.greenDim)(`Scanning ${totalFiles} source files across the workspace...`)}`,
{ title: "crisp-audit", borderColor: theme.amber, padding: 0 },
),
)

return { type: "message", message: header }
}
106 changes: 106 additions & 0 deletions apps/supercode-cli/server/src/cli/commands/slashCommands/crisp-debt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import chalk from "chalk"
import { execSync } from "node:child_process"
import { theme, frame } from "src/cli/utils/tui.ts"
import type { SlashCommandResult } from "./index.ts"

export async function crispDebtCommand(argsStr: string): Promise<SlashCommandResult> {
let tagCounts: Record<string, number> = {}
let rawMatches = ""
let crispCommentMatches = ""
try {
rawMatches = execSync("rg -n '\\[crisp:\\d+\\]' --type ts --type tsx --type js --type jsx -g '!node_modules' -g '!dist'", { encoding: "utf-8" })
const matches = rawMatches.split("\n").filter(Boolean)
for (const m of matches) {
const tag = m.match(/\[crisp:(\d+)\]/)
if (tag) {
const rung = tag[1]!
tagCounts[rung] = (tagCounts[rung] ?? 0) + 1
}
Comment on lines +11 to +18

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

Scan and count every Crisp marker.

The type filters exclude the # and -- comment styles this command claims to detect, and match() counts only the first [crisp:N] on a line. This can report a false-clean or undercounted workspace ledger. Scan the intended workspace file set and iterate matchAll(/\[crisp:(\d+)\]/g) for each result line.

Also applies to: 24-24

🤖 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/commands/slashCommands/crisp-debt.ts`
around lines 11 - 18, Update the scan in the command’s marker-counting loop to
include the intended workspace comment file types, including files using # and
-- comments, rather than limiting ripgrep to the current TypeScript and
JavaScript filters. For each matching result line, iterate all occurrences with
matchAll using the global Crisp-marker pattern, and increment tagCounts for
every captured rung instead of only the first match.

}
} catch {
rawMatches = ""
}
try {
crispCommentMatches = execSync("rg -n '(//|#|--)\\s*crisp:' --type ts --type tsx --type js --type jsx -g '!node_modules' -g '!dist'", { encoding: "utf-8" })
} catch {
crispCommentMatches = ""
}

const totalTags = Object.values(tagCounts).reduce((a, b) => a + b, 0)

const rungLabels: Record<string, string> = {
"1": "YAGNI",
"2": "Reuse what exists",
"3": "Standard library first",
"4": "Native platform APIs",
"5": "Dependencies are debt",
"6": "One line > many",
"7": "Minimum code to satisfy the spec",
}

const summaryLines = Object.entries(rungLabels)
.map(([rung, label]) => {
const count = tagCounts[rung] ?? 0
const color = count > 0 ? theme.green : theme.muted
return ` ${chalk.hex(color)(`[crisp:${rung}]`)} ${chalk.hex(color)(label.padEnd(28))} ${count > 0 ? chalk.hex(theme.amber)(`${count} tags`) : chalk.hex(theme.muted)("—")}`
})
.join("\n")

const detailLines = rawMatches
.split("\n")
.filter(Boolean)
.slice(0, 40)
.map((line) => {
const idx = line.indexOf(":")
if (idx > 0) {
const file = line.slice(0, idx)
const rest = line.slice(idx + 1)
return ` ${chalk.hex(theme.greenDim)(file)}:${chalk.hex(theme.muted)(rest.slice(0, 80))}`
}
return ` ${chalk.hex(theme.muted)(line.slice(0, 80))}`
})
.join("\n")

const crispComments = crispCommentMatches
.split("\n")
.filter(Boolean)
.slice(0, 20)

const commentLines = crispComments.map((line) => {
const idx = line.indexOf(":")
if (idx > 0) {
const file = line.slice(0, idx)
const rest = line.slice(idx + 1)
const hasUpgrade = /\b(upgrade|when|if|once|until|trigger|todo)\b/i.test(rest)
const triggerTag = hasUpgrade ? "" : chalk.hex(theme.red)(" no-trigger")
return ` ${chalk.hex(theme.greenDim)(file)}:${chalk.hex(theme.muted)(rest.slice(0, 80))}${triggerTag}`
}
return ` ${chalk.hex(theme.muted)(line.slice(0, 80))}`
}).join("\n")

const noTriggerCount = crispComments.filter((l) => !/\b(upgrade|when|if|once|until|trigger|todo)\b/i.test(l)).length

const sections: string[] = [
chalk.hex(theme.amber).bold("Crisp Debt Ledger"),
"",
`${chalk.hex(theme.muted)(`[crisp:N] tag ledger: ${totalTags} markers`)}`,
"",
...(totalTags > 0
? [summaryLines, "", detailLines]
: [`${chalk.hex(theme.greenDim)("No [crisp:N] tags found. Clean — or crisp hasn't been applied yet.")}`]),
]

if (crispComments.length > 0) {
sections.push(
"",
chalk.hex(theme.amber).bold(`Deliberate shortcuts (crisp: comments)`),
"",
commentLines,
"",
`${chalk.hex(theme.muted)(`${crispComments.length} shortcuts, ${noTriggerCount} with no upgrade trigger`)}`,
)
}

console.log(frame(sections.join("\n"), { title: "crisp-debt", borderColor: theme.amber, padding: 0 }))
return { type: "help" }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import chalk from "chalk"
import { theme, frame } from "src/cli/utils/tui.ts"
import { getCrispModeSync } from "src/cli/crisp/index.ts"
import type { SlashCommandResult } from "./index.ts"

const bar = (pct: number, width = 20): string => {
const filled = Math.round((pct / 100) * width)
return "█".repeat(filled) + "·".repeat(width - filled)
}

export async function crispGainCommand(argsStr: string): Promise<SlashCommandResult> {
const mode = getCrispModeSync()

const content = [
chalk.hex(theme.amber).bold("Crisp Gain"),
chalk.hex(theme.muted)("benchmark median · 5 tasks · 3 models"),
"",
chalk.hex(theme.green)("Lines of code"),
` no-crisp ${chalk.hex(theme.white)(bar(100))} 100%`,
` crisp ${chalk.hex(theme.green)(bar(13))}${chalk.hex(theme.muted)(bar(87))} 6-20% ${chalk.hex(theme.red)("▼ 80-94%")}`,
"",
chalk.hex(theme.green)("Cost"),
` no-crisp ${chalk.hex(theme.white)(bar(100))} 100%`,
` crisp ${chalk.hex(theme.green)(bar(38))}${chalk.hex(theme.muted)(bar(62))} 23-53% ${chalk.hex(theme.red)("▼ 47-77%")}`,
"",
chalk.hex(theme.green)("Speed"),
` crisp ${chalk.hex(theme.amber)("▸ 3-6× faster")}`,
"",
chalk.hex(theme.muted)("These are published benchmark medians (see README)."),
chalk.hex(theme.muted)("They are measured, not computed from this repo."),
"",
chalk.hex(theme.amber)("This repo:"),
` ${chalk.hex(theme.green)("/crisp-debt")} ${chalk.hex(theme.muted)("(shortcuts you deferred — the counted ledger)")}`,
` ${chalk.hex(theme.green)("/crisp-audit")} ${chalk.hex(theme.muted)("(what's still cuttable)")}`,
].join("\n")

console.log(frame(content, { title: "crisp-gain", borderColor: theme.amber, padding: 0 }))
return { type: "help" }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import chalk from "chalk"
import { theme, frame } from "src/cli/utils/tui.ts"
import { getCrispModeSync } from "src/cli/crisp/index.ts"
import type { SlashCommandResult } from "./index.ts"

export async function crispHelpCommand(): Promise<SlashCommandResult> {
const current = getCrispModeSync()

const content = [
chalk.hex(theme.amber).bold("Supercode Crisp — Simplicity Ladder Commands"),
"",
chalk.hex(theme.amber).bold("Levels"),
` ${chalk.hex(theme.green)("/crisp")} ${chalk.hex(theme.muted)("Show current mode")}`,
` ${chalk.hex(theme.green)("/crisp full")} ${chalk.hex(theme.muted)("Ladder enforced. Every abstraction must be justified. Default.")}`,
` ${chalk.hex(theme.green)("/crisp lite")} ${chalk.hex(theme.muted)("Build what's asked, name the lazier alternative. User picks.")}`,
` ${chalk.hex(theme.green)("/crisp ultra")} ${chalk.hex(theme.muted)("YAGNI extremist. Deletion before addition. Challenges requirements.")}`,
` ${chalk.hex(theme.green)("/crisp off")} ${chalk.hex(theme.muted)("Disable crisp mode.")}`,
"",
chalk.hex(theme.amber).bold("Skills (one-shot)"),
` ${chalk.hex(theme.green)("/crisp-review")} ${chalk.hex(theme.muted)("Over-engineering review: L42: yagni: factory, one product. Inline.")}`,
` ${chalk.hex(theme.green)("/crisp-audit")} ${chalk.hex(theme.muted)("Whole-repo over-engineering audit: ranked list of what to delete.")}`,
` ${chalk.hex(theme.green)("/crisp-debt")} ${chalk.hex(theme.muted)("Harvest [crisp:N] tags and crisp: shortcut comments.")}`,
` ${chalk.hex(theme.green)("/crisp-gain")} ${chalk.hex(theme.muted)("Measured-impact scoreboard: less code, less cost, more speed.")}`,
` ${chalk.hex(theme.green)("/crisp-help")} ${chalk.hex(theme.muted)("This card.")}`,
"",
chalk.hex(theme.amber).bold("The ladder"),
" 1. YAGNI — don't build what you don't need",
" 2. Reuse what exists — stdlib > library > new code",
" 3. Standard library first — built-in APIs > third-party",
" 4. Native platform APIs — platform > framework > userland",
" 5. Dependencies are debt — every dep is a liability",
" 6. One line > many — single expression > temp var chain",
" 7. Minimum code to satisfy the spec — delete everything else",
"",
chalk.hex(theme.amber).bold("Deactivate"),
` ${chalk.hex(theme.green)('"stop crisp"')} ${chalk.hex(theme.muted)("or")} ${chalk.hex(theme.green)("normal mode")} ${chalk.hex(theme.muted)("revert to normal. Resume anytime with /crisp.")}`,
"",
chalk.hex(theme.amber).bold("Configure default"),
chalk.hex(theme.muted)("Environment variable (highest priority):"),
` ${chalk.hex(theme.greenDim)("export CRISP_DEFAULT_MODE=ultra")}`,
chalk.hex(theme.muted)("Config file (~/.config/supercode/crisp.json):"),
` ${chalk.hex(theme.greenDim)('{"defaultMode": "lite"}')}`,
Comment on lines +38 to +42

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

Document the configuration mechanism that actually exists.

Neither CRISP_DEFAULT_MODE nor ~/.config/supercode/crisp.json is read by the supplied config implementation. Users following this help cannot configure Crisp. Either implement those inputs or document ~/.config/supercode/cli-config.json with its crispMode field.

🤖 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/commands/slashCommands/crisp-help.ts`
around lines 38 - 42, Update the configuration guidance in the Crisp help
command to document the supported ~/.config/supercode/cli-config.json file and
its crispMode field, replacing the unsupported CRISP_DEFAULT_MODE and crisp.json
examples. Do not implement new configuration inputs; keep the help text aligned
with the existing config implementation.

"",
chalk.hex(theme.greenDim)(`Current mode: ${chalk.hex(theme.amber)(current)}`),
].join("\n")

console.log(frame(content, { title: "crisp-help", borderColor: theme.amber, padding: 0 }))
return { type: "help" }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import chalk from "chalk"
import { execSync } from "node:child_process"
import { theme, frame } from "src/cli/utils/tui.ts"
import { getCrispModeSync } from "src/cli/crisp/index.ts"
import type { SlashCommandResult } from "./index.ts"

export async function crispReviewCommand(argsStr: string): Promise<SlashCommandResult> {
const mode = getCrispModeSync()

let diff: string
try {
const staged = execSync("git diff --cached", { encoding: "utf-8" })
const unstaged = execSync("git diff", { encoding: "utf-8" })
diff = staged || unstaged
Comment on lines +12 to +14

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

Include both staged and unstaged changes in the review.

When both diffs exist, staged || unstaged discards the unstaged diff, so /crisp-review misses part of the working tree.

Proposed fix
-    diff = staged || unstaged
+    diff = [staged, unstaged].filter(Boolean).join("\n")
📝 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
const staged = execSync("git diff --cached", { encoding: "utf-8" })
const unstaged = execSync("git diff", { encoding: "utf-8" })
diff = staged || unstaged
const staged = execSync("git diff --cached", { encoding: "utf-8" })
const unstaged = execSync("git diff", { encoding: "utf-8" })
diff = [staged, unstaged].filter(Boolean).join("\n")
🤖 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/commands/slashCommands/crisp-review.ts`
around lines 12 - 14, Update the diff assembly in the crisp-review command so it
includes both staged and unstaged output rather than selecting only the staged
result. Preserve each command’s captured content and combine them in order,
including empty results safely.

if (!diff) {
console.log(
frame(
`${chalk.hex(theme.amber).bold("No changes to review")}\n\n${chalk.hex(theme.muted)("There are no staged or unstaged diffs. Make some changes first, then run /crisp-review.")}`,
{ title: "crisp-review", borderColor: theme.amber, padding: 0 },
),
)
return { type: "help" }
}
} catch {
console.log(
frame(
`${chalk.hex(theme.red).bold("Git error")}\n\n${chalk.hex(theme.muted)("Could not read git diff. Are you in a git repository?")}`,
{ title: "crisp-review", borderColor: theme.red, padding: 0 },
),
)
return { type: "help" }
}

const modeLabel = mode === "off" ? "full" : mode
const header =
`Review this diff against the Crisp simplicity ladder (${modeLabel}).\n` +
`One line per finding. Format: L<line>: <tag> <what>. <replacement>.\n\n` +
`Tags:\n` +
` delete: dead code, unused flexibility, speculative feature. Replacement: nothing.\n` +
` stdlib: hand-rolled thing the stdlib ships. Name the function.\n` +
` native: dependency or code doing what the platform already does. Name the feature.\n` +
` yagni: abstraction with one implementation, config nobody sets, layer with one caller.\n` +
` shrink: same logic, fewer lines. Show the shorter form.\n\n` +
`End with: net: -<N> lines possible.\n` +
`If nothing to cut: "Lean already. Ship."\n\n` +
`${diff}`

console.log(
frame(
`${chalk.hex(theme.amber).bold("Supercode Crisp Review")} ${chalk.hex(theme.muted)(`mode: ${modeLabel}`)}\n\n${chalk.hex(theme.greenDim)("Diff loaded. Evaluating against simplicity ladder...")}`,
{ title: "crisp-review", borderColor: theme.amber, padding: 0 },
),
)

return { type: "message", message: header }
}
48 changes: 48 additions & 0 deletions apps/supercode-cli/server/src/cli/commands/slashCommands/crisp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import chalk from "chalk"
import { CRISP_MODES, getCrispModeSync, setCrispMode, CRISP_MODE_LABELS } from "src/cli/crisp/index.ts"
import { theme, frame } from "src/cli/utils/tui.ts"
import type { SlashCommandResult } from "./index.ts"
import type { CrispMode } from "src/cli/crisp/config.ts"

export async function crispCommand(argsStr: string): Promise<SlashCommandResult> {
const parts = argsStr.trim().split(/\s+/).filter(Boolean)
const raw = parts[0] ?? ""

if (raw === "lite" || raw === "full" || raw === "ultra" || raw === "off" || raw === "on") {
const mode: CrispMode = raw === "on" ? "full" : raw
await setCrispMode(mode)
if (mode === "off") {
console.log(` ${chalk.hex(theme.green)("✓")} ${chalk.hex(theme.muted)("Crisp disabled")}`)
} else {
console.log(
frame(
`${chalk.hex(theme.amber).bold(`Supercode Crisp: ${mode}`)}\n\n${chalk.hex(theme.muted)(CRISP_MODE_LABELS[mode])}\n\nThe simplicity ladder will be injected into every agent session.\nTry: /crisp status`,
{ title: `crisp:${mode}`, borderColor: theme.amber, padding: 0 },
),
)
}
return { type: "help" }
}

// Default / "on" / "status" → show status
const current = getCrispModeSync()
const statusLines = [
chalk.hex(theme.amber).bold(`Supercode Crisp: ${current}`),
"",
`${chalk.hex(theme.muted)("Usage:")} /crisp { lite | full | ultra | off | status }`,
"",
...CRISP_MODES.map((m) => {
const indicator = m === current ? chalk.hex(theme.green)("▸") : " "
return ` ${indicator} ${chalk.hex(m === current ? theme.white : theme.muted)(`/${m}`).padEnd(18)} ${chalk.hex(m === current ? theme.green : theme.greenDim)(CRISP_MODE_LABELS[m])}`
}),
].join("\n")

console.log(
frame(statusLines, {
title: "crisp",
borderColor: theme.amber,
padding: 0,
}),
)
return { type: "help" }
}
30 changes: 30 additions & 0 deletions apps/supercode-cli/server/src/cli/commands/slashCommands/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ export const COMMANDS = [
{ cmd: "/mcp", desc: "Manage MCP connections (add, connect, toggle, remove)" },
{ cmd: "/connectors", desc: "Manage app connectors (Merge, Composer, etc.)" },
{ cmd: "/skills", desc: "List installed agent skills and view their SKILL.md" },
{ cmd: "/crisp", desc: "Set crisp mode (on|lite|full|ultra|off) or show status" },
{ cmd: "/crisp-review", desc: "Review git diff against the simplicity ladder" },
{ cmd: "/crisp-audit", desc: "Audit workspace for over-engineering patterns" },
{ cmd: "/crisp-debt", desc: "Find [crisp:N] tagged shortcuts / deferred work" },
{ cmd: "/crisp-gain", desc: "Impact scoreboard — LOC, deps, simplification potential" },
{ cmd: "/crisp-help", desc: "Quick reference for crisp commands" },
{ cmd: "/sk", desc: "Alias for /skills" },
]

Expand Down Expand Up @@ -130,6 +136,30 @@ const handlers: Record<string, (args: string) => Promise<SlashCommandResult>> =
}
return { type: "skills" }
},
crisp: async (args) => {
const { crispCommand } = await import("./crisp.ts")
return crispCommand(args)
},
"crisp-review": async (args) => {
const { crispReviewCommand } = await import("./crisp-review.ts")
return crispReviewCommand(args)
},
"crisp-audit": async (args) => {
const { crispAuditCommand } = await import("./crisp-audit.ts")
return crispAuditCommand(args)
},
"crisp-debt": async () => {
const { crispDebtCommand } = await import("./crisp-debt.ts")
return crispDebtCommand("")
},
"crisp-gain": async (args) => {
const { crispGainCommand } = await import("./crisp-gain.ts")
return crispGainCommand(args)
},
"crisp-help": async () => {
const { crispHelpCommand } = await import("./crisp-help.ts")
return crispHelpCommand()
},
search: async (args) => ({ type: "message", message: chatify("search", args) }),
scrape: async (args) => ({ type: "message", message: chatify("scrape", args) }),
interact: async (args) => ({ type: "message", message: chatify("interact", args) }),
Expand Down
Loading
Loading