opencode-thrum-pro 0.1.0
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/assets/commands/thrum-_deviations-protocol.md +87 -0
- package/assets/commands/thrum-_generator-idempotency-protocol.md +15 -0
- package/assets/commands/thrum-_project-rules-protocol.md +18 -0
- package/assets/commands/thrum-_snapshot-protocol.md +265 -0
- package/assets/commands/thrum-_stamp-protocol.md +112 -0
- package/assets/commands/thrum-_verify-before-write-protocol.md +27 -0
- package/assets/commands/thrum-inbox.md +22 -0
- package/assets/commands/thrum-load-context.md +17 -0
- package/assets/commands/thrum-overview.md +17 -0
- package/assets/commands/thrum-prime.md +30 -0
- package/assets/commands/thrum-quickstart.md +14 -0
- package/assets/commands/thrum-reply.md +28 -0
- package/assets/commands/thrum-restart-extended.md +195 -0
- package/assets/commands/thrum-restart.md +293 -0
- package/assets/commands/thrum-send.md +31 -0
- package/assets/commands/thrum-sleep-extended.md +186 -0
- package/assets/commands/thrum-sleep.md +212 -0
- package/assets/commands/thrum-team.md +12 -0
- package/assets/commands/thrum-update-project.md +259 -0
- package/assets/commands/thrum-wait.md +14 -0
- package/assets/skills/adversarial-critique/SKILL.md +242 -0
- package/assets/skills/brainstorm-queue-aware/SKILL.md +167 -0
- package/assets/skills/brainstorm-steward/SKILL.md +181 -0
- package/assets/skills/choosing-subagent-models/SKILL.md +244 -0
- package/assets/skills/closing-findings-while-warm/SKILL.md +104 -0
- package/assets/skills/configure-roles/SKILL.md +229 -0
- package/assets/skills/coordinator-assessing-agent-completion/SKILL.md +205 -0
- package/assets/skills/coordinator-assessing-agent-completion/scan.py +117 -0
- package/assets/skills/coordinator-branch-split-on-block/SKILL.md +168 -0
- package/assets/skills/coordinator-context-monitoring/SKILL.md +674 -0
- package/assets/skills/coordinator-dispatching-work/SKILL.md +288 -0
- package/assets/skills/coordinator-hotpath-merge-gate/SKILL.md +522 -0
- package/assets/skills/coordinator-interacting-with-the-owner/SKILL.md +171 -0
- package/assets/skills/coordinator-maintaining-memory/SKILL.md +89 -0
- package/assets/skills/coordinator-managing-state-and-lifecycle/SKILL.md +192 -0
- package/assets/skills/coordinator-merging-code/SKILL.md +331 -0
- package/assets/skills/coordinator-philosophy-merge-gate/SKILL.md +442 -0
- package/assets/skills/coordinator-plan-reconcile-gate/SKILL.md +198 -0
- package/assets/skills/coordinator-post-restart-sweep/SKILL.md +135 -0
- package/assets/skills/coordinator-running-autonomous-feature-development/SKILL.md +199 -0
- package/assets/skills/coordinator-running-brainstorm-cycles/SKILL.md +654 -0
- package/assets/skills/coordinator-running-review-cycles/SKILL.md +153 -0
- package/assets/skills/coordinator-single-responsibility-merge-gate/SKILL.md +14 -0
- package/assets/skills/diagnosing-fleet-and-daemon/SKILL.md +255 -0
- package/assets/skills/efficient-multi-agent-research/SKILL.md +177 -0
- package/assets/skills/fixing-documentation-errors/SKILL.md +258 -0
- package/assets/skills/implementer-maintaining-memory/SKILL.md +84 -0
- package/assets/skills/implementer-orchestrating-execution/SKILL.md +125 -0
- package/assets/skills/implementer-receiving-dispatch/SKILL.md +127 -0
- package/assets/skills/implementer-receiving-review-feedback/SKILL.md +125 -0
- package/assets/skills/implementer-status-and-handoff/SKILL.md +150 -0
- package/assets/skills/implementer-tdd-and-quality/SKILL.md +99 -0
- package/assets/skills/memory-audit-recover/SKILL.md +153 -0
- package/assets/skills/memory-curate/SKILL.md +139 -0
- package/assets/skills/memory-maintenance/SKILL.md +87 -0
- package/assets/skills/memory-read-discipline/SKILL.md +164 -0
- package/assets/skills/memory-search-advanced/SKILL.md +217 -0
- package/assets/skills/memory-write-discipline/SKILL.md +135 -0
- package/assets/skills/orchestrate/SKILL.md +795 -0
- package/assets/skills/persistent-watcher-archetype/SKILL.md +225 -0
- package/assets/skills/prime-agent/SKILL.md +92 -0
- package/assets/skills/project-hotpath-gate/SKILL.md +255 -0
- package/assets/skills/project-philosophy/SKILL.md +418 -0
- package/assets/skills/project-philosophy/resources/philosophy-template.md +155 -0
- package/assets/skills/project-philosophy/resources/reliability-class-library.md +653 -0
- package/assets/skills/project-setup/SKILL.md +1007 -0
- package/assets/skills/project-setup/resources/implementation-agent.md +826 -0
- package/assets/skills/researcher-answering-queries/SKILL.md +100 -0
- package/assets/skills/researcher-investigating/SKILL.md +152 -0
- package/assets/skills/researcher-maintaining-memory/SKILL.md +176 -0
- package/assets/skills/state-migration/SKILL.md +54 -0
- package/assets/skills/thrum/SKILL.md +161 -0
- package/assets/skills/thrum/resources/ANTI_PATTERNS.md +132 -0
- package/assets/skills/thrum/resources/BOUNDARIES.md +56 -0
- package/assets/skills/thrum/resources/CLI_REFERENCE.md +1066 -0
- package/assets/skills/thrum/resources/IDENTITY.md +49 -0
- package/assets/skills/thrum/resources/LISTENER_PATTERN.md +64 -0
- package/assets/skills/thrum/resources/MESSAGING.md +163 -0
- package/assets/skills/thrum/resources/TMUX_SESSIONS.md +118 -0
- package/assets/skills/thrum/resources/WORKTREES.md +117 -0
- package/assets/skills/thrum-memory/SKILL.md +138 -0
- package/assets/skills/trace-spec-to-plan/SKILL.md +104 -0
- package/assets/skills/trace-spec-to-plan/resources/sweep-agent-prompt.md +57 -0
- package/assets/skills/update-queue/SKILL.md +149 -0
- package/assets/skills/using-the-queue/SKILL.md +172 -0
- package/assets/skills/using-thrum-state/SKILL.md +104 -0
- package/assets/skills/verify-against-plan/SKILL.md +323 -0
- package/assets/skills/verify-against-source/SKILL.md +237 -0
- package/assets/skills/watcher-archetype/SKILL.md +84 -0
- package/assets/skills/writing-durable-markdown/SKILL.md +66 -0
- package/dist/hooks/block-sync-cd.d.ts +9 -0
- package/dist/hooks/block-sync-cd.js +71 -0
- package/dist/hooks/block-sync-cd.test.d.ts +1 -0
- package/dist/hooks/block-sync-cd.test.js +61 -0
- package/dist/hooks/shell-env.d.ts +8 -0
- package/dist/hooks/shell-env.js +22 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +25 -0
- package/dist/installer.d.ts +2 -0
- package/dist/installer.js +86 -0
- package/package.json +49 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: writing-durable-markdown
|
|
3
|
+
description: "Use when creating or editing ANY markdown file - CLAUDE.md, a role preamble, a SKILL.md, a runbook, project state, State.md, a spec, a plan, a README, or docs. Fires on adding a rule, writing a note, correcting a wrong claim, recording a lesson, or documenting an incident. Enforces state-the-rule-and-stop, because narrative in a loaded file costs tokens at every wake and leaves contradictions nobody can untangle."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Writing or editing ANY markdown file — state the rule and stop
|
|
7
|
+
|
|
8
|
+
Not what was done. Not why it was wrong. Not what the previous version said. Not
|
|
9
|
+
what not to do. Not the incident. **Just the rule, in the imperative.**
|
|
10
|
+
|
|
11
|
+
## Before you save: measure
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
git diff --numstat -- <file>
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
**More than ~5 added lines for one rule means you wrote a story.** Cut it.
|
|
18
|
+
|
|
19
|
+
## The three costs
|
|
20
|
+
|
|
21
|
+
**1. Tokens, paid at every wake, by everyone.** `CLAUDE.md`, role preambles and
|
|
22
|
+
state files are injected into every agent's context at every wake. A 40-line
|
|
23
|
+
explanation is not paid once by the author — it is paid by every agent, every
|
|
24
|
+
session, forever. Inside a session it is worse than linear: injected text is
|
|
25
|
+
re-read on every later turn.
|
|
26
|
+
|
|
27
|
+
**2. Contradiction.** Stories assert facts. Three stories about one rule disagree
|
|
28
|
+
about its scope, whether it still applies, and which version is current. **An
|
|
29
|
+
agent cannot tell which sentence is authoritative, so it obeys both.** Prose rots
|
|
30
|
+
silently — a test goes red when it drifts, a paragraph never does.
|
|
31
|
+
|
|
32
|
+
**3. Abandonment.** Past a certain size nobody can audit the file for
|
|
33
|
+
contradictions, so nobody tries, and the contradictions become permanent.
|
|
34
|
+
**The bloat is what makes the file unfixable** — every story added removes
|
|
35
|
+
someone's ability to fix the story before it.
|
|
36
|
+
|
|
37
|
+
## A correction replaces; it does not accumulate
|
|
38
|
+
|
|
39
|
+
Delete the wrong text and write the right rule in its place. A correction left
|
|
40
|
+
*beside* what it corrects leaves two claims and no way to rank them. Corrections
|
|
41
|
+
are always longer than what they replace, so this is the main way these files
|
|
42
|
+
grow.
|
|
43
|
+
|
|
44
|
+
## Where the story goes
|
|
45
|
+
|
|
46
|
+
`thrum memory` — retrieved on demand by the agent who needs it, instead of
|
|
47
|
+
injected into everyone. Keep the rule in the doc; leave at most a one-line
|
|
48
|
+
pointer.
|
|
49
|
+
|
|
50
|
+
## Exempt from the cap
|
|
51
|
+
|
|
52
|
+
Tables, CLI output, command strings, file paths, enumerated vocabularies. **Never
|
|
53
|
+
truncate those to hit a line count.** The cap stops narrative; it does not
|
|
54
|
+
shorten data.
|
|
55
|
+
|
|
56
|
+
## Shipped content carries no internal particulars
|
|
57
|
+
|
|
58
|
+
No issue IDs, no dates, no agent names, no measurements in customer-facing files.
|
|
59
|
+
The tracker references the change; the change never references the tracker.
|
|
60
|
+
|
|
61
|
+
## A skill is generic — never specific to the repo you edit from
|
|
62
|
+
|
|
63
|
+
Write the rule so it holds for any repo running the skill. No repo names, config
|
|
64
|
+
keys, box names, agent names, or paths particular to the repo you happen to be in.
|
|
65
|
+
Repo-specific values live in that repo's config or memory; the skill names them by
|
|
66
|
+
role, not by literal.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Returns true if the command must be blocked. Exported for testing. */
|
|
2
|
+
export declare function isBlockedCommand(command: string): boolean;
|
|
3
|
+
export declare function blockSyncCdHook(): (input: {
|
|
4
|
+
tool: string;
|
|
5
|
+
sessionID: string;
|
|
6
|
+
callID: string;
|
|
7
|
+
}, output: {
|
|
8
|
+
args: Record<string, any>;
|
|
9
|
+
}) => Promise<void>;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// tool.execute.before hook: protect the a-sync JSONL worktree from destruction.
|
|
2
|
+
//
|
|
3
|
+
// .git/thrum-sync/a-sync/ is a detached git worktree living INSIDE .git/.
|
|
4
|
+
// It holds the append-only JSONL event log for thrum message sync.
|
|
5
|
+
//
|
|
6
|
+
// DANGER: If you check out a different branch in that worktree, git replaces
|
|
7
|
+
// its contents — but since it lives inside .git/, this DESTROYS the git object
|
|
8
|
+
// store, refs, and config. The entire repo and ALL worktrees are wiped.
|
|
9
|
+
//
|
|
10
|
+
// This happened once and required significant effort to recover.
|
|
11
|
+
//
|
|
12
|
+
// Faithful port of claude-plugin/scripts/block-sync-worktree-cd.sh (4 patterns
|
|
13
|
+
// + command-position anchor). OpenCode contract: throwing inside
|
|
14
|
+
// tool.execute.before ABORTS the tool call (the bash command never runs) —
|
|
15
|
+
// verified against sst/opencode plugin dispatch (raw `yield*`, no catch).
|
|
16
|
+
//
|
|
17
|
+
// Blocked:
|
|
18
|
+
// 1. cd/pushd/chdir into the a-sync worktree (prevents arbitrary commands there)
|
|
19
|
+
// 2. git -C <a-sync-path> with branch-changing commands (checkout, switch,
|
|
20
|
+
// reset, merge, rebase, pull) — these change HEAD without cd
|
|
21
|
+
// 3. git --work-tree=<a-sync-path> with branch-changing commands
|
|
22
|
+
// 4. git --git-dir=<a-sync-path> with branch-changing commands
|
|
23
|
+
//
|
|
24
|
+
// Allowed:
|
|
25
|
+
// git -C <a-sync-path> add/commit/push/status/rm/log/diff — safe operations
|
|
26
|
+
// ls/cat/grep/rm on absolute paths — no CWD change
|
|
27
|
+
// Matches the literal sync-worktree path. Mirrors SYNC_PATTERN in the bash hook.
|
|
28
|
+
const SYNC = String.raw `\.git/thrum-sync/a-sync`;
|
|
29
|
+
// Command-position anchor: line start or immediately after a shell separator
|
|
30
|
+
// (;, &&, ||), then optional whitespace. This matches `cd`/`git` only when they
|
|
31
|
+
// are the actual command — NOT when those words appear inside a quoted argument
|
|
32
|
+
// preceded by a bare space (the D5 false-positive: `bd remember "... cd
|
|
33
|
+
// .git/thrum-sync/a-sync ..."` must NOT be denied). Match op-shape, not the mere
|
|
34
|
+
// presence of the path string.
|
|
35
|
+
const CMDPOS = String.raw `(^|;|&&|\|\|)\s*`;
|
|
36
|
+
// Branch-changing git subcommands that mutate HEAD / working tree.
|
|
37
|
+
const BRANCH_CHANGING = String.raw `(checkout|switch|reset|merge|rebase|pull)\b`;
|
|
38
|
+
// The `m` flag makes `^` in CMDPOS match the start of each line, mirroring the
|
|
39
|
+
// line-by-line behavior of `echo "$command" | grep -E` in the bash hook.
|
|
40
|
+
const PATTERNS = [
|
|
41
|
+
// 1. cd/pushd/chdir into the a-sync worktree (command-position only)
|
|
42
|
+
new RegExp(String.raw `${CMDPOS}(cd|pushd|chdir)\s+['"]?[^\s]*${SYNC}`, "m"),
|
|
43
|
+
// 2. git -C <a-sync-path> with a branch-changing command
|
|
44
|
+
new RegExp(String.raw `${CMDPOS}git\s+-C\s+['"]?[^\s]*${SYNC}['"]?\s+${BRANCH_CHANGING}`, "m"),
|
|
45
|
+
// 3. git --work-tree=<a-sync-path> ... branch-changing command
|
|
46
|
+
new RegExp(String.raw `${CMDPOS}git\s+--work-tree[= ]['"]?[^\s]*${SYNC}['"]?\s+.*${BRANCH_CHANGING}`, "m"),
|
|
47
|
+
// 4. git --git-dir=<a-sync-path> ... branch-changing command
|
|
48
|
+
new RegExp(String.raw `${CMDPOS}git\s+--git-dir[= ]['"]?[^\s]*${SYNC}['"]?\s+.*${BRANCH_CHANGING}`, "m"),
|
|
49
|
+
];
|
|
50
|
+
const DENY_MESSAGE = "BLOCKED: branch-changing git operations on (or cd into) the " +
|
|
51
|
+
".git/thrum-sync/a-sync/ worktree are forbidden. That worktree lives INSIDE " +
|
|
52
|
+
".git/ — checking out a different branch there DESTROYS the entire git object " +
|
|
53
|
+
"store, wiping the repo and all worktrees. Use absolute paths with safe " +
|
|
54
|
+
"operations (ls, grep, rm, git -C ... add/commit/push) instead.";
|
|
55
|
+
/** Returns true if the command must be blocked. Exported for testing. */
|
|
56
|
+
export function isBlockedCommand(command) {
|
|
57
|
+
if (!command)
|
|
58
|
+
return false;
|
|
59
|
+
return PATTERNS.some((re) => re.test(command));
|
|
60
|
+
}
|
|
61
|
+
export function blockSyncCdHook() {
|
|
62
|
+
return async (input, output) => {
|
|
63
|
+
if (input.tool !== "bash")
|
|
64
|
+
return;
|
|
65
|
+
const cmd = String(output.args?.command ?? "");
|
|
66
|
+
if (isBlockedCommand(cmd)) {
|
|
67
|
+
// Throwing aborts the tool call in OpenCode (tool.execute.before contract).
|
|
68
|
+
throw new Error(DENY_MESSAGE);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// Regression tests for the a-sync worktree guard (block-sync-cd.ts).
|
|
2
|
+
// Mirrors claude-plugin/scripts/block-sync-worktree-cd_test.sh so the OpenCode
|
|
3
|
+
// port stays behavior-faithful to the reference bash hook.
|
|
4
|
+
//
|
|
5
|
+
// Run: npm test (builds, then `node --test dist`)
|
|
6
|
+
import { test } from "node:test";
|
|
7
|
+
import assert from "node:assert/strict";
|
|
8
|
+
import { isBlockedCommand, blockSyncCdHook } from "./block-sync-cd.js";
|
|
9
|
+
const ASYNC = ".git/thrum-sync/a-sync";
|
|
10
|
+
// DENY: branch-changing ops on / cd into the a-sync worktree.
|
|
11
|
+
const DENY = [
|
|
12
|
+
["cd into a-sync", `cd ${ASYNC}`],
|
|
13
|
+
["pushd into a-sync", `pushd ${ASYNC}`],
|
|
14
|
+
["cd after &&", `git status && cd ${ASYNC}`],
|
|
15
|
+
["cd after ;", `echo hi; cd ${ASYNC}`],
|
|
16
|
+
["git -C a-sync checkout", `git -C ${ASYNC} checkout main`],
|
|
17
|
+
["git -C a-sync switch", `git -C ${ASYNC} switch main`],
|
|
18
|
+
["git -C a-sync reset --hard", `git -C ${ASYNC} reset --hard`],
|
|
19
|
+
["git -C a-sync merge", `git -C ${ASYNC} merge main`],
|
|
20
|
+
["git -C a-sync rebase", `git -C ${ASYNC} rebase main`],
|
|
21
|
+
["git -C a-sync pull", `git -C ${ASYNC} pull`],
|
|
22
|
+
["git --work-tree= checkout", `git --work-tree=${ASYNC} checkout main`],
|
|
23
|
+
["git --git-dir= checkout", `git --git-dir=${ASYNC} checkout main`],
|
|
24
|
+
];
|
|
25
|
+
// ALLOW: safe ops on a-sync, non-command-position mentions, unrelated commands.
|
|
26
|
+
const ALLOW = [
|
|
27
|
+
["bare git status", "git status"],
|
|
28
|
+
["git -C a-sync add", `git -C ${ASYNC} add .`],
|
|
29
|
+
["git -C a-sync status", `git -C ${ASYNC} status`],
|
|
30
|
+
["git -C a-sync commit", `git -C ${ASYNC} commit -m x`],
|
|
31
|
+
["git -C a-sync push", `git -C ${ASYNC} push`],
|
|
32
|
+
["git -C a-sync log (not branch-changing)", `git -C ${ASYNC} log --oneline`],
|
|
33
|
+
["ls the path", `ls ${ASYNC}/`],
|
|
34
|
+
// D5 false-positive guard: cd inside a quoted argument (bare space before it),
|
|
35
|
+
// not at command position -> must be ALLOWED.
|
|
36
|
+
["quoted cd inside bd remember", `bd remember "note: do not cd ${ASYNC} directly"`],
|
|
37
|
+
];
|
|
38
|
+
for (const [name, cmd] of DENY) {
|
|
39
|
+
test(`DENY: ${name}`, () => {
|
|
40
|
+
assert.equal(isBlockedCommand(cmd), true, `expected blocked: ${cmd}`);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
for (const [name, cmd] of ALLOW) {
|
|
44
|
+
test(`ALLOW: ${name}`, () => {
|
|
45
|
+
assert.equal(isBlockedCommand(cmd), false, `expected allowed: ${cmd}`);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
// Contract: the hook THROWS on a blocked bash command (OpenCode aborts the
|
|
49
|
+
// tool call on throw) and resolves for allowed / non-bash tools.
|
|
50
|
+
test("hook throws on blocked bash command", async () => {
|
|
51
|
+
const hook = blockSyncCdHook();
|
|
52
|
+
await assert.rejects(() => hook({ tool: "bash", sessionID: "s", callID: "c" }, { args: { command: `git -C ${ASYNC} checkout main` } }));
|
|
53
|
+
});
|
|
54
|
+
test("hook resolves on allowed bash command", async () => {
|
|
55
|
+
const hook = blockSyncCdHook();
|
|
56
|
+
await assert.doesNotReject(() => hook({ tool: "bash", sessionID: "s", callID: "c" }, { args: { command: `git -C ${ASYNC} add .` } }));
|
|
57
|
+
});
|
|
58
|
+
test("hook ignores non-bash tools", async () => {
|
|
59
|
+
const hook = blockSyncCdHook();
|
|
60
|
+
await assert.doesNotReject(() => hook({ tool: "read", sessionID: "s", callID: "c" }, { args: { command: `cd ${ASYNC}` } }));
|
|
61
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
export function shellEnvHook(ctx) {
|
|
4
|
+
return async (input, output) => {
|
|
5
|
+
const dirs = [input.cwd, ctx.directory, ctx.worktree].filter(Boolean);
|
|
6
|
+
for (const dir of dirs) {
|
|
7
|
+
const identityPath = path.join(dir, ".thrum", "identity.json");
|
|
8
|
+
try {
|
|
9
|
+
const raw = fs.readFileSync(identityPath, "utf8");
|
|
10
|
+
const identity = JSON.parse(raw);
|
|
11
|
+
output.env.THRUM_NAME = identity.agent_id ?? "";
|
|
12
|
+
output.env.THRUM_ROLE = identity.role ?? "";
|
|
13
|
+
output.env.THRUM_MODULE = identity.module ?? "";
|
|
14
|
+
output.env.THRUM_HOME = dir;
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
// No identity file in this dir, try next
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { installAssets } from "./installer.js";
|
|
2
|
+
import { shellEnvHook } from "./hooks/shell-env.js";
|
|
3
|
+
import { blockSyncCdHook } from "./hooks/block-sync-cd.js";
|
|
4
|
+
const TOOL_MAPPING = `
|
|
5
|
+
## Thrum Plugin — Tool Mapping for OpenCode
|
|
6
|
+
|
|
7
|
+
When Thrum skills reference Claude Code tool names, use these OpenCode equivalents:
|
|
8
|
+
- TodoWrite → todowrite
|
|
9
|
+
- Task (subagents) → @mention syntax (e.g., @explore, @general)
|
|
10
|
+
- Skill tool → OpenCode's native skill tool
|
|
11
|
+
- Agent tool → @mention syntax for subagents
|
|
12
|
+
- Read, Write, Edit, Bash → your native tools (same names, lowercase)
|
|
13
|
+
`.trim();
|
|
14
|
+
const server = async (ctx) => {
|
|
15
|
+
await installAssets(ctx);
|
|
16
|
+
return {
|
|
17
|
+
"shell.env": shellEnvHook(ctx),
|
|
18
|
+
"tool.execute.before": blockSyncCdHook(),
|
|
19
|
+
"experimental.chat.system.transform": async (_input, output) => {
|
|
20
|
+
;
|
|
21
|
+
(output.system ||= []).push(TOOL_MAPPING);
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
export default { id: "opencode-thrum", server };
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import os from "os";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const ASSETS_DIR = path.resolve(__dirname, "..", "assets");
|
|
7
|
+
const VERSION_FILE = ".thrum-plugin-version";
|
|
8
|
+
function getPluginVersion() {
|
|
9
|
+
const pkgPath = path.resolve(__dirname, "..", "package.json");
|
|
10
|
+
try {
|
|
11
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
12
|
+
return pkg.version ?? "0.0.0";
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return "0.0.0";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function getConfigDir() {
|
|
19
|
+
const xdg = process.env.XDG_CONFIG_HOME;
|
|
20
|
+
if (xdg)
|
|
21
|
+
return path.join(xdg, "opencode");
|
|
22
|
+
return path.join(os.homedir(), ".config", "opencode");
|
|
23
|
+
}
|
|
24
|
+
function copyDirRecursive(src, dest) {
|
|
25
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
26
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
27
|
+
const srcPath = path.join(src, entry.name);
|
|
28
|
+
const destPath = path.join(dest, entry.name);
|
|
29
|
+
if (entry.isDirectory()) {
|
|
30
|
+
copyDirRecursive(srcPath, destPath);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
fs.copyFileSync(srcPath, destPath);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export async function installAssets(ctx) {
|
|
38
|
+
const configDir = getConfigDir();
|
|
39
|
+
const skillsDir = path.join(configDir, "skills");
|
|
40
|
+
const commandsDir = path.join(configDir, "commands");
|
|
41
|
+
const versionPath = path.join(skillsDir, "thrum", VERSION_FILE);
|
|
42
|
+
const currentVersion = getPluginVersion();
|
|
43
|
+
// Check if already installed at this version
|
|
44
|
+
try {
|
|
45
|
+
const installed = fs.readFileSync(versionPath, "utf8").trim();
|
|
46
|
+
if (installed === currentVersion)
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// Not installed yet
|
|
51
|
+
}
|
|
52
|
+
const log = (msg) => ctx.client.app.log({
|
|
53
|
+
body: { service: "opencode-thrum", level: "info", message: msg },
|
|
54
|
+
});
|
|
55
|
+
// Copy each skill subdirectory
|
|
56
|
+
const assetsSkillsDir = path.join(ASSETS_DIR, "skills");
|
|
57
|
+
if (fs.existsSync(assetsSkillsDir)) {
|
|
58
|
+
for (const entry of fs.readdirSync(assetsSkillsDir, {
|
|
59
|
+
withFileTypes: true,
|
|
60
|
+
})) {
|
|
61
|
+
if (!entry.isDirectory())
|
|
62
|
+
continue;
|
|
63
|
+
const src = path.join(assetsSkillsDir, entry.name);
|
|
64
|
+
const dest = path.join(skillsDir, entry.name);
|
|
65
|
+
copyDirRecursive(src, dest);
|
|
66
|
+
await log(`Installed skill: ${entry.name}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// Copy commands
|
|
70
|
+
const assetsCommandsDir = path.join(ASSETS_DIR, "commands");
|
|
71
|
+
if (fs.existsSync(assetsCommandsDir)) {
|
|
72
|
+
fs.mkdirSync(commandsDir, { recursive: true });
|
|
73
|
+
let count = 0;
|
|
74
|
+
for (const entry of fs.readdirSync(assetsCommandsDir)) {
|
|
75
|
+
if (!entry.endsWith(".md"))
|
|
76
|
+
continue;
|
|
77
|
+
fs.copyFileSync(path.join(assetsCommandsDir, entry), path.join(commandsDir, entry));
|
|
78
|
+
count++;
|
|
79
|
+
}
|
|
80
|
+
await log(`Installed ${count} commands`);
|
|
81
|
+
}
|
|
82
|
+
// Write version marker
|
|
83
|
+
fs.mkdirSync(path.dirname(versionPath), { recursive: true });
|
|
84
|
+
fs.writeFileSync(versionPath, currentVersion);
|
|
85
|
+
await log(`opencode-thrum v${currentVersion} assets installed`);
|
|
86
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "opencode-thrum-pro",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Thrum multi-agent coordination plugin for Open Code",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"import": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts"
|
|
11
|
+
},
|
|
12
|
+
"./server": {
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsc",
|
|
19
|
+
"clean": "rm -rf dist",
|
|
20
|
+
"test": "npm run build && node --test dist",
|
|
21
|
+
"prepublishOnly": "npm run build"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist",
|
|
25
|
+
"assets"
|
|
26
|
+
],
|
|
27
|
+
"keywords": [
|
|
28
|
+
"opencode",
|
|
29
|
+
"plugin",
|
|
30
|
+
"thrum",
|
|
31
|
+
"multi-agent",
|
|
32
|
+
"coordination"
|
|
33
|
+
],
|
|
34
|
+
"author": "Leon Letto",
|
|
35
|
+
"license": "Apache-2.0",
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "git+https://github.com/leonletto/thrum-pro.git",
|
|
39
|
+
"directory": "opencode-plugin"
|
|
40
|
+
},
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"@opencode-ai/plugin": ">=1.0.0"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@opencode-ai/plugin": "^1.4.0",
|
|
46
|
+
"@types/node": "^25.5.2",
|
|
47
|
+
"typescript": "^5.3.0"
|
|
48
|
+
}
|
|
49
|
+
}
|