Skip to content
Open
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
3 changes: 2 additions & 1 deletion .agents/skills/caveman/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ description: >
Ultra-compressed communication mode. Cuts token usage ~75% by dropping
filler, articles, and pleasantries while keeping full technical accuracy.
Use when user says "caveman mode", "talk like caveman", "use caveman",
"less tokens", "be brief", or invokes /caveman.
"less tokens", or invokes /caveman. A one-off "be brief" is not a trigger;
this mode persists until switched off.
metadata:
upstream: mattpocock/skills
forked: 2026-05-17
Expand Down
12 changes: 4 additions & 8 deletions .agents/skills/change-proposal/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
---
name: change-proposal
description: 'Present proposed code changes visually before implementing. Use when: "show me options", "compare approaches", "what should we do", or when changes need before/after comparison.'
description: 'Present proposed code changes visually before implementing: before/after diffs, ASCII diagrams, comparison tables. Use when: "show me options for this change", "compare approaches", or a multi-file change needs a before/after comparison before editing. Not for planning questions with no concrete code change on the table.'
---

# Change Proposal

When proposing non-trivial changes, make your reasoning visible before acting. The user should see what will change, why, and what alternatives were considered:before a single file is edited.
When proposing non-trivial changes, make your reasoning visible before acting. The user should see what will change, why, and what alternatives were considered, before a single file is edited.

Follow [writing-voice](../writing-voice/SKILL.md) for prose sections.

Expand All @@ -15,7 +15,7 @@ Follow [writing-voice](../writing-voice/SKILL.md) for prose sections.
- Changes span 3+ files (show the dependency graph)
- Architecture or ownership shifts (show before/after diagrams)
- Lifecycle or data flow changes (show the flow)
- The user asks "what do you think?" or "how should we do this?"
- The user asks "what do you think?" or "how should we do this?" about a concrete code change

For trivial changes (typo fix, single-line edit, obvious bug), skip this and just do it.

Expand Down Expand Up @@ -72,11 +72,7 @@ auth ──signOut()──→ workspace.current.dispose()
workspace is a reactive slot ← auth owns lifecycle
```

When to use which diagram type:
- **Ownership diagrams**: Who controls what (arrows show control flow)
- **Layer diagrams**: Stacked boxes for architectural layers
- **Flow diagrams**: Data or control moving between components
- **Journey diagrams**: Evolution from attempt A → B → C
Default to an ownership diagram: who controls what, with arrows showing control flow. Reach for stacked layers, a data-flow shape, or an attempt A → B → C journey only when ownership is not the question being decided.

### 3. Comparison Tables

Expand Down
10 changes: 5 additions & 5 deletions .agents/skills/control-flow/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: control-flow
description: 'Control flow: early returns, guard clauses, linearizing nested logic. Use for "simplify this", "flatten these conditions", "too many nested ifs".'
description: 'Control flow: early returns, guard clauses, linearizing nested logic. Use for "flatten these conditions", "too many nested ifs", "linearize this try-catch", or handlers mixing throw and return. For a broad "simplify this" pass over a diff or package, use collapse-pass instead.'
metadata:
author: epicenter
version: '1.0'
Expand Down Expand Up @@ -131,9 +131,9 @@ async ({ body, status }) => {

The transformation follows the same human reasoning pattern:

1. **Try the risky thing** : wrap only what can fail
2. **Check if it failed** : early return with the appropriate error
3. **Continue with the happy path** : the rest of the function assumes success
1. **Try the risky thing**: wrap only what can fail
2. **Check if it failed**: early return with the appropriate error
3. **Continue with the happy path**: the rest of the function assumes success

This eliminates the nesting, makes `return` vs `throw` consistent, and separates the error boundary from the safe code that follows it.

Expand Down Expand Up @@ -161,7 +161,7 @@ async ({ body, status }) => {
});
if (error) return status('Bad Gateway', error.message);

// Happy path : all guards passed
// Happy path: all guards passed
return toServerSentEventsResponse(stream);
};
```
Expand Down
106 changes: 22 additions & 84 deletions .agents/skills/error-handling/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Load these on demand based on what you're working on:
- If working with **toast notifications for errors** (`toastOnError`, `extractErrorMessage` in UI), read [references/toast-on-error.md](references/toast-on-error.md)
- If working with **real-world codebase examples and wrapping scenario guidelines**, read [references/real-world-examples.md](references/real-world-examples.md)
- If working with **HTTP route handlers and status-response error conversion**, read [references/http-handlers.md](references/http-handlers.md)
- If working with **workspace actions** (`defineQuery` / `defineMutation` : when to throw vs. return `Err`, how remote callers see your errors, `ActionFailed` semantics), read [../workspace-api/references/action-return-shapes.md](../workspace-api/references/action-return-shapes.md)
- If working with **workspace actions** (`defineQuery` / `defineMutation`: when to throw vs. return `Err`, how remote callers see your errors, `ActionFailed` semantics), read [../workspace-api/references/action-return-shapes.md](../workspace-api/references/action-return-shapes.md)

## Use trySync/tryAsync Instead of try-catch for Graceful Error Handling

Expand All @@ -47,7 +47,6 @@ const { data, error } = trySync({
},
catch: (e) => {
// Gracefully handle parsing/validation errors
console.log('Using default configuration');
return Ok(defaultConfig); // Return Ok with fallback
},
});
Expand All @@ -57,11 +56,9 @@ await tryAsync({
try: async () => {
const child = new Child(session.pid);
await child.kill();
console.log(`Process killed successfully`);
},
catch: (e) => {
// Gracefully handle the error
console.log(`Process was already terminated`);
// Process was already terminated; nothing to do
return Ok(undefined); // Return Ok(undefined) for void functions
},
});
Expand All @@ -72,7 +69,7 @@ const syncResult = trySync({
catch: (error) => {
// For recoverable errors, return Ok with fallback value
return Ok('fallback-value');
// For unrecoverable errors, pass the raw cause : the constructor handles extractErrorMessage
// For unrecoverable errors, pass the raw cause: the constructor handles extractErrorMessage
return CompletionError.ConnectionFailed({ cause: error });
},
});
Expand Down Expand Up @@ -135,72 +132,24 @@ if (error) {
}
```

### Examples
## Consuming Result values: destructure `error` explicitly

```typescript
// SYNCHRONOUS: JSON parsing with fallback
const { data: config } = trySync({
try: () => JSON.parse(configString),
catch: (e) => {
console.log('Invalid config, using defaults');
return Ok({ theme: 'dark', autoSave: true });
},
});

// SYNCHRONOUS: File system check
const { data: exists } = trySync({
try: () => fs.existsSync(path),
catch: () => Ok(false), // Assume doesn't exist if check fails
});

// ASYNCHRONOUS: Graceful process termination
await tryAsync({
try: async () => {
await process.kill();
},
catch: (e) => {
console.log('Process already dead, continuing...');
return Ok(undefined);
},
});

// ASYNCHRONOUS: File operations with fallback
const { data: content } = await tryAsync({
try: () => readFile(path),
catch: (e) => {
console.log('File not found, using default');
return Ok('default content');
},
});

// EITHER: Error propagation (works with both)
// Pass the raw caught error as cause : the defineErrors constructor calls extractErrorMessage
const { data, error } = await tryAsync({
try: () => criticalOperation(),
catch: (error) =>
CompletionError.ConnectionFailed({ cause: error }),
});
if (error) return Err(error);
```

## Consuming Result values : destructure `error` explicitly

When reading a `Result<T, E>` that a library (or your own code) returns
: like `table.get(id)`, `tryAsync(...)`, or a service method : **always
When reading a `Result<T, E>` that a library (or your own code) returns,
like `table.get(id)`, `tryAsync(...)`, or a service method, **always
destructure both `data` and `error` and check `error` on its own line**,
even when both paths should produce the same action.

```typescript
// ✅ GOOD : error is destructured and checked explicitly
// ✅ GOOD: error is destructured and checked explicitly
const { data: row, error } = table.get(id);
if (error) {
console.warn('[context] corrupted row:', error.message);
log.warn(error);
return null;
}
if (row === null) return null; // legitimate absence
use(row); // row: TRow

// ❌ BAD : relies on "data is null if error exists" by coincidence
// ❌ BAD: relies on "data is null if error exists" by coincidence
const { data: row } = table.get(id); // error silently swallowed
if (row === null) return null;
use(row);
Expand All @@ -220,10 +169,10 @@ Why:

If both cases *genuinely* produce the same action (no log, no toast,
no retry, no distinction worth writing down), one combined condition
is fine : as long as `error` is still destructured:
is fine, as long as `error` is still destructured:

```typescript
// ✅ OK : error destructured, both cases deliberately collapsed
// ✅ OK: error destructured, both cases deliberately collapsed
const { data: row, error } = table.get(id);
if (error || row === null) continue; // skip in both cases
use(row);
Expand Down Expand Up @@ -251,36 +200,25 @@ if (row === null) {
use(row);
```

This is the form to prefer by default : collapse back only when
This is the form to prefer by default; collapse back only when
there's truly nothing distinct to say.

## When to Use trySync vs tryAsync vs try-catch

- **Use trySync when**:
- Working with synchronous operations (JSON parsing, validation, calculations)
- You need immediate Result types without promises
- Handling errors in synchronous utility functions
- Working with filesystem sync operations
## When traditional try-catch is still right

- **Use tryAsync when**:
- Working with async/await operations
- Making network requests or database calls
- Reading/writing files asynchronously
- Any operation that returns a Promise
`trySync` covers synchronous work and `tryAsync` covers anything returning a Promise. Keep a traditional try-catch only when:

- **Use traditional try-catch when**:
- In module-level initialization code where you can't await
- For simple fire-and-forget operations
- When you're outside of a function context
- When integrating with code that expects thrown exceptions
- In module-level initialization code where you can't await
- For simple fire-and-forget operations
- When you're outside of a function context
- When integrating with code that expects thrown exceptions

## Logging errors

Typed errors are structured values, so they're also what the `wellcrafted/logger` wants. `log.warn` / `log.error` take a typed error unary : no message argument, no format string. The error owns its message, and the log sink gets the full object (name, fields, cause) alongside it.
Typed errors are structured values, so they're also what the `wellcrafted/logger` wants. `log.warn` / `log.error` take a typed error unary: no message argument, no format string. The error owns its message, and the log sink gets the full object (name, fields, cause) alongside it.

### The canonical pattern

Mint the typed error inside `catch:`, then branch on the Result and log inside the branch. The caller picks the level (`.warn` for recoverable, `.error` for loud) at the call site : matching Rust's `tracing::warn!(?err)` convention, where level lives at the call site and never on the error variant.
Mint the typed error inside `catch:`, then branch on the Result and log inside the branch. The caller picks the level (`.warn` for recoverable, `.error` for loud) at the call site, matching Rust's `tracing::warn!(?err)` convention, where level lives at the call site and never on the error variant.

```ts
import { createLogger } from 'wellcrafted/logger';
Expand Down Expand Up @@ -309,11 +247,11 @@ Most epicenter call sites need the Ok branch's data locally, so they branch firs

`log.warn` / `log.error` accept either the raw tagged error (`result.error` after narrowing) or the `Err`-wrapped factory output (`MyError.Variant({ ... })`) and unwrap structurally.

For the rarer Result-chain shape (`tryAsync(...).then(...)` where the Result flows out of the function), `tapErr(log.warn)` from `wellcrafted/result` is the combinator : see the logging SKILL's See also section.
For the rarer Result-chain shape (`tryAsync(...).then(...)` where the Result flows out of the function), `tapErr(log.warn)` from `wellcrafted/result` is the combinator; see the logging SKILL's See also section.

### Why no `log.error(message, error)`?

Level is context-dependent (same error can be `warn` on a retry, `error` on the last attempt) and message lives on the error variant. That's the whole point of `defineErrors` : the variant's `message:` template encodes the "what operation failed" clause. Duplicating it at the call site would drift and rot.
Level is context-dependent (same error can be `warn` on a retry, `error` on the last attempt) and message lives on the error variant. That's the whole point of `defineErrors`: the variant's `message:` template encodes the "what operation failed" clause. Duplicating it at the call site would drift and rot.

### Testing with `memorySink`

Expand Down
32 changes: 12 additions & 20 deletions .agents/skills/github-issues/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ Thanks again for catching it!

## Opening Pattern

Always start with a personal greeting using the user's GitHub handle:
Open your first reply on a thread with a personal greeting using the user's GitHub handle (ongoing back-and-forth can skip it):

- "Hey @username, thank you for the issue"
- "Hey everyone, thanks for the notice!"
Expand Down Expand Up @@ -98,23 +98,22 @@ For complex issues, offer direct help:

- "If you have time, I would love to hop on a call with you, and we can debug this together"
- "Let's hop on a call sometime in the coming days, and I'll debug it with you"
- Always include cal.com link: "https://cal.com/epicenter/whispering"
- Add availability: "I'm free as early as tomorrow"
- When offering a call, include the cal.com link: "https://cal.com/epicenter/whispering"
- Do not claim specific availability ("I'm free as early as tomorrow") you cannot verify

### 4. Discord Promotion

When appropriate, mention Discord:
Occasionally, when the reporter seems invested in the project, invite them to Discord:

- "PS: I've also recently created a Discord group, and I'd love for you to join! You can ping me directly for more features."
- Include link: "https://go.epicenter.so/discord"

### 5. Follow-up Questions

Ask clarifying questions to understand the issue better:
Ask clarifying questions to understand the issue better. Ask for whatever is missing: exact version, platform, and the smallest reproduction.

- "To clarify, could you confirm that this issue persists even with the latest v7.1.0 installer?"
- "Did you ever get a popup to grant permission to access recording devices?"
- "Does this happen when you make recordings for more than 4 seconds?"
- "To clarify, could you confirm that this issue persists even with the latest installer?"
- "Could you share which OS and version you're on?"

### 6. Closing

Expand All @@ -130,7 +129,7 @@ End with gratitude:
### Feature Implementation Response

```
Hey @username, thank you for the issue, and good news! [Whispering v7.1.0](link) now includes the [feature]! Thank you for the inspiration.
Hey @username, thank you for the issue, and good news! [The latest release](link) now includes the [feature]! Thank you for the inspiration.

[Brief description of how it works]

Expand All @@ -142,11 +141,11 @@ https://go.epicenter.so/discord
### Debugging Response

```
Hey @username, so sorry to hear this! I apologize for the delayed response; I was finalizing [the latest release v7.1.0](link).
Hey @username, so sorry to hear this! I apologize for the delayed response; I was finalizing [the latest release](link).

To clarify, could you confirm that this issue persists even with the latest v7.1.0 installer?
To clarify, could you confirm that this issue persists even with the latest installer?

If you have time, I would love to hop on a call with you, and we can debug this together. You can book a meeting with me using my cal.com link right here, I'm free as early as tomorrow:
If you have time, I would love to hop on a call with you, and we can debug this together. You can book a meeting with me using my cal.com link right here:

https://cal.com/epicenter/whispering

Expand All @@ -173,13 +172,6 @@ Let me know if you want a confirmation prompt before clearing.

## Writing Style Notes

See [writing-voice](../writing-voice/SKILL.md) for punctuation and tone guidelines.

- Use casual, approachable language
- Be genuinely enthusiastic about user contributions
- Reference specific users and give credit
- Link to relevant issues, releases, or commits
- Keep responses personal and conversational
- Avoid corporate or overly formal language
- Reference specific users and give credit; link to relevant issues, releases, or commits
- PR comments can be brief: ongoing discussions don't need full greetings/closings
- Match the energy: short question gets short answer, detailed report gets detailed response
4 changes: 2 additions & 2 deletions .agents/skills/govuk-style/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: govuk-style
description: Write and edit in GOV.UK / GDS house styleplain English, active voice, front-loaded content, sentence case, and no bold or italics for emphasis. Use when writing or editing reports, research write-ups, guidance, documentation, summaries, or any prose where clarity and accessibility matter.
description: 'Write and edit in GOV.UK / GDS house style: plain English, active voice, front-loaded content, sentence case, and no bold or italics for emphasis. Use when the user asks for GOV.UK or GDS style, or when writing reports, research write-ups, or guidance for external readers. Not for repo docs, UI strings, commit messages, or code comments; those follow writing-voice.'
user-invokable: true
args:
- name: target
Expand All @@ -10,7 +10,7 @@ args:

Open the content up so anyone can understand it the first time they read it — without losing any of the substance, nuance or precision. The goal is to open up, not to dumb down. This skill applies the GOV.UK style guide and the Government Digital Service (GDS) content design principles. It is based on the GOV.UK A to Z style guide and writing guidelines (guidance.publishing.service.gov.uk).

Apply it to reports, research write-ups, guidance and any prose meant to be read. When you write a report, default to this style. When you brief a research agent, pass this skill so its report follows the same style.
Apply it to reports, research write-ups and guidance written for external readers. When you write a report, default to this style. When you brief a research agent, pass this skill so its report follows the same style. Repo documentation, UI strings, commit messages and code comments follow writing-voice instead.

## Content design principles

Expand Down
Loading