AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified Apache-2.0 Self-run

Ts Dev

skill-mdproctor-cc-praxis-ts-dev · by mdproctor

>

No reviews yet
0 installs
36 views
0.0% view→install

Install

$ agentstack add skill-mdproctor-cc-praxis-ts-dev

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access Used
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-mdproctor-cc-praxis-ts-dev)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
Archived

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Ts Dev? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

TypeScript Development

Quick Reference

| Category | Rule | How to Apply | |----------|------|--------------| | Type Safety | Never use any | Use unknown with type guards; any defeats the compiler | | | Avoid type assertions | as Type suppresses errors without fixing them | | | Avoid non-null assertions | ! crashes on edge cases; use ?. and ?? instead | | | Strict mode | strict: true in tsconfig.json is non-negotiable | | | Discriminated unions | Model state explicitly; no boolean flags | | Async Patterns | Always await | Unhandled rejections crash Node.js silently | | | No await in loops | Use Promise.all for parallel; sequential only when required | | | Partial failures | Promise.allSettled when one failure shouldn't abort all | | Error Handling | Type-check caught errors | catch (e) is unknown in strict mode — narrow before use | | | Never swallow errors | Log or rethrow; empty catch blocks hide bugs | | | Result types | Use discriminated unions for expected failures | | Testing | Prefer real implementations | Mocks drift from production; real integrations catch real bugs | | | Test behavior not impl | Don't test internal state; test observable outcomes | | | Type-level tests | Use expectType() for type assertions | | Code Quality | const over let | Prevents accidental reassignment; never var | | | readonly where possible | Communicates intent; prevents mutation bugs | | | Template literals | Clearer than concatenation; no escaping issues |

Rule Priority Decision Flow

flowchart TD
    Writing_code((Writing code))
    Type_safety_violation_{Type safety violation?}
    Apply_Type_Safety_rules[Apply Type Safety rules]
    Async_issue_{Async correctness issue?}
    Apply_Async_rules[Apply Async rules]
    Error_handling_gap_{Error handling gap?}
    Apply_Error_Handling_rules[Apply Error Handling rules]
    Apply_Code_Quality_rules[Apply Code Quality rules]
    Code_complete((Code complete))
    Writing_code --> Type_safety_violation_
    Type_safety_violation_ -->|"yes (NEVER compromise)"| Apply_Type_Safety_rules
    Type_safety_violation_ -->|no| Async_issue_
    Apply_Type_Safety_rules --> Code_complete
    Async_issue_ -->|"yes (unhandled promise / race)"| Apply_Async_rules
    Async_issue_ -->|no| Error_handling_gap_
    Apply_Async_rules --> Code_complete
    Error_handling_gap_ -->|"yes (swallowed/untyped)"| Apply_Error_Handling_rules
    Error_handling_gap_ -->|no| Apply_Code_Quality_rules
    Apply_Error_Handling_rules --> Code_complete
    Apply_Code_Quality_rules --> Code_complete

Priority order: Type Safety > Async Correctness > Error Handling > Code Quality

Why These Rules Matter

Unhandled promise rejections: An async function called without await silently discarded its error in a payment handler. Node.js emitted UnhandledPromiseRejectionWarning — ignored in the logs — while orders were silently dropped. Fix: one missing await keyword.

any typing allowing runtime crashes: A third-party API response typed as any was accessed with .userId.toUpperCase(). The field was renamed to user_id in a patch release. TypeScript emitted no error; production crashed with Cannot read properties of undefined. Fix: type the response with unknown, validate the shape.

Missing null checks: A function returned undefined when no record was found, but the caller assumed it always returned a value. The non-null assertion ! had been added "because we know it's there". It wasn't, in one edge case. Fix: remove !, add a null check.

Silent promise failures in loops: for...of with await inside processed 500 items sequentially — 25 seconds instead of 1 second. The real bug was the developer's next change: wrapping the loop in Promise.all, which silently omitted the outer await, and the entire batch appeared to succeed while nothing ran. Fix: understand the async model before writing it.

These are real incidents. The rules exist because the pain is real.

Type Safety

TypeScript's value is catching errors at compile time. Rules that bypass the type system defeat the entire purpose of using TypeScript.

Never use any — use unknown with type guards:

// ❌ BAD: any silences the compiler entirely
function parseResponse(data: any): string {
    return data.user.name.toUpperCase();  // Crashes if shape changes
}

// ✅ GOOD: unknown forces you to verify the shape
function parseResponse(data: unknown): string {
    if (
        typeof data === "object" &&
        data !== null &&
        "user" in data &&
        typeof (data as { user: unknown }).user === "object"
    ) {
        const user = (data as { user: { name: string } }).user;
        return user.name.toUpperCase();
    }
    throw new Error("Unexpected response shape");
}

Avoid type assertions (as Type) — they suppress the compiler:

// ❌ BAD: Forces a type without verification
const user = response.data as User;
console.log(user.email.toLowerCase());  // Crashes if email is undefined

// ✅ GOOD: Parse and validate
function toUser(data: unknown): User {
    if (!isUser(data)) throw new Error("Invalid user shape");
    return data;
}

function isUser(data: unknown): data is User {
    return (
        typeof data === "object" &&
        data !== null &&
        typeof (data as Record).email === "string"
    );
}

Avoid non-null assertions (!) — prefer optional chaining and nullish coalescing:

// ❌ BAD: Asserts non-null without evidence
const email = user!.profile!.email!.toLowerCase();

// ✅ GOOD: Safe navigation with fallback
const email = user?.profile?.email?.toLowerCase() ?? "unknown";

strict: true in tsconfig.json is non-negotiable. This enables strictNullChecks, noImplicitAny, strictFunctionTypes, and related checks. A codebase without strict: true is TypeScript in name only.

{
  "compilerOptions": {
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true
  }
}

Use discriminated unions for state modeling:

// ❌ BAD: Boolean flags create impossible states
interface FetchState {
    loading: boolean;
    error: boolean;
    data?: User;
}

// ✅ GOOD: Discriminated union — every state is explicit and valid
type FetchState =
    | { status: "idle" }
    | { status: "loading" }
    | { status: "error"; error: Error }
    | { status: "success"; data: User };

Prefer interface for object shapes, type for unions and intersections:

// Object shapes → interface (extensible, better error messages)
interface User {
    id: string;
    email: string;
}

// Unions and intersections → type
type ID = string | number;
type AdminUser = User & { role: "admin" };

Async Patterns

Async bugs are among the hardest to reproduce. They often only surface under load, on slow networks, or in specific timing windows.

Always await promises — unhandled rejections crash Node.js:

// ❌ BAD: Promise returned but not awaited — error silently discarded
async function saveOrder(order: Order): Promise {
    db.insert(order);  // Missing await — failure is invisible
    sendConfirmationEmail(order.email);  // Also missing await
}

// ✅ GOOD: Explicit await — errors propagate correctly
async function saveOrder(order: Order): Promise {
    await db.insert(order);
    await sendConfirmationEmail(order.email);
}

Never mix callbacks and promises without promisifying first:

// ❌ BAD: Error from callback not caught in promise chain
async function readFile(path: string): Promise {
    return new Promise((resolve) => {
        fs.readFile(path, "utf-8", (err, data) => {
            if (err) throw err;  // Throws inside callback — unhandled
            resolve(data);
        });
    });
}

// ✅ GOOD: Rejection passed to the promise
async function readFile(path: string): Promise {
    return new Promise((resolve, reject) => {
        fs.readFile(path, "utf-8", (err, data) => {
            if (err) reject(err);
            else resolve(data);
        });
    });
}

Use Promise.allSettled instead of Promise.all when partial failure is acceptable:

// ❌ BAD: One failure aborts all — other results lost
const results = await Promise.all(userIds.map(fetchUser));

// ✅ GOOD: Each settles independently
const results = await Promise.allSettled(userIds.map(fetchUser));
const users = results
    .filter((r): r is PromiseFulfilledResult => r.status === "fulfilled")
    .map((r) => r.value);

Never await inside a loop unless sequential order is required:

// ❌ BAD: Sequential when parallel is possible — 500ms × N items
for (const id of userIds) {
    const user = await fetchUser(id);  // Waits for each before starting next
    results.push(user);
}

// ✅ GOOD: Parallel — all requests in flight simultaneously
const results = await Promise.all(userIds.map(fetchUser));

Error Handling

catch (e) gives unknown in strict mode — type-check before accessing:

// ❌ BAD: Accessing properties on unknown type
try {
    await processPayment(order);
} catch (e) {
    console.error(e.message);  // Error: 'e' is of type 'unknown'
}

// ✅ GOOD: Narrow the type before use
try {
    await processPayment(order);
} catch (e) {
    const message = e instanceof Error ? e.message : String(e);
    logger.error("Payment failed", { orderId: order.id, error: message });
    throw e;
}

Don't swallow errors silently — log or rethrow:

// ❌ BAD: Error swallowed — failure invisible to caller
try {
    await syncInventory();
} catch (_e) {
    // silently continue
}

// ✅ GOOD: Log and propagate — caller can handle or escalate
try {
    await syncInventory();
} catch (e) {
    logger.error("Inventory sync failed", { error: e });
    throw e;
}

Use discriminated union Result types for expected failures (not exceptions):

// For expected error paths (validation, not-found), prefer Result types over exceptions
type Result =
    | { ok: true; value: T }
    | { ok: false; error: E };

async function findUser(id: string): Promise> {
    try {
        const user = await db.users.findById(id);
        if (!user) return { ok: false, error: "not-found" };
        return { ok: true, value: user };
    } catch {
        return { ok: false, error: "db-error" };
    }
}

// Callers must handle both paths — the type enforces it
const result = await findUser(id);
if (!result.ok) {
    if (result.error === "not-found") return Response.notFound();
    return Response.serverError();
}
return Response.ok(result.value);

Separate expected errors from unexpected errors. Network timeouts and validation failures are expected — use Result types. Programmer errors and invariant violations are unexpected — throw and let them crash loudly.

Testing

Preferred stack:

  • Jest or Vitest — both support TypeScript natively with minimal config
  • @testing-library/* — for UI component tests; test what the user sees, not implementation
  • ts-expect or expectTypeOf (Vitest) — for type-level assertions
  • msw (Mock Service Worker) — for HTTP mocking at the network level; prefer over manual fetch mocks
  • testcontainers — for real database/service integration tests

Prefer real implementations over mocks for integration tests:

// ❌ BAD: Mock hides real integration surface
jest.mock("../db/userRepository");
const mockRepo = userRepository as jest.Mocked;
mockRepo.findById.mockResolvedValue({ id: "1", email: "test@example.com" });

// ✅ GOOD: In-memory implementation matches production contract
class InMemoryUserRepository implements UserRepository {
    private users = new Map();
    async findById(id: string): Promise {
        return this.users.get(id) ?? null;
    }
    async save(user: User): Promise {
        this.users.set(user.id, user);
    }
}

Test the types too — use type-level assertions:

import { expectTypeOf } from "vitest";

test("parseUser returns User type", () => {
    const result = parseUser({ id: "1", email: "a@b.com" });
    expectTypeOf(result).toEqualTypeOf();
});

Don't test implementation details — test behavior:

// ❌ BAD: Tests internal structure — breaks on refactor
expect(service["_cache"].size).toBe(1);

// ✅ GOOD: Tests observable behavior
const result = await service.getUser("1");
expect(result).toEqual({ id: "1", email: "a@b.com" });

Use beforeEach to reset state; never share mutable state between tests:

// ❌ BAD: Shared state causes test-order dependencies
const repo = new InMemoryUserRepository();

// ✅ GOOD: Fresh instance per test
let repo: InMemoryUserRepository;
beforeEach(() => {
    repo = new InMemoryUserRepository();
});

⛔ Bug Fix Workflow — Mandatory

See ~/.hortora/garden/approaches/testing.md — Bug Fix Workflow section for the mandatory 5-step process.

Code Quality

Mark parameters and variables readonly where possible:

// ❌ BAD: Mutable parameter — caller's array could be mutated
function processUsers(users: User[]): void {
    users.sort((a, b) => a.name.localeCompare(b.name));  // Mutates caller's array
}

// ✅ GOOD: readonly prevents accidental mutation
function processUsers(users: readonly User[]): void {
    const sorted = [...users].sort((a, b) => a.name.localeCompare(b.name));
    // ...
}

Prefer const over let, never var:

// ❌ BAD
var count = 0;
let message = "hello";  // message is never reassigned

// ✅ GOOD
const count = 0;
const message = "hello";

Use template literals over string concatenation:

// ❌ BAD
const url = "https://api.example.com/users/" + userId + "/orders/" + orderId;

// ✅ GOOD
const url = `https://api.example.com/users/${userId}/orders/${orderId}`;

Avoid deeply nested callbacks — use async/await:

// ❌ BAD: Callback pyramid
fetchUser(id, (user) => {
    fetchOrders(user.id, (orders) => {
        fetchInvoices(orders[0].id, (invoices) => {
            processAll(user, orders, invoices);
        });
    });
});

// ✅ GOOD: Linear async flow
const user = await fetchUser(id);
const orders = await fetchOrders(user.id);
const invoices = await fetchInvoices(orders[0].id);
processAll(user, orders, invoices);

Refactoring — IntelliJ First

Prerequisites: ide-tooling — invoke it for the full IntelliJ MCP tool guide. Always prefer IntelliJ MCPs over bash for any rename, move, find-references, or navigation. If no MCP is available for a semantic operation, inform the user — do not silently fall back.

Common Pitfalls — These Thoughts Mean STOP

If you catch yourself thinking any of these, STOP and apply the correct approach:

| Rationalization | Problem | Impact | Fix | |-----------------|---------|--------|-----| | "TypeScript is just JavaScript, type errors are fine" | Type errors signal real bugs | Runtime crashes that strict mode prevents | Fix the type error; don't suppress it | | "I'll add proper types later" | Technical debt never gets paid | any spreads — typed code calls untyped code | Type it now while context is fresh | | "as any fixes it quickly" | Defeats the entire type system | Silent runtime failures TypeScript was meant to catch | Use unknown and a type guard | | "Promise errors are handled somewhere up the chain" | They're not unless explicitly propagated | UnhandledPromiseRejection crashes Node.js | Explicitly await and catch | | "await in a loop is fine for now" | Sequential when parallel is correct | 50× performance regression | Use Promise.all | | "I'll mock the database in tests" | Mock diverges from real behavior | Tests pass, production burns | Use in-memory implementation or real DB | | "catch (e) { console.log(e) }" | Error swallowed aft

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.