wogiflow 1.0.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/.workflow/agents/reviewer.md +81 -0
- package/.workflow/agents/security.md +94 -0
- package/.workflow/agents/story-writer.md +58 -0
- package/.workflow/bridges/base-bridge.js +395 -0
- package/.workflow/bridges/claude-bridge.js +434 -0
- package/.workflow/bridges/index.js +130 -0
- package/.workflow/lib/assumption-detector.js +481 -0
- package/.workflow/lib/config-substitution.js +371 -0
- package/.workflow/lib/failure-categories.js +478 -0
- package/.workflow/state/app-map.md.template +15 -0
- package/.workflow/state/architecture.md.template +24 -0
- package/.workflow/state/component-index.json.template +5 -0
- package/.workflow/state/decisions.md.template +15 -0
- package/.workflow/state/feedback-patterns.md.template +9 -0
- package/.workflow/state/knowledge-sync.json.template +6 -0
- package/.workflow/state/progress.md.template +14 -0
- package/.workflow/state/ready.json.template +7 -0
- package/.workflow/state/request-log.md.template +14 -0
- package/.workflow/state/session-state.json.template +11 -0
- package/.workflow/state/stack.md.template +33 -0
- package/.workflow/state/testing.md.template +36 -0
- package/.workflow/templates/claude-md.hbs +257 -0
- package/.workflow/templates/correction-report.md +67 -0
- package/.workflow/templates/gemini-md.hbs +52 -0
- package/README.md +1802 -0
- package/bin/flow +205 -0
- package/lib/index.js +33 -0
- package/lib/installer.js +467 -0
- package/lib/release-channel.js +269 -0
- package/lib/skill-registry.js +526 -0
- package/lib/upgrader.js +401 -0
- package/lib/utils.js +305 -0
- package/package.json +64 -0
- package/scripts/flow +985 -0
- package/scripts/flow-adaptive-learning.js +1259 -0
- package/scripts/flow-aggregate.js +488 -0
- package/scripts/flow-archive +133 -0
- package/scripts/flow-auto-context.js +1015 -0
- package/scripts/flow-auto-learn.js +615 -0
- package/scripts/flow-bridge.js +223 -0
- package/scripts/flow-browser-suggest.js +316 -0
- package/scripts/flow-bug.js +247 -0
- package/scripts/flow-cascade.js +711 -0
- package/scripts/flow-changelog +85 -0
- package/scripts/flow-checkpoint.js +483 -0
- package/scripts/flow-cli.js +403 -0
- package/scripts/flow-code-intelligence.js +760 -0
- package/scripts/flow-complexity.js +502 -0
- package/scripts/flow-config-set.js +152 -0
- package/scripts/flow-constants.js +157 -0
- package/scripts/flow-context +152 -0
- package/scripts/flow-context-init.js +482 -0
- package/scripts/flow-context-monitor.js +384 -0
- package/scripts/flow-context-scoring.js +886 -0
- package/scripts/flow-correct.js +458 -0
- package/scripts/flow-damage-control.js +985 -0
- package/scripts/flow-deps +101 -0
- package/scripts/flow-diff.js +700 -0
- package/scripts/flow-done +151 -0
- package/scripts/flow-done.js +489 -0
- package/scripts/flow-durable-session.js +1541 -0
- package/scripts/flow-entropy-monitor.js +345 -0
- package/scripts/flow-export-profile +349 -0
- package/scripts/flow-export-scanner.js +1046 -0
- package/scripts/flow-figma-confirm.js +400 -0
- package/scripts/flow-figma-extract.js +496 -0
- package/scripts/flow-figma-generate.js +683 -0
- package/scripts/flow-figma-index.js +909 -0
- package/scripts/flow-figma-match.js +617 -0
- package/scripts/flow-figma-mcp-server.js +518 -0
- package/scripts/flow-figma-pipeline.js +414 -0
- package/scripts/flow-file-ops.js +301 -0
- package/scripts/flow-gate-confidence.js +825 -0
- package/scripts/flow-guided-edit.js +659 -0
- package/scripts/flow-health +185 -0
- package/scripts/flow-health.js +413 -0
- package/scripts/flow-hooks.js +556 -0
- package/scripts/flow-http-client.js +249 -0
- package/scripts/flow-hybrid-detect.js +167 -0
- package/scripts/flow-hybrid-interactive.js +591 -0
- package/scripts/flow-hybrid-test.js +152 -0
- package/scripts/flow-import-profile +439 -0
- package/scripts/flow-init +253 -0
- package/scripts/flow-instruction-richness.js +827 -0
- package/scripts/flow-jira-integration.js +579 -0
- package/scripts/flow-knowledge-router.js +522 -0
- package/scripts/flow-knowledge-sync.js +589 -0
- package/scripts/flow-linear-integration.js +631 -0
- package/scripts/flow-links.js +774 -0
- package/scripts/flow-log-manager.js +559 -0
- package/scripts/flow-loop-enforcer.js +1246 -0
- package/scripts/flow-loop-retry-learning.js +630 -0
- package/scripts/flow-lsp.js +923 -0
- package/scripts/flow-map-index +348 -0
- package/scripts/flow-map-sync +201 -0
- package/scripts/flow-memory-blocks.js +668 -0
- package/scripts/flow-memory-compactor.js +350 -0
- package/scripts/flow-memory-db.js +1110 -0
- package/scripts/flow-memory-sync.js +484 -0
- package/scripts/flow-metrics.js +353 -0
- package/scripts/flow-migrate-ids.js +370 -0
- package/scripts/flow-model-adapter.js +802 -0
- package/scripts/flow-model-router.js +884 -0
- package/scripts/flow-models.js +1231 -0
- package/scripts/flow-morning.js +517 -0
- package/scripts/flow-multi-approach.js +660 -0
- package/scripts/flow-new-feature +86 -0
- package/scripts/flow-onboard +1042 -0
- package/scripts/flow-orchestrate-llm.js +459 -0
- package/scripts/flow-orchestrate.js +3592 -0
- package/scripts/flow-output.js +123 -0
- package/scripts/flow-parallel-detector.js +399 -0
- package/scripts/flow-parallel-dispatch.js +987 -0
- package/scripts/flow-parallel.js +428 -0
- package/scripts/flow-pattern-enforcer.js +600 -0
- package/scripts/flow-prd-manager.js +282 -0
- package/scripts/flow-progress.js +323 -0
- package/scripts/flow-project-analyzer.js +975 -0
- package/scripts/flow-prompt-composer.js +487 -0
- package/scripts/flow-providers.js +1381 -0
- package/scripts/flow-queue.js +308 -0
- package/scripts/flow-ready +82 -0
- package/scripts/flow-ready.js +189 -0
- package/scripts/flow-regression.js +396 -0
- package/scripts/flow-response-parser.js +450 -0
- package/scripts/flow-resume.js +284 -0
- package/scripts/flow-rules-sync.js +439 -0
- package/scripts/flow-run-trace.js +718 -0
- package/scripts/flow-safety.js +587 -0
- package/scripts/flow-search +104 -0
- package/scripts/flow-security.js +481 -0
- package/scripts/flow-session-end +106 -0
- package/scripts/flow-session-end.js +437 -0
- package/scripts/flow-session-state.js +671 -0
- package/scripts/flow-setup-hooks +216 -0
- package/scripts/flow-setup-hooks.js +377 -0
- package/scripts/flow-skill-create.js +329 -0
- package/scripts/flow-skill-creator.js +572 -0
- package/scripts/flow-skill-generator.js +1046 -0
- package/scripts/flow-skill-learn.js +880 -0
- package/scripts/flow-skill-matcher.js +578 -0
- package/scripts/flow-spec-generator.js +820 -0
- package/scripts/flow-stack-wizard.js +895 -0
- package/scripts/flow-standup +162 -0
- package/scripts/flow-start +74 -0
- package/scripts/flow-start.js +235 -0
- package/scripts/flow-status +110 -0
- package/scripts/flow-status.js +301 -0
- package/scripts/flow-step-browser.js +83 -0
- package/scripts/flow-step-changelog.js +217 -0
- package/scripts/flow-step-comments.js +306 -0
- package/scripts/flow-step-complexity.js +234 -0
- package/scripts/flow-step-coverage.js +218 -0
- package/scripts/flow-step-knowledge.js +193 -0
- package/scripts/flow-step-pr-tests.js +364 -0
- package/scripts/flow-step-regression.js +89 -0
- package/scripts/flow-step-review.js +516 -0
- package/scripts/flow-step-security.js +162 -0
- package/scripts/flow-step-silent-failures.js +290 -0
- package/scripts/flow-step-simplifier.js +346 -0
- package/scripts/flow-story +105 -0
- package/scripts/flow-story.js +500 -0
- package/scripts/flow-suspend.js +252 -0
- package/scripts/flow-sync-daemon.js +654 -0
- package/scripts/flow-task-analyzer.js +606 -0
- package/scripts/flow-team-dashboard.js +748 -0
- package/scripts/flow-team-sync.js +752 -0
- package/scripts/flow-team.js +977 -0
- package/scripts/flow-tech-options.js +528 -0
- package/scripts/flow-templates.js +812 -0
- package/scripts/flow-tiered-learning.js +728 -0
- package/scripts/flow-trace +204 -0
- package/scripts/flow-transcript-chunking.js +1106 -0
- package/scripts/flow-transcript-digest.js +7918 -0
- package/scripts/flow-transcript-language.js +465 -0
- package/scripts/flow-transcript-parsing.js +1085 -0
- package/scripts/flow-transcript-stories.js +2194 -0
- package/scripts/flow-update-map +224 -0
- package/scripts/flow-utils.js +2242 -0
- package/scripts/flow-verification.js +644 -0
- package/scripts/flow-verify.js +1177 -0
- package/scripts/flow-voice-input.js +638 -0
- package/scripts/flow-watch +168 -0
- package/scripts/flow-workflow-steps.js +521 -0
- package/scripts/flow-workflow.js +1029 -0
- package/scripts/flow-worktree.js +489 -0
- package/scripts/hooks/adapters/base-adapter.js +102 -0
- package/scripts/hooks/adapters/claude-code.js +359 -0
- package/scripts/hooks/adapters/index.js +79 -0
- package/scripts/hooks/core/component-check.js +341 -0
- package/scripts/hooks/core/index.js +35 -0
- package/scripts/hooks/core/loop-check.js +241 -0
- package/scripts/hooks/core/session-context.js +294 -0
- package/scripts/hooks/core/task-gate.js +177 -0
- package/scripts/hooks/core/validation.js +230 -0
- package/scripts/hooks/entry/claude-code/post-tool-use.js +65 -0
- package/scripts/hooks/entry/claude-code/pre-tool-use.js +89 -0
- package/scripts/hooks/entry/claude-code/session-end.js +87 -0
- package/scripts/hooks/entry/claude-code/session-start.js +46 -0
- package/scripts/hooks/entry/claude-code/stop.js +43 -0
- package/scripts/postinstall.js +139 -0
- package/templates/browser-test-flow.json +56 -0
- package/templates/bug-report.md +43 -0
- package/templates/component-detail.md +42 -0
- package/templates/component.stories.tsx +49 -0
- package/templates/context/constraints.md +83 -0
- package/templates/context/conventions.md +177 -0
- package/templates/context/stack.md +60 -0
- package/templates/correction-report.md +90 -0
- package/templates/feature-proposal.md +35 -0
- package/templates/hybrid/_base.md +254 -0
- package/templates/hybrid/_patterns.md +45 -0
- package/templates/hybrid/create-component.md +127 -0
- package/templates/hybrid/create-file.md +56 -0
- package/templates/hybrid/create-hook.md +145 -0
- package/templates/hybrid/create-service.md +70 -0
- package/templates/hybrid/fix-bug.md +33 -0
- package/templates/hybrid/modify-file.md +55 -0
- package/templates/story.md +68 -0
- package/templates/task.json +56 -0
- package/templates/trace.md +69 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Wogi Flow - Complete Task
|
|
4
|
+
|
|
5
|
+
set -e
|
|
6
|
+
|
|
7
|
+
WORKFLOW_DIR=".workflow"
|
|
8
|
+
READY_JSON="$WORKFLOW_DIR/state/ready.json"
|
|
9
|
+
CONFIG_JSON="$WORKFLOW_DIR/config.json"
|
|
10
|
+
REQUEST_LOG="$WORKFLOW_DIR/state/request-log.md"
|
|
11
|
+
|
|
12
|
+
# Colors
|
|
13
|
+
GREEN='\033[0;32m'
|
|
14
|
+
YELLOW='\033[1;33m'
|
|
15
|
+
RED='\033[0;31m'
|
|
16
|
+
NC='\033[0m'
|
|
17
|
+
|
|
18
|
+
if [ -z "$1" ]; then
|
|
19
|
+
echo "Usage: flow done <task-id> [commit-message]"
|
|
20
|
+
exit 1
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
TASK_ID="$1"
|
|
24
|
+
COMMIT_MSG="${2:-Complete $TASK_ID}"
|
|
25
|
+
|
|
26
|
+
if [ ! -f "$READY_JSON" ]; then
|
|
27
|
+
echo -e "${RED}Error: No ready.json found${NC}"
|
|
28
|
+
exit 1
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
# Check quality gates from config
|
|
32
|
+
gates_passed=true
|
|
33
|
+
|
|
34
|
+
if [ -f "$CONFIG_JSON" ]; then
|
|
35
|
+
echo -e "${YELLOW}Running quality gates...${NC}"
|
|
36
|
+
echo ""
|
|
37
|
+
|
|
38
|
+
# Parse config and run gates
|
|
39
|
+
CONFIG_FILE="$CONFIG_JSON" REQUEST_LOG_FILE="$REQUEST_LOG" TASK_ID_ARG="$TASK_ID" python3 << 'GATES'
|
|
40
|
+
import json
|
|
41
|
+
import subprocess
|
|
42
|
+
import sys
|
|
43
|
+
import os
|
|
44
|
+
|
|
45
|
+
with open(os.environ['CONFIG_FILE']) as f:
|
|
46
|
+
config = json.load(f)
|
|
47
|
+
|
|
48
|
+
task_id = os.environ['TASK_ID_ARG']
|
|
49
|
+
request_log = os.environ['REQUEST_LOG_FILE']
|
|
50
|
+
gates = config.get('qualityGates', {}).get('feature', {}).get('require', [])
|
|
51
|
+
testing = config.get('testing', {})
|
|
52
|
+
failed = []
|
|
53
|
+
|
|
54
|
+
for gate in gates:
|
|
55
|
+
if gate == 'tests':
|
|
56
|
+
if testing.get('runAfterTask', False) or testing.get('runBeforeCommit', False):
|
|
57
|
+
print(' Running tests...')
|
|
58
|
+
result = subprocess.run(['npm', 'test'], capture_output=True, text=True)
|
|
59
|
+
if result.returncode == 0:
|
|
60
|
+
print(' \033[0;32m✓\033[0m tests passed')
|
|
61
|
+
else:
|
|
62
|
+
print(' \033[0;31m✗\033[0m tests failed')
|
|
63
|
+
failed.append('tests')
|
|
64
|
+
else:
|
|
65
|
+
print(' \033[0;33m○\033[0m tests (not configured to run)')
|
|
66
|
+
|
|
67
|
+
elif gate == 'requestLogEntry':
|
|
68
|
+
# Check if request-log has an entry for this task
|
|
69
|
+
try:
|
|
70
|
+
with open(request_log) as f:
|
|
71
|
+
content = f.read()
|
|
72
|
+
if task_id in content or content.strip().endswith('---'):
|
|
73
|
+
print(' \033[0;33m○\033[0m requestLogEntry (verify manually)')
|
|
74
|
+
else:
|
|
75
|
+
print(' \033[0;33m○\033[0m requestLogEntry (verify manually)')
|
|
76
|
+
except:
|
|
77
|
+
print(' \033[0;33m○\033[0m requestLogEntry (verify manually)')
|
|
78
|
+
|
|
79
|
+
elif gate == 'appMapUpdate':
|
|
80
|
+
print(' \033[0;33m○\033[0m appMapUpdate (verify manually if components created)')
|
|
81
|
+
|
|
82
|
+
else:
|
|
83
|
+
print(f' \033[0;33m○\033[0m {gate} (manual check)')
|
|
84
|
+
|
|
85
|
+
if failed:
|
|
86
|
+
print(f'\n\033[0;31mFailed gates: {", ".join(failed)}\033[0m')
|
|
87
|
+
sys.exit(1)
|
|
88
|
+
GATES
|
|
89
|
+
|
|
90
|
+
if [ $? -ne 0 ]; then
|
|
91
|
+
echo -e "${RED}Quality gates failed. Fix issues before completing.${NC}"
|
|
92
|
+
exit 1
|
|
93
|
+
fi
|
|
94
|
+
echo ""
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
READY_FILE="$READY_JSON" TASK_ID_ARG="$TASK_ID" python3 << 'EOF'
|
|
98
|
+
import json, os
|
|
99
|
+
|
|
100
|
+
task_id = os.environ['TASK_ID_ARG']
|
|
101
|
+
ready_file = os.environ['READY_FILE']
|
|
102
|
+
|
|
103
|
+
with open(ready_file) as f:
|
|
104
|
+
data = json.load(f)
|
|
105
|
+
|
|
106
|
+
# Find task in inProgress
|
|
107
|
+
in_progress = data.get('inProgress', [])
|
|
108
|
+
found = None
|
|
109
|
+
found_idx = None
|
|
110
|
+
|
|
111
|
+
for i, task in enumerate(in_progress):
|
|
112
|
+
if isinstance(task, dict):
|
|
113
|
+
if task.get('id') == task_id:
|
|
114
|
+
found = task
|
|
115
|
+
found_idx = i
|
|
116
|
+
break
|
|
117
|
+
elif task == task_id:
|
|
118
|
+
found = {'id': task_id}
|
|
119
|
+
found_idx = i
|
|
120
|
+
break
|
|
121
|
+
|
|
122
|
+
if found is None:
|
|
123
|
+
print(f'\033[0;31mTask {task_id} not found in progress\033[0m')
|
|
124
|
+
exit(1)
|
|
125
|
+
|
|
126
|
+
# Move to completed
|
|
127
|
+
in_progress.pop(found_idx)
|
|
128
|
+
data['inProgress'] = in_progress
|
|
129
|
+
|
|
130
|
+
completed = data.get('recentlyCompleted', [])
|
|
131
|
+
completed.insert(0, found)
|
|
132
|
+
data['recentlyCompleted'] = completed[:10] # Keep last 10
|
|
133
|
+
|
|
134
|
+
# Update timestamp
|
|
135
|
+
from datetime import datetime
|
|
136
|
+
data['lastUpdated'] = datetime.now().isoformat()
|
|
137
|
+
|
|
138
|
+
with open(ready_file, 'w') as f:
|
|
139
|
+
json.dump(data, f, indent=2)
|
|
140
|
+
|
|
141
|
+
print(f'\033[0;32m✓ Completed: {task_id}\033[0m')
|
|
142
|
+
EOF
|
|
143
|
+
|
|
144
|
+
# Commit if there are changes
|
|
145
|
+
if [ -n "$(git status --porcelain 2>/dev/null)" ]; then
|
|
146
|
+
echo ""
|
|
147
|
+
echo -e "${YELLOW}Committing changes...${NC}"
|
|
148
|
+
git add -A
|
|
149
|
+
git commit -m "feat: $COMMIT_MSG"
|
|
150
|
+
echo -e "${GREEN}✓ Changes committed${NC}"
|
|
151
|
+
fi
|
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Wogi Flow - Complete Task
|
|
5
|
+
*
|
|
6
|
+
* Runs quality gates and moves task from inProgress to completed.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const { execSync, execFileSync, spawnSync } = require('child_process');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const {
|
|
12
|
+
PATHS,
|
|
13
|
+
fileExists,
|
|
14
|
+
getConfig,
|
|
15
|
+
moveTaskAsync,
|
|
16
|
+
findTask,
|
|
17
|
+
readFile,
|
|
18
|
+
writeJson,
|
|
19
|
+
color,
|
|
20
|
+
success,
|
|
21
|
+
warn,
|
|
22
|
+
error
|
|
23
|
+
} = require('./flow-utils');
|
|
24
|
+
|
|
25
|
+
// v1.7.0 context memory management
|
|
26
|
+
const { warnIfContextHigh } = require('./flow-context-monitor');
|
|
27
|
+
const { clearCurrentTask, addKeyFact } = require('./flow-memory-blocks');
|
|
28
|
+
const { trackTaskComplete } = require('./flow-session-state');
|
|
29
|
+
const { autoArchiveIfNeeded } = require('./flow-log-manager');
|
|
30
|
+
|
|
31
|
+
// v1.9.0 regression testing and browser test suggestions (legacy - now in workflow steps)
|
|
32
|
+
const { runRegressionTests } = require('./flow-regression');
|
|
33
|
+
const { suggestBrowserTests } = require('./flow-browser-suggest');
|
|
34
|
+
|
|
35
|
+
// v2.2 modular workflow steps
|
|
36
|
+
const { runSteps, getAllSteps } = require('./flow-workflow-steps');
|
|
37
|
+
|
|
38
|
+
// v2.0 durable session support
|
|
39
|
+
const { loadDurableSession, archiveDurableSession } = require('./flow-durable-session');
|
|
40
|
+
|
|
41
|
+
// v2.1 loop enforcement as explicit quality gate
|
|
42
|
+
const { canExitLoop, getActiveLoop } = require('./flow-loop-enforcer');
|
|
43
|
+
|
|
44
|
+
// Path for last failure artifact
|
|
45
|
+
const LAST_FAILURE_PATH = path.join(PATHS.state, 'last-failure.json');
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Get files modified in current task (from git)
|
|
49
|
+
*/
|
|
50
|
+
function getModifiedFiles() {
|
|
51
|
+
try {
|
|
52
|
+
// Get staged and unstaged changes
|
|
53
|
+
const staged = execSync('git diff --cached --name-only', {
|
|
54
|
+
encoding: 'utf-8',
|
|
55
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
56
|
+
}).trim().split('\n').filter(Boolean);
|
|
57
|
+
|
|
58
|
+
const unstaged = execSync('git diff --name-only', {
|
|
59
|
+
encoding: 'utf-8',
|
|
60
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
61
|
+
}).trim().split('\n').filter(Boolean);
|
|
62
|
+
|
|
63
|
+
const untracked = execSync('git ls-files --others --exclude-standard', {
|
|
64
|
+
encoding: 'utf-8',
|
|
65
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
66
|
+
}).trim().split('\n').filter(Boolean);
|
|
67
|
+
|
|
68
|
+
// Combine and dedupe
|
|
69
|
+
const all = [...new Set([...staged, ...unstaged, ...untracked])];
|
|
70
|
+
return all.filter(f => f && f.length > 0);
|
|
71
|
+
} catch (err) {
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Truncate error output to reasonable length
|
|
78
|
+
*/
|
|
79
|
+
function truncateOutput(text, maxLines = 30, maxChars = 2000) {
|
|
80
|
+
if (!text) return '';
|
|
81
|
+
const lines = text.split('\n').slice(0, maxLines);
|
|
82
|
+
let result = lines.join('\n');
|
|
83
|
+
if (result.length > maxChars) {
|
|
84
|
+
result = result.substring(0, maxChars) + '\n... (truncated)';
|
|
85
|
+
}
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Run quality gates from config
|
|
91
|
+
*/
|
|
92
|
+
function runQualityGates(taskId) {
|
|
93
|
+
if (!fileExists(PATHS.config)) {
|
|
94
|
+
return { passed: true, failed: [], errors: {} };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
console.log(color('yellow', 'Running quality gates...'));
|
|
98
|
+
console.log('');
|
|
99
|
+
|
|
100
|
+
const config = getConfig();
|
|
101
|
+
const gates = config.qualityGates?.feature?.require || [];
|
|
102
|
+
const testing = config.testing || {};
|
|
103
|
+
const failed = [];
|
|
104
|
+
const errors = {}; // Store error output for correction artifact
|
|
105
|
+
|
|
106
|
+
for (const gate of gates) {
|
|
107
|
+
if (gate === 'tests') {
|
|
108
|
+
if (testing.runAfterTask || testing.runBeforeCommit) {
|
|
109
|
+
console.log(' Running tests...');
|
|
110
|
+
const result = spawnSync('npm', ['test'], {
|
|
111
|
+
encoding: 'utf-8',
|
|
112
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
113
|
+
});
|
|
114
|
+
if (result.status === 0) {
|
|
115
|
+
console.log(` ${color('green', '✓')} tests passed`);
|
|
116
|
+
} else {
|
|
117
|
+
console.log(` ${color('red', '✗')} tests failed`);
|
|
118
|
+
// Capture error output
|
|
119
|
+
const errorOutput = result.stderr || result.stdout || '';
|
|
120
|
+
if (errorOutput) {
|
|
121
|
+
console.log(color('dim', ' Error output:'));
|
|
122
|
+
const truncated = truncateOutput(errorOutput, 20, 1000);
|
|
123
|
+
truncated.split('\n').forEach(line => {
|
|
124
|
+
console.log(color('dim', ` ${line}`));
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
errors.tests = errorOutput;
|
|
128
|
+
failed.push('tests');
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
console.log(` ${color('yellow', '○')} tests (not configured to run)`);
|
|
132
|
+
}
|
|
133
|
+
} else if (gate === 'lint') {
|
|
134
|
+
console.log(' Running lint...');
|
|
135
|
+
let result = spawnSync('npm', ['run', 'lint'], {
|
|
136
|
+
encoding: 'utf-8',
|
|
137
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
if (result.status !== 0) {
|
|
141
|
+
// Try auto-fix
|
|
142
|
+
console.log(` ${color('yellow', '⟳')} lint issues found, attempting auto-fix...`);
|
|
143
|
+
const fixResult = spawnSync('npm', ['run', 'lint', '--', '--fix'], {
|
|
144
|
+
encoding: 'utf-8',
|
|
145
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Re-run lint to check if issues are fixed
|
|
149
|
+
result = spawnSync('npm', ['run', 'lint'], {
|
|
150
|
+
encoding: 'utf-8',
|
|
151
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
if (result.status === 0) {
|
|
155
|
+
console.log(` ${color('green', '✓')} lint passed (auto-fixed)`);
|
|
156
|
+
} else {
|
|
157
|
+
console.log(` ${color('red', '✗')} lint failed (manual fix required)`);
|
|
158
|
+
const errorOutput = result.stderr || result.stdout || '';
|
|
159
|
+
if (errorOutput) {
|
|
160
|
+
console.log(color('dim', ' Remaining issues:'));
|
|
161
|
+
const truncated = truncateOutput(errorOutput, 15, 800);
|
|
162
|
+
truncated.split('\n').forEach(line => {
|
|
163
|
+
console.log(color('dim', ` ${line}`));
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
errors.lint = errorOutput;
|
|
167
|
+
failed.push('lint');
|
|
168
|
+
}
|
|
169
|
+
} else {
|
|
170
|
+
console.log(` ${color('green', '✓')} lint passed`);
|
|
171
|
+
}
|
|
172
|
+
} else if (gate === 'typecheck') {
|
|
173
|
+
console.log(' Running typecheck...');
|
|
174
|
+
const result = spawnSync('npm', ['run', 'typecheck'], {
|
|
175
|
+
encoding: 'utf-8',
|
|
176
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
177
|
+
});
|
|
178
|
+
if (result.status === 0) {
|
|
179
|
+
console.log(` ${color('green', '✓')} typecheck passed`);
|
|
180
|
+
} else {
|
|
181
|
+
console.log(` ${color('red', '✗')} typecheck failed`);
|
|
182
|
+
const errorOutput = result.stderr || result.stdout || '';
|
|
183
|
+
if (errorOutput) {
|
|
184
|
+
console.log(color('dim', ' Type errors:'));
|
|
185
|
+
const truncated = truncateOutput(errorOutput, 20, 1000);
|
|
186
|
+
truncated.split('\n').forEach(line => {
|
|
187
|
+
console.log(color('dim', ` ${line}`));
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
errors.typecheck = errorOutput;
|
|
191
|
+
failed.push('typecheck');
|
|
192
|
+
}
|
|
193
|
+
} else if (gate === 'requestLogEntry') {
|
|
194
|
+
// Check if request-log has an entry for this task
|
|
195
|
+
try {
|
|
196
|
+
const content = readFile(PATHS.requestLog, '');
|
|
197
|
+
if (content.includes(taskId)) {
|
|
198
|
+
console.log(` ${color('green', '✓')} requestLogEntry (found in request-log)`);
|
|
199
|
+
} else {
|
|
200
|
+
console.log(` ${color('yellow', '○')} requestLogEntry (add entry to request-log.md)`);
|
|
201
|
+
}
|
|
202
|
+
} catch (err) {
|
|
203
|
+
if (process.env.DEBUG) console.error(`[DEBUG] requestLogEntry check: ${err.message}`);
|
|
204
|
+
console.log(` ${color('yellow', '○')} requestLogEntry (could not check)`);
|
|
205
|
+
}
|
|
206
|
+
} else if (gate === 'appMapUpdate') {
|
|
207
|
+
console.log(` ${color('yellow', '○')} appMapUpdate (verify manually if components created)`);
|
|
208
|
+
} else if (gate === 'loopComplete') {
|
|
209
|
+
// v2.1: Explicit loop completion check
|
|
210
|
+
const activeLoop = getActiveLoop();
|
|
211
|
+
if (!activeLoop) {
|
|
212
|
+
// No active loop - either completed or not used
|
|
213
|
+
console.log(` ${color('green', '✓')} loopComplete (no active loop session)`);
|
|
214
|
+
} else {
|
|
215
|
+
const exitResult = canExitLoop();
|
|
216
|
+
if (exitResult.canExit) {
|
|
217
|
+
console.log(` ${color('green', '✓')} loopComplete (${exitResult.reason})`);
|
|
218
|
+
} else {
|
|
219
|
+
console.log(` ${color('red', '✗')} loopComplete (${exitResult.pending || 0} pending, ${exitResult.failed || 0} failed)`);
|
|
220
|
+
errors.loopComplete = exitResult.message || 'Loop not complete';
|
|
221
|
+
failed.push('loopComplete');
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
} else if (gate === 'noNewFeatures') {
|
|
225
|
+
// Refactor-specific gate - manual check
|
|
226
|
+
console.log(` ${color('yellow', '○')} noNewFeatures (verify no behavior changes)`);
|
|
227
|
+
} else {
|
|
228
|
+
console.log(` ${color('yellow', '○')} ${gate} (manual check)`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (failed.length > 0) {
|
|
233
|
+
console.log('');
|
|
234
|
+
console.log(color('red', `Failed gates: ${failed.join(', ')}`));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return { passed: failed.length === 0, failed, errors };
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Commit changes if any
|
|
242
|
+
*/
|
|
243
|
+
function commitChanges(commitMsg) {
|
|
244
|
+
try {
|
|
245
|
+
const status = execSync('git status --porcelain', {
|
|
246
|
+
encoding: 'utf-8',
|
|
247
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
if (status.trim()) {
|
|
251
|
+
console.log('');
|
|
252
|
+
console.log(color('yellow', 'Committing changes...'));
|
|
253
|
+
// Use execFileSync to prevent command injection
|
|
254
|
+
execFileSync('git', ['add', '-A'], { stdio: 'pipe' });
|
|
255
|
+
execFileSync('git', ['commit', '-m', `feat: ${commitMsg}`], { stdio: 'pipe' });
|
|
256
|
+
success('Changes committed');
|
|
257
|
+
}
|
|
258
|
+
} catch (err) {
|
|
259
|
+
// Log git errors but don't fail the task completion
|
|
260
|
+
warn(`Git operation skipped: ${err.message || 'not a git repo or no changes'}`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
async function main() {
|
|
265
|
+
const taskId = process.argv[2];
|
|
266
|
+
const commitMsg = process.argv[3] || `Complete ${taskId}`;
|
|
267
|
+
|
|
268
|
+
if (!taskId) {
|
|
269
|
+
console.log('Usage: flow done <task-id> [commit-message]');
|
|
270
|
+
process.exit(1);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (!fileExists(PATHS.ready)) {
|
|
274
|
+
error('No ready.json found');
|
|
275
|
+
process.exit(1);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Run quality gates
|
|
279
|
+
const gateResult = runQualityGates(taskId);
|
|
280
|
+
|
|
281
|
+
if (!gateResult.passed) {
|
|
282
|
+
// Create correction artifact for AI self-repair
|
|
283
|
+
try {
|
|
284
|
+
writeJson(LAST_FAILURE_PATH, {
|
|
285
|
+
taskId,
|
|
286
|
+
timestamp: new Date().toISOString(),
|
|
287
|
+
failedGates: gateResult.failed,
|
|
288
|
+
errors: gateResult.errors
|
|
289
|
+
});
|
|
290
|
+
console.log('');
|
|
291
|
+
console.log(color('dim', `Failure details saved to: ${LAST_FAILURE_PATH}`));
|
|
292
|
+
} catch (err) {
|
|
293
|
+
if (process.env.DEBUG) console.error(`[DEBUG] Failed to save failure artifact: ${err.message}`);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
console.log('');
|
|
297
|
+
error('Quality gates failed. Fix issues before completing.');
|
|
298
|
+
console.log(color('dim', 'Tip: Review the error output above or check .workflow/state/last-failure.json'));
|
|
299
|
+
process.exit(1);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
console.log('');
|
|
303
|
+
|
|
304
|
+
// Check if task exists
|
|
305
|
+
const found = findTask(taskId);
|
|
306
|
+
|
|
307
|
+
if (!found) {
|
|
308
|
+
console.log(color('red', `Task ${taskId} not found in any queue`));
|
|
309
|
+
process.exit(1);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (found.list !== 'inProgress') {
|
|
313
|
+
console.log(color('red', `Task ${taskId} is in ${found.list}, not inProgress`));
|
|
314
|
+
process.exit(1);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Move task from inProgress to recentlyCompleted (with file locking)
|
|
318
|
+
const result = await moveTaskAsync(taskId, 'inProgress', 'recentlyCompleted');
|
|
319
|
+
|
|
320
|
+
if (!result.success) {
|
|
321
|
+
error(result.error);
|
|
322
|
+
process.exit(1);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
console.log(color('green', `✓ Completed: ${taskId}`));
|
|
326
|
+
|
|
327
|
+
// v2.0: Archive durable session if one exists for this task
|
|
328
|
+
try {
|
|
329
|
+
const durableSession = loadDurableSession();
|
|
330
|
+
if (durableSession && durableSession.taskId === taskId) {
|
|
331
|
+
const archived = archiveDurableSession('completed');
|
|
332
|
+
if (archived && process.env.DEBUG) {
|
|
333
|
+
console.log(color('dim', `Archived durable session: ${archived.metrics.stepsCompleted} steps completed`));
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
} catch (err) {
|
|
337
|
+
if (process.env.DEBUG) console.error(`[DEBUG] Durable session archive: ${err.message}`);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// v1.7.0: Track task completion in session state and memory blocks
|
|
341
|
+
try {
|
|
342
|
+
trackTaskComplete(taskId);
|
|
343
|
+
clearCurrentTask();
|
|
344
|
+
|
|
345
|
+
// Add completion as a key fact
|
|
346
|
+
const taskTitle = result.task?.title || taskId;
|
|
347
|
+
addKeyFact(`Completed: ${taskTitle}`);
|
|
348
|
+
} catch (err) {
|
|
349
|
+
if (process.env.DEBUG) console.error(`[DEBUG] Task tracking: ${err.message}`);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// v1.7.0: Auto-archive request log if threshold exceeded
|
|
353
|
+
try {
|
|
354
|
+
const archiveResult = autoArchiveIfNeeded();
|
|
355
|
+
if (archiveResult && archiveResult.archived > 0) {
|
|
356
|
+
success(`Archived ${archiveResult.archived} request log entries`);
|
|
357
|
+
}
|
|
358
|
+
} catch (err) {
|
|
359
|
+
if (process.env.DEBUG) console.error(`[DEBUG] Auto-archive: ${err.message}`);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// v2.2: Run afterTask workflow steps
|
|
363
|
+
const modifiedFiles = getModifiedFiles();
|
|
364
|
+
const taskTitle = result.task?.title || taskId;
|
|
365
|
+
const taskType = result.task?.type || 'feature';
|
|
366
|
+
|
|
367
|
+
try {
|
|
368
|
+
const allSteps = getAllSteps();
|
|
369
|
+
const hasAfterTaskSteps = Object.values(allSteps).some(s => s.enabled && s.when === 'afterTask');
|
|
370
|
+
|
|
371
|
+
if (hasAfterTaskSteps) {
|
|
372
|
+
console.log('');
|
|
373
|
+
console.log(color('cyan', 'Running afterTask workflow steps...'));
|
|
374
|
+
const afterTaskResult = await runSteps('afterTask', {
|
|
375
|
+
taskId,
|
|
376
|
+
taskTitle,
|
|
377
|
+
taskType,
|
|
378
|
+
files: modifiedFiles,
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
if (afterTaskResult.blocked) {
|
|
382
|
+
error('Workflow step blocked task completion');
|
|
383
|
+
process.exit(1);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
} catch (err) {
|
|
387
|
+
if (process.env.DEBUG) console.error(`[DEBUG] afterTask steps: ${err.message}`);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Auto-capture learnings from bug fixes
|
|
391
|
+
if (taskType === 'bugfix' || taskType === 'fix') {
|
|
392
|
+
try {
|
|
393
|
+
const { captureFromBugFix } = require('./flow-auto-learn');
|
|
394
|
+
captureFromBugFix(taskId, modifiedFiles, taskTitle);
|
|
395
|
+
} catch (err) {
|
|
396
|
+
if (process.env.DEBUG) console.error(`[DEBUG] auto-learn: ${err.message}`);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// v2.2: Run beforeCommit workflow steps
|
|
401
|
+
try {
|
|
402
|
+
const allSteps = getAllSteps();
|
|
403
|
+
const hasBeforeCommitSteps = Object.values(allSteps).some(s => s.enabled && s.when === 'beforeCommit');
|
|
404
|
+
|
|
405
|
+
if (hasBeforeCommitSteps) {
|
|
406
|
+
console.log('');
|
|
407
|
+
console.log(color('cyan', 'Running beforeCommit workflow steps...'));
|
|
408
|
+
const beforeCommitResult = await runSteps('beforeCommit', {
|
|
409
|
+
taskId,
|
|
410
|
+
taskTitle,
|
|
411
|
+
taskType,
|
|
412
|
+
files: modifiedFiles,
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
if (beforeCommitResult.blocked) {
|
|
416
|
+
error('Workflow step blocked commit');
|
|
417
|
+
process.exit(1);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
} catch (err) {
|
|
421
|
+
if (process.env.DEBUG) console.error(`[DEBUG] beforeCommit steps: ${err.message}`);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Commit if there are changes
|
|
425
|
+
commitChanges(commitMsg);
|
|
426
|
+
|
|
427
|
+
// v1.9.0: Run regression tests if configured (legacy - skipped if using workflowSteps)
|
|
428
|
+
const config = getConfig();
|
|
429
|
+
const usingWorkflowSteps = config.workflowSteps?.regressionTest?.enabled;
|
|
430
|
+
if (!usingWorkflowSteps && config.regressionTesting?.enabled && config.regressionTesting?.runOnTaskComplete) {
|
|
431
|
+
console.log('');
|
|
432
|
+
try {
|
|
433
|
+
const regressionResult = await runRegressionTests({ force: true });
|
|
434
|
+
if (!regressionResult.success && config.regressionTesting?.onFailure === 'block') {
|
|
435
|
+
warn('Regression tests failed - review before continuing');
|
|
436
|
+
process.exit(1);
|
|
437
|
+
} else if (!regressionResult.success) {
|
|
438
|
+
warn('Regression tests failed - consider reviewing');
|
|
439
|
+
}
|
|
440
|
+
} catch (err) {
|
|
441
|
+
if (process.env.DEBUG) console.error(`[DEBUG] Regression tests: ${err.message}`);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// v1.9.0: Suggest browser tests for UI tasks (legacy - skipped if using workflowSteps)
|
|
446
|
+
const usingBrowserWorkflowStep = config.workflowSteps?.browserTest?.enabled;
|
|
447
|
+
if (!usingBrowserWorkflowStep && config.browserTesting?.enabled && config.browserTesting?.runOnTaskComplete) {
|
|
448
|
+
try {
|
|
449
|
+
const browserSuggestion = suggestBrowserTests(taskId, result.task);
|
|
450
|
+
if (browserSuggestion.suggested && browserSuggestion.flows.length > 0) {
|
|
451
|
+
console.log('');
|
|
452
|
+
console.log(color('cyan', '🌐 Browser tests available:'));
|
|
453
|
+
browserSuggestion.flows.forEach(flow => {
|
|
454
|
+
console.log(color('dim', ` - ${flow}`));
|
|
455
|
+
});
|
|
456
|
+
console.log(color('dim', ` Run: /wogi-test-browser ${browserSuggestion.flows[0]}`));
|
|
457
|
+
}
|
|
458
|
+
} catch (err) {
|
|
459
|
+
if (process.env.DEBUG) console.error(`[DEBUG] Browser test suggestion: ${err.message}`);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// v2.0: Refresh component index after task if configured
|
|
464
|
+
const scanOn = config.componentIndex?.scanOn || [];
|
|
465
|
+
if (config.componentIndex?.autoScan !== false && scanOn.includes('afterTask')) {
|
|
466
|
+
try {
|
|
467
|
+
console.log(color('dim', '🔄 Refreshing component index...'));
|
|
468
|
+
execFileSync('bash', ['scripts/flow-map-index', 'scan', '--quiet'], {
|
|
469
|
+
encoding: 'utf-8',
|
|
470
|
+
stdio: 'pipe'
|
|
471
|
+
});
|
|
472
|
+
if (process.env.DEBUG) {
|
|
473
|
+
console.log(color('dim', ' Component index updated'));
|
|
474
|
+
}
|
|
475
|
+
} catch (err) {
|
|
476
|
+
if (process.env.DEBUG) console.error(`[DEBUG] Component index refresh: ${err.message}`);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// v1.7.0: Check context health after task
|
|
481
|
+
if (config.contextMonitor?.checkAfterTask !== false) {
|
|
482
|
+
warnIfContextHigh();
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
main().catch(err => {
|
|
487
|
+
console.error(`Error: ${err.message}`);
|
|
488
|
+
process.exit(1);
|
|
489
|
+
});
|