Skip to content

Commit 0aa8f48

Browse files
feat(hooks): add sisyphus-junior-notepad hook for conditional notepad rules injection (#1092)
* refactor(shared): extract isCallerOrchestrator to session-utils * refactor(atlas): use shared isCallerOrchestrator, change to prepend * refactor(prometheus-md-only): change to prepend pattern * refactor(sisyphus-junior): remove Work_Context (moved to hook) * feat(hooks): add sisyphus-junior-notepad hook * fix(shared): replace dynamic require with static import in session-utils - Change from dynamic require to static import for better bundler compatibility - Fix import path: ../../features -> ../features - Add barrel export to src/shared/index.ts * feat(hooks): register sisyphus-junior-notepad hook - Add to HookNameSchema in schema.ts - Export from hooks/index.ts - Register with isHookEnabled in index.ts - Auto-generated schema.json update --------- Co-authored-by: justsisyphus <justsisyphus@users.noreply.github.com>
1 parent a5db86e commit 0aa8f48

File tree

11 files changed

+117
-53
lines changed

11 files changed

+117
-53
lines changed

assets/oh-my-opencode.schema.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
"edit-error-recovery",
7777
"delegate-task-retry",
7878
"prometheus-md-only",
79+
"sisyphus-junior-notepad",
7980
"start-work",
8081
"atlas"
8182
]

src/agents/sisyphus-junior.ts

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -20,32 +20,6 @@ ALLOWED: call_omo_agent - You CAN spawn explore/librarian agents for research.
2020
You work ALONE for implementation. No delegation of implementation tasks.
2121
</Critical_Constraints>
2222
23-
<Work_Context>
24-
## Notepad Location (for recording learnings)
25-
NOTEPAD PATH: .sisyphus/notepads/{plan-name}/
26-
- learnings.md: Record patterns, conventions, successful approaches
27-
- issues.md: Record problems, blockers, gotchas encountered
28-
- decisions.md: Record architectural choices and rationales
29-
- problems.md: Record unresolved issues, technical debt
30-
31-
You SHOULD append findings to notepad files after completing work.
32-
IMPORTANT: Always APPEND to notepad files - never overwrite or use Edit tool.
33-
34-
## Plan Location (READ ONLY)
35-
PLAN PATH: .sisyphus/plans/{plan-name}.md
36-
37-
CRITICAL RULE: NEVER MODIFY THE PLAN FILE
38-
39-
The plan file (.sisyphus/plans/*.md) is SACRED and READ-ONLY.
40-
- You may READ the plan to understand tasks
41-
- You may READ checkbox items to know what to do
42-
- You MUST NOT edit, modify, or update the plan file
43-
- You MUST NOT mark checkboxes as complete in the plan
44-
- Only the Orchestrator manages the plan file
45-
46-
VIOLATION = IMMEDIATE FAILURE. The Orchestrator tracks plan state.
47-
</Work_Context>
48-
4923
<Todo_Discipline>
5024
TODO OBSESSION (NON-NEGOTIABLE):
5125
- 2+ steps → todowrite FIRST, atomic breakdown

src/config/schema.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ export const HookNameSchema = z.enum([
8383
"edit-error-recovery",
8484
"delegate-task-retry",
8585
"prometheus-md-only",
86+
"sisyphus-junior-notepad",
8687
"start-work",
8788
"atlas",
8889
])

src/hooks/atlas/index.ts

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { getMainSessionID, subagentSessions } from "../../features/claude-code-s
1111
import { findNearestMessageWithFields, MESSAGE_STORAGE } from "../../features/hook-message-injector"
1212
import { log } from "../../shared/logger"
1313
import { createSystemDirective, SYSTEM_DIRECTIVE_PREFIX, SystemDirectiveTypes } from "../../shared/system-directive"
14+
import { isCallerOrchestrator, getMessageDir } from "../../shared/session-utils"
1415
import type { BackgroundManager } from "../../features/background-agent"
1516

1617
export const HOOK_NAME = "atlas"
@@ -380,28 +381,6 @@ interface ToolExecuteAfterOutput {
380381
metadata: Record<string, unknown>
381382
}
382383

383-
function getMessageDir(sessionID: string): string | null {
384-
if (!existsSync(MESSAGE_STORAGE)) return null
385-
386-
const directPath = join(MESSAGE_STORAGE, sessionID)
387-
if (existsSync(directPath)) return directPath
388-
389-
for (const dir of readdirSync(MESSAGE_STORAGE)) {
390-
const sessionPath = join(MESSAGE_STORAGE, dir, sessionID)
391-
if (existsSync(sessionPath)) return sessionPath
392-
}
393-
394-
return null
395-
}
396-
397-
function isCallerOrchestrator(sessionID?: string): boolean {
398-
if (!sessionID) return false
399-
const messageDir = getMessageDir(sessionID)
400-
if (!messageDir) return false
401-
const nearest = findNearestMessageWithFields(messageDir)
402-
return nearest?.agent?.toLowerCase() === "atlas"
403-
}
404-
405384
interface SessionState {
406385
lastEventWasAbortError?: boolean
407386
lastContinuationInjectedAt?: number
@@ -672,7 +651,7 @@ export function createAtlasHook(
672651
if (input.tool === "delegate_task") {
673652
const prompt = output.args.prompt as string | undefined
674653
if (prompt && !prompt.includes(SYSTEM_DIRECTIVE_PREFIX)) {
675-
output.args.prompt = prompt + `\n<system-reminder>${SINGLE_TASK_DIRECTIVE}</system-reminder>`
654+
output.args.prompt = `<system-reminder>${SINGLE_TASK_DIRECTIVE}</system-reminder>\n` + prompt
676655
log(`[${HOOK_NAME}] Injected single-task directive to delegate_task`, {
677656
sessionID: input.sessionID,
678657
})

src/hooks/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export { createRalphLoopHook, type RalphLoopHook } from "./ralph-loop";
2626
export { createAutoSlashCommandHook } from "./auto-slash-command";
2727
export { createEditErrorRecoveryHook } from "./edit-error-recovery";
2828
export { createPrometheusMdOnlyHook } from "./prometheus-md-only";
29+
export { createSisyphusJuniorNotepadHook } from "./sisyphus-junior-notepad";
2930
export { createTaskResumeInfoHook } from "./task-resume-info";
3031
export { createStartWorkHook } from "./start-work";
3132
export { createAtlasHook } from "./atlas";

src/hooks/prometheus-md-only/index.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,10 +89,10 @@ export function createPrometheusMdOnlyHook(ctx: PluginInput) {
8989
const toolName = input.tool
9090

9191
// Inject read-only warning for task tools called by Prometheus
92-
if (TASK_TOOLS.includes(toolName)) {
93-
const prompt = output.args.prompt as string | undefined
94-
if (prompt && !prompt.includes(SYSTEM_DIRECTIVE_PREFIX)) {
95-
output.args.prompt = prompt + PLANNING_CONSULT_WARNING
92+
if (TASK_TOOLS.includes(toolName)) {
93+
const prompt = output.args.prompt as string | undefined
94+
if (prompt && !prompt.includes(SYSTEM_DIRECTIVE_PREFIX)) {
95+
output.args.prompt = PLANNING_CONSULT_WARNING + prompt
9696
log(`[${HOOK_NAME}] Injected read-only planning warning to ${toolName}`, {
9797
sessionID: input.sessionID,
9898
tool: toolName,
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
export const HOOK_NAME = "sisyphus-junior-notepad"
2+
3+
export const NOTEPAD_DIRECTIVE = `
4+
<Work_Context>
5+
## Notepad Location (for recording learnings)
6+
NOTEPAD PATH: .sisyphus/notepads/{plan-name}/
7+
- learnings.md: Record patterns, conventions, successful approaches
8+
- issues.md: Record problems, blockers, gotchas encountered
9+
- decisions.md: Record architectural choices and rationales
10+
- problems.md: Record unresolved issues, technical debt
11+
12+
You SHOULD append findings to notepad files after completing work.
13+
IMPORTANT: Always APPEND to notepad files - never overwrite or use Edit tool.
14+
15+
## Plan Location (READ ONLY)
16+
PLAN PATH: .sisyphus/plans/{plan-name}.md
17+
18+
CRITICAL RULE: NEVER MODIFY THE PLAN FILE
19+
20+
The plan file (.sisyphus/plans/*.md) is SACRED and READ-ONLY.
21+
- You may READ the plan to understand tasks
22+
- You may READ checkbox items to know what to do
23+
- You MUST NOT edit, modify, or update the plan file
24+
- You MUST NOT mark checkboxes as complete in the plan
25+
- Only the Orchestrator manages the plan file
26+
27+
VIOLATION = IMMEDIATE FAILURE. The Orchestrator tracks plan state.
28+
</Work_Context>
29+
`
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import type { PluginInput } from "@opencode-ai/plugin"
2+
import { isCallerOrchestrator } from "../../shared/session-utils"
3+
import { SYSTEM_DIRECTIVE_PREFIX } from "../../shared/system-directive"
4+
import { log } from "../../shared/logger"
5+
import { HOOK_NAME, NOTEPAD_DIRECTIVE } from "./constants"
6+
7+
export * from "./constants"
8+
9+
export function createSisyphusJuniorNotepadHook(ctx: PluginInput) {
10+
return {
11+
"tool.execute.before": async (
12+
input: { tool: string; sessionID: string; callID: string },
13+
output: { args: Record<string, unknown>; message?: string }
14+
): Promise<void> => {
15+
// 1. Check if tool is delegate_task
16+
if (input.tool !== "delegate_task") {
17+
return
18+
}
19+
20+
// 2. Check if caller is Atlas (orchestrator)
21+
if (!isCallerOrchestrator(input.sessionID)) {
22+
return
23+
}
24+
25+
// 3. Get prompt from output.args
26+
const prompt = output.args.prompt as string | undefined
27+
if (!prompt) {
28+
return
29+
}
30+
31+
// 4. Check for double injection
32+
if (prompt.includes(SYSTEM_DIRECTIVE_PREFIX)) {
33+
return
34+
}
35+
36+
// 5. Prepend directive
37+
output.args.prompt = NOTEPAD_DIRECTIVE + prompt
38+
39+
// 6. Log injection
40+
log(`[${HOOK_NAME}] Injected notepad directive to delegate_task`, {
41+
sessionID: input.sessionID,
42+
})
43+
},
44+
}
45+
}

src/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {
3131
createStartWorkHook,
3232
createAtlasHook,
3333
createPrometheusMdOnlyHook,
34+
createSisyphusJuniorNotepadHook,
3435
createQuestionLabelTruncatorHook,
3536
} from "./hooks";
3637
import {
@@ -204,6 +205,10 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
204205
? createPrometheusMdOnlyHook(ctx)
205206
: null;
206207

208+
const sisyphusJuniorNotepad = isHookEnabled("sisyphus-junior-notepad")
209+
? createSisyphusJuniorNotepadHook(ctx)
210+
: null;
211+
207212
const questionLabelTruncator = createQuestionLabelTruncatorHook();
208213

209214
const taskResumeInfo = createTaskResumeInfoHook();
@@ -495,6 +500,7 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
495500
await directoryReadmeInjector?.["tool.execute.before"]?.(input, output);
496501
await rulesInjector?.["tool.execute.before"]?.(input, output);
497502
await prometheusMdOnly?.["tool.execute.before"]?.(input, output);
503+
await sisyphusJuniorNotepad?.["tool.execute.before"]?.(input, output);
498504
await atlasHook?.["tool.execute.before"]?.(input, output);
499505

500506
if (input.tool === "task") {

src/shared/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,4 @@ export * from "./model-requirements"
2929
export * from "./model-resolver"
3030
export * from "./model-availability"
3131
export * from "./case-insensitive"
32+
export * from "./session-utils"

0 commit comments

Comments
 (0)