wogiflow 2.4.2 → 2.4.4
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-start.md +124 -0
- package/.claude/docs/claude-code-compatibility.md +51 -0
- package/.claude/docs/explore-agents.md +11 -0
- package/.claude/settings.json +12 -1
- package/.workflow/models/registry.json +1 -1
- package/bin/flow +11 -1
- package/lib/workspace-contracts.js +599 -0
- package/lib/workspace-intelligence.js +600 -0
- package/lib/workspace-messages.js +441 -0
- package/lib/workspace-routing.js +485 -0
- package/lib/workspace-sync.js +339 -0
- package/lib/workspace.js +1073 -0
- 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 +28 -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-calibration.js +257 -0
- package/scripts/flow-eval-judge.js +10 -1
- package/scripts/flow-eval.js +14 -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 +31 -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/task-created.js +83 -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/task-created.js +15 -0
- 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/postinstall.js +2 -0
- 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
|
@@ -19,13 +19,11 @@
|
|
|
19
19
|
|
|
20
20
|
const fs = require('node:fs');
|
|
21
21
|
const path = require('node:path');
|
|
22
|
-
const { getProjectRoot, colors: c, readJson } = require('./flow-utils');
|
|
22
|
+
const { getProjectRoot, colors: c, readJson, PATHS } = require('./flow-utils');
|
|
23
23
|
const { success: printSuccess } = require('./flow-output');
|
|
24
24
|
const { detectPackageManager } = require('./flow-script-resolver');
|
|
25
25
|
|
|
26
|
-
const
|
|
27
|
-
const WORKFLOW_DIR = path.join(PROJECT_ROOT, '.workflow');
|
|
28
|
-
const CONTEXT_DIR = path.join(WORKFLOW_DIR, 'context');
|
|
26
|
+
const CONTEXT_DIR = path.join(PATHS.workflow, 'context');
|
|
29
27
|
const TEMPLATES_DIR = path.join(__dirname, '..', 'templates', 'context');
|
|
30
28
|
|
|
31
29
|
/**
|
|
@@ -53,7 +51,7 @@ function detectStack() {
|
|
|
53
51
|
};
|
|
54
52
|
|
|
55
53
|
// Check package.json for Node.js projects
|
|
56
|
-
const packageJsonPath = path.join(
|
|
54
|
+
const packageJsonPath = path.join(PATHS.root, 'package.json');
|
|
57
55
|
if (fs.existsSync(packageJsonPath)) {
|
|
58
56
|
try {
|
|
59
57
|
const pkg = readJson(packageJsonPath, null);
|
|
@@ -196,7 +194,7 @@ function detectStack() {
|
|
|
196
194
|
}
|
|
197
195
|
|
|
198
196
|
// Detect package manager — uses canonical detectPackageManager() from flow-script-resolver
|
|
199
|
-
const detectedPm = detectPackageManager(
|
|
197
|
+
const detectedPm = detectPackageManager(PATHS.root);
|
|
200
198
|
stack.packageManager = detectedPm;
|
|
201
199
|
if (detectedPm === 'bun') {
|
|
202
200
|
stack.runtime = 'Bun';
|
|
@@ -205,17 +203,17 @@ function detectStack() {
|
|
|
205
203
|
// Check for Python projects
|
|
206
204
|
// Note: When a non-JS primary language is detected, JS framework fields are cleared
|
|
207
205
|
// to prevent incoherent reports (e.g., "Python + React" from a polyglot repo)
|
|
208
|
-
const requirementsPath = path.join(
|
|
209
|
-
const pyprojectPath = path.join(
|
|
206
|
+
const requirementsPath = path.join(PATHS.root, 'requirements.txt');
|
|
207
|
+
const pyprojectPath = path.join(PATHS.root, 'pyproject.toml');
|
|
210
208
|
if (fs.existsSync(requirementsPath) || fs.existsSync(pyprojectPath)) {
|
|
211
209
|
stack.language = 'Python';
|
|
212
210
|
stack.runtime = 'Python';
|
|
213
211
|
stack.frameworks.frontend = '';
|
|
214
212
|
stack.frameworks.fullStack = '';
|
|
215
213
|
|
|
216
|
-
if (fs.existsSync(path.join(
|
|
214
|
+
if (fs.existsSync(path.join(PATHS.root, 'poetry.lock'))) {
|
|
217
215
|
stack.packageManager = 'Poetry';
|
|
218
|
-
} else if (fs.existsSync(path.join(
|
|
216
|
+
} else if (fs.existsSync(path.join(PATHS.root, 'Pipfile.lock'))) {
|
|
219
217
|
stack.packageManager = 'Pipenv';
|
|
220
218
|
} else {
|
|
221
219
|
stack.packageManager = 'pip';
|
|
@@ -240,7 +238,7 @@ function detectStack() {
|
|
|
240
238
|
}
|
|
241
239
|
|
|
242
240
|
// Check for Rust projects
|
|
243
|
-
const cargoPath = path.join(
|
|
241
|
+
const cargoPath = path.join(PATHS.root, 'Cargo.toml');
|
|
244
242
|
if (fs.existsSync(cargoPath)) {
|
|
245
243
|
stack.language = 'Rust';
|
|
246
244
|
stack.runtime = 'Rust';
|
|
@@ -257,7 +255,7 @@ function detectStack() {
|
|
|
257
255
|
}
|
|
258
256
|
|
|
259
257
|
// Check for Go projects
|
|
260
|
-
const goModPath = path.join(
|
|
258
|
+
const goModPath = path.join(PATHS.root, 'go.mod');
|
|
261
259
|
if (fs.existsSync(goModPath)) {
|
|
262
260
|
stack.language = 'Go';
|
|
263
261
|
stack.runtime = 'Go';
|
|
@@ -36,7 +36,7 @@ const {
|
|
|
36
36
|
outputJson,
|
|
37
37
|
printHeader,
|
|
38
38
|
printSection,
|
|
39
|
-
estimateTokens
|
|
39
|
+
estimateTokens, PATHS
|
|
40
40
|
} = require('./flow-utils');
|
|
41
41
|
|
|
42
42
|
// ============================================================
|
|
@@ -356,7 +356,7 @@ function collectContext({ description, targetFiles = [], additionalContext = []
|
|
|
356
356
|
}
|
|
357
357
|
|
|
358
358
|
// Add patterns from decisions.md
|
|
359
|
-
const decisionsPath =
|
|
359
|
+
const decisionsPath = PATHS.decisions;
|
|
360
360
|
try {
|
|
361
361
|
if (fs.existsSync(decisionsPath)) {
|
|
362
362
|
const decisions = fs.readFileSync(decisionsPath, 'utf8');
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
'use strict';
|
|
4
|
-
|
|
5
3
|
/**
|
|
6
4
|
* Wogi Flow - Contract Surface Scanner CLI
|
|
7
5
|
*
|
|
@@ -18,10 +16,9 @@
|
|
|
18
16
|
|
|
19
17
|
const fs = require('node:fs');
|
|
20
18
|
const path = require('node:path');
|
|
21
|
-
const { getProjectRoot, getConfig } = require('./flow-utils');
|
|
19
|
+
const { getProjectRoot, getConfig, PATHS } = require('./flow-utils');
|
|
22
20
|
|
|
23
|
-
const
|
|
24
|
-
const DEFAULT_OUTPUT = path.join(PROJECT_ROOT, '.workflow', 'state', 'contract-surface.json');
|
|
21
|
+
const DEFAULT_OUTPUT = path.join(PATHS.state, 'contract-surface.json');
|
|
25
22
|
|
|
26
23
|
function parseArgs(args) {
|
|
27
24
|
const options = {
|
|
@@ -102,7 +99,7 @@ function main() {
|
|
|
102
99
|
const { scanContracts } = require('./registries/contract-scanner');
|
|
103
100
|
|
|
104
101
|
const scanOptions = {
|
|
105
|
-
projectName: path.basename(
|
|
102
|
+
projectName: path.basename(PATHS.root),
|
|
106
103
|
projectType: options.type || contractConfig.projectType || undefined,
|
|
107
104
|
maxFiles: contractConfig.maxFiles || 500,
|
|
108
105
|
maxDepth: contractConfig.maxDepth || 6,
|
|
@@ -110,11 +107,11 @@ function main() {
|
|
|
110
107
|
};
|
|
111
108
|
|
|
112
109
|
if (options.verbose) {
|
|
113
|
-
console.log(`Scanning ${
|
|
110
|
+
console.log(`Scanning ${PATHS.root}...`);
|
|
114
111
|
console.log('');
|
|
115
112
|
}
|
|
116
113
|
|
|
117
|
-
const surface = scanContracts(
|
|
114
|
+
const surface = scanContracts(PATHS.root, scanOptions);
|
|
118
115
|
|
|
119
116
|
if (options.json) {
|
|
120
117
|
console.log(JSON.stringify(surface, null, 2));
|
|
@@ -126,7 +123,7 @@ function main() {
|
|
|
126
123
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
127
124
|
fs.writeFileSync(options.output, JSON.stringify(surface, null, 2));
|
|
128
125
|
|
|
129
|
-
const relOutput = path.relative(
|
|
126
|
+
const relOutput = path.relative(PATHS.root, options.output);
|
|
130
127
|
console.log(`Contract surface saved to ${relOutput}`);
|
|
131
128
|
console.log('');
|
|
132
129
|
console.log('Summary:');
|
package/scripts/flow-correct.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
const fs = require('node:fs');
|
|
17
17
|
const path = require('node:path');
|
|
18
|
-
const readline = require('node:readline');
|
|
18
|
+
const readline = require('node:readline/promises');
|
|
19
19
|
const {
|
|
20
20
|
PATHS,
|
|
21
21
|
PROJECT_ROOT,
|
|
@@ -44,7 +44,7 @@ function getCorrectionsDir() {
|
|
|
44
44
|
} catch (err) {
|
|
45
45
|
// Fall back to default if config can't be read
|
|
46
46
|
}
|
|
47
|
-
return
|
|
47
|
+
return PATHS.corrections;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
const CORRECTIONS_DIR = getCorrectionsDir();
|
|
@@ -60,33 +60,23 @@ function createInterface() {
|
|
|
60
60
|
});
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
function prompt(rl, question, defaultValue = '') {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
resolve(answer.trim() || defaultValue);
|
|
68
|
-
});
|
|
69
|
-
});
|
|
63
|
+
async function prompt(rl, question, defaultValue = '') {
|
|
64
|
+
const suffix = defaultValue ? ` (${defaultValue})` : '';
|
|
65
|
+
const answer = await rl.question(`${question}${suffix}: `);
|
|
66
|
+
return answer.trim() || defaultValue;
|
|
70
67
|
}
|
|
71
68
|
|
|
72
|
-
function promptMultiline(rl, question) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const lines = [];
|
|
76
|
-
|
|
77
|
-
const readLine = () => {
|
|
78
|
-
rl.question(' ', (line) => {
|
|
79
|
-
if (line === '') {
|
|
80
|
-
resolve(lines.join('\n'));
|
|
81
|
-
} else {
|
|
82
|
-
lines.push(line);
|
|
83
|
-
readLine();
|
|
84
|
-
}
|
|
85
|
-
});
|
|
86
|
-
};
|
|
69
|
+
async function promptMultiline(rl, question) {
|
|
70
|
+
console.log(`${question} (end with empty line):`);
|
|
71
|
+
const lines = [];
|
|
87
72
|
|
|
88
|
-
|
|
89
|
-
|
|
73
|
+
while (true) {
|
|
74
|
+
const line = await rl.question(' ');
|
|
75
|
+
if (line === '') {
|
|
76
|
+
return lines.join('\n');
|
|
77
|
+
}
|
|
78
|
+
lines.push(line);
|
|
79
|
+
}
|
|
90
80
|
}
|
|
91
81
|
|
|
92
82
|
// ============================================================
|
|
@@ -221,7 +211,19 @@ function updateFeedbackPatterns(brief, taskId, skillName) {
|
|
|
221
211
|
}
|
|
222
212
|
}
|
|
223
213
|
|
|
224
|
-
|
|
214
|
+
try {
|
|
215
|
+
const { writeToFeedbackPatterns } = require('./flow-learning-orchestrator');
|
|
216
|
+
writeToFeedbackPatterns({
|
|
217
|
+
content,
|
|
218
|
+
entryText: brief,
|
|
219
|
+
caller: 'flow-correct/updateFeedbackPatterns',
|
|
220
|
+
skipDedup: true // We already handle dedup via existing entry check above
|
|
221
|
+
}).catch(_err => {
|
|
222
|
+
if (process.env.DEBUG) console.error(`[DEBUG] updateFeedbackPatterns: ${_err.message}`);
|
|
223
|
+
});
|
|
224
|
+
} catch (_err) {
|
|
225
|
+
writeFile(PATHS.feedbackPatterns, content); // Fallback to direct write
|
|
226
|
+
}
|
|
225
227
|
return existingCount;
|
|
226
228
|
}
|
|
227
229
|
|
|
@@ -289,6 +289,10 @@ function spawnBackgroundDetection(userMessage, taskId) {
|
|
|
289
289
|
const { spawn } = require('node:child_process');
|
|
290
290
|
// Pass user message via env var instead of CLI args to prevent argument injection.
|
|
291
291
|
// Only propagate necessary env vars to minimize exposure.
|
|
292
|
+
// NOTE: When CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=1 (Claude Code 2.1.83+), Anthropic/cloud
|
|
293
|
+
// credentials are stripped from subprocess environments. Since hooks run as subprocesses,
|
|
294
|
+
// ANTHROPIC_API_KEY may already be scrubbed by the time we reach here. The detector
|
|
295
|
+
// gracefully degrades (returns isCorrection: false) when no API key is available.
|
|
292
296
|
const child = spawn(process.execPath, [
|
|
293
297
|
__filename, 'detect-and-queue'
|
|
294
298
|
], {
|
|
@@ -298,7 +302,7 @@ function spawnBackgroundDetection(userMessage, taskId) {
|
|
|
298
302
|
PATH: process.env.PATH,
|
|
299
303
|
HOME: process.env.HOME,
|
|
300
304
|
NODE_PATH: process.env.NODE_PATH || '',
|
|
301
|
-
ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY,
|
|
305
|
+
ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY || '',
|
|
302
306
|
DEBUG: process.env.DEBUG || '',
|
|
303
307
|
WOGI_DETECT_MESSAGE: userMessage.trim().slice(0, MAX_PROMPT_LENGTH),
|
|
304
308
|
WOGI_DETECT_TASK_ID: taskId || ''
|
|
@@ -8,9 +8,10 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Inspired by Hookify plugin patterns, adapted for multi-CLI compatibility.
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* DESIGN NOTE: The 'prompt' event type uses pattern-matching only for safety evaluation.
|
|
12
|
+
* AI-based prompt analysis was considered but is out of scope — the pattern-matching
|
|
13
|
+
* approach (blocked/ask lists + event-based rules) provides sufficient protection
|
|
14
|
+
* without requiring external API calls or model inference.
|
|
14
15
|
*
|
|
15
16
|
* Usage:
|
|
16
17
|
* flow damage-control check "<command>" Check if command is allowed
|
|
@@ -22,11 +23,9 @@
|
|
|
22
23
|
|
|
23
24
|
const fs = require('node:fs');
|
|
24
25
|
const path = require('node:path');
|
|
25
|
-
const { getProjectRoot, colors, getConfig } = require('./flow-utils');
|
|
26
|
+
const { getProjectRoot, colors, getConfig, PATHS } = require('./flow-utils');
|
|
26
27
|
|
|
27
|
-
const
|
|
28
|
-
const WORKFLOW_DIR = path.join(PROJECT_ROOT, '.workflow');
|
|
29
|
-
const PATTERNS_FILE = path.join(WORKFLOW_DIR, 'damage-control.yaml');
|
|
28
|
+
const PATTERNS_FILE = path.join(PATHS.workflow, 'damage-control.yaml');
|
|
30
29
|
|
|
31
30
|
// ============================================================
|
|
32
31
|
// Event Types and Actions
|
|
@@ -200,7 +199,7 @@ function parseSimpleYaml(content) {
|
|
|
200
199
|
// Sub-section under paths (indent 2): zeroAccess:, readOnly:, noDelete:
|
|
201
200
|
if (currentSection === 'paths' && indent === 2 && trimmed.endsWith(':')) {
|
|
202
201
|
currentSubSection = trimmed.slice(0, -1);
|
|
203
|
-
result.paths[currentSubSection] = result.paths[currentSubSection]
|
|
202
|
+
result.paths[currentSubSection] = result.paths[currentSubSection] ?? [];
|
|
204
203
|
continue;
|
|
205
204
|
}
|
|
206
205
|
|
|
@@ -351,9 +350,9 @@ function parseSimpleYaml(content) {
|
|
|
351
350
|
*/
|
|
352
351
|
function loadPatterns() {
|
|
353
352
|
const config = getConfig();
|
|
354
|
-
const dcConfig = config.damageControl
|
|
353
|
+
const dcConfig = config.damageControl ?? {};
|
|
355
354
|
const patternsPath = dcConfig.patternsFile
|
|
356
|
-
? path.join(
|
|
355
|
+
? path.join(PATHS.root, dcConfig.patternsFile)
|
|
357
356
|
: PATTERNS_FILE;
|
|
358
357
|
|
|
359
358
|
if (!fs.existsSync(patternsPath)) {
|
|
@@ -435,7 +434,7 @@ function checkEventRule(rule, eventType, context) {
|
|
|
435
434
|
*/
|
|
436
435
|
function checkEvent(eventType, context = {}) {
|
|
437
436
|
const config = getConfig();
|
|
438
|
-
const dcConfig = config.damageControl
|
|
437
|
+
const dcConfig = config.damageControl ?? {};
|
|
439
438
|
|
|
440
439
|
// Check if damage control is enabled
|
|
441
440
|
if (!dcConfig.enabled) {
|
|
@@ -443,7 +442,7 @@ function checkEvent(eventType, context = {}) {
|
|
|
443
442
|
}
|
|
444
443
|
|
|
445
444
|
// Check if this event type is enabled
|
|
446
|
-
const events = dcConfig.events
|
|
445
|
+
const events = dcConfig.events ?? { bash: true };
|
|
447
446
|
if (events[eventType] === false) {
|
|
448
447
|
return { allowed: true, action: 'allow', message: `Event type '${eventType}' disabled` };
|
|
449
448
|
}
|
|
@@ -451,7 +450,7 @@ function checkEvent(eventType, context = {}) {
|
|
|
451
450
|
const patterns = loadPatterns();
|
|
452
451
|
|
|
453
452
|
// Check event-based rules first (new format)
|
|
454
|
-
for (const rule of patterns.rules
|
|
453
|
+
for (const rule of patterns.rules ?? []) {
|
|
455
454
|
const action = checkEventRule(rule, eventType, context);
|
|
456
455
|
if (action) {
|
|
457
456
|
const result = {
|
|
@@ -481,7 +480,7 @@ function checkEvent(eventType, context = {}) {
|
|
|
481
480
|
|
|
482
481
|
// Fall back to legacy patterns for bash events
|
|
483
482
|
if (eventType === 'bash') {
|
|
484
|
-
const cmd = context.command
|
|
483
|
+
const cmd = context.command ?? '';
|
|
485
484
|
const legacyResult = checkCommand(cmd);
|
|
486
485
|
if (legacyResult.action !== 'allow') {
|
|
487
486
|
return {
|
|
@@ -494,7 +493,8 @@ function checkEvent(eventType, context = {}) {
|
|
|
494
493
|
|
|
495
494
|
// Fall back to legacy path patterns for file events
|
|
496
495
|
if (eventType === 'file') {
|
|
497
|
-
const
|
|
496
|
+
const rawPath = context.file_path ?? context.filePath ?? '';
|
|
497
|
+
const filePath = typeof rawPath === 'string' ? rawPath : '';
|
|
498
498
|
const operation = context.operation || 'edit';
|
|
499
499
|
const pathResult = checkPath(filePath, operation);
|
|
500
500
|
if (!pathResult.allowed) {
|
|
@@ -515,11 +515,11 @@ function checkEvent(eventType, context = {}) {
|
|
|
515
515
|
*/
|
|
516
516
|
function logDamageControl(eventType, context, result) {
|
|
517
517
|
const config = getConfig();
|
|
518
|
-
const dcConfig = config.damageControl
|
|
518
|
+
const dcConfig = config.damageControl ?? {};
|
|
519
519
|
|
|
520
520
|
if (!dcConfig.logging) return;
|
|
521
521
|
|
|
522
|
-
const logDir =
|
|
522
|
+
const logDir = PATHS.logs;
|
|
523
523
|
const logPath = path.join(logDir, 'damage-control.log');
|
|
524
524
|
|
|
525
525
|
// Ensure log directory exists
|
|
@@ -586,7 +586,7 @@ function isSafeCommand(cmd) {
|
|
|
586
586
|
*/
|
|
587
587
|
function checkCommand(cmd) {
|
|
588
588
|
const config = getConfig();
|
|
589
|
-
const dcConfig = config.damageControl
|
|
589
|
+
const dcConfig = config.damageControl ?? {};
|
|
590
590
|
|
|
591
591
|
if (!dcConfig.enabled) {
|
|
592
592
|
return { action: 'allow' };
|
|
@@ -600,7 +600,7 @@ function checkCommand(cmd) {
|
|
|
600
600
|
const patterns = loadPatterns();
|
|
601
601
|
|
|
602
602
|
// Check blocked patterns
|
|
603
|
-
for (const pattern of patterns.blocked
|
|
603
|
+
for (const pattern of patterns.blocked ?? []) {
|
|
604
604
|
const regex = safeRegExp(pattern, 'i');
|
|
605
605
|
if (!regex) continue; // Skip invalid/unsafe patterns
|
|
606
606
|
if (regex.test(cmd)) {
|
|
@@ -613,7 +613,7 @@ function checkCommand(cmd) {
|
|
|
613
613
|
}
|
|
614
614
|
|
|
615
615
|
// Check ask patterns
|
|
616
|
-
for (const item of patterns.ask
|
|
616
|
+
for (const item of patterns.ask ?? []) {
|
|
617
617
|
const pattern = typeof item === 'string' ? item : item.pattern;
|
|
618
618
|
const reason = typeof item === 'object' ? item.reason : 'Matches sensitive pattern';
|
|
619
619
|
|
|
@@ -660,20 +660,20 @@ function pathMatchesPattern(normalizedPath, pattern) {
|
|
|
660
660
|
*/
|
|
661
661
|
function checkPath(filePath, operation) {
|
|
662
662
|
const config = getConfig();
|
|
663
|
-
const dcConfig = config.damageControl
|
|
663
|
+
const dcConfig = config.damageControl ?? {};
|
|
664
664
|
|
|
665
665
|
if (!dcConfig.enabled) {
|
|
666
666
|
return { allowed: true };
|
|
667
667
|
}
|
|
668
668
|
|
|
669
669
|
const patterns = loadPatterns();
|
|
670
|
-
const paths = patterns.paths
|
|
670
|
+
const paths = patterns.paths ?? {};
|
|
671
671
|
|
|
672
672
|
// Normalize path (handle both forward and backslashes)
|
|
673
673
|
const normalizedPath = filePath.replace(/\\/g, '/');
|
|
674
674
|
|
|
675
675
|
// Zero access - block all operations (read, write, delete)
|
|
676
|
-
for (const p of paths.zeroAccess
|
|
676
|
+
for (const p of paths.zeroAccess ?? []) {
|
|
677
677
|
if (pathMatchesPattern(normalizedPath, p)) {
|
|
678
678
|
return {
|
|
679
679
|
allowed: false,
|
|
@@ -685,7 +685,7 @@ function checkPath(filePath, operation) {
|
|
|
685
685
|
|
|
686
686
|
// Read-only - block write/delete
|
|
687
687
|
if (operation === 'write' || operation === 'delete') {
|
|
688
|
-
for (const p of paths.readOnly
|
|
688
|
+
for (const p of paths.readOnly ?? []) {
|
|
689
689
|
if (pathMatchesPattern(normalizedPath, p)) {
|
|
690
690
|
return {
|
|
691
691
|
allowed: false,
|
|
@@ -698,7 +698,7 @@ function checkPath(filePath, operation) {
|
|
|
698
698
|
|
|
699
699
|
// No-delete - block delete only
|
|
700
700
|
if (operation === 'delete') {
|
|
701
|
-
for (const p of paths.noDelete
|
|
701
|
+
for (const p of paths.noDelete ?? []) {
|
|
702
702
|
if (pathMatchesPattern(normalizedPath, p)) {
|
|
703
703
|
return {
|
|
704
704
|
allowed: false,
|
|
@@ -713,44 +713,37 @@ function checkPath(filePath, operation) {
|
|
|
713
713
|
}
|
|
714
714
|
|
|
715
715
|
/**
|
|
716
|
-
*
|
|
717
|
-
*
|
|
716
|
+
* Prompt hook for unknown dangerous commands — pattern-matching only.
|
|
717
|
+
*
|
|
718
|
+
* Checks the command against blocked/ask pattern lists and safe command whitelist.
|
|
719
|
+
* No AI/LLM evaluation is performed; the pattern-matching approach provides
|
|
720
|
+
* sufficient protection without external API calls.
|
|
718
721
|
*
|
|
719
|
-
*
|
|
720
|
-
*
|
|
722
|
+
* @param {string} cmd - The command to check
|
|
723
|
+
* @returns {Promise<{ action: 'allow' | 'block' | 'ask', reason?: string }>}
|
|
721
724
|
*/
|
|
722
725
|
async function promptHookCheck(cmd) {
|
|
723
726
|
const config = getConfig();
|
|
724
|
-
const dcConfig = config.damageControl
|
|
725
|
-
const promptConfig = dcConfig.promptHook
|
|
727
|
+
const dcConfig = config.damageControl ?? {};
|
|
728
|
+
const promptConfig = dcConfig.promptHook ?? {};
|
|
726
729
|
|
|
727
730
|
if (!dcConfig.enabled || !promptConfig.enabled) {
|
|
728
731
|
return { action: 'allow' };
|
|
729
732
|
}
|
|
730
733
|
|
|
731
|
-
//
|
|
732
|
-
// Pattern-based checking below still works — only the AI analysis is stubbed.
|
|
733
|
-
if (process.env.DEBUG) {
|
|
734
|
-
console.error('[damage-control] promptHook AI analysis is not yet implemented — using pattern matching only');
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
// Skip if already caught by patterns
|
|
734
|
+
// Check against blocked/ask patterns
|
|
738
735
|
const patternResult = checkCommand(cmd);
|
|
739
736
|
if (patternResult.action !== 'allow') {
|
|
740
737
|
return patternResult;
|
|
741
738
|
}
|
|
742
739
|
|
|
743
|
-
//
|
|
740
|
+
// Safe commands are always allowed
|
|
744
741
|
if (isSafeCommand(cmd)) {
|
|
745
742
|
return { action: 'allow', reason: 'Safe command' };
|
|
746
743
|
}
|
|
747
744
|
|
|
748
|
-
//
|
|
749
|
-
|
|
750
|
-
return {
|
|
751
|
-
action: 'allow',
|
|
752
|
-
reason: 'Prompt hook enabled but API not yet integrated'
|
|
753
|
-
};
|
|
745
|
+
// No pattern matched — allow by default
|
|
746
|
+
return { action: 'allow', reason: 'No dangerous patterns matched' };
|
|
754
747
|
}
|
|
755
748
|
|
|
756
749
|
/**
|
|
@@ -758,22 +751,22 @@ async function promptHookCheck(cmd) {
|
|
|
758
751
|
*/
|
|
759
752
|
function getStatus() {
|
|
760
753
|
const config = getConfig();
|
|
761
|
-
const dcConfig = config.damageControl
|
|
754
|
+
const dcConfig = config.damageControl ?? {};
|
|
762
755
|
const patterns = loadPatterns();
|
|
763
756
|
|
|
764
757
|
return {
|
|
765
|
-
enabled: dcConfig.enabled
|
|
758
|
+
enabled: dcConfig.enabled ?? false,
|
|
766
759
|
promptHook: {
|
|
767
|
-
enabled: dcConfig.promptHook?.enabled
|
|
760
|
+
enabled: dcConfig.promptHook?.enabled ?? false,
|
|
768
761
|
model: dcConfig.promptHook?.model || 'haiku',
|
|
769
|
-
|
|
762
|
+
mode: 'pattern-matching-only'
|
|
770
763
|
},
|
|
771
764
|
patternsFile: dcConfig.patternsFile || '.workflow/damage-control.yaml',
|
|
772
765
|
events: dcConfig.events || { bash: true, file: false, stop: false, prompt: false },
|
|
773
766
|
patternsLoaded: {
|
|
774
|
-
rules: (patterns.rules
|
|
775
|
-
blocked: (patterns.blocked
|
|
776
|
-
ask: (patterns.ask
|
|
767
|
+
rules: (patterns.rules ?? []).length,
|
|
768
|
+
blocked: (patterns.blocked ?? []).length,
|
|
769
|
+
ask: (patterns.ask ?? []).length,
|
|
777
770
|
paths: {
|
|
778
771
|
zeroAccess: (patterns.paths?.zeroAccess || []).length,
|
|
779
772
|
readOnly: (patterns.paths?.readOnly || []).length,
|
|
@@ -943,10 +936,10 @@ Configuration (config.json):
|
|
|
943
936
|
|
|
944
937
|
// Show legacy patterns
|
|
945
938
|
log('cyan', 'Legacy Blocked Patterns:');
|
|
946
|
-
(patterns.blocked
|
|
939
|
+
(patterns.blocked ?? []).forEach(p => log('red', ` - ${p}`));
|
|
947
940
|
console.log('');
|
|
948
941
|
log('cyan', 'Legacy Ask Patterns:');
|
|
949
|
-
(patterns.ask
|
|
942
|
+
(patterns.ask ?? []).forEach(p => {
|
|
950
943
|
if (typeof p === 'object') {
|
|
951
944
|
log('yellow', ` - ${p.pattern}`);
|
|
952
945
|
log('dim', ` Reason: ${p.reason}`);
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
|
|
21
21
|
const fs = require('node:fs');
|
|
22
22
|
const path = require('node:path');
|
|
23
|
+
const { safeJsonParse } = require('./flow-io');
|
|
23
24
|
|
|
24
25
|
// ============================================================
|
|
25
26
|
// Section Parser
|
|
@@ -368,20 +369,9 @@ function main() {
|
|
|
368
369
|
process.exit(1);
|
|
369
370
|
}
|
|
370
371
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
resolutions = JSON.parse(raw);
|
|
375
|
-
// Prototype pollution protection (per security-patterns.md #2)
|
|
376
|
-
if (resolutions && typeof resolutions === 'object') {
|
|
377
|
-
for (const key of Object.keys(resolutions)) {
|
|
378
|
-
if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
|
|
379
|
-
delete resolutions[key];
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
} catch (err) {
|
|
384
|
-
console.error(`Error reading resolutions file: ${err.message}`);
|
|
372
|
+
const resolutions = safeJsonParse(resolutionsPath, null);
|
|
373
|
+
if (!resolutions) {
|
|
374
|
+
console.error(`Error reading resolutions file: ${resolutionsPath}`);
|
|
385
375
|
process.exit(1);
|
|
386
376
|
}
|
|
387
377
|
|
package/scripts/flow-diff.js
CHANGED
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
|
|
18
18
|
const fs = require('node:fs');
|
|
19
19
|
const path = require('node:path');
|
|
20
|
-
const readline = require('node:readline');
|
|
21
|
-
const { colors: c, getProjectRoot, readJson } = require('./flow-utils');
|
|
20
|
+
const readline = require('node:readline/promises');
|
|
21
|
+
const { colors: c, getProjectRoot, readJson, PATHS } = require('./flow-utils');
|
|
22
22
|
const { success: printSuccess, warn: printWarn, error: printError } = require('./flow-output');
|
|
23
23
|
|
|
24
24
|
/**
|
|
@@ -462,12 +462,9 @@ async function confirmApply(diffs) {
|
|
|
462
462
|
output: process.stdout
|
|
463
463
|
});
|
|
464
464
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
|
|
469
|
-
});
|
|
470
|
-
});
|
|
465
|
+
const answer = await rl.question(`\nApply these changes? [${c.green}y${c.reset}/${c.red}N${c.reset}]: `);
|
|
466
|
+
rl.close();
|
|
467
|
+
return answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes';
|
|
471
468
|
}
|
|
472
469
|
|
|
473
470
|
/**
|