wogiflow 2.4.2 → 2.4.3
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/docs/claude-code-compatibility.md +27 -0
- package/.claude/settings.json +1 -1
- package/.workflow/models/registry.json +1 -1
- package/package.json +4 -4
- package/scripts/MEMORY-ARCHITECTURE.md +1 -1
- package/scripts/base-workflow-step.js +136 -0
- package/scripts/flow-adaptive-learning.js +8 -9
- package/scripts/flow-aggregate.js +11 -6
- package/scripts/flow-api-index.js +4 -6
- package/scripts/flow-assumption-detector.js +0 -2
- package/scripts/flow-audit.js +15 -2
- package/scripts/flow-auto-context.js +8 -12
- package/scripts/flow-auto-learn.js +49 -49
- package/scripts/flow-background.js +5 -6
- package/scripts/flow-bridge-state.js +8 -10
- package/scripts/flow-bulk-loop.js +1 -3
- package/scripts/flow-bulk-orchestrator.js +1 -3
- package/scripts/flow-cascade-completion.js +0 -2
- package/scripts/flow-cascade.js +4 -4
- package/scripts/flow-checkpoint.js +10 -13
- package/scripts/flow-code-intelligence.js +10 -12
- package/scripts/flow-community-sync.js +4 -4
- package/scripts/flow-community.js +12 -20
- package/scripts/flow-config-defaults.js +0 -2
- package/scripts/flow-config-interactive.js +9 -5
- package/scripts/flow-config-loader.js +49 -92
- package/scripts/flow-config-substitution.js +0 -2
- package/scripts/flow-context-estimator.js +4 -4
- package/scripts/flow-context-init.js +10 -12
- package/scripts/flow-context-manager.js +0 -2
- package/scripts/flow-context-scoring.js +2 -2
- package/scripts/flow-contract-scan.js +6 -9
- package/scripts/flow-correct.js +29 -27
- package/scripts/flow-correction-detector.js +5 -1
- package/scripts/flow-damage-control.js +47 -54
- package/scripts/flow-decisions-merge.js +4 -14
- package/scripts/flow-diff.js +5 -8
- package/scripts/flow-done-gates.js +786 -0
- package/scripts/flow-done-report.js +123 -0
- package/scripts/flow-done.js +71 -717
- package/scripts/flow-entropy-monitor.js +1 -3
- package/scripts/flow-eval.js +5 -5
- package/scripts/flow-extraction-review.js +1 -0
- package/scripts/flow-failure-categories.js +0 -2
- package/scripts/flow-figma-confirm.js +5 -9
- package/scripts/flow-figma-generate.js +8 -10
- package/scripts/flow-figma-index.js +8 -10
- package/scripts/flow-figma-match.js +3 -5
- package/scripts/flow-figma-mcp-server.js +2 -4
- package/scripts/flow-figma-orchestrator.js +2 -3
- package/scripts/flow-figma-registry.js +2 -3
- package/scripts/flow-framework-resolver.js +0 -2
- package/scripts/flow-function-index.js +4 -6
- package/scripts/flow-gate-confidence.js +2 -2
- package/scripts/flow-gitignore.js +0 -2
- package/scripts/flow-guided-edit.js +5 -6
- package/scripts/flow-health.js +5 -6
- package/scripts/flow-hook-errors.js +6 -0
- package/scripts/flow-hook-status.js +263 -0
- package/scripts/flow-hooks.js +17 -29
- package/scripts/flow-http-client.js +9 -8
- package/scripts/flow-hybrid-interactive.js +7 -12
- package/scripts/flow-hybrid-test.js +12 -13
- package/scripts/flow-instruction-richness.js +1 -1
- package/scripts/flow-io.js +21 -4
- package/scripts/flow-knowledge-router.js +9 -3
- package/scripts/flow-learning-orchestrator.js +318 -13
- package/scripts/flow-links.js +5 -7
- package/scripts/flow-long-input-association.js +275 -0
- package/scripts/flow-long-input-chunking.js +1 -0
- package/scripts/flow-long-input-cli.js +0 -2
- package/scripts/flow-long-input-complexity.js +0 -2
- package/scripts/flow-long-input-constants.js +0 -2
- package/scripts/flow-long-input-contradictions.js +351 -0
- package/scripts/flow-long-input-detection.js +0 -2
- package/scripts/flow-long-input-passes.js +885 -0
- package/scripts/flow-long-input-stories.js +1 -1
- package/scripts/flow-long-input-voice.js +0 -2
- package/scripts/flow-long-input.js +425 -3005
- package/scripts/flow-loop-retry-learning.js +2 -3
- package/scripts/flow-lsp.js +3 -3
- package/scripts/flow-mcp-docs.js +3 -4
- package/scripts/flow-memory-db.js +6 -8
- package/scripts/flow-memory-sync.js +18 -11
- package/scripts/flow-metrics.js +1 -2
- package/scripts/flow-model-adapter.js +2 -3
- package/scripts/flow-model-config.js +72 -104
- package/scripts/flow-model-router.js +2 -2
- package/scripts/flow-model-types.js +0 -2
- package/scripts/flow-multi-approach.js +5 -6
- package/scripts/flow-orchestrate-context.js +3 -7
- package/scripts/flow-orchestrate-rollback.js +3 -8
- package/scripts/flow-orchestrate-state.js +8 -14
- package/scripts/flow-orchestrate-templates.js +2 -6
- package/scripts/flow-orchestrate-validator.js +5 -9
- package/scripts/flow-orchestrate.js +126 -103
- package/scripts/flow-output.js +0 -2
- package/scripts/flow-parallel.js +1 -1
- package/scripts/flow-paths.js +23 -2
- package/scripts/flow-pattern-enforcer.js +30 -28
- package/scripts/flow-pattern-extractor.js +3 -4
- package/scripts/flow-pending.js +0 -2
- package/scripts/flow-permissions.js +2 -3
- package/scripts/flow-plugin-registry.js +10 -12
- package/scripts/flow-prd-manager.js +1 -1
- package/scripts/flow-progress.js +7 -9
- package/scripts/flow-prompt-composer.js +3 -3
- package/scripts/flow-prompt-template.js +2 -2
- package/scripts/flow-providers.js +7 -4
- package/scripts/flow-registry-manager.js +7 -12
- package/scripts/flow-regression.js +9 -11
- package/scripts/flow-roadmap.js +2 -2
- package/scripts/flow-run-trace.js +16 -15
- package/scripts/flow-safety.js +2 -5
- package/scripts/flow-scanner-base.js +5 -7
- package/scripts/flow-scenario-engine.js +1 -5
- package/scripts/flow-security.js +29 -0
- package/scripts/flow-session-end.js +32 -41
- package/scripts/flow-session-learning.js +53 -49
- package/scripts/flow-setup-hooks.js +2 -3
- package/scripts/flow-skill-create.js +7 -12
- package/scripts/flow-skill-generator.js +12 -16
- package/scripts/flow-skill-learn.js +17 -8
- package/scripts/flow-skill-matcher.js +1 -2
- package/scripts/flow-spec-generator.js +2 -4
- package/scripts/flow-stack-wizard.js +5 -7
- package/scripts/flow-standards-learner.js +35 -16
- package/scripts/flow-start.js +2 -0
- package/scripts/flow-stats-collector.js +2 -2
- package/scripts/flow-status.js +10 -10
- package/scripts/flow-statusline-setup.js +2 -2
- package/scripts/flow-step-changelog.js +2 -3
- package/scripts/flow-step-comments.js +66 -81
- package/scripts/flow-step-complexity.js +50 -70
- package/scripts/flow-step-coverage.js +3 -5
- package/scripts/flow-step-knowledge.js +2 -3
- package/scripts/flow-step-pr-tests.js +64 -74
- package/scripts/flow-step-regression.js +3 -5
- package/scripts/flow-step-review.js +86 -103
- package/scripts/flow-step-security.js +111 -121
- package/scripts/flow-step-silent-failures.js +56 -83
- package/scripts/flow-step-simplifier.js +52 -70
- package/scripts/flow-story.js +4 -7
- package/scripts/flow-strict-adherence.js +3 -4
- package/scripts/flow-task-checkpoint.js +36 -5
- package/scripts/flow-task-enforcer.js +2 -24
- package/scripts/flow-tech-debt.js +1 -1
- package/scripts/flow-template-extractor.js +1 -0
- package/scripts/flow-templates.js +11 -13
- package/scripts/flow-test-api.js +9 -13
- package/scripts/flow-test-discovery.js +1 -1
- package/scripts/flow-test-generate.js +5 -9
- package/scripts/flow-test-integrity.js +3 -7
- package/scripts/flow-test-ui.js +5 -9
- package/scripts/flow-testing-deps.js +1 -3
- package/scripts/flow-tiered-learning.js +4 -4
- package/scripts/flow-todowrite-sync.js +1 -1
- package/scripts/flow-tokens.js +0 -2
- package/scripts/flow-verification-profile.js +6 -10
- package/scripts/flow-verify.js +12 -16
- package/scripts/flow-version-check.js +4 -12
- package/scripts/flow-webmcp-generator.js +3 -5
- package/scripts/flow-workflow-steps.js +0 -2
- package/scripts/flow-workflow.js +9 -11
- package/scripts/hooks/adapters/claude-code.js +2 -0
- package/scripts/hooks/core/config-change.js +1 -0
- package/scripts/hooks/core/extension-registry.js +0 -2
- package/scripts/hooks/core/instructions-loaded.js +1 -1
- package/scripts/hooks/core/observation-capture.js +5 -5
- package/scripts/hooks/core/phase-gate.js +5 -0
- package/scripts/hooks/core/post-compact.js +1 -12
- package/scripts/hooks/core/research-gate.js +2 -12
- package/scripts/hooks/core/routing-gate.js +6 -0
- package/scripts/hooks/core/task-completed.js +12 -0
- package/scripts/hooks/core/worktree-lifecycle.js +1 -1
- package/scripts/hooks/entry/claude-code/config-change.js +6 -29
- package/scripts/hooks/entry/claude-code/instructions-loaded.js +5 -30
- package/scripts/hooks/entry/claude-code/post-compact.js +4 -31
- package/scripts/hooks/entry/claude-code/post-tool-use.js +121 -172
- package/scripts/hooks/entry/claude-code/pre-tool-use.js +260 -361
- package/scripts/hooks/entry/claude-code/session-end.js +4 -28
- package/scripts/hooks/entry/claude-code/session-start.js +205 -243
- package/scripts/hooks/entry/claude-code/setup.js +8 -49
- package/scripts/hooks/entry/claude-code/stop.js +40 -72
- package/scripts/hooks/entry/claude-code/task-completed.js +4 -28
- package/scripts/hooks/entry/claude-code/user-prompt-submit.js +113 -195
- package/scripts/hooks/entry/claude-code/worktree-create.js +6 -25
- package/scripts/hooks/entry/claude-code/worktree-remove.js +6 -25
- package/scripts/hooks/entry/shared/hook-runner.js +99 -0
- package/scripts/hooks/entry/shared/read-stdin.js +0 -2
- package/scripts/registries/api-registry.js +0 -2
- package/scripts/registries/component-registry.js +5 -9
- package/scripts/registries/contract-scanner.js +2 -9
- package/scripts/registries/function-registry.js +0 -2
- package/scripts/registries/schema-registry.js +14 -18
- package/scripts/registries/service-registry.js +23 -27
|
@@ -8,150 +8,140 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
const { execSync } = require('node:child_process');
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const { getProjectRoot } = require('./flow-utils');
|
|
11
|
+
const { BaseWorkflowStep } = require('./base-workflow-step');
|
|
12
|
+
const { PATHS } = require('./flow-utils');
|
|
14
13
|
const { CREDENTIAL_SCAN_PATTERNS } = require('./flow-security');
|
|
15
14
|
|
|
16
|
-
|
|
15
|
+
class SecurityStep extends BaseWorkflowStep {
|
|
16
|
+
constructor() {
|
|
17
|
+
// Security step checks ALL file types (not just code), so use broad extensions
|
|
18
|
+
super('securityScan', {
|
|
19
|
+
extensions: ['.js', '.ts', '.jsx', '.tsx', '.py', '.go', '.rs', '.json', '.yml', '.yaml', '.env', '.sh'],
|
|
20
|
+
excludeTests: true,
|
|
21
|
+
excludeDts: true,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
17
24
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
async function run(options = {}) {
|
|
28
|
-
const { files = [], stepConfig = {}, mode } = options;
|
|
29
|
-
const severity = stepConfig.severity || 'high';
|
|
30
|
-
const issues = [];
|
|
31
|
-
|
|
32
|
-
// 1. Check for secrets in modified files (using centralized patterns)
|
|
33
|
-
for (const file of files) {
|
|
34
|
-
const filePath = path.join(PROJECT_ROOT, file);
|
|
35
|
-
if (!fs.existsSync(filePath)) continue;
|
|
36
|
-
|
|
37
|
-
// Skip test files and config examples
|
|
38
|
-
if (file.includes('.test.') || file.includes('.spec.') || file.includes('.example')) {
|
|
39
|
-
continue;
|
|
40
|
-
}
|
|
25
|
+
// Override filterFiles for security-specific exclusions
|
|
26
|
+
filterFiles(files) {
|
|
27
|
+
return files.filter(f => !f.includes('.example'));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async execute(files, options) {
|
|
31
|
+
const { stepConfig = {} } = options;
|
|
32
|
+
const severity = stepConfig.severity || 'high';
|
|
33
|
+
const issues = [];
|
|
41
34
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
35
|
+
// 1. Check for secrets in modified files (using centralized patterns)
|
|
36
|
+
for (const file of files) {
|
|
37
|
+
const content = this.readFile(file);
|
|
38
|
+
if (!content) continue;
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
for (const { pattern, name } of CREDENTIAL_SCAN_PATTERNS) {
|
|
42
|
+
pattern.lastIndex = 0;
|
|
43
|
+
if (pattern.test(content)) {
|
|
44
|
+
issues.push({
|
|
45
|
+
type: 'secret',
|
|
46
|
+
severity: 'high',
|
|
47
|
+
file,
|
|
48
|
+
message: name || 'Potential secret or credential detected',
|
|
49
|
+
});
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
55
52
|
}
|
|
53
|
+
} catch (_err) {
|
|
54
|
+
// Skip unreadable files
|
|
56
55
|
}
|
|
57
|
-
} catch (err) {
|
|
58
|
-
// Skip unreadable files
|
|
59
56
|
}
|
|
60
|
-
}
|
|
61
57
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
issues.push({
|
|
80
|
-
type: 'npm_audit',
|
|
81
|
-
severity: 'critical',
|
|
82
|
-
message: `${vulns.critical} critical vulnerabilities found`,
|
|
83
|
-
count: vulns.critical,
|
|
84
|
-
});
|
|
85
|
-
} else if (severity === 'high' && (vulns.critical > 0 || vulns.high > 0)) {
|
|
86
|
-
const count = vulns.critical + vulns.high;
|
|
87
|
-
issues.push({
|
|
88
|
-
type: 'npm_audit',
|
|
89
|
-
severity: 'high',
|
|
90
|
-
message: `${count} high/critical vulnerabilities found`,
|
|
91
|
-
count,
|
|
92
|
-
});
|
|
93
|
-
} else if (severity === 'moderate') {
|
|
94
|
-
const count = vulns.critical + vulns.high + vulns.moderate;
|
|
95
|
-
if (count > 0) {
|
|
58
|
+
// 2. Run npm audit if package.json was modified
|
|
59
|
+
const packageModified = files.some(f => f.endsWith('package.json') || f.endsWith('package-lock.json'));
|
|
60
|
+
|
|
61
|
+
if (packageModified || stepConfig.alwaysAudit) {
|
|
62
|
+
try {
|
|
63
|
+
const auditResult = execSync('npm audit --json 2>/dev/null', {
|
|
64
|
+
cwd: PATHS.root,
|
|
65
|
+
encoding: 'utf8',
|
|
66
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const audit = JSON.parse(auditResult);
|
|
70
|
+
|
|
71
|
+
if (audit.metadata && audit.metadata.vulnerabilities) {
|
|
72
|
+
const vulns = audit.metadata.vulnerabilities;
|
|
73
|
+
|
|
74
|
+
if (severity === 'critical' && vulns.critical > 0) {
|
|
96
75
|
issues.push({
|
|
97
76
|
type: 'npm_audit',
|
|
98
|
-
severity: '
|
|
99
|
-
message: `${
|
|
100
|
-
count,
|
|
77
|
+
severity: 'critical',
|
|
78
|
+
message: `${vulns.critical} critical vulnerabilities found`,
|
|
79
|
+
count: vulns.critical,
|
|
101
80
|
});
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
} catch (err) {
|
|
106
|
-
// npm audit failed or returned non-zero
|
|
107
|
-
if (e.stdout) {
|
|
108
|
-
try {
|
|
109
|
-
const audit = JSON.parse(e.stdout);
|
|
110
|
-
if (audit.metadata && audit.metadata.vulnerabilities) {
|
|
111
|
-
const vulns = audit.metadata.vulnerabilities;
|
|
81
|
+
} else if (severity === 'high' && (vulns.critical > 0 || vulns.high > 0)) {
|
|
112
82
|
const count = vulns.critical + vulns.high;
|
|
113
|
-
|
|
83
|
+
issues.push({
|
|
84
|
+
type: 'npm_audit',
|
|
85
|
+
severity: 'high',
|
|
86
|
+
message: `${count} high/critical vulnerabilities found`,
|
|
87
|
+
count,
|
|
88
|
+
});
|
|
89
|
+
} else if (severity === 'moderate') {
|
|
90
|
+
const count = vulns.critical + vulns.high + vulns.moderate;
|
|
91
|
+
if (count > 0) {
|
|
114
92
|
issues.push({
|
|
115
93
|
type: 'npm_audit',
|
|
116
|
-
severity: '
|
|
117
|
-
message: `${count}
|
|
94
|
+
severity: 'moderate',
|
|
95
|
+
message: `${count} moderate+ vulnerabilities found`,
|
|
118
96
|
count,
|
|
119
97
|
});
|
|
120
98
|
}
|
|
121
99
|
}
|
|
122
|
-
}
|
|
123
|
-
|
|
100
|
+
}
|
|
101
|
+
} catch (err) {
|
|
102
|
+
// npm audit failed or returned non-zero
|
|
103
|
+
if (err.stdout) {
|
|
104
|
+
try {
|
|
105
|
+
const audit = JSON.parse(err.stdout);
|
|
106
|
+
if (audit.metadata && audit.metadata.vulnerabilities) {
|
|
107
|
+
const vulns = audit.metadata.vulnerabilities;
|
|
108
|
+
const count = vulns.critical + vulns.high;
|
|
109
|
+
if (count > 0 && (severity === 'high' || severity === 'critical')) {
|
|
110
|
+
issues.push({
|
|
111
|
+
type: 'npm_audit',
|
|
112
|
+
severity: 'high',
|
|
113
|
+
message: `${count} high/critical vulnerabilities`,
|
|
114
|
+
count,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
} catch (_parseError) {
|
|
119
|
+
// Ignore parse errors
|
|
120
|
+
}
|
|
124
121
|
}
|
|
125
122
|
}
|
|
126
123
|
}
|
|
127
|
-
}
|
|
128
124
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
125
|
+
// 3. Evaluate results
|
|
126
|
+
if (issues.length === 0) {
|
|
127
|
+
return this.pass('Security scan passed');
|
|
128
|
+
}
|
|
133
129
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
message: `${blockingIssues.length} security issue(s) found`,
|
|
145
|
-
details: blockingIssues,
|
|
146
|
-
};
|
|
147
|
-
}
|
|
130
|
+
// Filter by severity for blocking
|
|
131
|
+
const blockingIssues = issues.filter(i => {
|
|
132
|
+
if (severity === 'critical') return i.severity === 'critical';
|
|
133
|
+
if (severity === 'high') return i.severity === 'high' || i.severity === 'critical';
|
|
134
|
+
return true;
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
if (blockingIssues.length > 0) {
|
|
138
|
+
return this.fail(`${blockingIssues.length} security issue(s) found`, blockingIssues);
|
|
139
|
+
}
|
|
148
140
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
message: `${issues.length} low-severity issue(s) found`,
|
|
153
|
-
details: issues,
|
|
154
|
-
};
|
|
141
|
+
// Non-blocking issues
|
|
142
|
+
return this.pass(`${issues.length} low-severity issue(s) found`);
|
|
143
|
+
}
|
|
155
144
|
}
|
|
156
145
|
|
|
157
|
-
|
|
146
|
+
const step = new SecurityStep();
|
|
147
|
+
module.exports = { run: (opts) => step.run(opts) };
|
|
@@ -11,86 +11,68 @@
|
|
|
11
11
|
* - try-finally without catch
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
* @param {string[]} options.files - Files modified
|
|
25
|
-
* @param {object} options.stepConfig - Step configuration
|
|
26
|
-
* @param {string} options.mode - Step mode (block/warn/prompt/auto)
|
|
27
|
-
* @returns {object} - { passed: boolean, message: string, details?: object[] }
|
|
28
|
-
*/
|
|
29
|
-
async function run(options = {}) {
|
|
30
|
-
const { files = [], stepConfig = {} } = options;
|
|
31
|
-
const checkEmptyCatch = stepConfig.checkEmptyCatch !== false;
|
|
32
|
-
const checkLogOnlyCatch = stepConfig.checkLogOnlyCatch !== false;
|
|
33
|
-
const checkUnhandledAsync = stepConfig.checkUnhandledAsync !== false;
|
|
34
|
-
const checkPromiseChains = stepConfig.checkPromiseChains !== false;
|
|
35
|
-
|
|
36
|
-
// Filter to analyzable files
|
|
37
|
-
const analyzableExtensions = ['.js', '.ts', '.jsx', '.tsx'];
|
|
38
|
-
const analyzableFiles = files.filter(f =>
|
|
39
|
-
analyzableExtensions.some(ext => f.endsWith(ext)) &&
|
|
40
|
-
!f.includes('.test.') &&
|
|
41
|
-
!f.includes('.spec.') &&
|
|
42
|
-
!f.includes('.d.ts')
|
|
43
|
-
);
|
|
44
|
-
|
|
45
|
-
if (analyzableFiles.length === 0) {
|
|
46
|
-
return { passed: true, message: 'No files to analyze' };
|
|
14
|
+
const { BaseWorkflowStep } = require('./base-workflow-step');
|
|
15
|
+
const { colors } = require('./flow-utils');
|
|
16
|
+
|
|
17
|
+
class SilentFailureStep extends BaseWorkflowStep {
|
|
18
|
+
constructor() {
|
|
19
|
+
super('silentFailureHunter', {
|
|
20
|
+
extensions: ['.js', '.ts', '.jsx', '.tsx'],
|
|
21
|
+
excludeTests: true,
|
|
22
|
+
excludeDts: true,
|
|
23
|
+
});
|
|
47
24
|
}
|
|
48
25
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
26
|
+
async execute(files, options) {
|
|
27
|
+
const { stepConfig = {} } = options;
|
|
28
|
+
const checkEmptyCatch = stepConfig.checkEmptyCatch !== false;
|
|
29
|
+
const checkLogOnlyCatch = stepConfig.checkLogOnlyCatch !== false;
|
|
30
|
+
const checkUnhandledAsync = stepConfig.checkUnhandledAsync !== false;
|
|
31
|
+
const checkPromiseChains = stepConfig.checkPromiseChains !== false;
|
|
32
|
+
|
|
33
|
+
const issues = [];
|
|
34
|
+
|
|
35
|
+
for (const file of files) {
|
|
36
|
+
const content = this.readFile(file);
|
|
37
|
+
if (!content) continue;
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const fileIssues = analyzeForSilentFailures(content, file, {
|
|
41
|
+
checkEmptyCatch,
|
|
42
|
+
checkLogOnlyCatch,
|
|
43
|
+
checkUnhandledAsync,
|
|
44
|
+
checkPromiseChains,
|
|
45
|
+
});
|
|
46
|
+
issues.push(...fileIssues);
|
|
47
|
+
} catch (_err) {
|
|
48
|
+
// Skip unreadable files
|
|
49
|
+
}
|
|
66
50
|
}
|
|
67
|
-
}
|
|
68
51
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
message: 'No silent failure patterns detected',
|
|
73
|
-
};
|
|
74
|
-
}
|
|
52
|
+
if (issues.length === 0) {
|
|
53
|
+
return this.pass('No silent failure patterns detected');
|
|
54
|
+
}
|
|
75
55
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
56
|
+
// Report issues
|
|
57
|
+
console.log(colors.yellow + '\n Silent Failure Patterns Detected:' + colors.reset);
|
|
58
|
+
for (const issue of issues) {
|
|
59
|
+
const icon = issue.severity === 'high' ? '\u{1F534}' : '\u{1F7E1}';
|
|
60
|
+
console.log(` ${icon} ${issue.file}:${issue.line}`);
|
|
61
|
+
console.log(` ${issue.type}: ${issue.message}`);
|
|
62
|
+
if (issue.suggestion) {
|
|
63
|
+
console.log(colors.dim + ` \u{2192} ${issue.suggestion}` + colors.reset);
|
|
64
|
+
}
|
|
84
65
|
}
|
|
85
|
-
}
|
|
86
66
|
|
|
87
|
-
|
|
67
|
+
const highSeverity = issues.filter(i => i.severity === 'high');
|
|
88
68
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
69
|
+
return highSeverity.length === 0
|
|
70
|
+
? this.pass(`${issues.length} silent failure pattern(s) found (${highSeverity.length} high severity)`)
|
|
71
|
+
: this.fail(
|
|
72
|
+
`${issues.length} silent failure pattern(s) found (${highSeverity.length} high severity)`,
|
|
73
|
+
issues
|
|
74
|
+
);
|
|
75
|
+
}
|
|
94
76
|
}
|
|
95
77
|
|
|
96
78
|
/**
|
|
@@ -159,9 +141,7 @@ function analyzeForSilentFailures(content, fileName, config) {
|
|
|
159
141
|
|
|
160
142
|
// Check for unhandled promise chains (config.checkPromiseChains)
|
|
161
143
|
if (config.checkPromiseChains) {
|
|
162
|
-
// Look for .then() without .catch()
|
|
163
144
|
if (/\.then\s*\(/.test(line) && !line.includes('.catch(') && !line.includes('await')) {
|
|
164
|
-
// Check if .catch() is on the next few lines
|
|
165
145
|
const nextLines = lines.slice(i, i + 5).join(' ');
|
|
166
146
|
if (!nextLines.includes('.catch(') && !nextLines.includes('.finally(')) {
|
|
167
147
|
issues.push({
|
|
@@ -180,7 +160,6 @@ function analyzeForSilentFailures(content, fileName, config) {
|
|
|
180
160
|
if (config.checkUnhandledAsync) {
|
|
181
161
|
const asyncMatch = line.match(/async\s+(?:function\s+)?(\w+)?/);
|
|
182
162
|
if (asyncMatch && !line.includes('test') && !line.includes('spec')) {
|
|
183
|
-
// Find the function body
|
|
184
163
|
const funcStart = i;
|
|
185
164
|
let funcBraceDepth = 0;
|
|
186
165
|
let funcStarted = false;
|
|
@@ -195,10 +174,8 @@ function analyzeForSilentFailures(content, fileName, config) {
|
|
|
195
174
|
if (funcStarted && funcBraceDepth === 0) break;
|
|
196
175
|
}
|
|
197
176
|
|
|
198
|
-
// Check if function has await but no try-catch
|
|
199
177
|
if (/\bawait\b/.test(funcContent) && !/\btry\s*\{/.test(funcContent)) {
|
|
200
178
|
const funcName = asyncMatch[1] || 'anonymous';
|
|
201
|
-
// Don't flag if it's a short function (likely a wrapper)
|
|
202
179
|
if (funcContent.split('\n').length > 5) {
|
|
203
180
|
issues.push({
|
|
204
181
|
file: fileName,
|
|
@@ -224,10 +201,8 @@ function analyzeCatchBlock(catchLines, startLine, fileName, config) {
|
|
|
224
201
|
const issues = [];
|
|
225
202
|
const catchBody = catchLines.join('\n').trim();
|
|
226
203
|
|
|
227
|
-
// Remove the closing brace if present
|
|
228
204
|
const cleanBody = catchBody.replace(/\}\s*$/, '').trim();
|
|
229
205
|
|
|
230
|
-
// Check for empty catch block
|
|
231
206
|
if (config.checkEmptyCatch && cleanBody.length === 0) {
|
|
232
207
|
issues.push({
|
|
233
208
|
file: fileName,
|
|
@@ -240,7 +215,6 @@ function analyzeCatchBlock(catchLines, startLine, fileName, config) {
|
|
|
240
215
|
return issues;
|
|
241
216
|
}
|
|
242
217
|
|
|
243
|
-
// Check for catch blocks that only log
|
|
244
218
|
if (config.checkLogOnlyCatch) {
|
|
245
219
|
const onlyLogging = /^(?:console\.(?:log|error|warn)|logger\.(?:log|error|warn|info))\s*\(/i.test(cleanBody);
|
|
246
220
|
const hasOtherStatements = cleanBody.split(';').filter(s => {
|
|
@@ -260,7 +234,6 @@ function analyzeCatchBlock(catchLines, startLine, fileName, config) {
|
|
|
260
234
|
}
|
|
261
235
|
}
|
|
262
236
|
|
|
263
|
-
// Check for catch blocks that swallow specific errors
|
|
264
237
|
if (/return\s*(?:null|undefined|false|''|""|``)/.test(cleanBody)) {
|
|
265
238
|
issues.push({
|
|
266
239
|
file: fileName,
|
|
@@ -272,7 +245,6 @@ function analyzeCatchBlock(catchLines, startLine, fileName, config) {
|
|
|
272
245
|
});
|
|
273
246
|
}
|
|
274
247
|
|
|
275
|
-
// Check for catch block with only a comment
|
|
276
248
|
if (/^\/[/*]/.test(cleanBody) && cleanBody.split('\n').every(l => l.trim().startsWith('//') || l.trim().startsWith('*') || l.trim() === '')) {
|
|
277
249
|
issues.push({
|
|
278
250
|
file: fileName,
|
|
@@ -287,4 +259,5 @@ function analyzeCatchBlock(catchLines, startLine, fileName, config) {
|
|
|
287
259
|
return issues;
|
|
288
260
|
}
|
|
289
261
|
|
|
290
|
-
|
|
262
|
+
const step = new SilentFailureStep();
|
|
263
|
+
module.exports = { run: (opts) => step.run(opts), analyzeForSilentFailures };
|
|
@@ -11,85 +11,66 @@
|
|
|
11
11
|
* Both can be enabled together for comprehensive analysis.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
* @param {string[]} options.files - Files modified
|
|
25
|
-
* @param {object} options.stepConfig - Step configuration
|
|
26
|
-
* @param {string} options.mode - Step mode (block/warn/prompt/auto)
|
|
27
|
-
* @returns {object} - { passed: boolean, message: string, details?: object[] }
|
|
28
|
-
*/
|
|
29
|
-
async function run(options = {}) {
|
|
30
|
-
const { files = [], stepConfig = {} } = options;
|
|
31
|
-
const maxFunctionLines = stepConfig.maxFunctionLines || 50;
|
|
32
|
-
const maxNestingDepth = stepConfig.maxNestingDepth || 3;
|
|
33
|
-
const suggestExtraction = stepConfig.suggestExtraction !== false;
|
|
34
|
-
|
|
35
|
-
// Filter to analyzable files
|
|
36
|
-
const analyzableExtensions = ['.js', '.ts', '.jsx', '.tsx'];
|
|
37
|
-
const analyzableFiles = files.filter(f =>
|
|
38
|
-
analyzableExtensions.some(ext => f.endsWith(ext)) &&
|
|
39
|
-
!f.includes('.test.') &&
|
|
40
|
-
!f.includes('.spec.') &&
|
|
41
|
-
!f.includes('.d.ts')
|
|
42
|
-
);
|
|
43
|
-
|
|
44
|
-
if (analyzableFiles.length === 0) {
|
|
45
|
-
return { passed: true, message: 'No analyzable files modified' };
|
|
14
|
+
const { BaseWorkflowStep } = require('./base-workflow-step');
|
|
15
|
+
const { colors } = require('./flow-utils');
|
|
16
|
+
|
|
17
|
+
class SimplifierStep extends BaseWorkflowStep {
|
|
18
|
+
constructor() {
|
|
19
|
+
super('codeSimplifier', {
|
|
20
|
+
extensions: ['.js', '.ts', '.jsx', '.tsx'],
|
|
21
|
+
excludeTests: true,
|
|
22
|
+
excludeDts: true,
|
|
23
|
+
});
|
|
46
24
|
}
|
|
47
25
|
|
|
48
|
-
|
|
26
|
+
async execute(files, options) {
|
|
27
|
+
const { stepConfig = {} } = options;
|
|
28
|
+
const maxFunctionLines = stepConfig.maxFunctionLines || 50;
|
|
29
|
+
const maxNestingDepth = stepConfig.maxNestingDepth || 3;
|
|
30
|
+
const suggestExtraction = stepConfig.suggestExtraction !== false;
|
|
49
31
|
|
|
50
|
-
|
|
51
|
-
const filePath = path.join(PROJECT_ROOT, file);
|
|
52
|
-
if (!fs.existsSync(filePath)) continue;
|
|
32
|
+
const suggestions = [];
|
|
53
33
|
|
|
54
|
-
|
|
55
|
-
const content =
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
34
|
+
for (const file of files) {
|
|
35
|
+
const content = this.readFile(file);
|
|
36
|
+
if (!content) continue;
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const fileSuggestions = analyzeForSimplification(content, file, {
|
|
40
|
+
maxFunctionLines,
|
|
41
|
+
maxNestingDepth,
|
|
42
|
+
suggestExtraction,
|
|
43
|
+
});
|
|
44
|
+
suggestions.push(...fileSuggestions);
|
|
45
|
+
} catch (_err) {
|
|
46
|
+
// Skip files that can't be analyzed
|
|
47
|
+
}
|
|
64
48
|
}
|
|
65
|
-
}
|
|
66
49
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
message: 'No simplification suggestions',
|
|
71
|
-
};
|
|
72
|
-
}
|
|
50
|
+
if (suggestions.length === 0) {
|
|
51
|
+
return this.pass('No simplification suggestions');
|
|
52
|
+
}
|
|
73
53
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
54
|
+
// Report suggestions
|
|
55
|
+
console.log(colors.cyan + '\n Code Simplification Suggestions:' + colors.reset);
|
|
56
|
+
for (const suggestion of suggestions) {
|
|
57
|
+
const icon = suggestion.severity === 'high' ? '\u{1F534}' : '\u{1F7E1}';
|
|
58
|
+
console.log(` ${icon} ${suggestion.file}:${suggestion.line}`);
|
|
59
|
+
console.log(` ${suggestion.type}: ${suggestion.message}`);
|
|
60
|
+
if (suggestion.suggestion) {
|
|
61
|
+
console.log(colors.dim + ` \u{2192} ${suggestion.suggestion}` + colors.reset);
|
|
62
|
+
}
|
|
82
63
|
}
|
|
83
|
-
}
|
|
84
64
|
|
|
85
|
-
|
|
86
|
-
const highSeverity = suggestions.filter(s => s.severity === 'high');
|
|
65
|
+
const highSeverity = suggestions.filter(s => s.severity === 'high');
|
|
87
66
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
67
|
+
return highSeverity.length === 0
|
|
68
|
+
? this.pass(`${suggestions.length} simplification suggestion(s) (${highSeverity.length} high severity)`)
|
|
69
|
+
: this.fail(
|
|
70
|
+
`${suggestions.length} simplification suggestion(s) (${highSeverity.length} high severity)`,
|
|
71
|
+
suggestions
|
|
72
|
+
);
|
|
73
|
+
}
|
|
93
74
|
}
|
|
94
75
|
|
|
95
76
|
/**
|
|
@@ -343,4 +324,5 @@ function findDuplicationPatterns(content, fileName) {
|
|
|
343
324
|
return suggestions;
|
|
344
325
|
}
|
|
345
326
|
|
|
346
|
-
|
|
327
|
+
const step = new SimplifierStep();
|
|
328
|
+
module.exports = { run: (opts) => step.run(opts), analyzeForSimplification };
|
package/scripts/flow-story.js
CHANGED
|
@@ -24,7 +24,7 @@ const {
|
|
|
24
24
|
outputJson,
|
|
25
25
|
withLock,
|
|
26
26
|
safeJsonParse,
|
|
27
|
-
isPathWithinProject
|
|
27
|
+
isPathWithinProject, PATHS
|
|
28
28
|
} = require('./flow-utils');
|
|
29
29
|
const { success, warn, error, info, print, printHeader } = require('./flow-output');
|
|
30
30
|
|
|
@@ -39,11 +39,8 @@ try {
|
|
|
39
39
|
// Import parallel execution detection
|
|
40
40
|
const { findParallelizable, getParallelConfig } = require('./flow-parallel');
|
|
41
41
|
|
|
42
|
-
const
|
|
43
|
-
const
|
|
44
|
-
const CHANGES_DIR = path.join(WORKFLOW_DIR, 'changes');
|
|
45
|
-
const STATE_DIR = path.join(WORKFLOW_DIR, 'state');
|
|
46
|
-
const READY_PATH = path.join(STATE_DIR, 'ready.json');
|
|
42
|
+
const CHANGES_DIR = PATHS.changes;
|
|
43
|
+
const READY_PATH = PATHS.ready;
|
|
47
44
|
|
|
48
45
|
function log(color, ...args) {
|
|
49
46
|
console.log(colors[color] + args.join(' ') + colors.reset);
|
|
@@ -335,7 +332,7 @@ async function createStory(title, options = {}) {
|
|
|
335
332
|
targetDir = path.join(CHANGES_DIR, featureFolder);
|
|
336
333
|
|
|
337
334
|
// Security: Validate path is within project (defense against path traversal)
|
|
338
|
-
if (!isPathWithinProject(targetDir,
|
|
335
|
+
if (!isPathWithinProject(targetDir, PATHS.root)) {
|
|
339
336
|
throw new Error(`Security: Target directory "${targetDir}" is outside project root`);
|
|
340
337
|
}
|
|
341
338
|
|
|
@@ -27,16 +27,15 @@ const {
|
|
|
27
27
|
warn,
|
|
28
28
|
error,
|
|
29
29
|
info,
|
|
30
|
-
success
|
|
30
|
+
success, PATHS
|
|
31
31
|
} = require('./flow-utils');
|
|
32
32
|
|
|
33
33
|
// ============================================================
|
|
34
34
|
// Constants
|
|
35
35
|
// ============================================================
|
|
36
36
|
|
|
37
|
-
const
|
|
38
|
-
const
|
|
39
|
-
const OVERRIDES_PATH = path.join(PROJECT_ROOT, '.workflow/state/adherence-overrides.json');
|
|
37
|
+
const STANDARDS_PATH = path.join(PATHS.root, '.workflow/state/project-standards.json');
|
|
38
|
+
const OVERRIDES_PATH = path.join(PATHS.root, '.workflow/state/adherence-overrides.json');
|
|
40
39
|
|
|
41
40
|
// Escape special regex characters for safe dynamic regex construction
|
|
42
41
|
function escapeRegExp(string) {
|