questionably-ultrathink 1.0.0 → 1.0.1

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/README.md CHANGED
@@ -12,11 +12,45 @@ UltraThink enhances Claude's reasoning with two research-backed frameworks:
12
12
  ## Installation
13
13
 
14
14
  ```bash
15
- # Add the marketplace
16
- /plugin marketplace add snowmead/questionably-ultrathink
15
+ bun add -g questionably-ultrathink
16
+ ```
17
+
18
+ Then run the interactive installer:
19
+
20
+ ```bash
21
+ questionably-ultrathink install
22
+ ```
23
+
24
+ Or install directly to a specific platform:
17
25
 
18
- # Install the plugin
19
- /plugin install questionably-ultrathink@snowmead-marketplace
26
+ ```bash
27
+ questionably-ultrathink install --claude-code # Claude Code only
28
+ questionably-ultrathink install --opencode # OpenCode only
29
+ questionably-ultrathink install --both # Both platforms
30
+ ```
31
+
32
+ **Install locations:**
33
+ - Claude Code: `~/.claude/plugins/questionably-ultrathink/`
34
+ - OpenCode: `~/.config/opencode/`
35
+
36
+ > **Alternative:** Claude Code users can also install via marketplace:
37
+ > ```bash
38
+ > /plugin marketplace add snowmead/questionably-ultrathink
39
+ > /plugin install questionably-ultrathink@snowmead-marketplace
40
+ > ```
41
+
42
+ ## Usage
43
+
44
+ ### Claude Code
45
+
46
+ ```
47
+ /questionably-ultrathink analyze whether this authentication approach is secure
48
+ ```
49
+
50
+ ### OpenCode
51
+
52
+ ```
53
+ @questionably-ultrathink analyze whether this authentication approach is secure
20
54
  ```
21
55
 
22
56
  ## Development Setup
@@ -31,7 +65,7 @@ This installs dependencies (lefthook, comrak) if missing and sets up git hooks f
31
65
 
32
66
  ## Commands
33
67
 
34
- ### `/questionably-ultrathink`
68
+ ### `/questionably-ultrathink` (Claude Code) / `@questionably-ultrathink` (OpenCode)
35
69
 
36
70
  Run the full reasoning pipeline on a problem:
37
71
 
@@ -43,10 +77,6 @@ Run the full reasoning pipeline on a problem:
43
77
  6. Synthesizes and verifies final response
44
78
  7. Iterates if confidence is below threshold (thorough/high-stakes only)
45
79
 
46
- ```
47
- /questionably-ultrathink analyze whether this authentication approach is secure
48
- ```
49
-
50
80
  ### `/decompose`
51
81
 
52
82
  Break down a complex problem into atomic sub-questions:
@@ -18,6 +18,26 @@ allowed-tools: [Task, Read, Grep, Glob, WebSearch, WebFetch, AskUserQuestion, Ba
18
18
 
19
19
  You now have access to advanced reasoning agents for rigorous analysis. Use them when problems require more than surface-level analysis.
20
20
 
21
+ \<critical\_warning\>
22
+
23
+ ## ⚠️ CRITICAL: DO NOT INVOKE YOURSELF
24
+
25
+ You ARE the `questionably-ultrathink` orchestrator. You must **NEVER** call:
26
+
27
+ ```
28
+ subagent_type: "questionably-ultrathink" ← FORBIDDEN (infinite recursion)
29
+ subagent_type: "questionably-ultrathink-skill" ← FORBIDDEN (infinite recursion)
30
+ ```
31
+
32
+ You can ONLY invoke these subagents:
33
+
34
+ - `subagent_type: "questionably-ultrathink:atom-of-thoughts"` ← Use this for decomposition
35
+ - `subagent_type: "questionably-ultrathink:chain-of-verification"` ← Use this for verification
36
+ - `subagent_type: "questionably-ultrathink:aot-recompute"` ← Use this for recomputation
37
+
38
+ Calling yourself causes infinite recursion and task failure.
39
+ \</critical\_warning\>
40
+
21
41
  \<clarification\_first\>
22
42
 
23
43
  ## Step 0: Clarify Intent First (MANDATORY)
@@ -72,6 +92,14 @@ After clarifying intent, use `AskUserQuestion` to determine the analysis depth:
72
92
 
73
93
  ## Available Agents
74
94
 
95
+ **CRITICAL WARNING:** You are the orchestrator. You must NEVER invoke yourself. NEVER use `subagent_type: "questionably-ultrathink"` or `subagent_type: "questionably-ultrathink-skill"`. You can ONLY invoke these three subagents:
96
+
97
+ - `questionably-ultrathink:atom-of-thoughts` - for decomposition
98
+ - `questionably-ultrathink:chain-of-verification` - for verification
99
+ - `questionably-ultrathink:aot-recompute` - for recomputation after corrections
100
+
101
+ If you call yourself, you create infinite recursion and fail the task.
102
+
75
103
  ### atom-of-thoughts
76
104
 
77
105
  **Purpose:** Decompose complex problems into atomic sub-questions
@@ -9,7 +9,6 @@ description: |-
9
9
  assistant: "I'll use the aot-recompute agent to update the dependent atoms with the correction."
10
10
  </example>
11
11
  mode: subagent
12
- model: anthropic/claude-3-5-haiku
13
12
  permission:
14
13
  read: allow
15
14
  write: allow
@@ -19,7 +19,6 @@ description: |-
19
19
  assistant: "I'll decompose this into atomic diagnostic questions."
20
20
  </example>
21
21
  mode: subagent
22
- model: anthropic/claude-3-5-haiku
23
22
  permission:
24
23
  read: allow
25
24
  grep: allow
@@ -18,7 +18,6 @@ description: |-
18
18
  assistant: "I'll verify this with the chain-of-verification agent."
19
19
  </example>
20
20
  mode: subagent
21
- model: anthropic/claude-3-5-haiku
22
21
  permission:
23
22
  read: allow
24
23
  grep: allow
@@ -1,5 +1,8 @@
1
1
  ---
2
2
  description: |-
3
+ CRITICAL: You ARE this agent. NEVER call subagent_type:"questionably-ultrathink" (infinite recursion).
4
+ ONLY use: subagent_type:"atom-of-thoughts", subagent_type:"chain-of-verification", or subagent_type:"aot-recompute".
5
+
3
6
  Use this skill when facing complex problems requiring rigorous reasoning, systematic decomposition, or factual verification.
4
7
 
5
8
  Activation triggers:
@@ -11,7 +14,6 @@ description: |-
11
14
  - High-stakes technical decisions
12
15
  - Debugging complex issues
13
16
  mode: primary
14
- model: anthropic/claude-sonnet-4-20250514
15
17
  permission:
16
18
  task: allow
17
19
  read: allow
@@ -26,6 +28,25 @@ permission:
26
28
 
27
29
  You now have access to advanced reasoning agents for rigorous analysis. Use them when problems require more than surface-level analysis.
28
30
 
31
+ \<critical\_warning\>
32
+
33
+ ## ⚠️ CRITICAL: DO NOT INVOKE YOURSELF
34
+
35
+ You ARE the `questionably-ultrathink` orchestrator. You must **NEVER** call:
36
+
37
+ ```
38
+ subagent_type: "questionably-ultrathink" ← FORBIDDEN (infinite recursion)
39
+ ```
40
+
41
+ You can ONLY invoke these subagents:
42
+
43
+ - `subagent_type: "atom-of-thoughts"` ← Use this for decomposition
44
+ - `subagent_type: "chain-of-verification"` ← Use this for verification
45
+ - `subagent_type: "aot-recompute"` ← Use this for recomputation
46
+
47
+ Calling yourself causes infinite recursion and task failure.
48
+ \</critical\_warning\>
49
+
29
50
  \<clarification\_first\>
30
51
 
31
52
  ## Step 0: Clarify Intent First (MANDATORY)
@@ -80,10 +101,18 @@ After clarifying intent, use `AskUserQuestion` to determine the analysis depth:
80
101
 
81
102
  ## Available Agents
82
103
 
104
+ **CRITICAL WARNING:** You are `questionably-ultrathink`, the orchestrator. You must NEVER invoke yourself. NEVER use `subagent_type: "questionably-ultrathink"`. You can ONLY invoke these three subagents:
105
+
106
+ - `atom-of-thoughts` - for decomposition
107
+ - `chain-of-verification` - for verification
108
+ - `aot-recompute` - for recomputation after corrections
109
+
110
+ If you call `subagent_type: "questionably-ultrathink"`, you create infinite recursion and fail the task.
111
+
83
112
  ### atom-of-thoughts
84
113
 
85
114
  **Purpose:** Decompose complex problems into atomic sub-questions
86
- **Invoke:** `Task` tool with `@atom-of-thoughts`
115
+ **Invoke:** `Task` tool with `subagent_type="atom-of-thoughts"`
87
116
 
88
117
  Use when:
89
118
 
@@ -95,7 +124,7 @@ Use when:
95
124
  ### chain-of-verification
96
125
 
97
126
  **Purpose:** Verify factual claims to reduce hallucinations
98
- **Invoke:** `Task` tool with `@chain-of-verification`
127
+ **Invoke:** `Task` tool with `subagent_type="chain-of-verification"`
99
128
 
100
129
  Use when:
101
130
 
@@ -107,7 +136,7 @@ Use when:
107
136
  ### aot-recompute
108
137
 
109
138
  **Purpose:** Recompute atoms after CoV finds corrections
110
- **Invoke:** `Task` tool with `@aot-recompute`
139
+ **Invoke:** `Task` tool with `subagent_type="aot-recompute"`
111
140
 
112
141
  Use when:
113
142
 
@@ -141,8 +170,8 @@ Example: `a1b2c3d4`
141
170
 
142
171
  **Step 2: Decompose with AoT**
143
172
 
144
- Use the task tool to invoke the subagent:
145
- - @atom-of-thoughts
173
+ Use the Task tool to invoke the subagent:
174
+ - subagent_type: "atom-of-thoughts"
146
175
  - prompt: "Session ID: {session-id}. Rigor: {rigor-level}. Decompose this query into atomic sub-questions: {clarified query}"
147
176
 
148
177
  **IMPORTANT:**
@@ -181,8 +210,9 @@ Process each level in order:
181
210
 
182
211
  For each atom requiring CoV, invoke the chain-of-verification agent:
183
212
 
184
- Task: @chain-of-verification
185
- prompt: "Session ID: {session-id}. Verify atom {atom-id}. Read the reasoning from .questionably-ultrathink/{session-id}/atoms/{atom-id}.md and verify both the factual claims AND the reasoning chain."
213
+ Task tool:
214
+ subagent_type: "chain-of-verification"
215
+ prompt: "Session ID: {session-id}. Verify atom {atom-id}. Read the reasoning from .questionably-ultrathink/{session-id}/atoms/{atom-id}.md and verify both the factual claims AND the reasoning chain."
186
216
 
187
217
  **Parallel execution:** Invoke ALL atoms at the same level in a SINGLE message with multiple Task calls. Wait for all results before proceeding to the next level.
188
218
 
@@ -203,8 +233,9 @@ When CoV finds errors, it writes correction files to `.questionably-ultrathink/{
203
233
 
204
234
  3. **Invoke aot-recompute:**
205
235
 
206
- Task: @aot-recompute
207
- prompt: "Session ID: {session-id}. Corrected atoms: [A1]. Atoms to recompute: [A3, FINAL]."
236
+ Task tool:
237
+ subagent_type: "aot-recompute"
238
+ prompt: "Session ID: {session-id}. Corrected atoms: [A1]. Atoms to recompute: [A3, FINAL]."
208
239
 
209
240
  4. **Re-verify recomputed atoms:**
210
241
  Add recomputed atoms back to the verification queue at their dependency level.
@@ -232,8 +263,8 @@ Display under "Phase 3: Synthesis".
232
263
 
233
264
  **Step 5: Final Verification (MANDATORY)**
234
265
 
235
- Use the task tool to invoke the subagent:
236
- - @chain-of-verification
266
+ Use the Task tool to invoke the subagent:
267
+ - subagent_type: "chain-of-verification"
237
268
  - prompt: "Verify this synthesized response: {synthesis}"
238
269
 
239
270
  **DO NOT SKIP THIS STEP.** The final response must be verified.
@@ -276,13 +307,15 @@ After Phase 4 (Final Verification), check if iteration is needed based on the us
276
307
 
277
308
  **Iteration invocation (new decomposition):**
278
309
 
279
- Task: @atom-of-thoughts
280
- prompt: "Session ID: {session-id}. Rigor: {rigor}. Re-analyze these problematic areas with fresh perspective: {list of issues}"
310
+ Task tool:
311
+ subagent_type: "atom-of-thoughts"
312
+ prompt: "Session ID: {session-id}. Rigor: {rigor}. Re-analyze these problematic areas with fresh perspective: {list of issues}"
281
313
 
282
314
  **Iteration invocation (correction-based update):**
283
315
 
284
- Task: @aot-recompute
285
- prompt: "Session ID: {session-id}. Corrected atoms: [...]. Atoms to recompute: [...]."
316
+ Task tool:
317
+ subagent_type: "aot-recompute"
318
+ prompt: "Session ID: {session-id}. Corrected atoms: [...]. Atoms to recompute: [...]."
286
319
 
287
320
  Then continue with Steps 3-5 for the new/revised atoms.
288
321
  \</iterative\_refinement\>
@@ -1,26 +1,6 @@
1
1
  ---
2
- description: Apply full UltraThink reasoning pipeline (AoT + CoVe) to analyze the current problem with maximum rigor
2
+ description: Apply full UltraThink reasoning pipeline (AoT + CoVe)
3
3
  ---
4
4
  Switch to the @questionably-ultrathink agent.
5
5
 
6
-
7
- # /questionably-ultrathink Command
8
-
9
- The user has requested full UltraThink analysis.
10
-
11
- **Immediately invoke the questionably-ultrathink-skill** using the Skill tool to execute the full pipeline:
12
-
13
- Use the Skill tool with:
14
-
15
- - skill: "questionably-ultrathink-skill"
16
- - args: "$ARGUMENTS"
17
-
18
- The skill contains the complete orchestration protocol for:
19
-
20
- 1. Intent clarification (AskUserQuestion if needed)
21
- 2. Atom of Thoughts decomposition (with complexity flagging)
22
- 3. Parallel Chain of Verification for critical atoms (by dependency level)
23
- 4. Synthesis and final verification
24
- 5. Optional iterative refinement for high-stakes or low-confidence results
25
-
26
- Do not duplicate the orchestration logic here—let the skill handle it.
6
+ User query: $ARGUMENTS
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "questionably-ultrathink",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Claude Code plugin integrating Atom of Thoughts (AoT) and Chain of Verification (CoVe) reasoning frameworks",
5
5
  "type": "module",
6
6
  "bin": {
@@ -12,9 +12,7 @@
12
12
  ],
13
13
  "scripts": {
14
14
  "install": "bun scripts/install-plugin.ts",
15
- "sync:opencode": "bun scripts/sync-opencode.ts",
16
- "watch:opencode": "bun scripts/sync-opencode.ts --watch",
17
- "build": "bun scripts/sync-opencode.ts && bun scripts/build-dist.ts",
15
+ "build": "bun scripts/build-dist.ts",
18
16
  "postinstall": "bun scripts/install-plugin.ts --auto"
19
17
  },
20
18
  "dependencies": {
@@ -3,9 +3,8 @@
3
3
  * Build script to prepare distribution files for npm publishing.
4
4
  *
5
5
  * This script:
6
- * 1. Runs sync-opencode.ts to generate OpenCode files
7
- * 2. Copies Claude Code files to dist/claude-code/
8
- * 3. Copies OpenCode files to dist/opencode/
6
+ * 1. Copies Claude Code files from claude-code/ to dist/claude-code/
7
+ * 2. Copies OpenCode files from opencode/ to dist/opencode/
9
8
  *
10
9
  * Run: bun scripts/build-dist.ts
11
10
  */
@@ -23,12 +22,9 @@ const DIST_DIR = join(ROOT_DIR, "dist");
23
22
  const DIST_CLAUDE_CODE = join(DIST_DIR, "claude-code");
24
23
  const DIST_OPENCODE = join(DIST_DIR, "opencode");
25
24
 
26
- // Source directories
27
- const AGENTS_DIR = join(ROOT_DIR, "agents");
28
- const COMMANDS_DIR = join(ROOT_DIR, "commands");
29
- const SKILLS_DIR = join(ROOT_DIR, "skills");
30
- const PLUGIN_DIR = join(ROOT_DIR, ".claude-plugin");
31
- const OPENCODE_DIR = join(ROOT_DIR, ".opencode");
25
+ // Source directories (separate sources for each platform)
26
+ const CLAUDE_CODE_DIR = join(ROOT_DIR, "claude-code");
27
+ const OPENCODE_DIR = join(ROOT_DIR, "opencode");
32
28
 
33
29
  // ============================================================================
34
30
  // Utilities
@@ -65,55 +61,26 @@ async function cleanDist(): Promise<void> {
65
61
 
66
62
  async function buildClaudeCode(): Promise<void> {
67
63
  console.log("\nBuilding Claude Code distribution...");
68
- await mkdir(DIST_CLAUDE_CODE, { recursive: true });
69
64
 
70
- // Copy agents
71
- if (existsSync(AGENTS_DIR)) {
72
- await copyDir(AGENTS_DIR, join(DIST_CLAUDE_CODE, "agents"));
73
- console.log(" ✓ agents/");
74
- }
75
-
76
- // Copy commands
77
- if (existsSync(COMMANDS_DIR)) {
78
- await copyDir(COMMANDS_DIR, join(DIST_CLAUDE_CODE, "commands"));
79
- console.log(" ✓ commands/");
80
- }
81
-
82
- // Copy skills
83
- if (existsSync(SKILLS_DIR)) {
84
- await copyDir(SKILLS_DIR, join(DIST_CLAUDE_CODE, "skills"));
85
- console.log(" ✓ skills/");
65
+ if (!existsSync(CLAUDE_CODE_DIR)) {
66
+ console.log(" claude-code/ not found.");
67
+ return;
86
68
  }
87
69
 
88
- // Copy plugin metadata
89
- if (existsSync(PLUGIN_DIR)) {
90
- await copyDir(PLUGIN_DIR, join(DIST_CLAUDE_CODE, ".claude-plugin"));
91
- console.log(" ✓ .claude-plugin/");
92
- }
70
+ await copyDir(CLAUDE_CODE_DIR, DIST_CLAUDE_CODE);
71
+ console.log(" ✓ Copied claude-code/ → dist/claude-code/");
93
72
  }
94
73
 
95
74
  async function buildOpenCode(): Promise<void> {
96
75
  console.log("\nBuilding OpenCode distribution...");
97
- await mkdir(DIST_OPENCODE, { recursive: true });
98
76
 
99
77
  if (!existsSync(OPENCODE_DIR)) {
100
- console.log(" ⚠ .opencode/ not found. Run sync-opencode.ts first.");
78
+ console.log(" ⚠ opencode/ not found.");
101
79
  return;
102
80
  }
103
81
 
104
- // Copy agent files
105
- const agentDir = join(OPENCODE_DIR, "agent");
106
- if (existsSync(agentDir)) {
107
- await copyDir(agentDir, join(DIST_OPENCODE, "agent"));
108
- console.log(" ✓ agent/");
109
- }
110
-
111
- // Copy command files
112
- const commandDir = join(OPENCODE_DIR, "command");
113
- if (existsSync(commandDir)) {
114
- await copyDir(commandDir, join(DIST_OPENCODE, "command"));
115
- console.log(" ✓ command/");
116
- }
82
+ await copyDir(OPENCODE_DIR, DIST_OPENCODE);
83
+ console.log(" ✓ Copied opencode/ dist/opencode/");
117
84
  }
118
85
 
119
86
  // ============================================================================
@@ -62,19 +62,29 @@ def validate_command_frontmatter(
62
62
  return errors
63
63
 
64
64
 
65
+ def is_opencode_path(file_path: Path) -> bool:
66
+ """Check if the file path is in the OpenCode directory structure."""
67
+ return "opencode" in file_path.parts
68
+
69
+
65
70
  def validate_agent_frontmatter(
66
71
  frontmatter: dict[str, Any], file_path: Path
67
72
  ) -> list[str]:
68
- """Validate agent file frontmatter."""
73
+ """Validate agent file frontmatter.
74
+
75
+ Claude Code agents require: name, description
76
+ OpenCode agents require: description (name is optional, uses filename)
77
+ """
69
78
  errors = []
70
79
 
71
- # Agents should have name and description
72
- if "name" not in frontmatter:
73
- errors.append("Missing required field 'name'")
74
- elif (
75
- not isinstance(frontmatter["name"], str) or not frontmatter["name"].strip()
76
- ):
77
- errors.append("Field 'name' must be a non-empty string")
80
+ # OpenCode agents don't require name - they use the filename
81
+ if not is_opencode_path(file_path):
82
+ if "name" not in frontmatter:
83
+ errors.append("Missing required field 'name'")
84
+ elif (
85
+ not isinstance(frontmatter["name"], str) or not frontmatter["name"].strip()
86
+ ):
87
+ errors.append("Field 'name' must be a non-empty string")
78
88
 
79
89
  if "description" not in frontmatter:
80
90
  errors.append("Missing required field 'description'")
@@ -88,14 +98,19 @@ def validate_agent_frontmatter(
88
98
 
89
99
 
90
100
  def get_file_type(file_path: Path) -> str | None:
91
- """Determine the type of file based on its directory."""
101
+ """Determine the type of file based on its directory.
102
+
103
+ Handles both Claude Code and OpenCode directory structures:
104
+ - Claude Code: commands/, skills/, agents/
105
+ - OpenCode: command/, agent/
106
+ """
92
107
  parts = file_path.parts
93
108
  for i, part in enumerate(parts):
94
109
  if part == "skills":
95
110
  return "skill"
96
- elif part == "commands":
111
+ elif part in ("commands", "command"):
97
112
  return "command"
98
- elif part == "agents":
113
+ elif part in ("agents", "agent"):
99
114
  return "agent"
100
115
  return None
101
116
 
@@ -17,7 +17,7 @@
17
17
  * OpenCode: ~/.config/opencode/
18
18
  */
19
19
 
20
- import { copyFile, readdir, mkdir, stat } from "fs/promises";
20
+ import { copyFile, readdir, mkdir, stat, rm } from "fs/promises";
21
21
  import { existsSync } from "fs";
22
22
  import { join } from "path";
23
23
  import { homedir } from "os";
@@ -35,9 +35,9 @@ const ROOT_DIR = join(SCRIPT_DIR, "..");
35
35
  const DIST_DIR = join(ROOT_DIR, "dist");
36
36
  const USE_DIST = existsSync(DIST_DIR);
37
37
 
38
- // Source paths (development)
39
- const DEV_CLAUDE_CODE = ROOT_DIR;
40
- const DEV_OPENCODE = join(ROOT_DIR, ".opencode");
38
+ // Source paths (development) - separate directories for each platform
39
+ const DEV_CLAUDE_CODE = join(ROOT_DIR, "claude-code");
40
+ const DEV_OPENCODE = join(ROOT_DIR, "opencode");
41
41
 
42
42
  // Dist paths (installed)
43
43
  const DIST_CLAUDE_CODE = join(DIST_DIR, "claude-code");
@@ -48,7 +48,7 @@ const CLAUDE_CODE_PLUGINS_DIR = join(
48
48
  homedir(),
49
49
  ".claude",
50
50
  "plugins",
51
- "questionably-ultrathink"
51
+ "questionably-ultrathink",
52
52
  );
53
53
  const OPENCODE_CONFIG_DIR = join(homedir(), ".config", "opencode");
54
54
 
@@ -109,26 +109,6 @@ function getDetectedList(detected: DetectionResult): Platform[] {
109
109
  return platforms;
110
110
  }
111
111
 
112
- // ============================================================================
113
- // OpenCode Sync (imports sync logic if needed in development)
114
- // ============================================================================
115
-
116
- async function syncOpenCode(): Promise<boolean> {
117
- // Only needed in development mode when .opencode doesn't exist
118
- if (USE_DIST) return true;
119
-
120
- if (!existsSync(DEV_OPENCODE)) {
121
- // Run the sync script
122
- const proc = Bun.spawn(["bun", join(SCRIPT_DIR, "sync-opencode.ts")], {
123
- stdout: "pipe",
124
- stderr: "pipe",
125
- });
126
- await proc.exited;
127
- return proc.exitCode === 0;
128
- }
129
- return true;
130
- }
131
-
132
112
  // ============================================================================
133
113
  // Installation Functions
134
114
  // ============================================================================
@@ -141,6 +121,10 @@ async function installClaudeCode(): Promise<boolean> {
141
121
  }
142
122
 
143
123
  try {
124
+ // Clean existing installation for a fresh update
125
+ if (existsSync(CLAUDE_CODE_PLUGINS_DIR)) {
126
+ await rm(CLAUDE_CODE_PLUGINS_DIR, { recursive: true });
127
+ }
144
128
  await mkdir(CLAUDE_CODE_PLUGINS_DIR, { recursive: true });
145
129
 
146
130
  // Copy agents
@@ -164,10 +148,7 @@ async function installClaudeCode(): Promise<boolean> {
164
148
  // Copy plugin metadata
165
149
  const pluginDir = join(sourceDir, ".claude-plugin");
166
150
  if (existsSync(pluginDir)) {
167
- await copyDir(
168
- pluginDir,
169
- join(CLAUDE_CODE_PLUGINS_DIR, ".claude-plugin")
170
- );
151
+ await copyDir(pluginDir, join(CLAUDE_CODE_PLUGINS_DIR, ".claude-plugin"));
171
152
  }
172
153
 
173
154
  return true;
@@ -186,7 +167,7 @@ async function installOpenCode(): Promise<boolean> {
186
167
  try {
187
168
  await mkdir(OPENCODE_CONFIG_DIR, { recursive: true });
188
169
 
189
- // Copy agent files
170
+ // Copy agent files (delete existing first for clean update)
190
171
  const agentDir = join(sourceDir, "agent");
191
172
  if (existsSync(agentDir)) {
192
173
  const destAgentDir = join(OPENCODE_CONFIG_DIR, "agent");
@@ -195,12 +176,17 @@ async function installOpenCode(): Promise<boolean> {
195
176
  const files = await readdir(agentDir);
196
177
  for (const file of files) {
197
178
  if (file.endsWith(".md")) {
198
- await copyFile(join(agentDir, file), join(destAgentDir, file));
179
+ const destPath = join(destAgentDir, file);
180
+ // Remove existing file first for clean update
181
+ if (existsSync(destPath)) {
182
+ await rm(destPath);
183
+ }
184
+ await copyFile(join(agentDir, file), destPath);
199
185
  }
200
186
  }
201
187
  }
202
188
 
203
- // Copy command files
189
+ // Copy command files (delete existing first for clean update)
204
190
  const commandDir = join(sourceDir, "command");
205
191
  if (existsSync(commandDir)) {
206
192
  const destCommandDir = join(OPENCODE_CONFIG_DIR, "command");
@@ -209,7 +195,12 @@ async function installOpenCode(): Promise<boolean> {
209
195
  const files = await readdir(commandDir);
210
196
  for (const file of files) {
211
197
  if (file.endsWith(".md")) {
212
- await copyFile(join(commandDir, file), join(destCommandDir, file));
198
+ const destPath = join(destCommandDir, file);
199
+ // Remove existing file first for clean update
200
+ if (existsSync(destPath)) {
201
+ await rm(destPath);
202
+ }
203
+ await copyFile(join(commandDir, file), destPath);
213
204
  }
214
205
  }
215
206
  }
@@ -258,7 +249,8 @@ async function runInteractive(): Promise<void> {
258
249
  hint: "~/.config/opencode/",
259
250
  },
260
251
  ],
261
- initialValues: detectedList.length > 0 ? detectedList : ["claude-code", "opencode"],
252
+ initialValues:
253
+ detectedList.length > 0 ? detectedList : ["claude-code", "opencode"],
262
254
  required: true,
263
255
  });
264
256
 
@@ -273,34 +265,27 @@ async function runInteractive(): Promise<void> {
273
265
 
274
266
  async function runNonInteractive(
275
267
  installClaudeCodeFlag: boolean,
276
- installOpenCodeFlag: boolean
268
+ installOpenCodeFlag: boolean,
277
269
  ): Promise<void> {
278
270
  console.log("UltraThink Plugin Installer");
279
271
  console.log("===========================");
280
- console.log(`Source: ${USE_DIST ? "dist/ (installed)" : "local (development)"}`);
272
+ console.log(
273
+ `Source: ${USE_DIST ? "dist/ (installed)" : "local (development)"}`,
274
+ );
281
275
 
282
276
  const platforms: Platform[] = [];
283
277
  if (installClaudeCodeFlag) platforms.push("claude-code");
284
278
  if (installOpenCodeFlag) platforms.push("opencode");
285
279
 
286
280
  if (platforms.length === 0) {
287
- console.log("\nNo platforms specified. Use --claude-code, --opencode, or --both.");
281
+ console.log(
282
+ "\nNo platforms specified. Use --claude-code, --opencode, or --both.",
283
+ );
288
284
  process.exit(1);
289
285
  }
290
286
 
291
287
  console.log("\nInstalling to:", platforms.join(", "));
292
288
 
293
- // Sync OpenCode if needed
294
- if (platforms.includes("opencode") && !USE_DIST) {
295
- console.log("\nSyncing OpenCode files...");
296
- const synced = await syncOpenCode();
297
- if (!synced) {
298
- console.log(" ⚠ Failed to sync OpenCode files");
299
- } else {
300
- console.log(" ✓ Synced OpenCode files");
301
- }
302
- }
303
-
304
289
  let success = true;
305
290
 
306
291
  if (platforms.includes("claude-code")) {
@@ -342,16 +327,6 @@ async function performInstallation(platforms: Platform[]): Promise<void> {
342
327
 
343
328
  const results: string[] = [];
344
329
 
345
- // Sync OpenCode if needed
346
- if (platforms.includes("opencode") && !USE_DIST) {
347
- const synced = await syncOpenCode();
348
- if (synced) {
349
- results.push("✓ Synced OpenCode files");
350
- } else {
351
- results.push("⚠ Failed to sync OpenCode files");
352
- }
353
- }
354
-
355
330
  // Install to Claude Code
356
331
  if (platforms.includes("claude-code")) {
357
332
  const success = await installClaudeCode();
@@ -449,7 +424,7 @@ async function main(): Promise<void> {
449
424
  if (hasClaudeCodeFlag || hasOpenCodeFlag || hasBothFlag) {
450
425
  await runNonInteractive(
451
426
  hasClaudeCodeFlag || hasBothFlag,
452
- hasOpenCodeFlag || hasBothFlag
427
+ hasOpenCodeFlag || hasBothFlag,
453
428
  );
454
429
  return;
455
430
  }
@@ -469,7 +444,7 @@ async function main(): Promise<void> {
469
444
 
470
445
  await runNonInteractive(
471
446
  platforms.includes("claude-code"),
472
- platforms.includes("opencode")
447
+ platforms.includes("opencode"),
473
448
  );
474
449
  return;
475
450
  }
@@ -106,7 +106,31 @@ validate_directory_structure() {
106
106
 
107
107
  local has_content=false
108
108
 
109
- # Check for required content directories
109
+ # Check Claude Code directories (claude-code/commands/, claude-code/skills/, claude-code/agents/)
110
+ for dir in "claude-code/commands" "claude-code/skills" "claude-code/agents"; do
111
+ if [[ -d "$dir" ]]; then
112
+ local md_count
113
+ md_count=$(find "$dir" -name "*.md" | wc -l)
114
+ if [[ $md_count -gt 0 ]]; then
115
+ has_content=true
116
+ echo " Found $md_count .md file(s) in $dir/"
117
+ fi
118
+ fi
119
+ done
120
+
121
+ # Check OpenCode directories (opencode/command/, opencode/agent/)
122
+ for dir in "opencode/command" "opencode/agent"; do
123
+ if [[ -d "$dir" ]]; then
124
+ local md_count
125
+ md_count=$(find "$dir" -name "*.md" | wc -l)
126
+ if [[ $md_count -gt 0 ]]; then
127
+ has_content=true
128
+ echo " Found $md_count .md file(s) in $dir/"
129
+ fi
130
+ fi
131
+ done
132
+
133
+ # Fallback: check legacy root directories (commands/, skills/, agents/)
110
134
  for dir in "commands" "skills" "agents"; do
111
135
  if [[ -d "$dir" ]]; then
112
136
  local md_count
@@ -119,7 +143,7 @@ validate_directory_structure() {
119
143
  done
120
144
 
121
145
  if [[ "$has_content" == "false" ]]; then
122
- error "Plugin must have at least one of: commands/, skills/, or agents/ with .md files"
146
+ error "Plugin must have at least one of: commands/, skills/, agents/, claude-code/*/, or opencode/*/ with .md files"
123
147
  exit 1
124
148
  fi
125
149
 
@@ -1,17 +0,0 @@
1
- {
2
- "name": "questionably-ultrathink",
3
- "owner": {
4
- "name": "snowmead"
5
- },
6
- "metadata": {
7
- "description": "Plugin marketplace for UltraThink reasoning framework integrating Chain of Verification and Atom of Thoughts",
8
- "version": "1.0.0"
9
- },
10
- "plugins": [
11
- {
12
- "name": "questionably-ultrathink",
13
- "description": "Advanced reasoning plugin integrating Chain of Verification (CoVe) and Atom of Thoughts (AoT) frameworks for rigorous, verifiable analysis",
14
- "source": "./"
15
- }
16
- ]
17
- }
@@ -1,349 +0,0 @@
1
- #!/usr/bin/env bun
2
- /**
3
- * Sync script to translate Claude Code plugin files to OpenCode format.
4
- *
5
- * This script maintains Claude Code as the source of truth and generates
6
- * OpenCode-compatible files in .opencode/
7
- *
8
- * Run: bun scripts/sync-opencode.ts
9
- * Watch: bun scripts/sync-opencode.ts --watch
10
- */
11
-
12
- import { readdir, readFile, writeFile, mkdir, rm } from "fs/promises";
13
- import { existsSync } from "fs";
14
- import { join, basename } from "path";
15
- import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
16
-
17
- // ============================================================================
18
- // Types
19
- // ============================================================================
20
-
21
- interface ClaudeCodeAgentFrontmatter {
22
- name: string;
23
- description: string;
24
- model?: string;
25
- tools?: string[];
26
- }
27
-
28
- interface ClaudeCodeCommandFrontmatter {
29
- name: string;
30
- description: string;
31
- "allowed-tools"?: string[];
32
- }
33
-
34
- interface ClaudeCodeSkillFrontmatter {
35
- name: string;
36
- description: string;
37
- "allowed-tools"?: string[];
38
- }
39
-
40
- interface OpenCodeAgentFrontmatter {
41
- description: string;
42
- mode: "primary" | "subagent";
43
- model?: string;
44
- permission?: Record<string, "allow" | "ask" | "deny">;
45
- hidden?: boolean;
46
- }
47
-
48
- interface OpenCodeCommandFrontmatter {
49
- description: string;
50
- }
51
-
52
- interface ParsedFile<T> {
53
- frontmatter: T;
54
- body: string;
55
- }
56
-
57
- // ============================================================================
58
- // Configuration
59
- // ============================================================================
60
-
61
- const ROOT_DIR = process.cwd();
62
- const AGENTS_DIR = join(ROOT_DIR, "agents");
63
- const COMMANDS_DIR = join(ROOT_DIR, "commands");
64
- const SKILLS_DIR = join(ROOT_DIR, "skills");
65
- const OPENCODE_AGENT_DIR = join(ROOT_DIR, ".opencode", "agent");
66
- const OPENCODE_COMMAND_DIR = join(ROOT_DIR, ".opencode", "command");
67
-
68
- // Model mapping from Claude Code shorthand to OpenCode full IDs
69
- const MODEL_MAP: Record<string, string> = {
70
- haiku: "anthropic/claude-3-5-haiku",
71
- sonnet: "anthropic/claude-sonnet-4-20250514",
72
- opus: "anthropic/claude-opus-4-20250514",
73
- };
74
-
75
- // Tool name mapping from Claude Code to OpenCode permission names
76
- const TOOL_MAP: Record<string, string> = {
77
- Read: "read",
78
- Write: "write",
79
- Edit: "edit",
80
- Bash: "bash",
81
- Grep: "grep",
82
- Glob: "glob",
83
- WebFetch: "webfetch",
84
- WebSearch: "webfetch",
85
- AskUserQuestion: "ask",
86
- Task: "task",
87
- };
88
-
89
- // ============================================================================
90
- // Utilities
91
- // ============================================================================
92
-
93
- /**
94
- * Parse a markdown file with YAML frontmatter
95
- */
96
- function parseFrontmatter<T>(content: string): ParsedFile<T> {
97
- const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
98
- if (!match) {
99
- throw new Error("Invalid frontmatter format");
100
- }
101
- return {
102
- frontmatter: parseYaml(match[1]) as T,
103
- body: match[2],
104
- };
105
- }
106
-
107
- /**
108
- * Format a file with YAML frontmatter
109
- */
110
- function formatWithFrontmatter<T>(frontmatter: T, body: string): string {
111
- const yamlContent = stringifyYaml(frontmatter, { lineWidth: 0 });
112
- return `---\n${yamlContent}---\n${body}`;
113
- }
114
-
115
- /**
116
- * Translate Claude Code model name to OpenCode model ID
117
- */
118
- function translateModel(model?: string): string | undefined {
119
- if (!model) return undefined;
120
- return MODEL_MAP[model] || model;
121
- }
122
-
123
- /**
124
- * Translate Claude Code tools array to OpenCode permission object
125
- */
126
- function translateTools(
127
- tools?: string[]
128
- ): Record<string, "allow"> | undefined {
129
- if (!tools || tools.length === 0) return undefined;
130
-
131
- const result: Record<string, "allow"> = {};
132
- for (const tool of tools) {
133
- // Handle MCP tool patterns (e.g., mcp__parallel-search__*)
134
- if (tool.startsWith("mcp__")) {
135
- result["mcp"] = "allow";
136
- continue;
137
- }
138
- const mapped = TOOL_MAP[tool];
139
- if (mapped) {
140
- result[mapped] = "allow";
141
- }
142
- }
143
- return Object.keys(result).length > 0 ? result : undefined;
144
- }
145
-
146
- /**
147
- * Translate body content from Claude Code patterns to OpenCode patterns
148
- */
149
- function translateBody(body: string): string {
150
- return (
151
- body
152
- // Translate subagent_type references in Task tool invocations
153
- .replace(
154
- /subagent_type:\s*["']questionably-ultrathink:([^"']+)["']/g,
155
- "@$1"
156
- )
157
- // Also handle unquoted variants
158
- .replace(/subagent_type:\s*questionably-ultrathink:(\S+)/g, "@$1")
159
- // Translate "Invoke Task tool" phrases
160
- .replace(
161
- /Invoke (?:the )?Task tool/gi,
162
- "Use the task tool to invoke the subagent"
163
- )
164
- // Translate "Task tool" in code blocks
165
- .replace(/Task tool:\s*\n/g, "Task:\n")
166
- );
167
- }
168
-
169
- /**
170
- * Clean the output directories
171
- */
172
- async function cleanOutputDirs(): Promise<void> {
173
- if (existsSync(OPENCODE_AGENT_DIR)) {
174
- await rm(OPENCODE_AGENT_DIR, { recursive: true });
175
- }
176
- if (existsSync(OPENCODE_COMMAND_DIR)) {
177
- await rm(OPENCODE_COMMAND_DIR, { recursive: true });
178
- }
179
- }
180
-
181
- // ============================================================================
182
- // Sync Functions
183
- // ============================================================================
184
-
185
- /**
186
- * Sync agents from agents/ to .opencode/agent/
187
- */
188
- async function syncAgents(): Promise<void> {
189
- console.log("Syncing agents...");
190
- await mkdir(OPENCODE_AGENT_DIR, { recursive: true });
191
-
192
- const files = await readdir(AGENTS_DIR);
193
- const mdFiles = files.filter((f) => f.endsWith(".md"));
194
-
195
- for (const file of mdFiles) {
196
- const content = await readFile(join(AGENTS_DIR, file), "utf-8");
197
- const { frontmatter, body } =
198
- parseFrontmatter<ClaudeCodeAgentFrontmatter>(content);
199
-
200
- const openCodeFrontmatter: OpenCodeAgentFrontmatter = {
201
- description: frontmatter.description.trim(),
202
- mode: "subagent",
203
- model: translateModel(frontmatter.model),
204
- permission: translateTools(frontmatter.tools),
205
- hidden: true, // Hide subagents from @ menu
206
- };
207
-
208
- // Remove undefined fields
209
- if (!openCodeFrontmatter.model) delete openCodeFrontmatter.model;
210
- if (!openCodeFrontmatter.permission) delete openCodeFrontmatter.permission;
211
-
212
- const translatedBody = translateBody(body);
213
- const outputContent = formatWithFrontmatter(
214
- openCodeFrontmatter,
215
- translatedBody
216
- );
217
-
218
- await writeFile(join(OPENCODE_AGENT_DIR, file), outputContent);
219
- console.log(` ✓ ${file}`);
220
- }
221
- }
222
-
223
- /**
224
- * Sync the skill as the primary orchestrator agent
225
- */
226
- async function syncSkill(): Promise<void> {
227
- console.log("Syncing skill as orchestrator...");
228
-
229
- const skillDir = join(SKILLS_DIR, "questionably-ultrathink-skill");
230
- const skillPath = join(skillDir, "SKILL.md");
231
-
232
- if (!existsSync(skillPath)) {
233
- console.log(" ⚠ No skill found, skipping orchestrator");
234
- return;
235
- }
236
-
237
- const content = await readFile(skillPath, "utf-8");
238
- const { frontmatter, body } =
239
- parseFrontmatter<ClaudeCodeSkillFrontmatter>(content);
240
-
241
- // Build permission with task subagent permissions
242
- const basePermissions = translateTools(frontmatter["allowed-tools"]) || {};
243
-
244
- const openCodeFrontmatter: OpenCodeAgentFrontmatter = {
245
- description: frontmatter.description.trim(),
246
- mode: "primary",
247
- model: "anthropic/claude-sonnet-4-20250514",
248
- permission: {
249
- ...basePermissions,
250
- task: "allow", // Allow invoking subagents
251
- },
252
- };
253
-
254
- const translatedBody = translateBody(body);
255
- const outputContent = formatWithFrontmatter(
256
- openCodeFrontmatter,
257
- translatedBody
258
- );
259
-
260
- await mkdir(OPENCODE_AGENT_DIR, { recursive: true });
261
- await writeFile(
262
- join(OPENCODE_AGENT_DIR, "questionably-ultrathink.md"),
263
- outputContent
264
- );
265
- console.log(" ✓ questionably-ultrathink.md (orchestrator)");
266
- }
267
-
268
- /**
269
- * Sync commands from commands/ to .opencode/command/
270
- */
271
- async function syncCommands(): Promise<void> {
272
- console.log("Syncing commands...");
273
- await mkdir(OPENCODE_COMMAND_DIR, { recursive: true });
274
-
275
- const files = await readdir(COMMANDS_DIR);
276
- const mdFiles = files.filter((f) => f.endsWith(".md"));
277
-
278
- for (const file of mdFiles) {
279
- const content = await readFile(join(COMMANDS_DIR, file), "utf-8");
280
- const { frontmatter, body } =
281
- parseFrontmatter<ClaudeCodeCommandFrontmatter>(content);
282
-
283
- const openCodeFrontmatter: OpenCodeCommandFrontmatter = {
284
- description: frontmatter.description,
285
- };
286
-
287
- // Translate body and add agent switch directive for skill-invoking commands
288
- let translatedBody = translateBody(body);
289
-
290
- // If the command invokes the Skill tool, add switch directive
291
- if (body.includes("Skill tool") || body.includes("skill:")) {
292
- translatedBody = `Switch to the @questionably-ultrathink agent.\n\n${translatedBody}`;
293
- }
294
-
295
- const outputContent = formatWithFrontmatter(
296
- openCodeFrontmatter,
297
- translatedBody
298
- );
299
-
300
- await writeFile(join(OPENCODE_COMMAND_DIR, file), outputContent);
301
- console.log(` ✓ ${file}`);
302
- }
303
- }
304
-
305
- // ============================================================================
306
- // Main
307
- // ============================================================================
308
-
309
- async function main(): Promise<void> {
310
- const isWatch = process.argv.includes("--watch");
311
-
312
- console.log("OpenCode Sync Script");
313
- console.log("====================\n");
314
-
315
- if (isWatch) {
316
- console.log("Watch mode not yet implemented. Running single sync.\n");
317
- }
318
-
319
- try {
320
- // Clean output directories
321
- console.log("Cleaning output directories...");
322
- await cleanOutputDirs();
323
- console.log(" ✓ Done\n");
324
-
325
- // Sync all components
326
- await syncAgents();
327
- console.log("");
328
- await syncSkill();
329
- console.log("");
330
- await syncCommands();
331
-
332
- console.log("\n✅ Sync complete!");
333
- console.log("\nGenerated files:");
334
- console.log(" .opencode/agent/");
335
- console.log(" - atom-of-thoughts.md");
336
- console.log(" - chain-of-verification.md");
337
- console.log(" - aot-recompute.md");
338
- console.log(" - questionably-ultrathink.md (orchestrator)");
339
- console.log(" .opencode/command/");
340
- console.log(" - questionably-ultrathink.md");
341
- console.log(" - decompose.md");
342
- console.log(" - verify.md");
343
- } catch (error) {
344
- console.error("\n❌ Sync failed:", error);
345
- process.exit(1);
346
- }
347
- }
348
-
349
- main();