wogiflow 2.0.0 → 2.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.
@@ -796,6 +796,18 @@ function formatContextForInjection(context) {
796
796
  output += `\`\`\`bash\nunset CLAUDE_CODE_SIMPLE\n\`\`\`\n\n`;
797
797
  }
798
798
 
799
+ // Version compatibility warning (Claude Code below hard minimum or missing features)
800
+ if (ctx.versionWarning) {
801
+ output += `### Version Warning\n`;
802
+ output += `${ctx.versionWarning}\n\n`;
803
+ }
804
+
805
+ // WogiFlow npm update available
806
+ if (ctx.updateWarning) {
807
+ output += `### Update Available\n`;
808
+ output += `${ctx.updateWarning}\n\n`;
809
+ }
810
+
799
811
  // PRIORITY: Setup required - show first if needs setup
800
812
  if (ctx.setupRequired && ctx.setupRequired.needsSetup) {
801
813
  output += `### ⚠️ Setup Required\n`;
@@ -68,6 +68,38 @@ function handleSessionEnd(input) {
68
68
  result.logged = false;
69
69
  }
70
70
 
71
+ // Scratch directory cleanup — remove temp files created during session
72
+ try {
73
+ const fs = require('node:fs');
74
+ const path = require('node:path');
75
+ const scratchDir = path.join(PATHS.workflow, 'scratch');
76
+ if (fs.existsSync(scratchDir)) {
77
+ const files = fs.readdirSync(scratchDir);
78
+ let scratchCleaned = 0;
79
+ for (const file of files) {
80
+ if (file === '.gitkeep') continue; // Keep the directory marker
81
+ try {
82
+ const filePath = path.join(scratchDir, file);
83
+ const stat = fs.statSync(filePath);
84
+ if (stat.isFile()) {
85
+ fs.unlinkSync(filePath);
86
+ scratchCleaned++;
87
+ } else if (stat.isDirectory()) {
88
+ fs.rmSync(filePath, { recursive: true, force: true });
89
+ scratchCleaned++;
90
+ }
91
+ } catch (_err) {
92
+ // Skip files that can't be deleted
93
+ }
94
+ }
95
+ if (scratchCleaned > 0) {
96
+ result.scratchCleaned = scratchCleaned;
97
+ }
98
+ }
99
+ } catch (_err) {
100
+ // Non-critical — never block session end
101
+ }
102
+
71
103
  // State folder hygiene — clean stale/orphan files (fire-and-forget)
72
104
  try {
73
105
  const hygiene = cleanStaleFiles();
@@ -251,6 +251,19 @@ async function handleTaskCompleted(input) {
251
251
  } catch (_err) {
252
252
  // Non-critical - registry manager may not be available
253
253
  }
254
+ // Check pending queue — notify user if items are waiting
255
+ try {
256
+ const { getPendingCount } = require('../../flow-pending');
257
+ const pendingCount = getPendingCount();
258
+ if (pendingCount > 0) {
259
+ result.pendingQueue = {
260
+ count: pendingCount,
261
+ message: `You have ${pendingCount} pending item${pendingCount !== 1 ? 's' : ''} queued. Run /wogi-pending --list to review, or I'll process them next.`
262
+ };
263
+ }
264
+ } catch (_err) {
265
+ // Non-critical — pending module may not be available
266
+ }
254
267
  } catch (err) {
255
268
  result.message = `Task completed handler error: ${err.message}`;
256
269
  }
@@ -105,7 +105,8 @@ function createMinimalStructure() {
105
105
  WORKFLOW_DIR,
106
106
  STATE_DIR,
107
107
  path.join(WORKFLOW_DIR, 'changes'),
108
- path.join(WORKFLOW_DIR, 'specs')
108
+ path.join(WORKFLOW_DIR, 'specs'),
109
+ path.join(WORKFLOW_DIR, 'scratch')
109
110
  ];
110
111
 
111
112
  for (const dir of dirs) {
@@ -244,6 +245,82 @@ function copyDir(src, dest, mergeMode = false, depth = 0) {
244
245
  }
245
246
  }
246
247
 
248
+ /**
249
+ * Hook types and the minimum Claude Code version that supports them.
250
+ * Claude Code rejects the ENTIRE settings.json if any hook key is unrecognized,
251
+ * so we must exclude hooks that the installed version doesn't support.
252
+ */
253
+ const HOOK_VERSION_MAP = {
254
+ // Hooks available since 2.1.23 (HARD_MIN)
255
+ SessionStart: { major: 2, minor: 1, patch: 23 },
256
+ UserPromptSubmit: { major: 2, minor: 1, patch: 23 },
257
+ PreToolUse: { major: 2, minor: 1, patch: 23 },
258
+ PostToolUse: { major: 2, minor: 1, patch: 23 },
259
+ Stop: { major: 2, minor: 1, patch: 23 },
260
+ SessionEnd: { major: 2, minor: 1, patch: 23 },
261
+ TaskCompleted: { major: 2, minor: 1, patch: 23 },
262
+ // Hooks added in 2.1.50+
263
+ WorktreeCreate: { major: 2, minor: 1, patch: 50 },
264
+ WorktreeRemove: { major: 2, minor: 1, patch: 50 },
265
+ // Hooks added in 2.1.72+
266
+ ConfigChange: { major: 2, minor: 1, patch: 72 },
267
+ InstructionsLoaded: { major: 2, minor: 1, patch: 72 },
268
+ // Hooks added in 2.1.76+
269
+ PostCompact: { major: 2, minor: 1, patch: 76 },
270
+ };
271
+
272
+ /**
273
+ * Detect Claude Code version.
274
+ * @returns {{ major: number, minor: number, patch: number } | null}
275
+ */
276
+ function detectClaudeCodeVersion() {
277
+ try {
278
+ const output = execFileSync('claude', ['--version'], {
279
+ encoding: 'utf-8',
280
+ stdio: ['pipe', 'pipe', 'pipe'],
281
+ timeout: 5000
282
+ }).trim();
283
+ const match = output.match(/(\d+)\.(\d+)\.(\d+)/);
284
+ if (match) {
285
+ return { major: parseInt(match[1], 10), minor: parseInt(match[2], 10), patch: parseInt(match[3], 10) };
286
+ }
287
+ } catch (_err) {
288
+ // Claude CLI not available — can't detect version
289
+ }
290
+ return null;
291
+ }
292
+
293
+ /**
294
+ * Check if version A meets minimum version B.
295
+ * @returns {boolean}
296
+ */
297
+ function versionMeetsMinimum(version, minimum) {
298
+ if (version.major !== minimum.major) return version.major > minimum.major;
299
+ if (version.minor !== minimum.minor) return version.minor > minimum.minor;
300
+ return version.patch >= minimum.patch;
301
+ }
302
+
303
+ /**
304
+ * Remove hook types from settings.json that the installed Claude Code version doesn't support.
305
+ * Claude Code rejects the ENTIRE settings file if any hook key is unrecognized.
306
+ * @param {Object} settings - Parsed settings object (mutated in place)
307
+ * @param {{ major: number, minor: number, patch: number } | null} ccVersion - Detected version
308
+ * @returns {string[]} List of removed hook names
309
+ */
310
+ function stripUnsupportedHooks(settings, ccVersion) {
311
+ if (!settings || !settings.hooks || !ccVersion) return [];
312
+
313
+ const removed = [];
314
+ for (const hookName of Object.keys(settings.hooks)) {
315
+ const minVersion = HOOK_VERSION_MAP[hookName];
316
+ if (minVersion && !versionMeetsMinimum(ccVersion, minVersion)) {
317
+ delete settings.hooks[hookName];
318
+ removed.push(`${hookName} (requires ${minVersion.major}.${minVersion.minor}.${minVersion.patch}+)`);
319
+ }
320
+ }
321
+ return removed;
322
+ }
323
+
247
324
  /**
248
325
  * Rewrite hook command paths from local dev paths to package paths.
249
326
  * The package's settings.json uses local paths (node scripts/hooks/...)
@@ -319,6 +396,18 @@ function copyClaudeResources() {
319
396
  // Rewrite hook paths: package uses local dev paths (node scripts/hooks/...)
320
397
  // but user projects need package paths (node node_modules/wogiflow/scripts/hooks/...)
321
398
  rewriteHookPaths(ours);
399
+ // Strip hooks unsupported by the installed Claude Code version.
400
+ // Claude Code rejects the ENTIRE settings.json if any hook key is unrecognized,
401
+ // so we must exclude hooks that the version doesn't support.
402
+ const ccVersion = detectClaudeCodeVersion();
403
+ const removedHooks = stripUnsupportedHooks(ours, ccVersion);
404
+ if (removedHooks.length > 0) {
405
+ console.log(`[wogiflow] Claude Code ${ccVersion.major}.${ccVersion.minor}.${ccVersion.patch} detected. Excluded unsupported hooks:`);
406
+ for (const h of removedHooks) {
407
+ console.log(` - ${h}`);
408
+ }
409
+ console.log('[wogiflow] Update Claude Code for full functionality: npm i -g @anthropic-ai/claude-code@latest');
410
+ }
322
411
  // Always update hooks (core WogiFlow functionality)
323
412
  existing.hooks = ours.hooks;
324
413
  existing._wogiFlowManaged = true;