wogiflow 2.8.0 → 2.9.1
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-learn.md +12 -5
- package/lib/workspace-gates.js +87 -0
- package/lib/workspace-session.js +308 -0
- package/lib/workspace.js +39 -3
- package/package.json +1 -1
- 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-health.js +180 -0
- package/scripts/flow-log-manager.js +38 -0
- package/scripts/flow-memory-db.js +53 -2
- package/scripts/flow-section-resolver.js +47 -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 +103 -0
- package/scripts/postinstall.js +20 -0
|
@@ -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 {
|
|
@@ -285,6 +311,83 @@ async function handleTaskCompleted(input) {
|
|
|
285
311
|
// Workspace notifications are handled by the Stop hook (via HTTP to manager port).
|
|
286
312
|
// Removed duplicate file-based notification here to prevent double messages (finding-004).
|
|
287
313
|
|
|
314
|
+
// Compound from success — capture positive patterns (fire-and-forget)
|
|
315
|
+
if (result.completed) {
|
|
316
|
+
try {
|
|
317
|
+
const config = getConfig();
|
|
318
|
+
if (config.skillLearning?.enabled) {
|
|
319
|
+
const { writeToFeedbackPatterns } = require('../../flow-learning-orchestrator');
|
|
320
|
+
const taskType = completedTask.type || 'unknown';
|
|
321
|
+
const changedFiles = input.changedFiles || [];
|
|
322
|
+
const criteriaCount = input.scenarioCount || completedTask.criteria || 0;
|
|
323
|
+
const firstPass = input.firstAttemptPass !== false;
|
|
324
|
+
|
|
325
|
+
// Only record success patterns for non-trivial tasks that passed on first attempt
|
|
326
|
+
if (firstPass && changedFiles.length >= 2 && criteriaCount >= 2) {
|
|
327
|
+
const today = new Date().toISOString().split('T')[0];
|
|
328
|
+
const filesSummary = changedFiles.slice(0, 5).map(f => path.basename(f)).join(', ');
|
|
329
|
+
const entryText = `success-pattern: ${taskType} task (${criteriaCount} criteria, ${changedFiles.length} files) completed first-pass. Files: ${filesSummary}`;
|
|
330
|
+
const tableRow = `| ${today} | ${entryText} | 1 | - | #success |`;
|
|
331
|
+
|
|
332
|
+
writeToFeedbackPatterns({
|
|
333
|
+
content: tableRow,
|
|
334
|
+
entryText,
|
|
335
|
+
caller: 'task-completed-success',
|
|
336
|
+
}).catch(() => {
|
|
337
|
+
// Non-critical
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
} catch (_err) {
|
|
342
|
+
// Non-critical — success pattern capture may not be available
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Skill learning extraction (fire-and-forget)
|
|
347
|
+
if (result.completed) {
|
|
348
|
+
try {
|
|
349
|
+
const { isLearningEnabled, extractLearningContext, matchFilesToSkills, appendLearning, discoverSkills, ensureKnowledgeDir, formatSemanticChanges } = require('../../flow-skill-learn');
|
|
350
|
+
const config = getConfig();
|
|
351
|
+
if (isLearningEnabled(config, 'task')) {
|
|
352
|
+
const changedFiles = input.changedFiles || [];
|
|
353
|
+
if (changedFiles.length > 0) {
|
|
354
|
+
const skills = discoverSkills();
|
|
355
|
+
const { matches: skillMap } = matchFilesToSkills(changedFiles, skills);
|
|
356
|
+
const context = extractLearningContext(changedFiles, 'task');
|
|
357
|
+
|
|
358
|
+
// Enrich context with task info
|
|
359
|
+
context.summary = `Task ${completedTask.id}: ${completedTask.title || ''}`;
|
|
360
|
+
context.taskType = completedTask.type || 'unknown';
|
|
361
|
+
|
|
362
|
+
for (const [skillName, matchedFiles] of skillMap) {
|
|
363
|
+
if (matchedFiles.length > 0) {
|
|
364
|
+
const skill = skills.find(s => s.name === skillName);
|
|
365
|
+
const skillDir = skill?.path;
|
|
366
|
+
if (skillDir) {
|
|
367
|
+
ensureKnowledgeDir(skillDir);
|
|
368
|
+
const entry = [
|
|
369
|
+
`### ${context.summary}`,
|
|
370
|
+
`**Type**: ${context.type} | **Trigger**: task-complete`,
|
|
371
|
+
`**Files**: ${matchedFiles.join(', ')}`,
|
|
372
|
+
];
|
|
373
|
+
if (context.semanticChanges.length > 0) {
|
|
374
|
+
entry.push(`**Changes**: ${formatSemanticChanges(context.semanticChanges).slice(0, 200)}`);
|
|
375
|
+
}
|
|
376
|
+
entry.push('');
|
|
377
|
+
appendLearning(skillDir, entry.join('\n'));
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
} catch (_err) {
|
|
384
|
+
// Non-critical — skill learning may not be available
|
|
385
|
+
if (process.env.DEBUG) {
|
|
386
|
+
console.error(`[Task Completed] Skill learning failed: ${_err.message}`);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
288
391
|
// Check pending queue — notify user if items are waiting
|
|
289
392
|
try {
|
|
290
393
|
const { getPendingCount } = require('../../flow-pending');
|
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.
|