oh-my-claude-sisyphus 3.8.5 → 3.8.7

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.
@@ -446,34 +446,15 @@ Ask user: "Would you like to install the OMC CLI for standalone analytics? (Reco
446
446
  1. **Yes (Recommended)** - Install CLI tools globally for `omc stats`, `omc agents`, etc.
447
447
  2. **No** - Skip CLI installation, use only plugin skills
448
448
 
449
- ### If User Chooses YES:
449
+ ### CLI Installation Note
450
450
 
451
- ```bash
452
- # Check for bun (preferred) or npm
453
- if command -v bun &> /dev/null; then
454
- echo "Installing OMC CLI via bun..."
455
- bun install -g oh-my-claude-sisyphus
456
- elif command -v npm &> /dev/null; then
457
- echo "Installing OMC CLI via npm..."
458
- npm install -g oh-my-claude-sisyphus
459
- else
460
- echo "ERROR: Neither bun nor npm found. Please install Node.js or Bun first."
461
- exit 1
462
- fi
463
-
464
- # Verify installation
465
- if command -v omc &> /dev/null; then
466
- echo "✓ OMC CLI installed successfully!"
467
- echo " Try: omc stats, omc agents, omc tui"
468
- else
469
- echo "⚠ CLI installed but 'omc' not in PATH."
470
- echo " You may need to restart your terminal or add npm/bun global bin to PATH."
471
- fi
472
- ```
451
+ The CLI (`omc` command) is **no longer supported** via npm/bun global install.
473
452
 
474
- ### If User Chooses NO:
453
+ All functionality is available through the plugin system:
454
+ - Use `/oh-my-claudecode:help` for guidance
455
+ - Use `/oh-my-claudecode:doctor` for diagnostics
475
456
 
476
- Skip this step. User can install later with `npm install -g oh-my-claude-sisyphus`.
457
+ Skip this step - the plugin provides all features.
477
458
 
478
459
  ## Step 4: Verify Plugin Installation
479
460
 
@@ -1,23 +1,16 @@
1
1
  #!/usr/bin/env node
2
- // OMC Persistent Mode Hook (Node.js)
3
- // Unified handler for ultrawork, ralph-loop, and todo continuation
4
- // Cross-platform: Windows, macOS, Linux
5
-
6
- import { existsSync, readFileSync, writeFileSync, readdirSync } from 'fs';
7
- import { join } from 'path';
8
- import { homedir } from 'os';
9
2
 
10
3
  /**
11
- * Validates session ID to prevent path traversal attacks.
12
- * @param {string} sessionId
13
- * @returns {boolean}
4
+ * OMC Persistent Mode Hook (Node.js)
5
+ * Minimal continuation enforcer for all OMC modes.
6
+ * Stripped down for reliability — no optional imports, no PRD, no notepad pruning.
7
+ *
8
+ * Supported modes: ralph, autopilot, ultrapilot, swarm, ultrawork, ecomode, ultraqa, pipeline
14
9
  */
15
- function isValidSessionId(sessionId) {
16
- if (!sessionId || typeof sessionId !== 'string') return false;
17
- // Allow alphanumeric, hyphens, and underscores only
18
- // Must not start with dot or hyphen
19
- return /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,255}$/.test(sessionId);
20
- }
10
+
11
+ import { existsSync, readFileSync, writeFileSync, readdirSync, mkdirSync } from 'fs';
12
+ import { join } from 'path';
13
+ import { homedir } from 'os';
21
14
 
22
15
  async function readStdin() {
23
16
  const chunks = [];
@@ -36,19 +29,43 @@ function readJsonFile(path) {
36
29
  }
37
30
  }
38
31
 
32
+ function writeJsonFile(path, data) {
33
+ try {
34
+ // Ensure directory exists
35
+ const dir = path.substring(0, path.lastIndexOf('/'));
36
+ if (dir && !existsSync(dir)) {
37
+ mkdirSync(dir, { recursive: true });
38
+ }
39
+ writeFileSync(path, JSON.stringify(data, null, 2));
40
+ return true;
41
+ } catch {
42
+ return false;
43
+ }
44
+ }
45
+
39
46
  /**
40
- * Count incomplete tasks in the new Task system.
41
- *
42
- * SYNC NOTICE: This function is intentionally duplicated across:
43
- * - templates/hooks/persistent-mode.mjs
44
- * - templates/hooks/stop-continuation.mjs
45
- * - src/hooks/todo-continuation/index.ts (as checkIncompleteTasks)
46
- *
47
- * Templates cannot import shared modules (they're standalone scripts).
48
- * When modifying this logic, update ALL THREE files to maintain consistency.
47
+ * Read state file from local or global location, tracking the source.
48
+ */
49
+ function readStateFile(stateDir, globalStateDir, filename) {
50
+ const localPath = join(stateDir, filename);
51
+ const globalPath = join(globalStateDir, filename);
52
+
53
+ let state = readJsonFile(localPath);
54
+ if (state) return { state, path: localPath };
55
+
56
+ state = readJsonFile(globalPath);
57
+ if (state) return { state, path: globalPath };
58
+
59
+ return { state: null, path: localPath }; // Default to local for new writes
60
+ }
61
+
62
+ /**
63
+ * Count incomplete Tasks from Claude Code's native Task system.
49
64
  */
50
65
  function countIncompleteTasks(sessionId) {
51
- if (!sessionId || !isValidSessionId(sessionId)) return 0;
66
+ if (!sessionId || typeof sessionId !== 'string') return 0;
67
+ if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,255}$/.test(sessionId)) return 0;
68
+
52
69
  const taskDir = join(homedir(), '.claude', 'tasks', sessionId);
53
70
  if (!existsSync(taskDir)) return 0;
54
71
 
@@ -59,49 +76,39 @@ function countIncompleteTasks(sessionId) {
59
76
  try {
60
77
  const content = readFileSync(join(taskDir, file), 'utf-8');
61
78
  const task = JSON.parse(content);
62
- // Match TypeScript isTaskIncomplete(): only pending/in_progress are incomplete
63
- // 'deleted' and 'completed' are both treated as done
64
79
  if (task.status === 'pending' || task.status === 'in_progress') count++;
65
- } catch { /* skip invalid files */ }
80
+ } catch { /* skip */ }
66
81
  }
67
- } catch { /* dir read error */ }
82
+ } catch { /* skip */ }
68
83
  return count;
69
84
  }
70
85
 
71
- function writeJsonFile(path, data) {
72
- try {
73
- writeFileSync(path, JSON.stringify(data, null, 2));
74
- return true;
75
- } catch {
76
- return false;
77
- }
78
- }
79
-
80
86
  function countIncompleteTodos(todosDir, projectDir) {
81
87
  let count = 0;
82
88
 
83
- // Check global todos
84
89
  if (existsSync(todosDir)) {
85
90
  try {
86
91
  const files = readdirSync(todosDir).filter(f => f.endsWith('.json'));
87
92
  for (const file of files) {
88
- const todos = readJsonFile(join(todosDir, file));
89
- if (Array.isArray(todos)) {
93
+ try {
94
+ const content = readFileSync(join(todosDir, file), 'utf-8');
95
+ const data = JSON.parse(content);
96
+ const todos = Array.isArray(data) ? data : (Array.isArray(data?.todos) ? data.todos : []);
90
97
  count += todos.filter(t => t.status !== 'completed' && t.status !== 'cancelled').length;
91
- }
98
+ } catch { /* skip */ }
92
99
  }
93
- } catch {}
100
+ } catch { /* skip */ }
94
101
  }
95
102
 
96
- // Check project todos
97
103
  for (const path of [
98
104
  join(projectDir, '.omc', 'todos.json'),
99
105
  join(projectDir, '.claude', 'todos.json')
100
106
  ]) {
101
- const todos = readJsonFile(path);
102
- if (Array.isArray(todos)) {
107
+ try {
108
+ const data = readJsonFile(path);
109
+ const todos = Array.isArray(data) ? data : (Array.isArray(data?.todos) ? data.todos : []);
103
110
  count += todos.filter(t => t.status !== 'completed' && t.status !== 'cancelled').length;
104
- }
111
+ } catch { /* skip */ }
105
112
  }
106
113
 
107
114
  return count;
@@ -113,178 +120,205 @@ async function main() {
113
120
  let data = {};
114
121
  try { data = JSON.parse(input); } catch {}
115
122
 
116
- const stopReason = data.stop_reason || data.stopReason || '';
117
- const userRequested = data.user_requested || data.userRequested || false;
118
- const sessionId = data.sessionId || data.session_id || '';
119
-
120
- // Check for user abort - skip all continuation enforcement
121
- // NOTE: Abort patterns are assumed - verify against actual Claude Code API values
122
- if (userRequested || /abort|cancel|interrupt|ctrl_c|manual_stop/i.test(stopReason)) {
123
- console.log(JSON.stringify({ continue: true }));
124
- return;
125
- }
126
-
127
123
  const directory = data.directory || process.cwd();
124
+ const sessionId = data.sessionId || data.session_id || '';
128
125
  const todosDir = join(homedir(), '.claude', 'todos');
129
-
130
- // Check for ultrawork state
131
- let ultraworkState = readJsonFile(join(directory, '.omc', 'ultrawork-state.json'))
132
- || readJsonFile(join(homedir(), '.claude', 'ultrawork-state.json'));
133
-
134
- // Check for ralph loop state
135
- const ralphState = readJsonFile(join(directory, '.omc', 'ralph-state.json'));
136
-
137
- // Check for verification state (oracle verification)
138
- const verificationState = readJsonFile(join(directory, '.omc', 'ralph-verification.json'));
139
-
140
- // Count incomplete todos
141
- const incompleteCount = countIncompleteTodos(todosDir, directory);
142
-
143
- // Count incomplete Tasks
126
+ const stateDir = join(directory, '.omc', 'state');
127
+ const globalStateDir = join(homedir(), '.omc', 'state');
128
+
129
+ // Read all mode states (local-first with fallback to global)
130
+ const ralph = readStateFile(stateDir, globalStateDir, 'ralph-state.json');
131
+ const autopilot = readStateFile(stateDir, globalStateDir, 'autopilot-state.json');
132
+ const ultrapilot = readStateFile(stateDir, globalStateDir, 'ultrapilot-state.json');
133
+ const ultrawork = readStateFile(stateDir, globalStateDir, 'ultrawork-state.json');
134
+ const ecomode = readStateFile(stateDir, globalStateDir, 'ecomode-state.json');
135
+ const ultraqa = readStateFile(stateDir, globalStateDir, 'ultraqa-state.json');
136
+ const pipeline = readStateFile(stateDir, globalStateDir, 'pipeline-state.json');
137
+
138
+ // Swarm uses swarm-summary.json (not swarm-state.json) + marker file
139
+ const swarmMarker = existsSync(join(stateDir, 'swarm-active.marker'));
140
+ const swarmSummary = readJsonFile(join(stateDir, 'swarm-summary.json'));
141
+
142
+ // Count incomplete items
144
143
  const taskCount = countIncompleteTasks(sessionId);
145
- const totalIncomplete = taskCount + incompleteCount;
144
+ const todoCount = countIncompleteTodos(todosDir, directory);
145
+ const totalIncomplete = taskCount + todoCount;
146
146
 
147
- // Priority 1: Ralph Loop with Oracle Verification
148
- if (ralphState?.active) {
149
- const iteration = ralphState.iteration || 1;
150
- const maxIter = ralphState.max_iterations || 10;
147
+ // Priority 1: Ralph Loop (explicit persistence mode)
148
+ if (ralph.state?.active) {
149
+ const iteration = ralph.state.iteration || 1;
150
+ const maxIter = ralph.state.max_iterations || 100;
151
151
 
152
- // Check if oracle verification is pending
153
- if (verificationState?.pending) {
154
- const attempt = (verificationState.verification_attempts || 0) + 1;
155
- const maxAttempts = verificationState.max_verification_attempts || 3;
152
+ if (iteration < maxIter) {
153
+ ralph.state.iteration = iteration + 1;
154
+ writeJsonFile(ralph.path, ralph.state);
156
155
 
157
156
  console.log(JSON.stringify({
158
- decision: "block",
159
- reason: `<ralph-verification>
160
-
161
- [ORACLE VERIFICATION REQUIRED - Attempt ${attempt}/${maxAttempts}]
162
-
163
- The agent claims the task is complete. Before accepting, YOU MUST verify with Oracle.
164
-
165
- **Original Task:**
166
- ${verificationState.original_task || ralphState.prompt || 'No task specified'}
167
-
168
- **Completion Claim:**
169
- ${verificationState.completion_claim || 'Task marked complete'}
170
-
171
- ${verificationState.oracle_feedback ? `**Previous Oracle Feedback (rejected):**
172
- ${verificationState.oracle_feedback}
173
- ` : ''}
174
-
175
- ## MANDATORY VERIFICATION STEPS
157
+ continue: false,
158
+ reason: `[RALPH LOOP - ITERATION ${iteration + 1}/${maxIter}] Work is NOT done. Continue. When complete, output: <promise>${ralph.state.completion_promise || 'DONE'}</promise>\n${ralph.state.prompt ? `Task: ${ralph.state.prompt}` : ''}`
159
+ }));
160
+ return;
161
+ }
162
+ }
176
163
 
177
- 1. **Spawn Oracle Agent** for verification:
178
- \`\`\`
179
- Task(subagent_type="oracle", prompt="Verify this task completion claim...")
180
- \`\`\`
164
+ // Priority 2: Autopilot (high-level orchestration)
165
+ if (autopilot.state?.active) {
166
+ const phase = autopilot.state.phase || 'unknown';
167
+ if (phase !== 'complete') {
168
+ const newCount = (autopilot.state.reinforcement_count || 0) + 1;
169
+ if (newCount <= 20) {
170
+ autopilot.state.reinforcement_count = newCount;
171
+ writeJsonFile(autopilot.path, autopilot.state);
172
+
173
+ console.log(JSON.stringify({
174
+ continue: false,
175
+ reason: `[AUTOPILOT - Phase: ${phase}] Autopilot not complete. Continue working.`
176
+ }));
177
+ return;
178
+ }
179
+ }
180
+ }
181
181
 
182
- 2. **Oracle must check:**
183
- - Are ALL requirements from the original task met?
184
- - Is the implementation complete, not partial?
185
- - Are there any obvious bugs or issues?
186
- - Does the code compile/run without errors?
187
- - Are tests passing (if applicable)?
182
+ // Priority 3: Ultrapilot (parallel autopilot)
183
+ if (ultrapilot.state?.active) {
184
+ const workers = ultrapilot.state.workers || [];
185
+ const incomplete = workers.filter(w => w.status !== 'complete' && w.status !== 'failed').length;
186
+ if (incomplete > 0) {
187
+ const newCount = (ultrapilot.state.reinforcement_count || 0) + 1;
188
+ if (newCount <= 20) {
189
+ ultrapilot.state.reinforcement_count = newCount;
190
+ writeJsonFile(ultrapilot.path, ultrapilot.state);
191
+
192
+ console.log(JSON.stringify({
193
+ continue: false,
194
+ reason: `[ULTRAPILOT] ${incomplete} workers still running. Continue.`
195
+ }));
196
+ return;
197
+ }
198
+ }
199
+ }
188
200
 
189
- 3. **Based on Oracle's response:**
190
- - If APPROVED: Output \`<oracle-approved>VERIFIED_COMPLETE</oracle-approved>\`
191
- - If REJECTED: Continue working on the identified issues
201
+ // Priority 4: Swarm (coordinated agents with SQLite)
202
+ if (swarmMarker && swarmSummary?.active) {
203
+ const pending = (swarmSummary.tasks_pending || 0) + (swarmSummary.tasks_claimed || 0);
204
+ if (pending > 0) {
205
+ const newCount = (swarmSummary.reinforcement_count || 0) + 1;
206
+ if (newCount <= 15) {
207
+ swarmSummary.reinforcement_count = newCount;
208
+ writeJsonFile(join(stateDir, 'swarm-summary.json'), swarmSummary);
209
+
210
+ console.log(JSON.stringify({
211
+ continue: false,
212
+ reason: `[SWARM ACTIVE] ${pending} tasks remain. Continue working.`
213
+ }));
214
+ return;
215
+ }
216
+ }
217
+ }
192
218
 
193
- DO NOT output the completion promise again until Oracle approves.
219
+ // Priority 5: Pipeline (sequential stages)
220
+ if (pipeline.state?.active) {
221
+ const currentStage = pipeline.state.current_stage || 0;
222
+ const totalStages = pipeline.state.stages?.length || 0;
223
+ if (currentStage < totalStages) {
224
+ const newCount = (pipeline.state.reinforcement_count || 0) + 1;
225
+ if (newCount <= 15) {
226
+ pipeline.state.reinforcement_count = newCount;
227
+ writeJsonFile(pipeline.path, pipeline.state);
228
+
229
+ console.log(JSON.stringify({
230
+ continue: false,
231
+ reason: `[PIPELINE - Stage ${currentStage + 1}/${totalStages}] Pipeline not complete. Continue.`
232
+ }));
233
+ return;
234
+ }
235
+ }
236
+ }
194
237
 
195
- </ralph-verification>
238
+ // Priority 6: UltraQA (QA cycling)
239
+ if (ultraqa.state?.active) {
240
+ const cycle = ultraqa.state.cycle || 1;
241
+ const maxCycles = ultraqa.state.max_cycles || 10;
242
+ if (cycle < maxCycles && !ultraqa.state.all_passing) {
243
+ ultraqa.state.cycle = cycle + 1;
244
+ writeJsonFile(ultraqa.path, ultraqa.state);
196
245
 
197
- ---
198
- `
246
+ console.log(JSON.stringify({
247
+ continue: false,
248
+ reason: `[ULTRAQA - Cycle ${cycle + 1}/${maxCycles}] Tests not all passing. Continue fixing.`
199
249
  }));
200
250
  return;
201
251
  }
252
+ }
202
253
 
203
- if (iteration < maxIter) {
204
- const newIter = iteration + 1;
205
- ralphState.iteration = newIter;
206
- writeJsonFile(join(directory, '.omc', 'ralph-state.json'), ralphState);
254
+ // Priority 7: Ultrawork with incomplete todos/tasks
255
+ if (ultrawork.state?.active && totalIncomplete > 0) {
256
+ const newCount = (ultrawork.state.reinforcement_count || 0) + 1;
257
+ const maxReinforcements = ultrawork.state.max_reinforcements || 15;
207
258
 
259
+ if (newCount > maxReinforcements) {
208
260
  console.log(JSON.stringify({
209
- decision: "block",
210
- reason: `<ralph-loop-continuation>
211
-
212
- [RALPH LOOP - ITERATION ${newIter}/${maxIter}]
213
-
214
- Your previous attempt did not output the completion promise. The work is NOT done yet.
215
-
216
- CRITICAL INSTRUCTIONS:
217
- 1. Review your progress and the original task
218
- 2. Check your todo list - are ALL items marked complete?
219
- 3. Continue from where you left off
220
- 4. When FULLY complete, output: <promise>${ralphState.completion_promise || 'TASK_COMPLETE'}</promise>
221
- 5. Do NOT stop until the task is truly done
222
-
223
- ${ralphState.prompt ? `Original task: ${ralphState.prompt}` : ''}
224
-
225
- </ralph-loop-continuation>
226
-
227
- ---
228
- `
261
+ continue: true,
262
+ reason: `[ULTRAWORK ESCAPE] Max reinforcements reached. Allowing stop.`
229
263
  }));
230
264
  return;
231
265
  }
232
- }
233
266
 
234
- // Priority 2: Ultrawork with incomplete todos
235
- if (ultraworkState?.active && totalIncomplete > 0) {
236
- const newCount = (ultraworkState.reinforcement_count || 0) + 1;
237
- ultraworkState.reinforcement_count = newCount;
238
- ultraworkState.last_checked_at = new Date().toISOString();
239
-
240
- writeJsonFile(join(directory, '.omc', 'ultrawork-state.json'), ultraworkState);
267
+ ultrawork.state.reinforcement_count = newCount;
268
+ ultrawork.state.last_checked_at = new Date().toISOString();
269
+ writeJsonFile(ultrawork.path, ultrawork.state);
241
270
 
271
+ const itemType = taskCount > 0 ? 'Tasks' : 'todos';
242
272
  console.log(JSON.stringify({
243
- decision: "block",
244
- reason: `<ultrawork-persistence>
245
-
246
- [ULTRAWORK MODE STILL ACTIVE - Reinforcement #${newCount}]
247
-
248
- Your ultrawork session is NOT complete. ${totalIncomplete} incomplete items remain.
249
-
250
- REMEMBER THE ULTRAWORK RULES:
251
- - **PARALLEL**: Fire independent calls simultaneously - NEVER wait sequentially
252
- - **BACKGROUND FIRST**: Use Task(run_in_background=true) for exploration (10+ concurrent)
253
- - **TODO**: Track EVERY step. Mark complete IMMEDIATELY after each
254
- - **VERIFY**: Check ALL requirements met before done
255
- - **NO Premature Stopping**: ALL TODOs must be complete
273
+ continue: false,
274
+ reason: `[ULTRAWORK #${newCount}] ${totalIncomplete} incomplete ${itemType}. Continue working.\n${ultrawork.state.original_prompt ? `Task: ${ultrawork.state.original_prompt}` : ''}`
275
+ }));
276
+ return;
277
+ }
256
278
 
257
- Continue working on the next pending task. DO NOT STOP until all tasks are marked complete.
279
+ // Priority 8: Ecomode with incomplete todos/tasks
280
+ if (ecomode.state?.active && totalIncomplete > 0) {
281
+ const newCount = (ecomode.state.reinforcement_count || 0) + 1;
282
+ const maxReinforcements = ecomode.state.max_reinforcements || 15;
258
283
 
259
- ${ultraworkState.original_prompt ? `Original task: ${ultraworkState.original_prompt}` : ''}
284
+ if (newCount > maxReinforcements) {
285
+ console.log(JSON.stringify({
286
+ continue: true,
287
+ reason: `[ECOMODE ESCAPE] Max reinforcements reached. Allowing stop.`
288
+ }));
289
+ return;
290
+ }
260
291
 
261
- </ultrawork-persistence>
292
+ ecomode.state.reinforcement_count = newCount;
293
+ writeJsonFile(ecomode.path, ecomode.state);
262
294
 
263
- ---
264
- `
295
+ const itemType = taskCount > 0 ? 'Tasks' : 'todos';
296
+ console.log(JSON.stringify({
297
+ continue: false,
298
+ reason: `[ECOMODE #${newCount}] ${totalIncomplete} incomplete ${itemType}. Continue working.`
265
299
  }));
266
300
  return;
267
301
  }
268
302
 
269
- // Priority 3: Todo Continuation
303
+ // Priority 9: Generic Task/Todo continuation (no specific mode)
270
304
  if (totalIncomplete > 0) {
271
- const itemType = taskCount > 0 ? 'Tasks' : 'todos';
272
- console.log(JSON.stringify({
273
- decision: "block",
274
- reason: `<todo-continuation>
275
-
276
- [SYSTEM REMINDER - CONTINUATION]
277
-
278
- Incomplete ${itemType} remain (${totalIncomplete} remaining). Continue working on the next pending item.
279
-
280
- - Proceed without asking for permission
281
- - Mark each item complete when finished
282
- - Do not stop until all items are done
305
+ const contFile = join(stateDir, 'continuation-count.json');
306
+ let contState = readJsonFile(contFile) || { count: 0 };
307
+ contState.count = (contState.count || 0) + 1;
308
+ writeJsonFile(contFile, contState);
283
309
 
284
- </todo-continuation>
310
+ if (contState.count > 15) {
311
+ console.log(JSON.stringify({
312
+ continue: true,
313
+ reason: `[CONTINUATION ESCAPE] Max continuations reached. Allowing stop.`
314
+ }));
315
+ return;
316
+ }
285
317
 
286
- ---
287
- `
318
+ const itemType = taskCount > 0 ? 'Tasks' : 'todos';
319
+ console.log(JSON.stringify({
320
+ continue: false,
321
+ reason: `[CONTINUATION ${contState.count}/15] ${totalIncomplete} incomplete ${itemType}. Continue working.`
288
322
  }));
289
323
  return;
290
324
  }
@@ -292,6 +326,8 @@ Incomplete ${itemType} remain (${totalIncomplete} remaining). Continue working o
292
326
  // No blocking needed
293
327
  console.log(JSON.stringify({ continue: true }));
294
328
  } catch (error) {
329
+ // On any error, allow stop rather than blocking forever
330
+ console.error(`[persistent-mode] Error: ${error.message}`);
295
331
  console.log(JSON.stringify({ continue: true }));
296
332
  }
297
333
  }