|
| 1 | +--- |
| 2 | +issue: "#201" |
| 3 | +date: 2026-05-17 |
| 4 | +slug: fix-201-circular-dep-controller |
| 5 | +plan: docs/plans/2026-05-17-fix-201-circular-dep-controller.md |
| 6 | +--- |
| 7 | + |
| 8 | +# Spec: Fix circular dependency in `src/util/controller.ts` (R5) |
| 9 | + |
| 10 | +## Goal |
| 11 | + |
| 12 | +Break the direct circular import loop between `src/util/controller.ts` and `src/index.ts` by replacing the barrel import with explicit relative imports. |
| 13 | + |
| 14 | +## Why now |
| 15 | + |
| 16 | +`controller.ts` is the service locator used by every command and provider. It imports from `src/index.ts` (the root barrel), but `src/index.ts` itself re-exports `src/util/` — which contains `controller.ts`. This creates a direct cycle: |
| 17 | + |
| 18 | +``` |
| 19 | +src/index.ts → src/util/index.ts → controller.ts → src/index.ts |
| 20 | +``` |
| 21 | + |
| 22 | +Node.js resolves circular imports by returning the partially-initialized module, which can cause `undefined` values on first access (initialization order sensitive). Beyond runtime risk, the cycle blocks tree-shaking, degrades IDE navigation, and makes unit-testing `Ctrl` in isolation impossible without loading the full barrel. This fix aligns with PLAN.md Phase 2.3 ("Replace `import * as J from '..'` Pattern"). |
| 23 | + |
| 24 | +## In scope |
| 25 | + |
| 26 | +- `src/util/controller.ts`: replace `import * as J from '../.'` with 8 direct relative imports; update all usages of `J.*` references in that file. |
| 27 | + |
| 28 | +## Out of scope |
| 29 | + |
| 30 | +- `src/actions/parser.ts`, `writer.ts`, `reader.ts`, `inject.ts` — these also use the barrel import; they are separate Phase 2.3 work items. |
| 31 | +- No changes to `src/ext/`, `src/model/`, `src/provider/`, or test files. |
| 32 | +- No behavior changes — this is a pure refactor of import paths. |
| 33 | + |
| 34 | +## Acceptance criteria |
| 35 | + |
| 36 | +1. `controller.ts` contains no `import * as J from` line. |
| 37 | +2. `npm run compile` passes with no errors. |
| 38 | +3. `npm test` passes with no regressions (all existing tests green). |
| 39 | +4. `npm run lint` passes with no new warnings. |
| 40 | +5. No new `import * as` barrel usage introduced. |
| 41 | + |
| 42 | +## Entities / contracts |
| 43 | + |
| 44 | +Direct imports that replace `import * as J from '../.'`: |
| 45 | + |
| 46 | +| Old reference | New import | |
| 47 | +|---|---| |
| 48 | +| `J.Extension.Configuration` | `import { Configuration } from '../ext/conf'` | |
| 49 | +| `J.Extension.Dialogues` | `import { Dialogues } from '../ext/dialogues'` | |
| 50 | +| `J.Actions.Parser` | `import { Parser } from '../actions/parser'` | |
| 51 | +| `J.Actions.Writer` | `import { Writer } from '../actions/writer'` | |
| 52 | +| `J.Actions.Reader` | `import { Reader } from '../actions/reader'` | |
| 53 | +| `J.Actions.Inject` | `import { Inject } from '../actions/inject'` | |
| 54 | +| `J.Util.Logger` | `import { Logger } from './logger'` | |
| 55 | +| `J.Util.isNullOrUndefined` | `import { isNullOrUndefined } from './util'` | |
| 56 | + |
| 57 | +The `controller.ts` field and parameter types use these classes/interfaces directly; the only change is how they are referenced (qualified `J.*` → bare name). |
| 58 | + |
| 59 | +## Constraints |
| 60 | + |
| 61 | +- Must not change public API of `Ctrl` (no getter/setter signature changes). |
| 62 | +- Must not introduce new barrel imports (`import * as X from '...'`) in the file. |
| 63 | +- ESLint flat config requires `camelCase`/`PascalCase` import names — all direct imports satisfy this. |
| 64 | + |
| 65 | +## Open questions |
| 66 | + |
| 67 | +None. |
| 68 | + |
| 69 | +## Related issues |
| 70 | + |
| 71 | +- PLAN.md Phase 2.3: broader named-imports refactor across all action modules (separate work). |
0 commit comments