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.
- package/.claude/commands/wogi-audit.md +156 -8
- package/.claude/commands/wogi-start.md +276 -0
- package/lib/workspace-channel-server.js +19 -4
- package/lib/workspace-gates.js +87 -0
- package/lib/workspace-routing.js +15 -5
- package/lib/workspace-session.js +308 -0
- package/lib/workspace.js +49 -3
- package/package.json +1 -1
- package/scripts/flow-audit-gates.js +766 -0
- package/scripts/flow-config-defaults.js +27 -4
- package/scripts/flow-config-migrate.js +270 -0
- package/scripts/flow-context-manifest.js +322 -0
- package/scripts/flow-done-gates.js +76 -0
- package/scripts/flow-done.js +14 -0
- package/scripts/flow-gate-latch.js +119 -0
- package/scripts/flow-runtime-verification.js +782 -0
- package/scripts/hooks/core/post-compact.js +11 -1
- package/scripts/hooks/core/session-context.js +51 -7
- package/scripts/hooks/core/task-completed.js +26 -0
- package/scripts/postinstall.js +20 -0
|
@@ -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.
|
|
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
|
-
|
|
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
|
-
//
|
|
901
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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 {
|
package/scripts/postinstall.js
CHANGED
|
@@ -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.
|