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
7 changes: 7 additions & 0 deletions .cursor/skills/mcp-docs-search/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,10 @@ description: Use MCP tools for docs lookup and web search in Lucky. Use when nee
- Prefer Context7 for official library docs before assuming API behavior.
- Use Tavily for errors, version-specific notes, or when Context7 doesn’t cover the topic.
- Don’t force MCPs; use the one that fits the task.

## OpenCode-specific guidance

- For OpenCode plugin, command, skill, or MCP configuration work, prefer official OpenCode docs first.
- Prefer local SDK and type inspection before broad web search when authoring OpenCode plugins or config.
- Use the official OpenCode ecosystem and community plugin index when evaluating third-party plugins.
- Treat community plugin marketing pages as discovery only; confirm install syntax from official docs or the plugin repository before wiring it into Lucky.
104 changes: 104 additions & 0 deletions .cursor/skills/opencode-lucky-workflows/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
---
name: opencode-lucky-workflows
description: Use for Lucky OpenCode setup, plugin and command expectations, local versus host-local config split, and server-do-luk remote attach or recovery.
---

# OpenCode Lucky Workflows

Use this skill when the task involves OpenCode configuration, plugins, commands, skills, local verification, or `server-do-luk` remote attach for Lucky.

## Scope

- Repo-local OpenCode behavior in `opencode.jsonc`
- Repo-local plugins under `.opencode/plugins`
- Project skill bridge under `.opencode/skills`
- Host-local config in `~/.config/opencode/opencode.jsonc`
- Remote attach flow for `server-do-luk`

## Config Split

- Repo-local:
- `opencode.jsonc`
- `.opencode/plugins`
- `.opencode/skills`
- helper scripts under `scripts/opencode-*`
- Host-local:
- `~/.config/opencode/opencode.jsonc`
- provider auth and MCP credentials
- host-only scripts and package cache
- Remote host-local:
- `/home/luk-server/.config/opencode/opencode.jsonc`
- `/home/luk-server/.local/bin/opencode-lucky-serve`

Do not commit host-local auth, tokens, or MCP credentials.

## Expected Plugin and Command Behavior

- Repo-local plugins enforce Lucky policy, context bootstrap, and doc reminders.
- Dangerous shell/file actions must be blocked, not merely documented.
- OpenCode commands must mirror `.cursor/COMMANDS.md`:
- `verify`
- `e2e`
- `db`

## Skill and MCP Expectations

- Prefer `.opencode/skills` as the project bridge for OpenCode.
- Keep host-local MCP/auth state out of git.
- Remote `server-do-luk` stays on the portable core MCP set:
- `serena`
- `context7`

## Validation Flow

Run this sequence after OpenCode changes:

```bash
./scripts/opencode-sync-project-skills.sh
./scripts/opencode-verify.sh
./scripts/opencode-verify.sh --remote server-do-luk
```

Useful direct checks:

```bash
opencode debug config
opencode debug skill
opencode mcp list
opencode run --format json "Say only OK"
```

## `server-do-luk` Attach

- Sync global skills before attach when host-local skills changed:

```bash
./scripts/opencode-sync-server-do-luk-skills.sh
```

- Attach through SSH tunnel:

```bash
./scripts/opencode-attach-server-do-luk.sh
```

- Override the remote working directory when validating a remote worktree:

```bash
OPENCODE_REMOTE_DIR=/home/luk-server/Lucky/.worktrees/<branch> \
./scripts/opencode-attach-server-do-luk.sh
```

## Auth Recovery

If the remote model auth is stale:

```bash
ssh server-do-luk 'cd /home/luk-server/Lucky && ~/.opencode/bin/opencode providers login -p openai'
```

## Guardrails

- Prefer isolated worktrees for repo changes.
- Keep README and CHANGELOG aligned with shipped tooling behavior.
- Use exact failing commands and signatures when verification breaks.
3 changes: 3 additions & 0 deletions .opencode/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules/
package.json
bun.lock
64 changes: 64 additions & 0 deletions .opencode/plugins/lucky-context.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { execFileSync } from 'node:child_process'
import fs from 'node:fs'
import path from 'node:path'

function git(cwd, ...args) {
try {
return execFileSync('git', args, { cwd, encoding: 'utf8' }).trim()
} catch {
return ''
}
}

function readSnippet(filePath, maxLines = 8) {
try {
return fs
.readFileSync(filePath, 'utf8')
.split(/\r?\n/)
.filter(Boolean)
.slice(0, maxLines)
.join('\n')
} catch {
return ''
}
}

export default async function luckyContext(ctx) {
const repoRoot = git(ctx.directory, 'rev-parse', '--show-toplevel') || path.resolve(ctx.directory)
const branch = git(repoRoot, 'branch', '--show-current') || 'unknown'
const worktreeRoot = path.resolve(ctx.worktree || ctx.directory)
const currentState = readSnippet(path.join(repoRoot, '.serena/memories/current-state.md'))
const nextPriorities = readSnippet(path.join(repoRoot, '.serena/memories/next-priorities.md'))

return {
'experimental.chat.system.transform': async (_input, output) => {
output.system.push(
[
'Lucky bootstrap:',
'- Read AGENTS.md before acting.',
'- Read .serena/memories/current-state.md and .serena/memories/next-priorities.md.',
'- Prefer isolated worktrees over the primary checkout.',
'- Use PR-only workflow; never push directly to main.',
'- Update README.md and CHANGELOG.md for behavior or setup changes.',
'- Run verification before completion and record failing commands exactly.',
].join('\n'),
)
},
'experimental.session.compacting': async (_input, output) => {
const changedFiles = git(repoRoot, 'status', '--short') || 'clean'
output.context.push(
[
`Branch: ${branch}`,
`Worktree: ${worktreeRoot}`,
`Repository: ${repoRoot}`,
currentState ? `Current state:\n${currentState}` : '',
nextPriorities ? `Next priorities:\n${nextPriorities}` : '',
`Changed files:\n${changedFiles}`,
'Preserve unresolved verification failures with exact command lines and error signatures.',
]
.filter(Boolean)
.join('\n\n'),
)
},
}
}
76 changes: 76 additions & 0 deletions .opencode/plugins/lucky-doc-reminders.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { execFileSync } from 'node:child_process'
import path from 'node:path'

const IMPACTED_PATHS = [
/^packages\//,
/^scripts\//,
/^\.github\/workflows\//,
/^docker-compose(?:\.[^/]+)?\.ya?ml$/,
/^\.env\.example$/,
/^opencode\.jsonc$/,
/^AGENTS\.md$/,
/^docs\//,
/^\.opencode\//,
]

function git(cwd, ...args) {
try {
return execFileSync('git', args, { cwd, encoding: 'utf8' }).trim()
} catch {
return ''
}
}

function changedFiles(repoRoot) {
const output = git(repoRoot, 'status', '--short')
if (!output) return []
return output
.split(/\r?\n/)
.map((line) => line.slice(3).trim())
.filter(Boolean)
}

function hasDocsTouch(files) {
return files.includes('README.md') || files.includes('CHANGELOG.md')
}

function hasRelevantChanges(files) {
return files.some((file) => IMPACTED_PATHS.some((pattern) => pattern.test(file)))
}

function isVerifyLike(command) {
return /npm run (verify|test|build|lint|type:check)|vitest|jest|playwright|pnpm test|bun test/i.test(
command,
)
}

function isFailure(output) {
const exitCode = Number(output.metadata?.exitCode ?? output.metadata?.code ?? 0)
if (Number.isFinite(exitCode) && exitCode !== 0) return true
return /error|failed/i.test(output.title || '')
}

function appendNote(output, note) {
if ((output.output || '').includes(note)) return
output.output = [output.output, note].filter(Boolean).join('\n\n')
}

export default async function luckyDocReminders(ctx) {
const repoRoot = git(ctx.directory, 'rev-parse', '--show-toplevel') || path.resolve(ctx.directory)

return {
'tool.execute.after': async (input, output) => {
const files = changedFiles(repoRoot)
if (hasRelevantChanges(files) && !hasDocsTouch(files)) {
appendNote(output, '[OpenCode reminder] README.md and CHANGELOG.md are still untouched.')
}

if (input.tool === 'bash' && isVerifyLike(input.args?.command || '') && isFailure(output)) {
appendNote(
output,
'[OpenCode reminder] Record the failing command and error signature before moving on.',
)
}
},
}
}
Loading
Loading