Skip to content

Reject invalid skill listing prices#487

Open
jtc268 wants to merge 2 commits into
profullstack:masterfrom
jtc268:fix-skills-price-validation
Open

Reject invalid skill listing prices#487
jtc268 wants to merge 2 commits into
profullstack:masterfrom
jtc268:fix-skills-price-validation

Conversation

@jtc268
Copy link
Copy Markdown

@jtc268 jtc268 commented May 30, 2026

Fixes #457.

Summary

  • validate sh1pt skills new --price as a non-negative integer in sats before writing the manifest
  • reject negative, fractional, non-numeric, and unsafe integer prices instead of parsing/truncating them
  • reuse the parsed price for marketplace commands so uGig output matches sh1pt.skill.json

Tests

  • pnpm test -- packages/cli/src/commands/skills.test.ts
  • pnpm --filter @profullstack/sh1pt typecheck

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 30, 2026

Greptile Summary

This PR adds strict validation for the --price option on sh1pt skills new, replacing the silent parseInt || 0 fallback with a parsePriceSats helper that rejects negative, fractional, non-numeric, and unsafe-integer values before any manifest is written.

  • skills.ts: Introduces parsePriceSats(value: string): number using a /^\d+$/ regex plus Number.isSafeInteger guard; extracts tags and price into local variables so marketplace command generation reuses the same parsed values rather than re-parsing opts.price.
  • skills.test.ts: New describe('skills new command') suite exercises the valid integer, zero-price, negative, fractional, and > MAX_SAFE_INTEGER paths, asserting both the thrown message and that no manifest file is written on failure.

Confidence Score: 5/5

Safe to merge — the change is a focused input-validation addition with no side effects on existing behaviour for well-formed inputs.

The validation logic is straightforward: a regex gate followed by an isSafeInteger check, called before any file I/O. The default value of '0' ensures the happy path is unchanged when --price is omitted. Tests cover all the explicitly listed rejection paths and verify the manifest is not written on failure.

No files require special attention.

Important Files Changed

Filename Overview
packages/cli/src/commands/skills.ts Adds parsePriceSats helper to validate --price as a non-negative safe integer; replaces Number.parseInt fallback with explicit rejection; reuses parsed price and tags variables in marketplace command generation.
packages/cli/src/commands/skills.test.ts Adds a full describe block for the skills new command covering valid integer, zero, negative, fractional, and unsafe-integer price cases, plus manifest-not-written assertions on rejection paths.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["sh1pt skills new --price <value>"] --> B["parsePriceSats(value)"]
    B --> C{"/^\d+$/ matches?"}
    C -- No --> D["throw Error\n'--price must be a non-negative integer in sats'"]
    C -- Yes --> E["price = Number(normalized)"]
    E --> F{"Number.isSafeInteger(price)?"}
    F -- No --> G["throw Error\n'--price must be a safe non-negative integer in sats'"]
    F -- Yes --> H["return price"]
    H --> I["Build SkillManifest\n(with tags + price)"]
    I --> J["mkdir + saveManifest(out, manifest)"]
    D --> K["❌ manifest NOT written"]
    G --> K
Loading

Reviews (2): Last reviewed commit: "Cover zero and unsafe skill prices" | Re-trigger Greptile

Comment on lines +87 to +89
if (!Number.isSafeInteger(price)) {
throw new Error('--price must be a safe non-negative integer in sats');
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 The unsafe-integer branch emits a different message ("a safe non-negative integer") than the regex branch ("a non-negative integer"). Both express the same user-facing constraint, so the messages should be identical — otherwise users who hit the large-number path see an unexplained "safe" qualifier that has no meaning outside JavaScript internals.

Suggested change
if (!Number.isSafeInteger(price)) {
throw new Error('--price must be a safe non-negative integer in sats');
}
if (!Number.isSafeInteger(price)) {
throw new Error('--price must be a non-negative integer in sats');
}

Comment thread packages/cli/src/commands/skills.test.ts
Comment on lines +106 to +108
describe('skills new command', () => {
let stdout: string[];
let tempDir: string;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 tempDir is declared as string but is never initialised, so its runtime value is undefined until skillFixture() runs. Typing it as string | undefined makes the intent explicit and prevents the compiler from masking accidental reads before assignment.

Suggested change
describe('skills new command', () => {
let stdout: string[];
let tempDir: string;
describe('skills new command', () => {
let stdout: string[];
let tempDir: string | undefined;

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

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.

skills new accepts invalid listing prices

1 participant