wogiflow 2.1.2 → 2.2.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 +189 -3
- package/.claude/commands/wogi-bulk.md +1 -1
- package/.claude/commands/wogi-help.md +1 -1
- package/.claude/commands/{wogi-compact.md → wogi-pre-compact.md} +6 -2
- package/.claude/commands/wogi-review.md +86 -13
- package/.claude/commands/wogi-setup-stack.md +1 -1
- package/.claude/commands/wogi-skill-learn.md +1 -1
- package/.claude/commands/wogi-start.md +65 -20
- package/.claude/docs/claude-code-compatibility.md +28 -0
- package/.claude/docs/commands.md +1 -1
- package/.claude/docs/knowledge-base/02-task-execution/04-completion.md +1 -1
- package/.claude/docs/knowledge-base/04-memory-context/README.md +2 -2
- package/.claude/docs/knowledge-base/04-memory-context/context-management.md +1 -1
- package/.claude/rules/_internal/README.md +64 -0
- package/.claude/rules/_internal/document-structure.md +77 -0
- package/.claude/rules/_internal/dual-repo-management.md +174 -0
- package/.claude/rules/_internal/feature-refactoring-cleanup.md +87 -0
- package/.claude/rules/_internal/github-releases.md +71 -0
- package/.claude/rules/_internal/model-management.md +35 -0
- package/.claude/rules/_internal/self-maintenance.md +87 -0
- package/.claude/rules/architecture/component-reuse.md +38 -0
- package/.claude/rules/code-style/naming-conventions.md +52 -0
- package/.claude/rules/operations/git-workflows.md +92 -0
- package/.claude/rules/operations/scratch-directory.md +54 -0
- package/.claude/rules/security/security-patterns.md +176 -0
- package/.claude/skills/figma-analyzer/knowledge/learnings.md +11 -0
- package/.workflow/bridges/claude-bridge.js +1 -1
- package/.workflow/models/registry.json +1 -1
- package/.workflow/specs/architecture.md.template +24 -0
- package/.workflow/specs/stack.md.template +33 -0
- package/.workflow/specs/testing.md.template +36 -0
- package/.workflow/templates/claude-md.hbs +33 -3
- package/README.md +1 -1
- package/package.json +1 -1
- package/scripts/flow-audit.js +158 -1
- package/scripts/flow-context-compact/index.js +1 -1
- package/scripts/flow-context-monitor.js +2 -2
- package/scripts/flow-loop-retry-learning.js +1 -1
- package/scripts/flow-proactive-compact.js +3 -3
- package/scripts/flow-progress-tracker.js +289 -0
- package/scripts/flow-prompt-capture.js +263 -170
- package/scripts/flow-standards-learner.js +167 -3
- package/scripts/flow-task-checkpoint.js +2 -0
- package/scripts/flow-version-check.js +1 -0
- package/scripts/hooks/core/commit-log-gate.js +146 -0
- package/scripts/hooks/core/post-compact.js +109 -4
- package/scripts/hooks/core/task-completed.js +19 -0
- package/scripts/hooks/entry/claude-code/post-tool-use.js +60 -0
- package/scripts/hooks/entry/claude-code/pre-tool-use.js +27 -0
package/scripts/flow-audit.js
CHANGED
|
@@ -254,6 +254,134 @@ function calculateHealthScore(scores) {
|
|
|
254
254
|
};
|
|
255
255
|
}
|
|
256
256
|
|
|
257
|
+
// ============================================================
|
|
258
|
+
// Pattern Promotion (Audit → Learning Pipeline)
|
|
259
|
+
// ============================================================
|
|
260
|
+
|
|
261
|
+
// Severity classification thresholds for audit patterns
|
|
262
|
+
const SYSTEMIC_THRESHOLD = 5; // 5+ files = HIGH severity / systemic
|
|
263
|
+
const MEDIUM_THRESHOLD = 3; // 3-4 files = MEDIUM severity
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Process AI-clustered audit findings through the learning pipeline.
|
|
267
|
+
*
|
|
268
|
+
* For each clustered pattern:
|
|
269
|
+
* 1. Check if a rule already exists in decisions.md → ENFORCEMENT_GAP
|
|
270
|
+
* 2. Record/increment in feedback-patterns.md
|
|
271
|
+
* 3. Check promotion threshold → auto-promote if met
|
|
272
|
+
* 4. Check last-audit.json → detect RECURRING patterns
|
|
273
|
+
*
|
|
274
|
+
* @param {Object[]} clusters - AI-clustered patterns from audit
|
|
275
|
+
* @param {Object} [previousAudit] - Previous audit data for recurrence detection
|
|
276
|
+
* @returns {Object} Promotion results per pattern
|
|
277
|
+
*/
|
|
278
|
+
function promoteAuditPatterns(clusters, previousAudit) {
|
|
279
|
+
const {
|
|
280
|
+
recordAuditPattern,
|
|
281
|
+
checkEnforcementGap,
|
|
282
|
+
promoteToDecisions,
|
|
283
|
+
syncToRulesDir,
|
|
284
|
+
mapAuditCategoryToLearnerCategory,
|
|
285
|
+
buildAuditRuleTemplate
|
|
286
|
+
} = require('./flow-standards-learner');
|
|
287
|
+
|
|
288
|
+
const previousPatternIds = new Set(
|
|
289
|
+
(previousAudit?.patterns || []).map(p => p.patternId)
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
const results = {
|
|
293
|
+
patterns: [],
|
|
294
|
+
summary: {
|
|
295
|
+
total: clusters.length,
|
|
296
|
+
promoted: 0,
|
|
297
|
+
promotionFailed: 0,
|
|
298
|
+
tracking: 0,
|
|
299
|
+
enforcementGaps: 0,
|
|
300
|
+
newPatterns: 0,
|
|
301
|
+
recurring: 0
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
for (const cluster of clusters) {
|
|
306
|
+
const patternResult = {
|
|
307
|
+
patternId: cluster.patternId,
|
|
308
|
+
category: cluster.category,
|
|
309
|
+
description: cluster.description,
|
|
310
|
+
instanceCount: cluster.instanceCount,
|
|
311
|
+
severity: cluster.severity || (cluster.instanceCount >= SYSTEMIC_THRESHOLD ? 'HIGH' : cluster.instanceCount >= MEDIUM_THRESHOLD ? 'MEDIUM' : 'LOW'),
|
|
312
|
+
isSystemic: cluster.isSystemic || cluster.instanceCount >= SYSTEMIC_THRESHOLD,
|
|
313
|
+
status: 'NEW',
|
|
314
|
+
count: 0,
|
|
315
|
+
rootCause: null,
|
|
316
|
+
recommendation: null
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
// Step 1: Check for enforcement gap
|
|
320
|
+
const gapCheck = checkEnforcementGap(cluster.patternId, cluster.description);
|
|
321
|
+
if (gapCheck.exists) {
|
|
322
|
+
patternResult.status = 'ENFORCEMENT_GAP';
|
|
323
|
+
patternResult.ruleLocation = gapCheck.section;
|
|
324
|
+
patternResult.ruleText = gapCheck.ruleText;
|
|
325
|
+
results.summary.enforcementGaps++;
|
|
326
|
+
} else {
|
|
327
|
+
// Step 2: Record/increment in feedback-patterns
|
|
328
|
+
const recordResult = recordAuditPattern(cluster);
|
|
329
|
+
|
|
330
|
+
if (recordResult.recorded) {
|
|
331
|
+
patternResult.count = recordResult.newCount;
|
|
332
|
+
|
|
333
|
+
// Step 3: Check promotion threshold
|
|
334
|
+
if (recordResult.shouldPromote) {
|
|
335
|
+
// Build a learning object for promotion (reuse learner functions)
|
|
336
|
+
const learning = {
|
|
337
|
+
canLearn: true,
|
|
338
|
+
violationType: cluster.category,
|
|
339
|
+
category: mapAuditCategoryToLearnerCategory(cluster.category),
|
|
340
|
+
patternName: cluster.description.slice(0, 100),
|
|
341
|
+
message: `${cluster.instanceCount} instances found (audit source, ${recordResult.newCount} occurrences)`,
|
|
342
|
+
ruleTemplate: buildAuditRuleTemplate(cluster)
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
const promoteResult = promoteToDecisions(learning, recordResult.newCount);
|
|
346
|
+
if (promoteResult.promoted) {
|
|
347
|
+
patternResult.status = 'PROMOTED';
|
|
348
|
+
results.summary.promoted++;
|
|
349
|
+
|
|
350
|
+
// Also sync to rules dir
|
|
351
|
+
const syncLearning = {
|
|
352
|
+
...learning,
|
|
353
|
+
subcategory: cluster.patternId
|
|
354
|
+
};
|
|
355
|
+
syncToRulesDir(syncLearning);
|
|
356
|
+
} else {
|
|
357
|
+
// Distinguish "not yet at threshold" from "promotion failed"
|
|
358
|
+
patternResult.status = 'PROMOTION_FAILED';
|
|
359
|
+
patternResult.failureReason = promoteResult.reason || 'Unknown promotion failure';
|
|
360
|
+
results.summary.promotionFailed++;
|
|
361
|
+
}
|
|
362
|
+
} else {
|
|
363
|
+
patternResult.status = `TRACKING (${recordResult.newCount}/${recordResult.threshold})`;
|
|
364
|
+
results.summary.tracking++;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Step 4: Check recurrence
|
|
370
|
+
if (previousPatternIds.has(cluster.patternId)) {
|
|
371
|
+
if (patternResult.status !== 'ENFORCEMENT_GAP' && patternResult.status !== 'PROMOTED') {
|
|
372
|
+
patternResult.status = `RECURRING — ${patternResult.status}`;
|
|
373
|
+
}
|
|
374
|
+
results.summary.recurring++;
|
|
375
|
+
} else if (patternResult.status === 'NEW' || patternResult.status.startsWith('TRACKING')) {
|
|
376
|
+
results.summary.newPatterns++;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
results.patterns.push(patternResult);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
return results;
|
|
383
|
+
}
|
|
384
|
+
|
|
257
385
|
// ============================================================
|
|
258
386
|
// CLI Interface
|
|
259
387
|
// ============================================================
|
|
@@ -307,6 +435,30 @@ function main() {
|
|
|
307
435
|
break;
|
|
308
436
|
}
|
|
309
437
|
|
|
438
|
+
case 'promote': {
|
|
439
|
+
// Process AI-clustered findings through the learning pipeline
|
|
440
|
+
// Usage: node scripts/flow-audit.js promote '<clusters-json>'
|
|
441
|
+
const clustersArg = process.argv[3];
|
|
442
|
+
if (!clustersArg) {
|
|
443
|
+
console.error('Usage: flow-audit.js promote \'[{patternId, category, description, instanceCount, instances}]\'');
|
|
444
|
+
process.exit(1);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const clusters = safeJsonParseString(clustersArg, null);
|
|
448
|
+
if (!Array.isArray(clusters)) {
|
|
449
|
+
console.error('Invalid JSON clusters argument — expected an array');
|
|
450
|
+
process.exit(1);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Load previous audit for recurrence detection
|
|
454
|
+
const lastAuditPath = path.join(PATHS.state, 'last-audit.json');
|
|
455
|
+
const previousAudit = safeJsonParse(lastAuditPath, {});
|
|
456
|
+
|
|
457
|
+
const promoteResults = promoteAuditPatterns(clusters, previousAudit);
|
|
458
|
+
console.log(JSON.stringify(promoteResults, null, 2));
|
|
459
|
+
break;
|
|
460
|
+
}
|
|
461
|
+
|
|
310
462
|
default: {
|
|
311
463
|
console.log(`
|
|
312
464
|
Wogi Flow - Project Audit Helpers
|
|
@@ -319,9 +471,13 @@ Commands:
|
|
|
319
471
|
outdated Run npm outdated (structured JSON output)
|
|
320
472
|
audit Run npm audit (structured JSON output)
|
|
321
473
|
score Calculate weighted health score from agent grades
|
|
474
|
+
promote Process AI-clustered findings through learning pipeline
|
|
322
475
|
|
|
323
476
|
Score usage:
|
|
324
477
|
node scripts/flow-audit.js score '{"architecture":"B+","dependencies":"A-"}'
|
|
478
|
+
|
|
479
|
+
Promote usage:
|
|
480
|
+
node scripts/flow-audit.js promote '[{"patternId":"missing-error-handling","category":"security","description":"...","instanceCount":7}]'
|
|
325
481
|
`);
|
|
326
482
|
break;
|
|
327
483
|
}
|
|
@@ -333,7 +489,8 @@ module.exports = {
|
|
|
333
489
|
findTodos,
|
|
334
490
|
getOutdatedDeps,
|
|
335
491
|
getAuditResults,
|
|
336
|
-
calculateHealthScore
|
|
492
|
+
calculateHealthScore,
|
|
493
|
+
promoteAuditPatterns
|
|
337
494
|
};
|
|
338
495
|
|
|
339
496
|
if (require.main === module) {
|
|
@@ -291,7 +291,7 @@ function clearAll() {
|
|
|
291
291
|
function getSerializedTree(level = 1) {
|
|
292
292
|
const tree = summaryTree.loadTree();
|
|
293
293
|
if (!tree) {
|
|
294
|
-
return '# No Context Saved\n\nRun /wogi-compact to save session context.';
|
|
294
|
+
return '# No Context Saved\n\nRun /wogi-pre-compact to save session context.';
|
|
295
295
|
}
|
|
296
296
|
|
|
297
297
|
return summaryTree.serializeTree(tree, level);
|
|
@@ -271,10 +271,10 @@ function checkContextHealth() {
|
|
|
271
271
|
|
|
272
272
|
if (usage >= config.criticalAt) {
|
|
273
273
|
status = 'critical';
|
|
274
|
-
recommendation = 'Run /wogi-compact NOW to avoid context overflow';
|
|
274
|
+
recommendation = 'Run /wogi-pre-compact NOW to avoid context overflow';
|
|
275
275
|
} else if (usage >= config.warnAt) {
|
|
276
276
|
status = 'warning';
|
|
277
|
-
recommendation = 'Consider running /wogi-compact soon';
|
|
277
|
+
recommendation = 'Consider running /wogi-pre-compact soon';
|
|
278
278
|
} else {
|
|
279
279
|
status = 'healthy';
|
|
280
280
|
recommendation = null;
|
|
@@ -99,7 +99,7 @@ const ROOT_CAUSE_CATEGORIES = {
|
|
|
99
99
|
},
|
|
100
100
|
CONTEXT_OVERFLOW: {
|
|
101
101
|
...FailureCategory.CONTEXT_OVERFLOW,
|
|
102
|
-
suggestion: 'Use /wogi-compact before large tasks',
|
|
102
|
+
suggestion: 'Use /wogi-pre-compact before large tasks',
|
|
103
103
|
targetFile: 'config.json'
|
|
104
104
|
},
|
|
105
105
|
CAPABILITY_MISMATCH: {
|
|
@@ -68,7 +68,7 @@ function getProactiveCompactionConfig() {
|
|
|
68
68
|
* Check whether proactive compaction should trigger at a phase boundary.
|
|
69
69
|
*
|
|
70
70
|
* This is called by /wogi-start at each phase transition.
|
|
71
|
-
* The actual compaction is performed by the AI agent using /wogi-compact.
|
|
71
|
+
* The actual compaction is performed by the AI agent using /wogi-pre-compact.
|
|
72
72
|
*
|
|
73
73
|
* @param {Object} params - Check parameters
|
|
74
74
|
* @param {string} params.phase - The phase that just completed
|
|
@@ -161,7 +161,7 @@ async function handlePhaseBoundary(params) {
|
|
|
161
161
|
|
|
162
162
|
/**
|
|
163
163
|
* Generate a compaction summary for the current task state.
|
|
164
|
-
* Used by /wogi-compact when proactive compaction triggers.
|
|
164
|
+
* Used by /wogi-pre-compact when proactive compaction triggers.
|
|
165
165
|
*
|
|
166
166
|
* @param {Object} checkpoint - Current checkpoint data
|
|
167
167
|
* @returns {string} Formatted compaction summary
|
|
@@ -253,7 +253,7 @@ function formatCompactionMessage(result, contextPercent) {
|
|
|
253
253
|
lines.push(`Context at ${pct}%. Compacting before next phase...`);
|
|
254
254
|
lines.push(`Reason: ${result.reason}`);
|
|
255
255
|
lines.push('');
|
|
256
|
-
lines.push('Task state has been checkpointed. Run /wogi-compact to compact now.');
|
|
256
|
+
lines.push('Task state has been checkpointed. Run /wogi-pre-compact to compact now.');
|
|
257
257
|
lines.push('After compaction, read task-checkpoint.json to restore context.');
|
|
258
258
|
|
|
259
259
|
return lines.join('\n');
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Wogi Flow - Progress Tracker
|
|
5
|
+
*
|
|
6
|
+
* Manages progress state for long-running tasks (reviews, audits, multi-criteria).
|
|
7
|
+
* Writes to .workflow/state/task-progress.json for hook/status line consumption.
|
|
8
|
+
* Optionally updates task title in ready.json with progress prefix for status line.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* flow-progress-tracker.js update <json> Update progress state
|
|
12
|
+
* flow-progress-tracker.js get Get current progress
|
|
13
|
+
* flow-progress-tracker.js clear Clear progress state
|
|
14
|
+
* flow-progress-tracker.js format <json> Format a progress bar string
|
|
15
|
+
*
|
|
16
|
+
* The AI calls this at natural checkpoints during execution.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const fs = require('node:fs');
|
|
20
|
+
const path = require('node:path');
|
|
21
|
+
const { PATHS, safeJsonParse, safeJsonParseString, getReadyData, saveReadyData } = require('./flow-utils');
|
|
22
|
+
|
|
23
|
+
const PROGRESS_PATH = path.join(PATHS.state, 'task-progress.json');
|
|
24
|
+
|
|
25
|
+
// ============================================================
|
|
26
|
+
// Progress State Management
|
|
27
|
+
// ============================================================
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Update the progress state file.
|
|
31
|
+
*
|
|
32
|
+
* @param {Object} progress
|
|
33
|
+
* @param {string} progress.taskId - Current task ID
|
|
34
|
+
* @param {string} progress.command - Command running (e.g., "/wogi-review")
|
|
35
|
+
* @param {string} progress.phase - Current phase name
|
|
36
|
+
* @param {number} progress.phaseNum - Current phase number (1-based)
|
|
37
|
+
* @param {number} progress.totalPhases - Total phases
|
|
38
|
+
* @param {string} [progress.step] - Current sub-step description
|
|
39
|
+
* @param {number} [progress.stepNum] - Current sub-step number
|
|
40
|
+
* @param {number} [progress.totalSteps] - Total sub-steps in this phase
|
|
41
|
+
* @param {boolean} [progress.updateTitle] - Update task title in ready.json
|
|
42
|
+
* @returns {{ saved: boolean }}
|
|
43
|
+
*/
|
|
44
|
+
function updateProgress(progress) {
|
|
45
|
+
const state = {
|
|
46
|
+
taskId: progress.taskId,
|
|
47
|
+
command: progress.command,
|
|
48
|
+
phase: progress.phase,
|
|
49
|
+
phaseNum: progress.phaseNum || 0,
|
|
50
|
+
totalPhases: progress.totalPhases || 0,
|
|
51
|
+
step: progress.step || null,
|
|
52
|
+
stepNum: progress.stepNum || 0,
|
|
53
|
+
totalSteps: progress.totalSteps || 0,
|
|
54
|
+
percentage: calculatePercentage(progress),
|
|
55
|
+
startedAt: getExistingStartTime() || new Date().toISOString(),
|
|
56
|
+
lastUpdate: new Date().toISOString()
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
fs.mkdirSync(path.dirname(PROGRESS_PATH), { recursive: true });
|
|
61
|
+
fs.writeFileSync(PROGRESS_PATH, JSON.stringify(state, null, 2));
|
|
62
|
+
} catch (err) {
|
|
63
|
+
return { saved: false, reason: err.message };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Update task title in ready.json for status line visibility (opt-in)
|
|
67
|
+
if (progress.updateTitle === true && state.taskId) {
|
|
68
|
+
updateTaskTitle(state);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return { saved: true, state };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Calculate progress percentage from phase/step numbers.
|
|
76
|
+
*/
|
|
77
|
+
function calculatePercentage(progress) {
|
|
78
|
+
const { phaseNum, totalPhases, stepNum, totalSteps } = progress;
|
|
79
|
+
if (!totalPhases || !phaseNum) return 0;
|
|
80
|
+
|
|
81
|
+
// Phase-level progress
|
|
82
|
+
const phaseProgress = ((phaseNum - 1) / totalPhases) * 100;
|
|
83
|
+
|
|
84
|
+
// Step-level progress within the current phase
|
|
85
|
+
const phaseWeight = 100 / totalPhases;
|
|
86
|
+
const stepProgress = (totalSteps && stepNum)
|
|
87
|
+
? (stepNum / totalSteps) * phaseWeight
|
|
88
|
+
: 0;
|
|
89
|
+
|
|
90
|
+
return Math.min(100, Math.round(phaseProgress + stepProgress));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Get existing start time to preserve across updates.
|
|
95
|
+
*/
|
|
96
|
+
function getExistingStartTime() {
|
|
97
|
+
try {
|
|
98
|
+
const existing = safeJsonParse(PROGRESS_PATH, null);
|
|
99
|
+
return existing?.startedAt || null;
|
|
100
|
+
} catch (err) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Update task title in ready.json with progress prefix.
|
|
107
|
+
* Format: "[2/5] Original title"
|
|
108
|
+
*/
|
|
109
|
+
function updateTaskTitle(state) {
|
|
110
|
+
try {
|
|
111
|
+
const data = getReadyData();
|
|
112
|
+
const task = data.inProgress.find(t => t.id === state.taskId);
|
|
113
|
+
if (!task) return;
|
|
114
|
+
|
|
115
|
+
// Strip any existing progress prefix
|
|
116
|
+
const cleanTitle = task.title.replace(/^\[\d+\/\d+\]\s*/, '');
|
|
117
|
+
|
|
118
|
+
// Add new prefix
|
|
119
|
+
task.title = `[${state.phaseNum}/${state.totalPhases}] ${cleanTitle}`;
|
|
120
|
+
saveReadyData(data);
|
|
121
|
+
} catch (err) {
|
|
122
|
+
// Non-fatal — title update is cosmetic
|
|
123
|
+
if (process.env.DEBUG) {
|
|
124
|
+
console.error(`[progress-tracker] Title update failed: ${err.message}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Get current progress state.
|
|
131
|
+
* @returns {Object|null}
|
|
132
|
+
*/
|
|
133
|
+
function getProgress() {
|
|
134
|
+
return safeJsonParse(PROGRESS_PATH, null);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Clear progress state (called on task completion).
|
|
139
|
+
*
|
|
140
|
+
* NOTE: Title restoration (stripping [N/M] prefix) is handled inside the
|
|
141
|
+
* task-completed hook's withLock() callback to avoid race conditions on ready.json.
|
|
142
|
+
* This function only deletes the progress state file.
|
|
143
|
+
*/
|
|
144
|
+
function clearProgress() {
|
|
145
|
+
try {
|
|
146
|
+
fs.unlinkSync(PROGRESS_PATH);
|
|
147
|
+
return { cleared: true };
|
|
148
|
+
} catch (err) {
|
|
149
|
+
if (err.code === 'ENOENT') return { cleared: true };
|
|
150
|
+
return { cleared: false, reason: err.message };
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Format a progress bar string for conversation output.
|
|
156
|
+
*
|
|
157
|
+
* @param {Object} opts
|
|
158
|
+
* @param {number} opts.current - Current step
|
|
159
|
+
* @param {number} opts.total - Total steps
|
|
160
|
+
* @param {string} opts.label - Label text
|
|
161
|
+
* @param {number} [opts.width=20] - Bar width
|
|
162
|
+
* @returns {string} Formatted progress line
|
|
163
|
+
*/
|
|
164
|
+
function formatProgressBar(opts) {
|
|
165
|
+
const { current, total, label, width = 20 } = opts;
|
|
166
|
+
const pct = total > 0 ? Math.round((current / total) * 100) : 0;
|
|
167
|
+
const filled = total > 0 ? Math.round((current / total) * width) : 0;
|
|
168
|
+
const empty = width - filled;
|
|
169
|
+
|
|
170
|
+
const bar = '\u2588'.repeat(filled) + '\u2591'.repeat(empty);
|
|
171
|
+
return `[${bar}] ${pct}% ${label} (${current}/${total})`;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Format a multi-level progress display for conversation output.
|
|
176
|
+
*
|
|
177
|
+
* @param {Object} opts
|
|
178
|
+
* @param {string} opts.command - Command name
|
|
179
|
+
* @param {string} opts.phase - Current phase
|
|
180
|
+
* @param {number} opts.phaseNum
|
|
181
|
+
* @param {number} opts.totalPhases
|
|
182
|
+
* @param {string} [opts.step] - Current step within phase
|
|
183
|
+
* @param {number} [opts.stepNum]
|
|
184
|
+
* @param {number} [opts.totalSteps]
|
|
185
|
+
* @returns {string} Multi-line progress display
|
|
186
|
+
*/
|
|
187
|
+
function formatProgress(opts) {
|
|
188
|
+
const lines = [];
|
|
189
|
+
const pct = calculatePercentage(opts);
|
|
190
|
+
|
|
191
|
+
// Phase-level bar
|
|
192
|
+
lines.push(formatProgressBar({
|
|
193
|
+
current: opts.phaseNum,
|
|
194
|
+
total: opts.totalPhases,
|
|
195
|
+
label: opts.phase
|
|
196
|
+
}));
|
|
197
|
+
|
|
198
|
+
// Step-level detail (if applicable)
|
|
199
|
+
if (opts.step && opts.totalSteps) {
|
|
200
|
+
lines.push(` ${opts.step} (${opts.stepNum}/${opts.totalSteps})`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return lines.join('\n');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// ============================================================
|
|
207
|
+
// CLI Interface
|
|
208
|
+
// ============================================================
|
|
209
|
+
|
|
210
|
+
function main() {
|
|
211
|
+
const command = process.argv[2];
|
|
212
|
+
const arg = process.argv[3];
|
|
213
|
+
|
|
214
|
+
switch (command) {
|
|
215
|
+
case 'update': {
|
|
216
|
+
if (!arg) {
|
|
217
|
+
console.error('Usage: flow-progress-tracker.js update \'{"taskId":"...","phase":"...","phaseNum":1,"totalPhases":5}\'');
|
|
218
|
+
process.exit(1);
|
|
219
|
+
}
|
|
220
|
+
const progress = safeJsonParseString(arg, null);
|
|
221
|
+
if (!progress) {
|
|
222
|
+
console.error('Invalid JSON argument');
|
|
223
|
+
process.exit(1);
|
|
224
|
+
}
|
|
225
|
+
const result = updateProgress(progress);
|
|
226
|
+
console.log(JSON.stringify(result, null, 2));
|
|
227
|
+
break;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
case 'get': {
|
|
231
|
+
const state = getProgress();
|
|
232
|
+
if (state) {
|
|
233
|
+
console.log(JSON.stringify(state, null, 2));
|
|
234
|
+
} else {
|
|
235
|
+
console.log(JSON.stringify({ active: false }));
|
|
236
|
+
}
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
case 'clear': {
|
|
241
|
+
const result = clearProgress();
|
|
242
|
+
console.log(JSON.stringify(result, null, 2));
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
case 'format': {
|
|
247
|
+
if (!arg) {
|
|
248
|
+
console.error('Usage: flow-progress-tracker.js format \'{"current":2,"total":5,"label":"Phase"}\'');
|
|
249
|
+
process.exit(1);
|
|
250
|
+
}
|
|
251
|
+
const opts = safeJsonParseString(arg, null);
|
|
252
|
+
if (!opts) {
|
|
253
|
+
console.error('Invalid JSON argument');
|
|
254
|
+
process.exit(1);
|
|
255
|
+
}
|
|
256
|
+
console.log(formatProgressBar(opts));
|
|
257
|
+
break;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
default:
|
|
261
|
+
console.log(`
|
|
262
|
+
Wogi Flow - Progress Tracker
|
|
263
|
+
|
|
264
|
+
Usage: flow-progress-tracker.js <command> [args]
|
|
265
|
+
|
|
266
|
+
Commands:
|
|
267
|
+
update <json> Update progress state + task title
|
|
268
|
+
get Get current progress state
|
|
269
|
+
clear Clear progress state
|
|
270
|
+
format <json> Format a progress bar string
|
|
271
|
+
|
|
272
|
+
Update format:
|
|
273
|
+
{"taskId":"wf-xxx","command":"/wogi-review","phase":"AI Review","phaseNum":2,"totalPhases":5}
|
|
274
|
+
`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
module.exports = {
|
|
279
|
+
updateProgress,
|
|
280
|
+
getProgress,
|
|
281
|
+
clearProgress,
|
|
282
|
+
formatProgressBar,
|
|
283
|
+
formatProgress,
|
|
284
|
+
calculatePercentage
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
if (require.main === module) {
|
|
288
|
+
main();
|
|
289
|
+
}
|