wogiflow 1.9.8 → 1.9.10
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/lib/installer.js +8 -0
- package/package.json +1 -1
- package/scripts/flow-config-loader.js +16 -0
- package/scripts/flow-context-estimator.js +26 -0
- package/scripts/flow-gitignore.js +263 -0
- package/scripts/flow-health.js +36 -2
- package/scripts/flow-utils.js +4 -0
- package/scripts/flow-version-check.js +3 -0
package/lib/installer.js
CHANGED
|
@@ -532,6 +532,14 @@ function createWorkflowStructure(projectRoot, config) {
|
|
|
532
532
|
});
|
|
533
533
|
fs.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
|
|
534
534
|
|
|
535
|
+
// Sync .gitignore entries for enabled features
|
|
536
|
+
try {
|
|
537
|
+
const { syncGitignore } = require('../scripts/flow-gitignore');
|
|
538
|
+
syncGitignore(configContent);
|
|
539
|
+
} catch (err) {
|
|
540
|
+
// Non-blocking — gitignore sync should never fail installation
|
|
541
|
+
}
|
|
542
|
+
|
|
535
543
|
// Create ready.json
|
|
536
544
|
const readyPath = path.join(workflowDir, 'state', 'ready.json');
|
|
537
545
|
const readyContent = {
|
package/package.json
CHANGED
|
@@ -518,6 +518,14 @@ async function setConfigValue(configPath, newValue) {
|
|
|
518
518
|
obj[parts[parts.length - 1]] = newValue;
|
|
519
519
|
writeJson(PATHS.config, config);
|
|
520
520
|
invalidateConfigCache();
|
|
521
|
+
|
|
522
|
+
// Auto-sync .gitignore when config changes enable features with runtime artifacts
|
|
523
|
+
try {
|
|
524
|
+
const { syncGitignore } = require('./flow-gitignore');
|
|
525
|
+
syncGitignore(config);
|
|
526
|
+
} catch (err) {
|
|
527
|
+
// Non-blocking — gitignore sync should never fail config writes
|
|
528
|
+
}
|
|
521
529
|
} finally {
|
|
522
530
|
if (release) release();
|
|
523
531
|
}
|
|
@@ -548,6 +556,14 @@ function setConfigValueSync(configPath, newValue) {
|
|
|
548
556
|
obj[parts[parts.length - 1]] = newValue;
|
|
549
557
|
writeJson(PATHS.config, config);
|
|
550
558
|
invalidateConfigCache();
|
|
559
|
+
|
|
560
|
+
// Auto-sync .gitignore when config changes enable features with runtime artifacts
|
|
561
|
+
try {
|
|
562
|
+
const { syncGitignore } = require('./flow-gitignore');
|
|
563
|
+
syncGitignore(config);
|
|
564
|
+
} catch (err) {
|
|
565
|
+
// Non-blocking — gitignore sync should never fail config writes
|
|
566
|
+
}
|
|
551
567
|
}
|
|
552
568
|
|
|
553
569
|
/**
|
|
@@ -44,6 +44,32 @@ function getSmartCompactionConfig() {
|
|
|
44
44
|
let safeThreshold = smartConfig.safeThreshold || 0.95;
|
|
45
45
|
let emergencyThreshold = smartConfig.emergencyThreshold || 0.90;
|
|
46
46
|
|
|
47
|
+
// Claude Code 2.1.75+: Token estimation fix — thinking/tool_use blocks no longer
|
|
48
|
+
// over-counted. We can safely raise thresholds since compaction triggers are now
|
|
49
|
+
// more accurate, giving users ~5% more working context before compaction.
|
|
50
|
+
let ccVersion = null;
|
|
51
|
+
try {
|
|
52
|
+
const { meetsVersion } = require('./flow-utils');
|
|
53
|
+
const versionCheckPath = path.join(PATHS.state, '.version-check.json');
|
|
54
|
+
if (fs.existsSync(versionCheckPath)) {
|
|
55
|
+
const check = JSON.parse(fs.readFileSync(versionCheckPath, 'utf-8'));
|
|
56
|
+
if (check.version) {
|
|
57
|
+
const [major, minor, patch] = check.version.split('.').map(Number);
|
|
58
|
+
ccVersion = { major, minor, patch };
|
|
59
|
+
if (meetsVersion(major, minor, patch, 2, 1, 75)) {
|
|
60
|
+
// Relax thresholds — token counting is now accurate
|
|
61
|
+
if (!smartConfig.safeThreshold) safeThreshold = 0.80;
|
|
62
|
+
if (!smartConfig.emergencyThreshold) emergencyThreshold = 0.92;
|
|
63
|
+
if (process.env.DEBUG) {
|
|
64
|
+
console.log('[context-estimator] CC 2.1.75+ detected — relaxed thresholds (safe: 0.80, emergency: 0.92)');
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
} catch (err) {
|
|
70
|
+
// Non-blocking — fall back to default thresholds
|
|
71
|
+
}
|
|
72
|
+
|
|
47
73
|
// Claude Code 2.1.50+: CLAUDE_CODE_DISABLE_1M_CONTEXT reduces the context window.
|
|
48
74
|
// Lower thresholds to account for the smaller available context.
|
|
49
75
|
const disableExtendedContext = process.env.CLAUDE_CODE_DISABLE_1M_CONTEXT;
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Wogi Flow - Gitignore Auto-Management
|
|
5
|
+
*
|
|
6
|
+
* Declarative mapping of config features to .gitignore entries.
|
|
7
|
+
* When a config change enables a feature that produces runtime artifacts,
|
|
8
|
+
* the relevant entries are automatically appended to .gitignore.
|
|
9
|
+
*
|
|
10
|
+
* Design:
|
|
11
|
+
* - Append-only: never removes existing entries
|
|
12
|
+
* - Idempotent: no duplicate entries on repeated calls
|
|
13
|
+
* - Grouped under "# WogiFlow runtime (auto-managed)" comment
|
|
14
|
+
* - Declarative: RUNTIME_ARTIFACT_MAP defines all mappings
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
'use strict';
|
|
18
|
+
|
|
19
|
+
const fs = require('fs');
|
|
20
|
+
const path = require('path');
|
|
21
|
+
const { getConfig } = require('./flow-config-loader');
|
|
22
|
+
const { PROJECT_ROOT } = require('./flow-paths');
|
|
23
|
+
|
|
24
|
+
// ============================================================================
|
|
25
|
+
// Declarative Config-to-Gitignore Mapping
|
|
26
|
+
// ============================================================================
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Each entry maps a config condition to gitignore patterns.
|
|
30
|
+
* - configPath: dot-notation path into config object
|
|
31
|
+
* - matchValue: value that triggers the entry (true for boolean, string for exact match)
|
|
32
|
+
* If matchValue is true, any truthy value triggers the entry.
|
|
33
|
+
* - patterns: gitignore entries to add when condition is met
|
|
34
|
+
* - description: human-readable description for health check output
|
|
35
|
+
*/
|
|
36
|
+
const RUNTIME_ARTIFACT_MAP = [
|
|
37
|
+
{
|
|
38
|
+
configPath: 'testing.uiProvider',
|
|
39
|
+
matchValue: 'playwright-mcp',
|
|
40
|
+
patterns: ['.playwright-mcp/'],
|
|
41
|
+
description: 'Playwright MCP logs and screenshots'
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
configPath: 'testing.enabled',
|
|
45
|
+
matchValue: true,
|
|
46
|
+
patterns: ['.workflow/verifications/', '.workflow/tests/generated/'],
|
|
47
|
+
description: 'Test verification artifacts and generated tests'
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
configPath: 'webmcp.enabled',
|
|
51
|
+
matchValue: true,
|
|
52
|
+
patterns: ['.workflow/webmcp/'],
|
|
53
|
+
description: 'WebMCP tool definitions'
|
|
54
|
+
}
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
const GITIGNORE_SECTION_HEADER = '# WogiFlow runtime (auto-managed)';
|
|
58
|
+
|
|
59
|
+
// ============================================================================
|
|
60
|
+
// Core Functions
|
|
61
|
+
// ============================================================================
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get the value at a dot-notation path in an object.
|
|
65
|
+
* @param {Object} obj
|
|
66
|
+
* @param {string} dotPath - e.g. 'testing.uiProvider'
|
|
67
|
+
* @returns {*} Value at path, or undefined
|
|
68
|
+
*/
|
|
69
|
+
function getNestedValue(obj, dotPath) {
|
|
70
|
+
const parts = dotPath.split('.');
|
|
71
|
+
let current = obj;
|
|
72
|
+
for (const part of parts) {
|
|
73
|
+
if (current == null || typeof current !== 'object') return undefined;
|
|
74
|
+
current = current[part];
|
|
75
|
+
}
|
|
76
|
+
return current;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Determine which gitignore entries are needed based on current config.
|
|
81
|
+
* @param {Object} [config] - Pre-loaded config (optional)
|
|
82
|
+
* @returns {string[]} Gitignore patterns that should exist
|
|
83
|
+
*/
|
|
84
|
+
function getRequiredEntries(config) {
|
|
85
|
+
if (!config) config = getConfig();
|
|
86
|
+
const needed = [];
|
|
87
|
+
|
|
88
|
+
for (const mapping of RUNTIME_ARTIFACT_MAP) {
|
|
89
|
+
const value = getNestedValue(config, mapping.configPath);
|
|
90
|
+
let matches = false;
|
|
91
|
+
|
|
92
|
+
if (mapping.matchValue === true) {
|
|
93
|
+
matches = !!value;
|
|
94
|
+
} else {
|
|
95
|
+
matches = value === mapping.matchValue;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (matches) {
|
|
99
|
+
needed.push(...mapping.patterns);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return needed;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Read the current .gitignore content.
|
|
108
|
+
* @returns {string} Content of .gitignore, or empty string if not found
|
|
109
|
+
*/
|
|
110
|
+
function readGitignore() {
|
|
111
|
+
const gitignorePath = path.join(PROJECT_ROOT, '.gitignore');
|
|
112
|
+
try {
|
|
113
|
+
return fs.readFileSync(gitignorePath, 'utf-8');
|
|
114
|
+
} catch (err) {
|
|
115
|
+
return '';
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Parse existing gitignore entries (trimmed, non-empty, non-comment lines).
|
|
121
|
+
* @param {string} content - .gitignore content
|
|
122
|
+
* @returns {Set<string>} Set of existing entries
|
|
123
|
+
*/
|
|
124
|
+
function parseExistingEntries(content) {
|
|
125
|
+
const entries = new Set();
|
|
126
|
+
for (const line of content.split('\n')) {
|
|
127
|
+
const trimmed = line.trim();
|
|
128
|
+
if (trimmed && !trimmed.startsWith('#')) {
|
|
129
|
+
entries.push ? entries.add(trimmed) : entries.add(trimmed);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return entries;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Sync .gitignore with config-required entries.
|
|
137
|
+
* Appends missing entries under the auto-managed section header.
|
|
138
|
+
* @param {Object} [config] - Pre-loaded config (optional)
|
|
139
|
+
* @returns {{ added: string[], alreadyPresent: string[] }} What was done
|
|
140
|
+
*/
|
|
141
|
+
function syncGitignore(config) {
|
|
142
|
+
const required = getRequiredEntries(config);
|
|
143
|
+
if (required.length === 0) {
|
|
144
|
+
return { added: [], alreadyPresent: [] };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const gitignorePath = path.join(PROJECT_ROOT, '.gitignore');
|
|
148
|
+
const content = readGitignore();
|
|
149
|
+
const existing = parseExistingEntries(content);
|
|
150
|
+
|
|
151
|
+
const missing = required.filter(entry => !existing.has(entry));
|
|
152
|
+
const alreadyPresent = required.filter(entry => existing.has(entry));
|
|
153
|
+
|
|
154
|
+
if (missing.length === 0) {
|
|
155
|
+
return { added: [], alreadyPresent };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Build the new section content
|
|
159
|
+
const hasSection = content.includes(GITIGNORE_SECTION_HEADER);
|
|
160
|
+
let newContent;
|
|
161
|
+
|
|
162
|
+
if (hasSection) {
|
|
163
|
+
// Append to existing section — find the section and add after it
|
|
164
|
+
const lines = content.split('\n');
|
|
165
|
+
const headerIdx = lines.findIndex(l => l.trim() === GITIGNORE_SECTION_HEADER);
|
|
166
|
+
// Find the end of the managed section (next blank line or next comment section)
|
|
167
|
+
let insertIdx = headerIdx + 1;
|
|
168
|
+
while (insertIdx < lines.length) {
|
|
169
|
+
const line = lines[insertIdx].trim();
|
|
170
|
+
if (line === '' || (line.startsWith('#') && line !== GITIGNORE_SECTION_HEADER)) {
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
insertIdx++;
|
|
174
|
+
}
|
|
175
|
+
// Insert missing entries
|
|
176
|
+
lines.splice(insertIdx, 0, ...missing);
|
|
177
|
+
newContent = lines.join('\n');
|
|
178
|
+
} else {
|
|
179
|
+
// Create new section at end of file
|
|
180
|
+
const separator = content.endsWith('\n') ? '\n' : '\n\n';
|
|
181
|
+
newContent = content + separator + GITIGNORE_SECTION_HEADER + '\n' + missing.join('\n') + '\n';
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
fs.writeFileSync(gitignorePath, newContent, 'utf-8');
|
|
185
|
+
|
|
186
|
+
return { added: missing, alreadyPresent };
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Check gitignore health — returns missing entries for /wogi-health.
|
|
191
|
+
* @param {Object} [config] - Pre-loaded config (optional)
|
|
192
|
+
* @returns {{ ok: boolean, missing: Array<{pattern: string, description: string}> }}
|
|
193
|
+
*/
|
|
194
|
+
function checkGitignoreHealth(config) {
|
|
195
|
+
if (!config) config = getConfig();
|
|
196
|
+
const content = readGitignore();
|
|
197
|
+
const existing = parseExistingEntries(content);
|
|
198
|
+
const missing = [];
|
|
199
|
+
|
|
200
|
+
for (const mapping of RUNTIME_ARTIFACT_MAP) {
|
|
201
|
+
const value = getNestedValue(config, mapping.configPath);
|
|
202
|
+
let matches = false;
|
|
203
|
+
|
|
204
|
+
if (mapping.matchValue === true) {
|
|
205
|
+
matches = !!value;
|
|
206
|
+
} else {
|
|
207
|
+
matches = value === mapping.matchValue;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (matches) {
|
|
211
|
+
for (const pattern of mapping.patterns) {
|
|
212
|
+
if (!existing.has(pattern)) {
|
|
213
|
+
missing.push({ pattern, description: mapping.description });
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return { ok: missing.length === 0, missing };
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// ============================================================================
|
|
223
|
+
// CLI
|
|
224
|
+
// ============================================================================
|
|
225
|
+
|
|
226
|
+
if (require.main === module) {
|
|
227
|
+
const args = process.argv.slice(2);
|
|
228
|
+
const command = args[0] || 'sync';
|
|
229
|
+
|
|
230
|
+
if (command === 'sync') {
|
|
231
|
+
const result = syncGitignore();
|
|
232
|
+
if (result.added.length > 0) {
|
|
233
|
+
console.log(`Added ${result.added.length} entries to .gitignore:`);
|
|
234
|
+
for (const entry of result.added) {
|
|
235
|
+
console.log(` + ${entry}`);
|
|
236
|
+
}
|
|
237
|
+
} else {
|
|
238
|
+
console.log('.gitignore is up to date');
|
|
239
|
+
}
|
|
240
|
+
} else if (command === 'check') {
|
|
241
|
+
const health = checkGitignoreHealth();
|
|
242
|
+
if (health.ok) {
|
|
243
|
+
console.log('.gitignore: all required entries present');
|
|
244
|
+
} else {
|
|
245
|
+
console.log(`Missing ${health.missing.length} .gitignore entries:`);
|
|
246
|
+
for (const m of health.missing) {
|
|
247
|
+
console.log(` - ${m.pattern} (${m.description})`);
|
|
248
|
+
}
|
|
249
|
+
console.log('\nRun: flow gitignore sync');
|
|
250
|
+
process.exit(1);
|
|
251
|
+
}
|
|
252
|
+
} else {
|
|
253
|
+
console.log('Usage: flow-gitignore.js [sync|check]');
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
module.exports = {
|
|
258
|
+
RUNTIME_ARTIFACT_MAP,
|
|
259
|
+
GITIGNORE_SECTION_HEADER,
|
|
260
|
+
getRequiredEntries,
|
|
261
|
+
syncGitignore,
|
|
262
|
+
checkGitignoreHealth
|
|
263
|
+
};
|
package/scripts/flow-health.js
CHANGED
|
@@ -77,9 +77,13 @@ function checkClaudeCodeVersion() {
|
|
|
77
77
|
// 2.1.73+ fixes: SessionStart double-fire, hook context pollution, subagent model on Bedrock/Vertex
|
|
78
78
|
const meets2173 = meetsVersion(major, minor, patch, 2, 1, 73);
|
|
79
79
|
|
|
80
|
-
|
|
80
|
+
// 2.1.75+: 1M context default, accurate token estimation, async hook suppression,
|
|
81
|
+
// hook source display, memory file timestamps
|
|
82
|
+
const meets2175 = meetsVersion(major, minor, patch, 2, 1, 75);
|
|
83
|
+
|
|
84
|
+
return { version, meetsMinimum: meetsMin, meets2150, meets2172, meets2173, meets2175 };
|
|
81
85
|
} catch {
|
|
82
|
-
return { version: null, meetsMinimum: true, meets2150: false, meets2172: false, meets2173: false };
|
|
86
|
+
return { version: null, meetsMinimum: true, meets2150: false, meets2172: false, meets2173: false, meets2175: false };
|
|
83
87
|
}
|
|
84
88
|
}
|
|
85
89
|
|
|
@@ -225,6 +229,16 @@ function main() {
|
|
|
225
229
|
console.log(` ${color('dim', '→ Subagent model parameter works on Bedrock/Vertex/Foundry')}`);
|
|
226
230
|
console.log(` ${color('dim', '→ modelOverrides setting for custom provider model IDs')}`);
|
|
227
231
|
}
|
|
232
|
+
|
|
233
|
+
// Report 2.1.75+ features
|
|
234
|
+
if (versionCheck.meets2175) {
|
|
235
|
+
console.log(` ${color('green', '✓')} Claude Code 2.1.75+ features available:`);
|
|
236
|
+
console.log(` ${color('dim', '→ 1M context window default for Opus (Max/Team/Enterprise)')}`);
|
|
237
|
+
console.log(` ${color('dim', '→ Accurate token estimation (no thinking/tool_use over-counting)')}`);
|
|
238
|
+
console.log(` ${color('dim', '→ Relaxed compaction thresholds (safe: 80%, emergency: 92%)')}`);
|
|
239
|
+
console.log(` ${color('dim', '→ Hook source displayed in permission prompts')}`);
|
|
240
|
+
console.log(` ${color('dim', '→ Memory file last-modified timestamps for freshness')}`);
|
|
241
|
+
}
|
|
228
242
|
}
|
|
229
243
|
}
|
|
230
244
|
|
|
@@ -651,6 +665,26 @@ function main() {
|
|
|
651
665
|
}
|
|
652
666
|
}
|
|
653
667
|
|
|
668
|
+
// Check .gitignore sync
|
|
669
|
+
console.log('');
|
|
670
|
+
printSection('Checking .gitignore sync...');
|
|
671
|
+
|
|
672
|
+
try {
|
|
673
|
+
const { checkGitignoreHealth } = require('./flow-gitignore');
|
|
674
|
+
const gitignoreHealth = checkGitignoreHealth(config);
|
|
675
|
+
if (gitignoreHealth.ok) {
|
|
676
|
+
console.log(` ${color('green', '✓')} All required .gitignore entries present`);
|
|
677
|
+
} else {
|
|
678
|
+
for (const m of gitignoreHealth.missing) {
|
|
679
|
+
console.log(` ${color('yellow', '⚠')} Missing: ${m.pattern} (${m.description})`);
|
|
680
|
+
}
|
|
681
|
+
console.log(` ${color('yellow', '⚠')} Run: node scripts/flow-gitignore.js sync`);
|
|
682
|
+
warnings += gitignoreHealth.missing.length;
|
|
683
|
+
}
|
|
684
|
+
} catch (err) {
|
|
685
|
+
console.log(` ${color('yellow', '○')} Gitignore check unavailable`);
|
|
686
|
+
}
|
|
687
|
+
|
|
654
688
|
// Check git status
|
|
655
689
|
console.log('');
|
|
656
690
|
printSection('Checking git status...');
|
package/scripts/flow-utils.js
CHANGED
|
@@ -2161,6 +2161,10 @@ module.exports = {
|
|
|
2161
2161
|
// CLI Tool Detection (Claude Code 2.1.72+ compatibility)
|
|
2162
2162
|
meetsVersion,
|
|
2163
2163
|
getFdCommand,
|
|
2164
|
+
|
|
2165
|
+
// Gitignore Auto-Management (v1.9.8)
|
|
2166
|
+
get syncGitignore() { return require('./flow-gitignore').syncGitignore; },
|
|
2167
|
+
get checkGitignoreHealth() { return require('./flow-gitignore').checkGitignoreHealth; },
|
|
2164
2168
|
};
|
|
2165
2169
|
|
|
2166
2170
|
// ============================================================
|
|
@@ -135,6 +135,9 @@ function checkClaudeCodeVersionOnce() {
|
|
|
135
135
|
// 2.1.50+: worktree hooks, agent isolation
|
|
136
136
|
// 2.1.72+: ExitWorktree, effort levels, model param on Agent
|
|
137
137
|
// 2.1.73+: SessionStart double-fire fix, hook context pollution fix, modelOverrides, subagent model fix
|
|
138
|
+
// 2.1.75+: 1M context default (Max/Team/Enterprise), accurate token estimation,
|
|
139
|
+
// async hook completion suppressed by default, hook source in permission prompts,
|
|
140
|
+
// memory file last-modified timestamps
|
|
138
141
|
|
|
139
142
|
return null;
|
|
140
143
|
}
|