wogiflow 2.29.1 → 2.29.2
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/intent-grounded-reasoning.md +1 -1
- package/.workflow/templates/partials/methodology-rules.hbs +31 -0
- package/lib/mode-schema.js +2 -1
- package/lib/workspace-messages.js +2 -1
- package/lib/workspace.js +3 -3
- package/package.json +2 -2
- package/scripts/flow-orchestrate-corrections.js +158 -0
- package/scripts/flow-orchestrate.js +16 -97
|
@@ -18,7 +18,7 @@ Research finding: across 1,309 user messages mined, first-pass agent output was
|
|
|
18
18
|
| 1 | **Intent Bootstrap** — scaffolds product/domain/glossary/user-journeys artifacts; agnostic trap-zone detector finds structural ambiguities | `scripts/flow-intent-bootstrap.js` + `scripts/flow-trap-zone.js` |
|
|
19
19
|
| 2 | **Intent Framing Pass** — per-task reasoning step; produces a Framing Artifact resolving ambiguities before any other work | `scripts/flow-intent-framing.js` |
|
|
20
20
|
| 3 | **Architect Pass** — read-only sub-agent produces an 8-section pre-spec plan | `scripts/flow-architect-pass.js` + persona `.workflow/agents/architect.md` |
|
|
21
|
-
| 4 | **Logic Adversary** — separate sub-agent on a different model critiques the plan against the 11-principle Logic Constitution (v2
|
|
21
|
+
| 4 | **Logic Adversary** — separate sub-agent on a different model critiques the plan against the 11-principle Logic Constitution (v2: P11 + sub-principles 11.1–11.4 covering observed-behavior, project rules, sibling features, and stacked-story integration) | `scripts/flow-logic-adversary.js` + rubric `.workflow/rubrics/logic-constitution-v2.md` |
|
|
22
22
|
| 5 | **Session Correction Memory** — detects user corrections during a session and cross-references back to gates that passed the contradicted work | extensions in `scripts/flow-correction-detector.js` |
|
|
23
23
|
| 6 | **Completion Truth Gate** — audits "done" claims against Tier 0–4 evidence; downgrades language when evidence is insufficient | `scripts/flow-completion-truth-gate.js` |
|
|
24
24
|
| 7 | **Pipeline wiring + rollout** — integrates all above into `/wogi-start`, the gate registry, the eval framework | (this story) |
|
|
@@ -135,6 +135,37 @@ If artifacts don't exist yet, run `node scripts/flow-intent-bootstrap.js bootstr
|
|
|
135
135
|
|
|
136
136
|
---
|
|
137
137
|
|
|
138
|
+
### Cross-Story Integration Tier-3 Rule
|
|
139
|
+
|
|
140
|
+
When Story B layers behavior on top of infrastructure shipped by Story A (or any prior commit), Story B's IGR pass MUST treat that infrastructure as an audited dependency, not as a given. Within-module unit tests that pin Story B's local behavior do NOT verify that Story A's contract holds for Story B's usage.
|
|
141
|
+
|
|
142
|
+
**Mandatory for every layering story:**
|
|
143
|
+
|
|
144
|
+
1. **Architect output names upstream dependencies.** A "Dependencies" section lists prior stories/commits + the specific contract relied on (interface signature, file format, transport, invariant). "I'm reusing Story A's X" is not enough; quote the contract.
|
|
145
|
+
|
|
146
|
+
2. **Adversary challenges the dependency.** "What if Story A's invariant doesn't hold? What's the failure mode? What evidence proves Story A's contract is intact for THIS usage?" The adversary's job is finding the assumption Story B silently inherits.
|
|
147
|
+
|
|
148
|
+
3. **At least one Tier-3 integration test exercises the chain end-to-end.** Not a unit test of Story B in isolation — a test that simulates a real run through both stories' code paths. If Story A's output flows into Story B's input, the test feeds a real Story-A output through Story B and asserts the output. Mark the test `// regression-tier3` so future readers know its purpose.
|
|
149
|
+
|
|
150
|
+
4. **Pre-release gate verifies stacked coverage.** Before tagging a release, identify any commits that layer on prior commits in the same release. For each, confirm a Tier-3 integration test exists. Missing Tier-3 + stacked stories → block release.
|
|
151
|
+
|
|
152
|
+
**Why:** unit tests within a story boundary catch the story's own bugs but miss every regression where the story's correct behavior depends on a broken upstream. The 2026-04-26 incident (audit-channel-transport-001) was caused by exactly this gap: Story A stripped MCP servers including the workspace-channel transport itself; Story B layered task-completion routing on top; both stories' tests passed; manager dispatch silently failed in production. Self-IGR caught Story B's local correctness but missed that the upstream contract was broken.
|
|
153
|
+
|
|
154
|
+
**Anti-rationalization:**
|
|
155
|
+
- *"The upstream story has its own tests"* → WRONG. Their tests pin THEIR contract. Your Tier-3 test pins YOUR usage of their contract.
|
|
156
|
+
- *"It's expensive to set up an integration test"* → WRONG. The 2026-04-26 incident cost a v2.29.1 hot-fix release. Set up time amortizes; regression cost compounds.
|
|
157
|
+
- *"Self-IGR is enough; we don't need the actual adversary subagent"* → WRONG. Self-IGR pattern-matches on the same model that wrote the plan; the cross-story dependency is exactly the blind spot a different-model adversary catches.
|
|
158
|
+
|
|
159
|
+
**How to apply** (concrete checks for any layering story):
|
|
160
|
+
- `git log --oneline <prior-N-commits>` — which earlier work does this story sit on?
|
|
161
|
+
- For each, write the contract you're relying on: "Story A delivers X via Y."
|
|
162
|
+
- `grep -r "<Story A's interface>"` — is the contract still intact in HEAD?
|
|
163
|
+
- Write the Tier-3 test BEFORE writing Story B's code. If the test cannot be written without first standing up infrastructure that makes the integration verifiable, that's a signal the architecture needs that infrastructure too.
|
|
164
|
+
|
|
165
|
+
Enforced by: Logic Constitution v2 sub-principle 11.4 (Stacked-story integration verification). Pre-release gate consumes this signal before tagging.
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
138
169
|
### Autonomous Walk-Away Mode
|
|
139
170
|
|
|
140
171
|
The user can dump N items, say "go until you finish" / "autonomous mode" / "run this autonomously" / "don't bother me, just do it" (or similar phrases — see `flow-autonomous-detector.js`), and walk away. While the run is active:
|
package/lib/mode-schema.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('node:fs');
|
|
4
4
|
const path = require('node:path');
|
|
5
|
+
const { DANGEROUS_KEYS } = require('../scripts/flow-io');
|
|
5
6
|
|
|
6
7
|
const MODES_DIR = path.join(process.cwd(), '.workflow', 'modes');
|
|
7
8
|
|
|
@@ -17,7 +18,7 @@ const REQUIRED_FIELDS = ['name', 'roleDefinition', 'whenToUse'];
|
|
|
17
18
|
const OPTIONAL_FIELDS = ['customInstructions', 'allowedToolGroups'];
|
|
18
19
|
const ALL_FIELDS = new Set([...REQUIRED_FIELDS, ...OPTIONAL_FIELDS]);
|
|
19
20
|
|
|
20
|
-
|
|
21
|
+
// DANGEROUS_KEYS imported from scripts/flow-io canonical (audit dup-002 / wf-9fc4970b).
|
|
21
22
|
|
|
22
23
|
function parseModeYaml(content, sourceLabel = '<inline>') {
|
|
23
24
|
const result = Object.create(null);
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
const fs = require('node:fs');
|
|
12
12
|
const path = require('node:path');
|
|
13
13
|
const { safeReadJson } = require('./utils');
|
|
14
|
+
const { DANGEROUS_KEYS } = require('../scripts/flow-io');
|
|
14
15
|
const crypto = require('node:crypto');
|
|
15
16
|
|
|
16
17
|
// ============================================================
|
|
@@ -170,7 +171,7 @@ function updateMessageStatus(workspaceRoot, messageId, newStatus, extra = {}) {
|
|
|
170
171
|
message.updatedAt = new Date().toISOString();
|
|
171
172
|
// Safe merge: filter dangerous keys to prevent prototype pollution
|
|
172
173
|
if (extra && typeof extra === 'object') {
|
|
173
|
-
|
|
174
|
+
// DANGEROUS_KEYS imported from scripts/flow-io canonical (audit dup-002 / wf-9fc4970b).
|
|
174
175
|
for (const [key, value] of Object.entries(extra)) {
|
|
175
176
|
if (!DANGEROUS_KEYS.has(key)) {
|
|
176
177
|
message[key] = value;
|
package/lib/workspace.js
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
const fs = require('node:fs');
|
|
19
19
|
const path = require('node:path');
|
|
20
|
-
const { safeJsonParse } = require('../scripts/flow-io');
|
|
20
|
+
const { safeJsonParse, DANGEROUS_KEYS } = require('../scripts/flow-io');
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* wf-f747f993 — resolve the claude-spawning command for workspace sessions.
|
|
@@ -61,8 +61,8 @@ function resolveClaudeSpawnCommand(role, flags) {
|
|
|
61
61
|
// Constants
|
|
62
62
|
// ============================================================
|
|
63
63
|
|
|
64
|
-
// Proto-pollution safe JSON parse (finding-007)
|
|
65
|
-
|
|
64
|
+
// Proto-pollution safe JSON parse (finding-007).
|
|
65
|
+
// DANGEROUS_KEYS imported from scripts/flow-io canonical (audit dup-002 / wf-9fc4970b).
|
|
66
66
|
function safeParseJson(str, fallback) {
|
|
67
67
|
try {
|
|
68
68
|
const obj = JSON.parse(str);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wogiflow",
|
|
3
|
-
"version": "2.29.
|
|
3
|
+
"version": "2.29.2",
|
|
4
4
|
"description": "AI-powered development workflow management system with multi-model support",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
},
|
|
11
11
|
"scripts": {
|
|
12
12
|
"flow": "./scripts/flow",
|
|
13
|
-
"test": "NODE_ENV=test node --test tests/auto-compact-prompt.test.js tests/flow-paths.test.js tests/flow-io.test.js tests/flow-config-loader.test.js tests/flow-damage-control.test.js tests/flow-output.test.js tests/flow-constants.test.js tests/flow-session-state.test.js tests/flow-hooks-integration.test.js tests/flow-utils.test.js tests/flow-security.test.js tests/flow-memory-db.test.js tests/flow-durable-session.test.js tests/flow-skill-matcher.test.js tests/flow-bridge.test.js tests/flow-proactive-compact.test.js tests/flow-cascade-completion.test.js tests/flow-capture-gate.test.js tests/flow-correction-detector-hybrid.test.js tests/flow-promote.test.js tests/flow-archive-runs.test.js tests/flow-memory.test.js tests/flow-hooks-pre-tool-helpers.test.js tests/flow-hooks-bugfix-scope-gate.test.js tests/flow-hooks-routing-gate.test.js tests/flow-hooks-phase-read-gate.test.js tests/flow-hooks-commit-log-gate.test.js tests/flow-hooks-deploy-gate.test.js tests/flow-hooks-todowrite-gate.test.js tests/flow-hooks-git-safety-gate.test.js tests/flow-hooks-scope-mutation-gate.test.js tests/flow-hooks-strike-gate.test.js tests/flow-hooks-component-check.test.js tests/flow-hooks-scope-gate.test.js tests/flow-hooks-implementation-gate.test.js tests/flow-hooks-research-gate.test.js tests/flow-hooks-loop-check.test.js tests/flow-hooks-manager-boundary-gate.test.js tests/flow-hooks-phase-gate.test.js tests/flow-hooks-pre-tool-orchestrator.test.js tests/flow-hooks-observation-capture.test.js tests/flow-hooks-task-gate.test.js tests/flow-durable-session-suspension.test.js tests/flow-health-mcp-scopes.test.js tests/flow-lean-config.test.js tests/flow-workspace-autopickup.test.js tests/flow-worker-boundary-gate.test.js tests/flow-worker-question-classifier.test.js tests/flow-completion-truth-gate-contradictions.test.js tests/flow-structure-sensor.test.js tests/flow-workspace-dispatch-tracking.test.js tests/workspace-ipc-sqlite.test.js tests/workspace-ipc-multi-worker.test.js tests/flow-story-gates.test.js tests/flow-workspace-restart-handoff.test.js tests/flow-wogi-claude-wrapper.test.js tests/flow-wave1-integrations.test.js tests/flow-wave2-integrations.test.js tests/flow-wave3-integrations.test.js tests/flow-commit-claims-gate.test.js tests/auto-review.test.js tests/gate-telemetry-surface.test.js tests/agents-md-alias.test.js tests/flow-skill-manage.test.js tests/fuzzy-patch.test.js tests/mode-schema.test.js tests/flow-feature-dossier.test.js tests/flow-autonomous-mode.test.js tests/flow-epic-cascade.test.js tests/flow-workspace-summary.test.js tests/flow-hooks-research-evidence-gate.test.js tests/flow-worker-mcp-strip.test.js && NODE_ENV=test node tests/run-quality-gates.test.js",
|
|
13
|
+
"test": "NODE_ENV=test node --test tests/auto-compact-prompt.test.js tests/flow-paths.test.js tests/flow-io.test.js tests/flow-config-loader.test.js tests/flow-damage-control.test.js tests/flow-output.test.js tests/flow-constants.test.js tests/flow-session-state.test.js tests/flow-hooks-integration.test.js tests/flow-utils.test.js tests/flow-security.test.js tests/flow-memory-db.test.js tests/flow-durable-session.test.js tests/flow-skill-matcher.test.js tests/flow-bridge.test.js tests/flow-proactive-compact.test.js tests/flow-cascade-completion.test.js tests/flow-capture-gate.test.js tests/flow-correction-detector-hybrid.test.js tests/flow-promote.test.js tests/flow-archive-runs.test.js tests/flow-memory.test.js tests/flow-hooks-pre-tool-helpers.test.js tests/flow-hooks-bugfix-scope-gate.test.js tests/flow-hooks-routing-gate.test.js tests/flow-hooks-phase-read-gate.test.js tests/flow-hooks-commit-log-gate.test.js tests/flow-hooks-deploy-gate.test.js tests/flow-hooks-todowrite-gate.test.js tests/flow-hooks-git-safety-gate.test.js tests/flow-hooks-scope-mutation-gate.test.js tests/flow-hooks-strike-gate.test.js tests/flow-hooks-component-check.test.js tests/flow-hooks-scope-gate.test.js tests/flow-hooks-implementation-gate.test.js tests/flow-hooks-research-gate.test.js tests/flow-hooks-loop-check.test.js tests/flow-hooks-manager-boundary-gate.test.js tests/flow-hooks-phase-gate.test.js tests/flow-hooks-pre-tool-orchestrator.test.js tests/flow-hooks-observation-capture.test.js tests/flow-hooks-task-gate.test.js tests/flow-durable-session-suspension.test.js tests/flow-health-mcp-scopes.test.js tests/flow-lean-config.test.js tests/flow-workspace-autopickup.test.js tests/flow-worker-boundary-gate.test.js tests/flow-worker-question-classifier.test.js tests/flow-completion-truth-gate-contradictions.test.js tests/flow-structure-sensor.test.js tests/flow-workspace-dispatch-tracking.test.js tests/workspace-ipc-sqlite.test.js tests/workspace-ipc-multi-worker.test.js tests/flow-story-gates.test.js tests/flow-workspace-restart-handoff.test.js tests/flow-wogi-claude-wrapper.test.js tests/flow-wave1-integrations.test.js tests/flow-wave2-integrations.test.js tests/flow-wave3-integrations.test.js tests/flow-commit-claims-gate.test.js tests/auto-review.test.js tests/gate-telemetry-surface.test.js tests/agents-md-alias.test.js tests/flow-skill-manage.test.js tests/fuzzy-patch.test.js tests/mode-schema.test.js tests/flow-feature-dossier.test.js tests/flow-autonomous-mode.test.js tests/flow-epic-cascade.test.js tests/flow-workspace-summary.test.js tests/flow-hooks-research-evidence-gate.test.js tests/flow-worker-mcp-strip.test.js tests/flow-orchestrate-corrections.test.js && NODE_ENV=test node tests/run-quality-gates.test.js",
|
|
14
14
|
"test:syntax": "find scripts/ lib/ -name '*.js' -not -path '*/node_modules/*' -exec node --check {} +",
|
|
15
15
|
"lint": "eslint scripts/ lib/ tests/",
|
|
16
16
|
"lint:ci": "eslint scripts/ lib/ tests/ --max-warnings 0",
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Wogi Flow — Auto-Correction Helpers (Story 14 / wf-d0937c83)
|
|
5
|
+
*
|
|
6
|
+
* Splits flow-orchestrate's autoCorrectCode into focused helpers:
|
|
7
|
+
*
|
|
8
|
+
* - fixForbiddenImports (section 1: doNotImport, default/combined/namespace forms)
|
|
9
|
+
* - fixComponentPaths (section 2: shadcn @/components/ui/X mapping)
|
|
10
|
+
* - fixFeatureTypePaths (section 3: ../types in /features/ files)
|
|
11
|
+
* - fixNoExternalUtils (section 4: @/lib/utils removal + formatCurrency inline + cn() unwrap)
|
|
12
|
+
* - normalizeQuotes (section 5: double-quote → single-quote when single dominates)
|
|
13
|
+
* - cleanupEmptyImports (section 6: empty `import {} from "..."` cleanup)
|
|
14
|
+
* - collapseBlankLines (section 7: 3+ blanks → 2)
|
|
15
|
+
*
|
|
16
|
+
* Each helper takes a `(code, ...args)` shape and returns
|
|
17
|
+
* `{ corrected, corrections }`. The orchestrator (flow-orchestrate.js
|
|
18
|
+
* `autoCorrectCode`) chains them in section order.
|
|
19
|
+
*
|
|
20
|
+
* Behavior is preserved verbatim from the pre-extraction implementation;
|
|
21
|
+
* pinned by characterization tests in
|
|
22
|
+
* `tests/flow-orchestrate-corrections.test.js` (Tier-3 integration test
|
|
23
|
+
* per the Cross-Story Integration Tier-3 Rule — feeds real input through
|
|
24
|
+
* the public `autoCorrectCode` API and asserts the output).
|
|
25
|
+
*
|
|
26
|
+
* Programmatic:
|
|
27
|
+
* const c = require('./flow-orchestrate-corrections');
|
|
28
|
+
* const { corrected, corrections } = c.fixForbiddenImports(code, ['React']);
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
'use strict';
|
|
32
|
+
|
|
33
|
+
function fixForbiddenImports(code, doNotImport) {
|
|
34
|
+
let corrected = code;
|
|
35
|
+
const corrections = [];
|
|
36
|
+
const list = Array.isArray(doNotImport) && doNotImport.length ? doNotImport : ['React'];
|
|
37
|
+
for (const forbidden of list) {
|
|
38
|
+
const defaultImportRegex = new RegExp(`^import ${forbidden} from ['"][^'"]+['"];?\\s*\\n?`, 'gm');
|
|
39
|
+
if (defaultImportRegex.test(corrected)) {
|
|
40
|
+
corrected = corrected.replace(defaultImportRegex, '');
|
|
41
|
+
corrections.push(`Removed forbidden import: ${forbidden}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const combinedImportRegex = new RegExp(`^import ${forbidden},\\s*(\\{[^}]+\\})\\s+from\\s+(['"][^'"]+['"])`, 'gm');
|
|
45
|
+
if (combinedImportRegex.test(corrected)) {
|
|
46
|
+
corrected = corrected.replace(combinedImportRegex, 'import $1 from $2');
|
|
47
|
+
corrections.push(`Removed ${forbidden} from combined import`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const namespaceImportRegex = new RegExp(`^import \\* as ${forbidden} from ['"][^'"]+['"];?\\s*\\n?`, 'gm');
|
|
51
|
+
if (namespaceImportRegex.test(corrected)) {
|
|
52
|
+
corrected = corrected.replace(namespaceImportRegex, '');
|
|
53
|
+
corrections.push(`Removed namespace import: ${forbidden}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return { corrected, corrections };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function fixComponentPaths(code, componentPaths) {
|
|
60
|
+
let corrected = code;
|
|
61
|
+
const corrections = [];
|
|
62
|
+
const map = (componentPaths && typeof componentPaths === 'object') ? componentPaths : {};
|
|
63
|
+
const shadcnPattern = /@\/components\/ui\/(\w+)/g;
|
|
64
|
+
corrected = corrected.replace(shadcnPattern, (match, component) => {
|
|
65
|
+
const capitalName = component.charAt(0).toUpperCase() + component.slice(1);
|
|
66
|
+
const configPath = map[capitalName];
|
|
67
|
+
if (configPath) {
|
|
68
|
+
corrections.push(`Fixed import: ${match} → ${configPath}`);
|
|
69
|
+
return configPath;
|
|
70
|
+
}
|
|
71
|
+
return match;
|
|
72
|
+
});
|
|
73
|
+
return { corrected, corrections };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function fixFeatureTypePaths(code, filePath, typePaths) {
|
|
77
|
+
let corrected = code;
|
|
78
|
+
const corrections = [];
|
|
79
|
+
const paths = (typePaths && typeof typePaths === 'object') ? typePaths : { features: '../api/types' };
|
|
80
|
+
if (filePath && filePath.includes('/features/') && paths.features) {
|
|
81
|
+
const wrongPaths = ["'../types'", '"../types"', "'./types'", '"./types"'];
|
|
82
|
+
for (const wrong of wrongPaths) {
|
|
83
|
+
if (corrected.includes(wrong)) {
|
|
84
|
+
corrected = corrected.replace(new RegExp(wrong.replace(/['"]/g, '[\'"]'), 'g'), `'${paths.features}'`);
|
|
85
|
+
corrections.push('Fixed type import path');
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return { corrected, corrections };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function fixNoExternalUtils(code, ctx) {
|
|
93
|
+
let corrected = code;
|
|
94
|
+
const corrections = [];
|
|
95
|
+
if (!(ctx && ctx.noExternalUtils && corrected.includes('@/lib/utils'))) {
|
|
96
|
+
return { corrected, corrections };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const hadFormatCurrency = corrected.includes('formatCurrency');
|
|
100
|
+
const hadCn = corrected.includes(' cn(') || corrected.includes(' cn`');
|
|
101
|
+
|
|
102
|
+
corrected = corrected.replace(/^import.*from ['"]@\/lib\/utils['"];?\s*\n?/gm, '');
|
|
103
|
+
corrections.push('Removed @/lib/utils import');
|
|
104
|
+
|
|
105
|
+
if (hadFormatCurrency) {
|
|
106
|
+
const formatCurrencyFn = `\nconst formatCurrency = (amount: number) =>\n new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(amount);\n`;
|
|
107
|
+
const lastImportMatch = corrected.match(/^import[^;]+;?\s*\n/gm);
|
|
108
|
+
if (lastImportMatch) {
|
|
109
|
+
const lastImport = lastImportMatch[lastImportMatch.length - 1];
|
|
110
|
+
const insertPos = corrected.lastIndexOf(lastImport) + lastImport.length;
|
|
111
|
+
corrected = corrected.slice(0, insertPos) + formatCurrencyFn + corrected.slice(insertPos);
|
|
112
|
+
}
|
|
113
|
+
corrections.push('Inlined formatCurrency');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (hadCn) {
|
|
117
|
+
corrected = corrected.replace(/cn\((['"`][^'"`]+['"`])\)/g, '$1');
|
|
118
|
+
corrections.push('Removed cn() wrapper');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return { corrected, corrections };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function normalizeQuotes(code) {
|
|
125
|
+
let corrected = code;
|
|
126
|
+
const corrections = [];
|
|
127
|
+
const singleQuoteCount = (corrected.match(/from '/g) || []).length;
|
|
128
|
+
const doubleQuoteCount = (corrected.match(/from "/g) || []).length;
|
|
129
|
+
if (singleQuoteCount > doubleQuoteCount && doubleQuoteCount > 0) {
|
|
130
|
+
corrected = corrected.replace(/from "([^"]+)"/g, "from '$1'");
|
|
131
|
+
corrections.push('Normalized import quotes to single quotes');
|
|
132
|
+
}
|
|
133
|
+
return { corrected, corrections };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function cleanupEmptyImports(code) {
|
|
137
|
+
return {
|
|
138
|
+
corrected: code.replace(/^import\s*\{\s*\}\s*from\s*['"][^'"]+['"];?\s*\n?/gm, ''),
|
|
139
|
+
corrections: []
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function collapseBlankLines(code) {
|
|
144
|
+
return {
|
|
145
|
+
corrected: code.replace(/\n{3,}/g, '\n\n'),
|
|
146
|
+
corrections: []
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
module.exports = {
|
|
151
|
+
fixForbiddenImports,
|
|
152
|
+
fixComponentPaths,
|
|
153
|
+
fixFeatureTypePaths,
|
|
154
|
+
fixNoExternalUtils,
|
|
155
|
+
normalizeQuotes,
|
|
156
|
+
cleanupEmptyImports,
|
|
157
|
+
collapseBlankLines
|
|
158
|
+
};
|
|
@@ -307,109 +307,28 @@ function autoCorrectCode(code, filePath, projectConfig = null) {
|
|
|
307
307
|
return { corrected: code, corrections: [] };
|
|
308
308
|
}
|
|
309
309
|
|
|
310
|
-
// Load project context from config if not provided
|
|
311
310
|
const ctx = projectConfig?.projectContext ?? getProjectContext();
|
|
312
|
-
|
|
313
|
-
let corrected = code;
|
|
314
311
|
const corrections = [];
|
|
315
312
|
|
|
316
|
-
//
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
// Case A: Default import - "import X from '...'"
|
|
320
|
-
const defaultImportRegex = new RegExp(`^import ${forbidden} from ['"][^'"]+['"];?\\s*\\n?`, 'gm');
|
|
321
|
-
if (defaultImportRegex.test(corrected)) {
|
|
322
|
-
corrected = corrected.replace(defaultImportRegex, '');
|
|
323
|
-
corrections.push(`Removed forbidden import: ${forbidden}`);
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
// Case B: Combined with named imports - "import X, { y, z } from '...'"
|
|
327
|
-
const combinedImportRegex = new RegExp(`^import ${forbidden},\\s*(\\{[^}]+\\})\\s+from\\s+(['"][^'"]+['"])`, 'gm');
|
|
328
|
-
if (combinedImportRegex.test(corrected)) {
|
|
329
|
-
corrected = corrected.replace(combinedImportRegex, 'import $1 from $2');
|
|
330
|
-
corrections.push(`Removed ${forbidden} from combined import`);
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
// Case C: Namespace import - "import * as X from '...'"
|
|
334
|
-
const namespaceImportRegex = new RegExp(`^import \\* as ${forbidden} from ['"][^'"]+['"];?\\s*\\n?`, 'gm');
|
|
335
|
-
if (namespaceImportRegex.test(corrected)) {
|
|
336
|
-
corrected = corrected.replace(namespaceImportRegex, '');
|
|
337
|
-
corrections.push(`Removed namespace import: ${forbidden}`);
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
// 2. Fix component paths based on config mappings
|
|
342
|
-
const componentPaths = ctx.componentPaths ?? {};
|
|
343
|
-
|
|
344
|
-
// Build reverse mapping from shadcn-style to project paths
|
|
345
|
-
// @/components/ui/button → project's Button path
|
|
346
|
-
const shadcnPattern = /@\/components\/ui\/(\w+)/g;
|
|
347
|
-
corrected = corrected.replace(shadcnPattern, (match, component) => {
|
|
348
|
-
const capitalName = component.charAt(0).toUpperCase() + component.slice(1);
|
|
349
|
-
const configPath = componentPaths[capitalName];
|
|
350
|
-
if (configPath) {
|
|
351
|
-
corrections.push(`Fixed import: ${match} → ${configPath}`);
|
|
352
|
-
return configPath;
|
|
353
|
-
}
|
|
354
|
-
return match; // Leave as-is if no mapping
|
|
355
|
-
});
|
|
356
|
-
|
|
357
|
-
// 3. Fix type paths for features (from config)
|
|
358
|
-
const typePaths = ctx.typePaths || { features: '../api/types' };
|
|
359
|
-
if (filePath && filePath.includes('/features/') && typePaths.features) {
|
|
360
|
-
const wrongPaths = ["'../types'", '"../types"', "'./types'", '"./types"'];
|
|
361
|
-
for (const wrong of wrongPaths) {
|
|
362
|
-
if (corrected.includes(wrong)) {
|
|
363
|
-
corrected = corrected.replace(new RegExp(wrong.replace(/['"]/g, '[\'"]'), 'g'), `'${typePaths.features}'`);
|
|
364
|
-
corrections.push('Fixed type import path');
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
// 4. Remove external utils if configured (noExternalUtils: true)
|
|
370
|
-
if (ctx.noExternalUtils && corrected.includes('@/lib/utils')) {
|
|
371
|
-
const hadFormatCurrency = corrected.includes('formatCurrency');
|
|
372
|
-
const hadCn = corrected.includes(' cn(') || corrected.includes(' cn`');
|
|
373
|
-
|
|
374
|
-
// Remove the import
|
|
375
|
-
corrected = corrected.replace(/^import.*from ['"]@\/lib\/utils['"];?\s*\n?/gm, '');
|
|
376
|
-
corrections.push('Removed @/lib/utils import');
|
|
377
|
-
|
|
378
|
-
// Inline formatCurrency if it was used
|
|
379
|
-
if (hadFormatCurrency) {
|
|
380
|
-
const formatCurrencyFn = `\nconst formatCurrency = (amount: number) =>\n new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(amount);\n`;
|
|
381
|
-
// Insert after imports
|
|
382
|
-
const lastImportMatch = corrected.match(/^import[^;]+;?\s*\n/gm);
|
|
383
|
-
if (lastImportMatch) {
|
|
384
|
-
const lastImport = lastImportMatch[lastImportMatch.length - 1];
|
|
385
|
-
const insertPos = corrected.lastIndexOf(lastImport) + lastImport.length;
|
|
386
|
-
corrected = corrected.slice(0, insertPos) + formatCurrencyFn + corrected.slice(insertPos);
|
|
387
|
-
}
|
|
388
|
-
corrections.push('Inlined formatCurrency');
|
|
389
|
-
}
|
|
313
|
+
// Lazy-load the helpers — avoids module-load-order issues with the
|
|
314
|
+
// legacy CLI bootstrap at the bottom of this file.
|
|
315
|
+
const c = require('./flow-orchestrate-corrections');
|
|
390
316
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
corrected = corrected
|
|
403
|
-
corrections.push(
|
|
317
|
+
let corrected = code;
|
|
318
|
+
for (const step of [
|
|
319
|
+
() => c.fixForbiddenImports(corrected, ctx.doNotImport),
|
|
320
|
+
() => c.fixComponentPaths(corrected, ctx.componentPaths),
|
|
321
|
+
() => c.fixFeatureTypePaths(corrected, filePath, ctx.typePaths),
|
|
322
|
+
() => c.fixNoExternalUtils(corrected, ctx),
|
|
323
|
+
() => c.normalizeQuotes(corrected),
|
|
324
|
+
() => c.cleanupEmptyImports(corrected),
|
|
325
|
+
() => c.collapseBlankLines(corrected)
|
|
326
|
+
]) {
|
|
327
|
+
const r = step();
|
|
328
|
+
corrected = r.corrected;
|
|
329
|
+
if (r.corrections.length) corrections.push(...r.corrections);
|
|
404
330
|
}
|
|
405
331
|
|
|
406
|
-
// 6. Remove empty import statements (artifact of removing imports)
|
|
407
|
-
corrected = corrected.replace(/^import\s*\{\s*\}\s*from\s*['"][^'"]+['"];?\s*\n?/gm, '');
|
|
408
|
-
|
|
409
|
-
// 7. Fix multiple consecutive blank lines (cleanup)
|
|
410
|
-
corrected = corrected.replace(/\n{3,}/g, '\n\n');
|
|
411
|
-
|
|
412
|
-
// Log corrections if any
|
|
413
332
|
if (corrections.length > 0 && typeof log === 'function') {
|
|
414
333
|
log('dim', ` 🔧 Auto-corrected: ${corrections.join(', ')}`);
|
|
415
334
|
}
|