opencodekit 0.15.9 → 0.15.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +9 -7
- package/dist/template/.opencode/AGENTS.md +7 -2
- package/dist/template/.opencode/agent/build.md +390 -0
- package/dist/template/.opencode/agent/general.md +1 -0
- package/dist/template/.opencode/agent/looker.md +1 -0
- package/dist/template/.opencode/agent/painter.md +218 -0
- package/dist/template/.opencode/agent/plan.md +3 -0
- package/dist/template/.opencode/agent/vision.md +1 -0
- package/dist/template/.opencode/command/edit-image.md +1 -2
- package/dist/template/.opencode/command/generate-icon.md +1 -2
- package/dist/template/.opencode/command/generate-image.md +1 -2
- package/dist/template/.opencode/command/generate-pattern.md +1 -2
- package/dist/template/.opencode/command/generate-storyboard.md +1 -2
- package/dist/template/.opencode/command/implement.md +136 -10
- package/dist/template/.opencode/command/restore-image.md +1 -2
- package/dist/template/.opencode/dcp.jsonc +1 -15
- package/dist/template/.opencode/memory/observations/2026-01-25-decision-agent-roles-build-orchestrates-general-e.md +14 -0
- package/dist/template/.opencode/memory/observations/2026-01-25-decision-simplified-swarm-helper-tool-to-fix-type.md +20 -0
- package/dist/template/.opencode/memory/observations/2026-01-25-decision-use-beads-as-swarm-board-source-of-truth.md +14 -0
- package/dist/template/.opencode/memory/observations/2026-01-25-learning-user-wants-real-swarm-coordination-guida.md +15 -0
- package/dist/template/.opencode/memory/research/opencode-mcp-bug-report.md +126 -0
- package/dist/template/.opencode/opencode.json +812 -704
- package/dist/template/.opencode/package.json +1 -1
- package/dist/template/.opencode/plans/swarm-protocol.md +123 -0
- package/dist/template/.opencode/plugin/README.md +10 -0
- package/dist/template/.opencode/plugin/swarm-enforcer.ts +297 -0
- package/dist/template/.opencode/skill/supabase-postgres-best-practices/AGENTS.md +1490 -0
- package/dist/template/.opencode/skill/supabase-postgres-best-practices/SKILL.md +57 -0
- package/dist/template/.opencode/skill/supabase-postgres-best-practices/rules/advanced-full-text-search.md +55 -0
- package/dist/template/.opencode/skill/supabase-postgres-best-practices/rules/advanced-jsonb-indexing.md +49 -0
- package/dist/template/.opencode/skill/supabase-postgres-best-practices/rules/conn-idle-timeout.md +46 -0
- package/dist/template/.opencode/skill/supabase-postgres-best-practices/rules/conn-limits.md +44 -0
- package/dist/template/.opencode/skill/supabase-postgres-best-practices/rules/conn-pooling.md +41 -0
- package/dist/template/.opencode/skill/supabase-postgres-best-practices/rules/conn-prepared-statements.md +46 -0
- package/dist/template/.opencode/skill/supabase-postgres-best-practices/rules/data-batch-inserts.md +54 -0
- package/dist/template/.opencode/skill/supabase-postgres-best-practices/rules/data-n-plus-one.md +53 -0
- package/dist/template/.opencode/skill/supabase-postgres-best-practices/rules/data-pagination.md +50 -0
- package/dist/template/.opencode/skill/supabase-postgres-best-practices/rules/data-upsert.md +50 -0
- package/dist/template/.opencode/skill/supabase-postgres-best-practices/rules/lock-advisory.md +56 -0
- package/dist/template/.opencode/skill/supabase-postgres-best-practices/rules/lock-deadlock-prevention.md +68 -0
- package/dist/template/.opencode/skill/supabase-postgres-best-practices/rules/lock-short-transactions.md +50 -0
- package/dist/template/.opencode/skill/supabase-postgres-best-practices/rules/lock-skip-locked.md +54 -0
- package/dist/template/.opencode/skill/supabase-postgres-best-practices/rules/monitor-explain-analyze.md +45 -0
- package/dist/template/.opencode/skill/supabase-postgres-best-practices/rules/monitor-pg-stat-statements.md +55 -0
- package/dist/template/.opencode/skill/supabase-postgres-best-practices/rules/monitor-vacuum-analyze.md +55 -0
- package/dist/template/.opencode/skill/supabase-postgres-best-practices/rules/query-composite-indexes.md +44 -0
- package/dist/template/.opencode/skill/supabase-postgres-best-practices/rules/query-covering-indexes.md +40 -0
- package/dist/template/.opencode/skill/supabase-postgres-best-practices/rules/query-index-types.md +45 -0
- package/dist/template/.opencode/skill/supabase-postgres-best-practices/rules/query-missing-indexes.md +43 -0
- package/dist/template/.opencode/skill/supabase-postgres-best-practices/rules/query-partial-indexes.md +45 -0
- package/dist/template/.opencode/skill/supabase-postgres-best-practices/rules/schema-data-types.md +46 -0
- package/dist/template/.opencode/skill/supabase-postgres-best-practices/rules/schema-foreign-key-indexes.md +59 -0
- package/dist/template/.opencode/skill/supabase-postgres-best-practices/rules/schema-lowercase-identifiers.md +55 -0
- package/dist/template/.opencode/skill/supabase-postgres-best-practices/rules/schema-partitioning.md +55 -0
- package/dist/template/.opencode/skill/supabase-postgres-best-practices/rules/schema-primary-keys.md +61 -0
- package/dist/template/.opencode/skill/supabase-postgres-best-practices/rules/security-privileges.md +54 -0
- package/dist/template/.opencode/skill/supabase-postgres-best-practices/rules/security-rls-basics.md +50 -0
- package/dist/template/.opencode/skill/supabase-postgres-best-practices/rules/security-rls-performance.md +57 -0
- package/dist/template/.opencode/skill/swarm-coordination/SKILL.md +405 -0
- package/dist/template/.opencode/tool/swarm-delegate.ts +175 -0
- package/dist/template/.opencode/tool/swarm-helper.ts +164 -0
- package/package.json +1 -1
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Swarm Protocol (Beads-as-Board)
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
|
|
5
|
+
Use `.beads/` as the single source of truth for task state + dependencies, while agents coordinate through structured artifacts (not freeform chatter) and verification gates.
|
|
6
|
+
|
|
7
|
+
## Roles (Strict)
|
|
8
|
+
|
|
9
|
+
- Lead: Owns scope, prioritization, and all Beads state transitions.
|
|
10
|
+
- Planner: Turns intent into Beads tasks + dependency DAG; no code changes.
|
|
11
|
+
- Worker: Executes exactly one claimed task; posts a structured report.
|
|
12
|
+
- Verifier: Independently validates acceptance checks; blocks close on missing evidence.
|
|
13
|
+
|
|
14
|
+
## Board = Beads
|
|
15
|
+
|
|
16
|
+
Canonical state lives in `.beads/issues.jsonl` (tasks) and `.beads/artifacts/<id>/` (task artifacts).
|
|
17
|
+
|
|
18
|
+
- Create tasks: `bd create "Title" -p <1-3>`
|
|
19
|
+
- See unblocked work: `bd ready`
|
|
20
|
+
- Inspect context: `bd show <id>`
|
|
21
|
+
- Claim work: `bd update <id> --status=in_progress`
|
|
22
|
+
- Close work: `bd close <id> --reason="..."` then `bd sync`
|
|
23
|
+
|
|
24
|
+
## Task Artifact Contract
|
|
25
|
+
|
|
26
|
+
Every task MUST have `.beads/artifacts/<id>/spec.md`.
|
|
27
|
+
|
|
28
|
+
Optional supporting files:
|
|
29
|
+
|
|
30
|
+
- `.beads/artifacts/<id>/plan.md`: multi-step implementation plan, files touched, and gates
|
|
31
|
+
- `.beads/artifacts/<id>/review.md`: verifier notes + evidence summary
|
|
32
|
+
|
|
33
|
+
### spec.md (Minimum Template)
|
|
34
|
+
|
|
35
|
+
- Context: why this exists
|
|
36
|
+
- Scope: in / out
|
|
37
|
+
- Constraints: MUST / MUST NOT (security, deps, dist/, etc.)
|
|
38
|
+
- Acceptance Criteria: checkboxes, each with a verification method
|
|
39
|
+
- Evidence Required: exact commands and expected signals (e.g. "typecheck command passes")
|
|
40
|
+
|
|
41
|
+
#### Verification Defaults (Language-Agnostic)
|
|
42
|
+
|
|
43
|
+
Do NOT assume npm/bun/pytest/etc. Prefer this pattern:
|
|
44
|
+
|
|
45
|
+
- `typecheck`: `<command>`
|
|
46
|
+
- `lint`: `<command>`
|
|
47
|
+
- `test`: `<command>`
|
|
48
|
+
- `build` (optional): `<command>`
|
|
49
|
+
|
|
50
|
+
If the repository has a canonical list of commands, reference it (e.g. `.opencode/memory/project/commands.md`).
|
|
51
|
+
|
|
52
|
+
Common stacks (examples only):
|
|
53
|
+
|
|
54
|
+
| Stack | typecheck | lint | test |
|
|
55
|
+
| ------- | ------------------------------------------ | ---------------------------- | ------------------------ |
|
|
56
|
+
| Node/TS | `npm run typecheck` or `bun run typecheck` | `npm run lint` | `bun test` or `npm test` |
|
|
57
|
+
| Python | `python -m mypy .` | `ruff check .` | `pytest` |
|
|
58
|
+
| Go | `go test ./...` (compile+test) | `golangci-lint run` | `go test ./...` |
|
|
59
|
+
| Rust | `cargo check` | `cargo fmt --check` (format) | `cargo test` |
|
|
60
|
+
|
|
61
|
+
## Delegation Packet (Worker Input)
|
|
62
|
+
|
|
63
|
+
Every delegated task MUST include the following envelope:
|
|
64
|
+
|
|
65
|
+
- TASK: `<id> - <title>`
|
|
66
|
+
- EXPECTED OUTCOME: measurable end state
|
|
67
|
+
- REQUIRED TOOLS: e.g. `read`, `grep`, `lsp`, `bash`
|
|
68
|
+
- MUST DO: e.g. LSP-before-edits, run typecheck, run lint
|
|
69
|
+
- MUST NOT DO: e.g. no new deps, no dist/ edits, no git push
|
|
70
|
+
- ACCEPTANCE CHECKS: list of commands + pass criteria
|
|
71
|
+
- CONTEXT: links to `.beads/artifacts/<id>/spec.md` + relevant files
|
|
72
|
+
|
|
73
|
+
### Helper Tool
|
|
74
|
+
|
|
75
|
+
If available, use the custom tool `swarm-delegate` to generate (and optionally write) a delegation packet:
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
swarm -
|
|
79
|
+
delegate({
|
|
80
|
+
bead_id: "opencodekit-template-xyz",
|
|
81
|
+
expected_outcome: "<measurable end state>",
|
|
82
|
+
required_tools: "read, grep, lsp, bash",
|
|
83
|
+
must_do: "LSP before edits, run project verification commands",
|
|
84
|
+
must_not_do: "no new deps, don't edit dist/",
|
|
85
|
+
acceptance_checks: "typecheck: <command>, lint: <command>, test: <command>",
|
|
86
|
+
context: "See .beads/artifacts/<id>/spec.md",
|
|
87
|
+
write: true,
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Worker Report (Worker Output)
|
|
92
|
+
|
|
93
|
+
Workers MUST respond with a structured report:
|
|
94
|
+
|
|
95
|
+
- Result: done / blocked / needs replan
|
|
96
|
+
- Changes: file list + what changed (with file references)
|
|
97
|
+
- Verification: commands run + pass/fail summary
|
|
98
|
+
- Risks/Notes: edge cases, follow-ups
|
|
99
|
+
- Confidence: high / medium / low
|
|
100
|
+
|
|
101
|
+
## Gates (Non-Negotiable)
|
|
102
|
+
|
|
103
|
+
- Planning gate: `.beads/artifacts/<id>/spec.md` exists with acceptance criteria BEFORE implementation starts
|
|
104
|
+
- Execution gate: worker provides verification evidence BEFORE close
|
|
105
|
+
- Review gate (risk-based): verifier signs off in `review.md` BEFORE close
|
|
106
|
+
|
|
107
|
+
## Replan Triggers
|
|
108
|
+
|
|
109
|
+
Immediately stop execution and return to planning if:
|
|
110
|
+
|
|
111
|
+
- Scope expands to 4+ files unexpectedly
|
|
112
|
+
- Requirement ambiguity changes implementation choice
|
|
113
|
+
- Two-strike tool failures
|
|
114
|
+
- New dependency or `.opencode/` structure change would be needed
|
|
115
|
+
|
|
116
|
+
## Lead Checklist
|
|
117
|
+
|
|
118
|
+
1. `bd ready` -> pick task -> `bd show <id>`
|
|
119
|
+
2. Ensure `.beads/artifacts/<id>/spec.md` has acceptance checks
|
|
120
|
+
3. `bd update <id> --status=in_progress`
|
|
121
|
+
4. Delegate with the packet format
|
|
122
|
+
5. Require worker report + evidence
|
|
123
|
+
6. `bd close <id> --reason="..."` + `bd sync`
|
|
@@ -80,6 +80,16 @@ Session 3: read_session("previous") → Tests (90k) → close
|
|
|
80
80
|
- High-priority or in-progress TODOs → Inject continuation prompt
|
|
81
81
|
- Low-priority pending TODOs → OS notification only (no forced continuation)
|
|
82
82
|
|
|
83
|
+
### swarm-enforcer.ts
|
|
84
|
+
|
|
85
|
+
**Swarm protocol guardrails (Beads-as-board)** - nudges agents to follow the Beads-first workflow.
|
|
86
|
+
|
|
87
|
+
- Treats `.beads/` as the single source of truth for the swarm task board
|
|
88
|
+
- If user expresses implementation intent and no task is `in_progress`, injects a nudge to use `bd ready/show/update`
|
|
89
|
+
- If there are `in_progress` tasks but `.beads/artifacts/<id>/spec.md` is missing, nudges to create it before implementation
|
|
90
|
+
- On `file.edited`, warns if code is being changed without a claimed task or with missing `spec.md`
|
|
91
|
+
- On `session.idle`, reminds to `bd close` + `bd sync` when work is done
|
|
92
|
+
|
|
83
93
|
### notification.ts
|
|
84
94
|
|
|
85
95
|
**Session completion alerts** - sends native notifications when AI finishes.
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Swarm Enforcer Plugin
|
|
3
|
+
*
|
|
4
|
+
* Beads is the single source of truth for the swarm board.
|
|
5
|
+
* This plugin nudges agents to:
|
|
6
|
+
* - Claim a Beads task before making code changes
|
|
7
|
+
* - Ensure `spec.md` exists for in-progress tasks
|
|
8
|
+
* - Close/sync in-progress work at session end
|
|
9
|
+
*
|
|
10
|
+
* This plugin is intentionally non-destructive: it never runs `bd update/close/sync`.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import fsPromises from "node:fs/promises";
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
import type { Plugin } from "@opencode-ai/plugin";
|
|
16
|
+
|
|
17
|
+
type BeadsIssue = {
|
|
18
|
+
id: string;
|
|
19
|
+
title?: string;
|
|
20
|
+
status?: string;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const BEADS_DIR = ".beads";
|
|
24
|
+
const ISSUES_FILE = "issues.jsonl";
|
|
25
|
+
|
|
26
|
+
const CODE_EXTENSIONS = [
|
|
27
|
+
".ts",
|
|
28
|
+
".tsx",
|
|
29
|
+
".js",
|
|
30
|
+
".jsx",
|
|
31
|
+
".mjs",
|
|
32
|
+
".cjs",
|
|
33
|
+
".py",
|
|
34
|
+
".go",
|
|
35
|
+
".rs",
|
|
36
|
+
".java",
|
|
37
|
+
".c",
|
|
38
|
+
".cpp",
|
|
39
|
+
".h",
|
|
40
|
+
".hpp",
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
const WORK_INTENT_PATTERNS = [
|
|
44
|
+
/\b(implement|fix|refactor|add|remove|delete|update|change|modify|create|build)\b/i,
|
|
45
|
+
/\b(edit|patch)\b/i,
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
function looksLikeWorkIntent(text: string): boolean {
|
|
49
|
+
return WORK_INTENT_PATTERNS.some((p) => p.test(text));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function isCodeFile(filePath: string): boolean {
|
|
53
|
+
return CODE_EXTENSIONS.some((ext) => filePath.endsWith(ext));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function isIgnoredPath(repoDir: string, filePath: string): boolean {
|
|
57
|
+
const absPath = path.isAbsolute(filePath)
|
|
58
|
+
? filePath
|
|
59
|
+
: path.join(repoDir, filePath);
|
|
60
|
+
const rel = path.relative(repoDir, absPath);
|
|
61
|
+
|
|
62
|
+
// Outside repo: ignore
|
|
63
|
+
if (rel.startsWith("..")) return true;
|
|
64
|
+
|
|
65
|
+
const normalized = rel.replace(/\\/g, "/");
|
|
66
|
+
return (
|
|
67
|
+
normalized.startsWith("node_modules/") ||
|
|
68
|
+
normalized.startsWith("dist/") ||
|
|
69
|
+
normalized.startsWith(".beads/") ||
|
|
70
|
+
normalized.startsWith(".git/")
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function summarizeIssues(issues: BeadsIssue[], limit = 5): string {
|
|
75
|
+
return issues
|
|
76
|
+
.slice(0, limit)
|
|
77
|
+
.map((i) => `${i.id}${i.title ? `: ${i.title}` : ""}`)
|
|
78
|
+
.join("\n");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function readIssuesJsonl(repoDir: string): Promise<BeadsIssue[]> {
|
|
82
|
+
const issuesPath = path.join(repoDir, BEADS_DIR, ISSUES_FILE);
|
|
83
|
+
|
|
84
|
+
let content: string;
|
|
85
|
+
try {
|
|
86
|
+
content = await fsPromises.readFile(issuesPath, "utf-8");
|
|
87
|
+
} catch {
|
|
88
|
+
return [];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const issues: BeadsIssue[] = [];
|
|
92
|
+
const lines = content.split(/\r?\n/);
|
|
93
|
+
for (const line of lines) {
|
|
94
|
+
const trimmed = line.trim();
|
|
95
|
+
if (!trimmed) continue;
|
|
96
|
+
try {
|
|
97
|
+
const parsed = JSON.parse(trimmed);
|
|
98
|
+
if (parsed && typeof parsed.id === "string") {
|
|
99
|
+
issues.push({
|
|
100
|
+
id: parsed.id,
|
|
101
|
+
title: typeof parsed.title === "string" ? parsed.title : undefined,
|
|
102
|
+
status: typeof parsed.status === "string" ? parsed.status : undefined,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
} catch {
|
|
106
|
+
// Ignore malformed JSONL lines
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return issues;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function specExists(repoDir: string, issueId: string): Promise<boolean> {
|
|
114
|
+
const specPath = path.join(
|
|
115
|
+
repoDir,
|
|
116
|
+
BEADS_DIR,
|
|
117
|
+
"artifacts",
|
|
118
|
+
issueId,
|
|
119
|
+
"spec.md",
|
|
120
|
+
);
|
|
121
|
+
try {
|
|
122
|
+
await fsPromises.access(specPath);
|
|
123
|
+
return true;
|
|
124
|
+
} catch {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function buildNudge(params: {
|
|
130
|
+
inProgress: BeadsIssue[];
|
|
131
|
+
missingSpec: BeadsIssue[];
|
|
132
|
+
}): string {
|
|
133
|
+
const { inProgress, missingSpec } = params;
|
|
134
|
+
|
|
135
|
+
if (inProgress.length === 0) {
|
|
136
|
+
return `
|
|
137
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
138
|
+
⚡ [SWARM PROTOCOL]
|
|
139
|
+
|
|
140
|
+
Beads is the swarm board. Before any code changes:
|
|
141
|
+
|
|
142
|
+
1) Pick a task: \`bd ready\` (or \`bd list\`)
|
|
143
|
+
2) Inspect: \`bd show <id>\`
|
|
144
|
+
3) Claim: \`bd update <id> --status=in_progress\`
|
|
145
|
+
|
|
146
|
+
Then proceed with work and collect verification evidence.
|
|
147
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
148
|
+
`;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (missingSpec.length > 0) {
|
|
152
|
+
return `
|
|
153
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
154
|
+
⚡ [SWARM PROTOCOL]
|
|
155
|
+
|
|
156
|
+
In-progress Beads exist, but \`spec.md\` is missing for:
|
|
157
|
+
|
|
158
|
+
${summarizeIssues(missingSpec)}
|
|
159
|
+
|
|
160
|
+
Create \`.beads/artifacts/<id>/spec.md\` before implementation.
|
|
161
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
162
|
+
`;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return "";
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export const SwarmEnforcer: Plugin = async ({ client, directory }) => {
|
|
169
|
+
const repoDir = directory || process.cwd();
|
|
170
|
+
let lastStateAt = 0;
|
|
171
|
+
let cachedInProgress: BeadsIssue[] = [];
|
|
172
|
+
let cachedMissingSpec: BeadsIssue[] = [];
|
|
173
|
+
|
|
174
|
+
const refreshState = async () => {
|
|
175
|
+
const now = Date.now();
|
|
176
|
+
if (now - lastStateAt < 1500) return;
|
|
177
|
+
lastStateAt = now;
|
|
178
|
+
|
|
179
|
+
const issues = await readIssuesJsonl(repoDir);
|
|
180
|
+
const inProgress = issues.filter((i) => i.status === "in_progress");
|
|
181
|
+
|
|
182
|
+
const missingSpec: BeadsIssue[] = [];
|
|
183
|
+
for (const issue of inProgress.slice(0, 10)) {
|
|
184
|
+
if (!(await specExists(repoDir, issue.id))) {
|
|
185
|
+
missingSpec.push(issue);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
cachedInProgress = inProgress;
|
|
190
|
+
cachedMissingSpec = missingSpec;
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const showToast = async (
|
|
194
|
+
title: string,
|
|
195
|
+
message: string,
|
|
196
|
+
variant: "info" | "success" | "warning" | "error" = "info",
|
|
197
|
+
) => {
|
|
198
|
+
try {
|
|
199
|
+
await client.tui.showToast({
|
|
200
|
+
body: {
|
|
201
|
+
title,
|
|
202
|
+
message,
|
|
203
|
+
variant,
|
|
204
|
+
duration: variant === "error" ? 8000 : 5000,
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
} catch {
|
|
208
|
+
// If toast is unavailable, fail silently
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
// Nudge early when user expresses implementation intent
|
|
214
|
+
"chat.message": async (input, output) => {
|
|
215
|
+
const { sessionID, messageID } = input;
|
|
216
|
+
const { message, parts } = output;
|
|
217
|
+
if (message.role !== "user") return;
|
|
218
|
+
|
|
219
|
+
const fullText = parts
|
|
220
|
+
.filter((p) => p.type === "text" && !("synthetic" in p && p.synthetic))
|
|
221
|
+
.map((p) => ("text" in p ? p.text : ""))
|
|
222
|
+
.join(" ");
|
|
223
|
+
|
|
224
|
+
if (!looksLikeWorkIntent(fullText)) return;
|
|
225
|
+
|
|
226
|
+
await refreshState();
|
|
227
|
+
|
|
228
|
+
const nudge = buildNudge({
|
|
229
|
+
inProgress: cachedInProgress,
|
|
230
|
+
missingSpec: cachedMissingSpec,
|
|
231
|
+
});
|
|
232
|
+
if (!nudge) return;
|
|
233
|
+
|
|
234
|
+
parts.push({
|
|
235
|
+
id: `swarm-nudge-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
236
|
+
sessionID,
|
|
237
|
+
messageID: messageID || "",
|
|
238
|
+
type: "text",
|
|
239
|
+
text: nudge,
|
|
240
|
+
synthetic: true,
|
|
241
|
+
} as import("@opencode-ai/sdk").Part);
|
|
242
|
+
},
|
|
243
|
+
|
|
244
|
+
// Warn if code gets edited while no task is claimed / spec missing
|
|
245
|
+
"file.edited": async ({ event }) => {
|
|
246
|
+
const filePath = event.properties?.file || event.properties?.path;
|
|
247
|
+
if (!filePath || typeof filePath !== "string") return;
|
|
248
|
+
if (isIgnoredPath(repoDir, filePath)) return;
|
|
249
|
+
|
|
250
|
+
const absPath = path.isAbsolute(filePath)
|
|
251
|
+
? filePath
|
|
252
|
+
: path.join(repoDir, filePath);
|
|
253
|
+
|
|
254
|
+
if (!isCodeFile(absPath)) return;
|
|
255
|
+
|
|
256
|
+
await refreshState();
|
|
257
|
+
|
|
258
|
+
if (cachedInProgress.length === 0) {
|
|
259
|
+
await showToast(
|
|
260
|
+
"Swarm: No task claimed",
|
|
261
|
+
"Beads is the board. Claim a task before code edits (bd ready/show/update).",
|
|
262
|
+
"warning",
|
|
263
|
+
);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (cachedMissingSpec.length > 0) {
|
|
268
|
+
await showToast(
|
|
269
|
+
"Swarm: Missing spec.md",
|
|
270
|
+
`Create .beads/artifacts/<id>/spec.md for: ${cachedMissingSpec
|
|
271
|
+
.slice(0, 3)
|
|
272
|
+
.map((i) => i.id)
|
|
273
|
+
.join(", ")}`,
|
|
274
|
+
"warning",
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
|
|
279
|
+
// Session end reminder: close/sync if tasks still in progress
|
|
280
|
+
"session.idle": async () => {
|
|
281
|
+
await refreshState();
|
|
282
|
+
if (cachedInProgress.length === 0) return;
|
|
283
|
+
|
|
284
|
+
const list = cachedInProgress
|
|
285
|
+
.slice(0, 5)
|
|
286
|
+
.map((i) => i.id)
|
|
287
|
+
.join(", ");
|
|
288
|
+
await showToast(
|
|
289
|
+
"Swarm: Work still in progress",
|
|
290
|
+
`In-progress Beads: ${list}. Close with bd close + bd sync when done.`,
|
|
291
|
+
"info",
|
|
292
|
+
);
|
|
293
|
+
},
|
|
294
|
+
};
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
export default SwarmEnforcer;
|