Browse Source

Add deepwork slash command

Alvin Unreal 2 months ago
parent
commit
d430630468
6 changed files with 167 additions and 1 deletions
  1. 7 0
      docs/skills.md
  2. 78 0
      src/hooks/deepwork/index.test.ts
  3. 62 0
      src/hooks/deepwork/index.ts
  4. 1 0
      src/hooks/index.ts
  5. 13 0
      src/index.ts
  6. 6 1
      src/skills/deepwork/SKILL.md

+ 7 - 0
docs/skills.md

@@ -88,6 +88,12 @@ See **[Clonedeps](clonedeps.md)** for the full workflow and file layout.
 
 `deepwork` is an orchestrator-only workflow skill for managing deep architectural work, multi-phase implementations, and complex refactoring. It provides a structured approach with mandatory review gates while maintaining flexibility in planning.
 
+Start it directly with:
+
+```text
+/deepwork <heavy coding task>
+```
+
 **How it works:**
 1. Orchestrator creates a session artifact at `.slim/deepwork/<task>.md`
 2. Draft plan → Oracle review → Revise until acceptable
@@ -98,6 +104,7 @@ See **[Clonedeps](clonedeps.md)** for the full workflow and file layout.
 **Key features:**
 - Persistent session state in markdown files
 - Mandatory oracle reviews at plan and phase boundaries
+- Oracle phase reviews include simplify/readability feedback alongside regular correctness and risk review
 - V2 scheduler integration (dispatch specialists, poll task_status, reconcile)
 - OpenCode todo lists for progress tracking
 - Flexible structure - orchestrator adapts format to task needs

+ 78 - 0
src/hooks/deepwork/index.test.ts

@@ -0,0 +1,78 @@
+import { describe, expect, test } from 'bun:test';
+import { SLIM_INTERNAL_INITIATOR_MARKER } from '../../utils';
+import { createDeepworkCommandHook } from './index';
+
+describe('deepwork command hook', () => {
+  test('registers /deepwork command when absent', () => {
+    const hook = createDeepworkCommandHook();
+    const config: Record<string, unknown> = {};
+
+    hook.registerCommand(config);
+
+    const command = (config.command as Record<string, unknown>).deepwork as {
+      template?: string;
+      description?: string;
+    };
+    expect(command).toBeDefined();
+    expect(command.template).toContain('deepwork');
+    expect(command.description).toContain('heavy');
+  });
+
+  test('does not overwrite existing /deepwork command', () => {
+    const hook = createDeepworkCommandHook();
+    const existing = { template: 'custom', description: 'custom command' };
+    const config: Record<string, unknown> = { command: { deepwork: existing } };
+
+    hook.registerCommand(config);
+
+    expect((config.command as Record<string, unknown>).deepwork).toBe(existing);
+  });
+
+  test('asks for a task when no arguments are provided', async () => {
+    const hook = createDeepworkCommandHook();
+    const output = { parts: [{ type: 'text', text: 'template' }] };
+
+    await hook.handleCommandExecuteBefore(
+      { command: 'deepwork', sessionID: 's1', arguments: '  ' },
+      output,
+    );
+
+    expect(output.parts).toHaveLength(1);
+    expect(output.parts[0].text).toContain('What task should deepwork manage?');
+    expect(output.parts[0].text).toContain(SLIM_INTERNAL_INITIATOR_MARKER);
+  });
+
+  test('expands arguments into a deepwork activation prompt', async () => {
+    const hook = createDeepworkCommandHook();
+    const output = { parts: [{ type: 'text', text: 'template' }] };
+
+    await hook.handleCommandExecuteBefore(
+      {
+        command: 'deepwork',
+        sessionID: 's1',
+        arguments: 'refactor scheduler state',
+      },
+      output,
+    );
+
+    expect(output.parts).toHaveLength(1);
+    expect(output.parts[0].text).toContain('Use the deepwork skill');
+    expect(output.parts[0].text).toContain('.slim/deepwork/');
+    expect(output.parts[0].text).toContain('@oracle');
+    expect(output.parts[0].text).toContain('simplify/readability');
+    expect(output.parts[0].text).toContain('refactor scheduler state');
+    expect(output.parts[0].text).not.toContain(SLIM_INTERNAL_INITIATOR_MARKER);
+  });
+
+  test('ignores other commands', async () => {
+    const hook = createDeepworkCommandHook();
+    const output = { parts: [{ type: 'text', text: 'template' }] };
+
+    await hook.handleCommandExecuteBefore(
+      { command: 'goal', sessionID: 's1', arguments: 'x' },
+      output,
+    );
+
+    expect(output.parts).toEqual([{ type: 'text', text: 'template' }]);
+  });
+});

+ 62 - 0
src/hooks/deepwork/index.ts

@@ -0,0 +1,62 @@
+import { createInternalAgentTextPart } from '../../utils';
+
+const COMMAND_NAME = 'deepwork';
+
+function activationPrompt(task: string): string {
+  return [
+    'Use the deepwork skill for this task. Treat it as a heavy coding session.',
+    '',
+    'Deepwork requirements:',
+    '- create/update a `.slim/deepwork/` progress file;',
+    '- keep OpenCode todos synced with the current phase;',
+    '- draft a plan and get `@oracle` review before implementation;',
+    '- create and review a phased implementation/delegation plan;',
+    '- execute phase by phase with background specialists where useful;',
+    '- poll `task_status`, reconcile results, validate, and ask `@oracle` to review each phase;',
+    '- ask `@oracle` to include simplify/readability feedback in phase reviews;',
+    '- fix actionable review issues before continuing.',
+    '',
+    'Task:',
+    task,
+  ].join('\n');
+}
+
+export function createDeepworkCommandHook(): {
+  registerCommand: (config: Record<string, unknown>) => void;
+  handleCommandExecuteBefore: (
+    input: { command: string; sessionID: string; arguments: string },
+    output: { parts: Array<{ type: string; text?: string }> },
+  ) => Promise<void>;
+} {
+  return {
+    registerCommand: (opencodeConfig) => {
+      const commandConfig = opencodeConfig.command as
+        | Record<string, unknown>
+        | undefined;
+      if (commandConfig?.[COMMAND_NAME]) return;
+      if (!opencodeConfig.command) opencodeConfig.command = {};
+      (opencodeConfig.command as Record<string, unknown>)[COMMAND_NAME] = {
+        template: 'Start a deepwork session for a complex coding task',
+        description:
+          'Use the deepwork workflow for heavy multi-phase coding work',
+      };
+    },
+
+    handleCommandExecuteBefore: async (input, output) => {
+      if (input.command !== COMMAND_NAME) return;
+
+      output.parts.length = 0;
+      const task = input.arguments.trim();
+      if (!task) {
+        output.parts.push(
+          createInternalAgentTextPart(
+            'What task should deepwork manage? Run `/deepwork <task>`.',
+          ),
+        );
+        return;
+      }
+
+      output.parts.push({ type: 'text', text: activationPrompt(task) });
+    },
+  };
+}

+ 1 - 0
src/hooks/index.ts

@@ -2,6 +2,7 @@ export { createApplyPatchHook } from './apply-patch';
 export type { AutoUpdateCheckerOptions } from './auto-update-checker';
 export { createAutoUpdateCheckerHook } from './auto-update-checker';
 export { createChatHeadersHook } from './chat-headers';
+export { createDeepworkCommandHook } from './deepwork';
 export { createDelegateTaskRetryHook } from './delegate-task-retry';
 export { createFilterAvailableSkillsHook } from './filter-available-skills';
 export {

+ 13 - 0
src/index.ts

@@ -20,6 +20,7 @@ import {
   createApplyPatchHook,
   createAutoUpdateCheckerHook,
   createChatHeadersHook,
+  createDeepworkCommandHook,
   createDelegateTaskRetryHook,
   createFilterAvailableSkillsHook,
   createGoalHook,
@@ -135,6 +136,7 @@ const OhMyOpenCodeLite: Plugin = async (ctx) => {
   let jsonErrorRecoveryHook: ReturnType<typeof createJsonErrorRecoveryHook>;
   let foregroundFallback: ForegroundFallbackManager;
   let todoContinuationHook: ReturnType<typeof createTodoContinuationHook>;
+  let deepworkCommandHook: ReturnType<typeof createDeepworkCommandHook>;
   let goalHook: ReturnType<typeof createGoalHook>;
   let taskSessionManagerHook: ReturnType<typeof createTaskSessionManagerHook>;
   let backgroundJobBoard: BackgroundJobBoard;
@@ -308,6 +310,7 @@ const OhMyOpenCodeLite: Plugin = async (ctx) => {
       autoEnableThreshold: config.todoContinuation?.autoEnableThreshold ?? 4,
       backgroundJobBoard,
     });
+    deepworkCommandHook = createDeepworkCommandHook();
     goalHook = createGoalHook(ctx, config, {
       getAgentName: (sessionID) => sessionAgentMap.get(sessionID),
     });
@@ -732,6 +735,7 @@ const OhMyOpenCodeLite: Plugin = async (ctx) => {
 
       interviewManager.registerCommand(opencodeConfig);
       goalHook.registerCommand(opencodeConfig);
+      deepworkCommandHook.registerCommand(opencodeConfig);
       presetManager.registerCommand(opencodeConfig);
     },
 
@@ -955,6 +959,15 @@ const OhMyOpenCodeLite: Plugin = async (ctx) => {
         },
         output as { parts: Array<{ type: string; text?: string }> },
       );
+
+      await deepworkCommandHook.handleCommandExecuteBefore(
+        input as {
+          command: string;
+          sessionID: string;
+          arguments: string;
+        },
+        output as { parts: Array<{ type: string; text?: string }> },
+      );
     },
 
     'chat.headers': chatHeadersHook['chat.headers'],

+ 6 - 1
src/skills/deepwork/SKILL.md

@@ -25,6 +25,8 @@ Required behavior:
 - execute phase by phase with specialist delegation where useful;
 - after each phase, validate, update the deepwork file, ask `@oracle` to review
   the phase result, fix actionable issues, then continue;
+- ask `@oracle` phase reviews to include simplify/readability feedback alongside
+  correctness, blockers, risks, and plan adherence;
 - finish with final validation and a concise summary.
 
 ## Deepwork File
@@ -77,7 +79,10 @@ Use the V2 scheduler model throughout:
   results are unreconciled.
 
 `@oracle` owns review and risk assessment. It should review plans and completed
-phase outputs, not become the default implementer.
+phase outputs, not become the default implementer. For phase reviews, explicitly
+ask oracle to use its simplify skill when available and report readability,
+maintainability, and unnecessary-complexity findings separately from blocking
+correctness issues.
 
 ## Lightweight Judgment