wogiflow 2.7.1 → 2.9.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.
@@ -198,7 +198,17 @@ function handlePostCompact() {
198
198
  fs.writeFileSync(compactStatePath, JSON.stringify(tracker, null, 2));
199
199
 
200
200
  if (tracker.count >= 3) {
201
- contextParts.push('**WARNING**: Multiple compactions detected in quick succession. Claude Code 2.1.76+ stops auto-compaction after 3 consecutive failures. If context keeps growing, consider starting a new session.');
201
+ contextParts.push('**WARNING**: Multiple compactions detected in quick succession. Claude Code 2.1.89+ stops auto-compaction after 3 consecutive thrash cycles. If context keeps growing, consider starting a new session.');
202
+ }
203
+
204
+ // CC 2.1.89 context-budget awareness: when 2+ compactions happened in quick succession,
205
+ // instruct Claude to load context incrementally to avoid the thrash loop.
206
+ if (tracker.count >= 2) {
207
+ contextParts.push('**Context budget: LOW** — 2+ compactions in quick succession. To avoid a thrash loop:\n' +
208
+ '- Load context ON DEMAND only (Read specific files when needed, not bulk)\n' +
209
+ '- Use the manifest in SessionStart context to know what exists\n' +
210
+ '- Prefer targeted `Read` of specific sections over loading entire registry files\n' +
211
+ '- Skip non-essential context (community knowledge, memory recall)');
202
212
  }
203
213
  } catch (err) {
204
214
  if (process.env.DEBUG) {
@@ -18,6 +18,7 @@ const setupCheck = require('./setup-check');
18
18
  const { findParallelizable, getParallelConfig } = require('../../flow-parallel');
19
19
  const { getBypassTracking } = require('../../flow-session-state');
20
20
  const { loadCheckpoint, clearCheckpoint } = require('../../flow-task-checkpoint');
21
+ const { generateManifest, formatManifestForInjection, hasContent } = require('../../flow-context-manifest');
21
22
 
22
23
  // ============================================================
23
24
  // State Folder Hygiene — Whitelist + Age-Based Cleanup
@@ -119,6 +120,9 @@ const KNOWN_STATE_FILES = new Set([
119
120
  'pending-skill.json',
120
121
  'pending-setup.json',
121
122
 
123
+ // Context manifest (tiered context T2)
124
+ 'session-manifest.md',
125
+
122
126
  // Archive
123
127
  'completed-archive.json',
124
128
 
@@ -132,6 +136,7 @@ const KNOWN_STATE_FILES = new Set([
132
136
  '.claude-md-regen-version',
133
137
  '.routing-pending',
134
138
  '.routing-cleared',
139
+ '.gates-passed.json',
135
140
  ]);
136
141
 
137
142
  /** Known directory names within state/ (not files) */
@@ -668,6 +673,7 @@ async function gatherSessionContext(options = {}) {
668
673
  }
669
674
 
670
675
  // Memory pipeline recall (surface relevant memories for current task)
676
+ // Capped at 2KB to prevent unbounded growth (CC 2.1.89 saves >50K hook output to disk)
671
677
  try {
672
678
  const currentTask = context.currentTask;
673
679
  if (currentTask) {
@@ -677,7 +683,10 @@ async function gatherSessionContext(options = {}) {
677
683
  currentTask.type || ''
678
684
  );
679
685
  if (memories) {
680
- context.relevantMemories = memories;
686
+ const MAX_MEMORY_CHARS = 2048;
687
+ context.relevantMemories = typeof memories === 'string' && memories.length > MAX_MEMORY_CHARS
688
+ ? memories.substring(0, MAX_MEMORY_CHARS) + '\n... (truncated — load full via memory DB)'
689
+ : memories;
681
690
  }
682
691
  }
683
692
  } catch (err) {
@@ -732,11 +741,12 @@ async function gatherSessionContext(options = {}) {
732
741
  // v7.0: Real-time correction surfacing
733
742
  // When 2+ corrections of the same type are detected in the same session,
734
743
  // surface a hint telling Claude to consider recording the pattern.
744
+ // Capped at 5 types to prevent unbounded growth (CC 2.1.89 >50K disk save)
735
745
  try {
736
746
  const { getRepeatedCorrectionTypes } = require('../../flow-correction-detector');
737
747
  const repeatedTypes = getRepeatedCorrectionTypes();
738
748
  if (repeatedTypes.length > 0) {
739
- context.correctionSurfacing = repeatedTypes;
749
+ context.correctionSurfacing = repeatedTypes.slice(0, 5);
740
750
  }
741
751
  } catch (_err) {
742
752
  // Non-critical
@@ -768,6 +778,20 @@ async function gatherSessionContext(options = {}) {
768
778
  // Non-critical — community sync module may not be available
769
779
  }
770
780
 
781
+ // T2: Context manifest — compact inventory of all registries
782
+ // Lets Claude know what coding rules, components, utilities, and APIs exist
783
+ // without injecting full content (loaded on-demand via Read)
784
+ try {
785
+ const manifest = generateManifest();
786
+ if (hasContent(manifest)) {
787
+ context.contextManifest = manifest;
788
+ }
789
+ } catch (err) {
790
+ if (process.env.DEBUG) {
791
+ console.error(`[session-context] Manifest generation failed: ${err.message}`);
792
+ }
793
+ }
794
+
771
795
  return {
772
796
  enabled: true,
773
797
  context
@@ -897,8 +921,16 @@ function formatContextForInjection(context) {
897
921
  output += `\nConsider running these tasks in parallel for faster completion.\n\n`;
898
922
  }
899
923
 
900
- // Key decisions
901
- if (ctx.keyDecisions && ctx.keyDecisions.length > 0) {
924
+ // T2: Context manifest — replaces key decisions with full registry inventory
925
+ // Provides one-line summaries so Claude knows WHAT EXISTS without full injection
926
+ if (ctx.contextManifest) {
927
+ const manifestText = formatManifestForInjection(ctx.contextManifest);
928
+ if (manifestText) {
929
+ output += `### Available Context (load on demand)\n`;
930
+ output += manifestText + '\n\n';
931
+ }
932
+ } else if (ctx.keyDecisions && ctx.keyDecisions.length > 0) {
933
+ // Fallback: legacy key decisions format (manifest generation failed or unavailable)
902
934
  output += `### Key Decisions\n`;
903
935
  for (const decision of ctx.keyDecisions) {
904
936
  output += `- **${decision.title}**: ${decision.summary}\n`;
@@ -984,13 +1016,14 @@ function formatContextForInjection(context) {
984
1016
  }
985
1017
 
986
1018
  // Community knowledge (pulled from server)
1019
+ // Capped: 3 model intelligence + 2 error strategies + 2 patterns (CC 2.1.89 >50K disk save)
987
1020
  if (ctx.communityKnowledge && typeof ctx.communityKnowledge === 'object') {
988
1021
  const ck = ctx.communityKnowledge;
989
1022
  const items = [];
990
1023
 
991
1024
  // Model intelligence
992
1025
  if (Array.isArray(ck.modelIntelligence)) {
993
- for (const item of ck.modelIntelligence.slice(0, 5)) {
1026
+ for (const item of ck.modelIntelligence.slice(0, 3)) {
994
1027
  if (item.model && (item.strengths || item.adjustments)) {
995
1028
  const detail = item.adjustments || item.strengths;
996
1029
  items.push(`Community: ${item.model} — ${detail}`);
@@ -1000,7 +1033,7 @@ function formatContextForInjection(context) {
1000
1033
 
1001
1034
  // Error strategies
1002
1035
  if (Array.isArray(ck.errorStrategies)) {
1003
- for (const item of ck.errorStrategies.slice(0, 3)) {
1036
+ for (const item of ck.errorStrategies.slice(0, 2)) {
1004
1037
  if (item.category && item.strategy) {
1005
1038
  items.push(`Community: ${item.category} — ${item.strategy}`);
1006
1039
  }
@@ -1009,7 +1042,7 @@ function formatContextForInjection(context) {
1009
1042
 
1010
1043
  // Patterns
1011
1044
  if (Array.isArray(ck.patterns)) {
1012
- for (const item of ck.patterns.slice(0, 3)) {
1045
+ for (const item of ck.patterns.slice(0, 2)) {
1013
1046
  if (item.description) {
1014
1047
  items.push(`Community: ${item.description}`);
1015
1048
  }
@@ -1025,6 +1058,17 @@ function formatContextForInjection(context) {
1025
1058
  }
1026
1059
  }
1027
1060
 
1061
+ // CC 2.1.89: Hook output >50K chars gets saved to disk with file path + preview.
1062
+ // Cap total output to stay under threshold and keep full context in-session.
1063
+ // If we exceed this, the most important context (T1: task state, routing) is at
1064
+ // the top and will appear in the preview. T2 (manifest) and lower-priority sections
1065
+ // are trimmed first.
1066
+ const MAX_OUTPUT_CHARS = 45000; // Stay safely under 50K
1067
+ if (output.length > MAX_OUTPUT_CHARS) {
1068
+ output = output.substring(0, MAX_OUTPUT_CHARS) +
1069
+ '\n\n*[Context truncated to stay within injection limit. Load full context via `/wogi-context` or Read registry files directly.]*\n';
1070
+ }
1071
+
1028
1072
  return output;
1029
1073
  }
1030
1074
 
@@ -21,6 +21,7 @@ const fs = require('node:fs');
21
21
  const { getConfig, PATHS, safeJsonParse, writeJson, withLock, validateTaskId, archiveCompletedTasksToLog } = require('../../flow-utils');
22
22
  const { resetPhase, isPhaseGateEnabled } = require('./phase-gate');
23
23
  const { clearOnTaskComplete } = require('../../flow-hook-status');
24
+ const { checkGateLatch, clearGateLatch } = require('../../flow-gate-latch');
24
25
 
25
26
  /**
26
27
  * Check if task completed handling is enabled
@@ -90,6 +91,22 @@ async function handleTaskCompleted(input) {
90
91
  }
91
92
  result.taskId = completedTask.id;
92
93
 
94
+ // Gate latch check — verify quality gates have passed before allowing completion.
95
+ // Without this, agents can call TaskUpdate and bypass all quality gates.
96
+ // The latch is written by flow-done.js after gates pass.
97
+ const config = getConfig();
98
+ const requireGateLatch = config.enforcement?.requireGateLatch !== false;
99
+ if (requireGateLatch) {
100
+ const latchResult = checkGateLatch(completedTask.id);
101
+ if (!latchResult.valid) {
102
+ result.message = `BLOCKED: ${latchResult.reason} ` +
103
+ 'Quality gates must pass before a task can be completed. ' +
104
+ 'Run the full /wogi-start pipeline (Step 4: Quality Gates) or `flow done` first.';
105
+ result.gateBlocked = true;
106
+ return;
107
+ }
108
+ }
109
+
93
110
  // Move task to recentlyCompleted
94
111
  completedTask.status = 'completed';
95
112
  completedTask.completedAt = new Date().toISOString();
@@ -158,6 +175,15 @@ async function handleTaskCompleted(input) {
158
175
  }
159
176
  }
160
177
 
178
+ // Clear gate latch after successful completion
179
+ if (result.completed) {
180
+ try {
181
+ clearGateLatch();
182
+ } catch (_err) {
183
+ // Non-critical
184
+ }
185
+ }
186
+
161
187
  // Clear progress tracker state on task completion
162
188
  if (result.completed) {
163
189
  try {
@@ -928,6 +928,26 @@ function main() {
928
928
  // so the correct hooks are already in place when stale ones are removed.
929
929
  migrateStaleLocalHooks();
930
930
 
931
+ // Migrate config.json defaults when they change between versions.
932
+ // Without this, existing users keep stale defaults forever because
933
+ // deepMerge(defaults, userConfig) lets user values win.
934
+ // Only upgrades keys that still match the OLD default — user-customized values are preserved.
935
+ try {
936
+ const configPath = path.join(WORKFLOW_DIR, 'config.json');
937
+ if (fs.existsSync(configPath)) {
938
+ const { migrateConfigFile } = require('./flow-config-migrate');
939
+ const migrationResult = migrateConfigFile(configPath);
940
+ if (migrationResult.migrated && !shouldBeSilent()) {
941
+ process.stderr.write(`\x1b[36mWogiFlow:\x1b[0m Migrated config (v${migrationResult.fromVersion} → v${migrationResult.toVersion}): ${migrationResult.applied.length} change(s).\n`);
942
+ }
943
+ }
944
+ } catch (err) {
945
+ // Non-fatal — config migration should never fail installation
946
+ if (process.env.DEBUG) {
947
+ console.error(`[postinstall] Config migration failed: ${err.message}`);
948
+ }
949
+ }
950
+
931
951
  // NOTE: Scripts and .workflow/ managed dirs (bridges, templates, agents, lib) are
932
952
  // NO LONGER copied to the project. They live exclusively in the wogiflow package
933
953
  // under node_modules/wogiflow/. This keeps user projects clean.