@prover-coder-ai/context-doc 1.0.18 → 1.0.19

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.
@@ -1,220 +0,0 @@
1
- // CHANGE: add Effect-TS compliance lint profile
2
- // WHY: detect current deviations from strict Effect-TS guidance
3
- // QUOTE(TZ): n/a
4
- // REF: AGENTS.md Effect-TS compliance checks
5
- // SOURCE: n/a
6
- // PURITY: SHELL
7
- // EFFECT: eslint config
8
- // INVARIANT: config only flags explicit policy deviations
9
- // COMPLEXITY: O(1)/O(1)
10
- import eslintComments from "@eslint-community/eslint-plugin-eslint-comments"
11
- import globals from "globals"
12
- import tseslint from "typescript-eslint"
13
-
14
- const restrictedImports = [
15
- {
16
- name: "node:fs",
17
- message: "Use @effect/platform FileSystem instead of node:fs."
18
- },
19
- {
20
- name: "fs",
21
- message: "Use @effect/platform FileSystem instead of fs."
22
- },
23
- {
24
- name: "node:fs/promises",
25
- message: "Use @effect/platform FileSystem instead of node:fs/promises."
26
- },
27
- {
28
- name: "node:path/posix",
29
- message: "Use @effect/platform Path instead of node:path/posix."
30
- },
31
- {
32
- name: "node:path",
33
- message: "Use @effect/platform Path instead of node:path."
34
- },
35
- {
36
- name: "path",
37
- message: "Use @effect/platform Path instead of path."
38
- },
39
- {
40
- name: "node:child_process",
41
- message: "Use @effect/platform Command instead of node:child_process."
42
- },
43
- {
44
- name: "child_process",
45
- message: "Use @effect/platform Command instead of child_process."
46
- },
47
- {
48
- name: "node:process",
49
- message: "Use @effect/platform Runtime instead of node:process."
50
- },
51
- {
52
- name: "process",
53
- message: "Use @effect/platform Runtime instead of process."
54
- }
55
- ]
56
-
57
- const restrictedSyntaxBase = [
58
- {
59
- selector: "SwitchStatement",
60
- message: "Switch is forbidden. Use Match.exhaustive."
61
- },
62
- {
63
- selector: "TryStatement",
64
- message: "Avoid try/catch in product code. Use Effect.try / Effect.catch*."
65
- },
66
- {
67
- selector: "AwaitExpression",
68
- message: "Avoid await. Use Effect.gen / Effect.flatMap."
69
- },
70
- {
71
- selector: "FunctionDeclaration[async=true], FunctionExpression[async=true], ArrowFunctionExpression[async=true]",
72
- message: "Avoid async/await. Use Effect.gen / Effect.tryPromise."
73
- },
74
- {
75
- selector: "NewExpression[callee.name='Promise']",
76
- message: "Avoid new Promise. Use Effect.async / Effect.tryPromise."
77
- },
78
- {
79
- selector: "CallExpression[callee.object.name='Promise']",
80
- message: "Avoid Promise.*. Use Effect combinators."
81
- },
82
- {
83
- selector: "CallExpression[callee.name='require']",
84
- message: "Avoid require(). Use ES module imports."
85
- },
86
- {
87
- selector: "TSAsExpression",
88
- message: "Casting is only allowed in src/core/axioms.ts."
89
- },
90
- {
91
- selector: "TSTypeAssertion",
92
- message: "Casting is only allowed in src/core/axioms.ts."
93
- },
94
- {
95
- selector: "CallExpression[callee.name='makeFilesystemService']",
96
- message: "Do not instantiate FilesystemService directly. Provide Layer and access via Tag."
97
- },
98
- {
99
- selector: "CallExpression[callee.property.name='catchAll']",
100
- message: "Avoid catchAll that discards typed errors; map or propagate explicitly."
101
- }
102
- ]
103
-
104
- const restrictedSyntaxCore = [
105
- ...restrictedSyntaxBase,
106
- {
107
- selector: "TSUnknownKeyword",
108
- message: "unknown is allowed only at shell boundaries with decoding."
109
- },
110
- {
111
- selector: "CallExpression[callee.property.name='runSyncExit']",
112
- message: "Effect.runSyncExit is shell-only. Move to a runner."
113
- },
114
- {
115
- selector: "CallExpression[callee.property.name='runSync']",
116
- message: "Effect.runSync is shell-only. Move to a runner."
117
- },
118
- {
119
- selector: "CallExpression[callee.property.name='runPromise']",
120
- message: "Effect.runPromise is shell-only. Move to a runner."
121
- }
122
- ]
123
-
124
- const restrictedSyntaxCoreNoAs = [
125
- ...restrictedSyntaxCore.filter((rule) =>
126
- rule.selector !== "TSAsExpression" && rule.selector !== "TSTypeAssertion"
127
- )
128
- ]
129
-
130
- const restrictedSyntaxBaseNoServiceFactory = [
131
- ...restrictedSyntaxBase.filter((rule) =>
132
- rule.selector !== "CallExpression[callee.name='makeFilesystemService']"
133
- )
134
- ]
135
-
136
- export default tseslint.config(
137
- {
138
- name: "effect-ts-compliance-check",
139
- files: ["src/**/*.ts", "scripts/**/*.ts"],
140
- languageOptions: {
141
- parser: tseslint.parser,
142
- globals: { ...globals.node }
143
- },
144
- plugins: {
145
- "@typescript-eslint": tseslint.plugin,
146
- "eslint-comments": eslintComments
147
- },
148
- rules: {
149
- "no-console": "error",
150
- "no-restricted-imports": ["error", {
151
- paths: restrictedImports,
152
- patterns: [
153
- {
154
- group: ["node:*"],
155
- message: "Do not import from node:* directly. Use @effect/platform-node or @effect/platform services."
156
- }
157
- ]
158
- }],
159
- "no-restricted-syntax": ["error", ...restrictedSyntaxBase],
160
- "@typescript-eslint/no-explicit-any": "error",
161
- "@typescript-eslint/ban-ts-comment": ["error", {
162
- "ts-ignore": true,
163
- "ts-nocheck": true,
164
- "ts-check": false,
165
- "ts-expect-error": true
166
- }],
167
- "@typescript-eslint/no-restricted-types": ["error", {
168
- types: {
169
- Promise: {
170
- message: "Avoid Promise in types. Use Effect.Effect<A, E, R>."
171
- },
172
- "Promise<*>": {
173
- message: "Avoid Promise<T>. Use Effect.Effect<T, E, R>."
174
- }
175
- }
176
- }],
177
- "eslint-comments/no-use": "error",
178
- "eslint-comments/no-unlimited-disable": "error",
179
- "eslint-comments/disable-enable-pair": "error",
180
- "eslint-comments/no-unused-disable": "error"
181
- }
182
- },
183
- {
184
- name: "effect-ts-compliance-core",
185
- files: ["src/core/**/*.ts"],
186
- rules: {
187
- "no-restricted-syntax": ["error", ...restrictedSyntaxCore],
188
- "no-restricted-imports": ["error", {
189
- paths: restrictedImports,
190
- patterns: [
191
- {
192
- group: [
193
- "../shell/**",
194
- "../../shell/**",
195
- "../../../shell/**",
196
- "./shell/**",
197
- "src/shell/**",
198
- "shell/**"
199
- ],
200
- message: "CORE must not import from SHELL."
201
- }
202
- ]
203
- }]
204
- }
205
- },
206
- {
207
- name: "effect-ts-compliance-axioms",
208
- files: ["src/core/axioms.ts"],
209
- rules: {
210
- "no-restricted-syntax": ["error", ...restrictedSyntaxCoreNoAs]
211
- }
212
- },
213
- {
214
- name: "effect-ts-compliance-filesystem-service",
215
- files: ["src/shell/services/filesystem.ts"],
216
- rules: {
217
- "no-restricted-syntax": ["error", ...restrictedSyntaxBaseNoServiceFactory]
218
- }
219
- }
220
- )
@@ -1,33 +0,0 @@
1
- {
2
- "priorityLevels": [
3
- {
4
- "level": 1,
5
- "name": "Critical Compiler Errors",
6
- "rules": [
7
- "ts(2835)",
8
- "ts(2307)",
9
- "@prover-coder-ai/suggest-members/suggest-members",
10
- "@prover-coder-ai/suggest-members/suggest-imports",
11
- "@prover-coder-ai/suggest-members/suggest-module-paths",
12
- "@prover-coder-ai/suggest-members/suggest-exports",
13
- "@prover-coder-ai/suggest-members/suggest-missing-names",
14
- "@typescript-eslint/no-explicit-any"
15
- ]
16
- },
17
- {
18
- "level": 2,
19
- "name": "Critical Compiler Errors",
20
- "rules": ["all"]
21
- },
22
- {
23
- "level": 3,
24
- "name": "Critical Compiler Errors (Code must follow Clean Code and best practices)",
25
- "rules": ["max-lines-per-function", "max-lines"]
26
- },
27
- {
28
- "level": 4,
29
- "name": "Critical Compiler Errors (Code must follow Clean Code and best practices)",
30
- "rules": ["complexity", "max-params", "max-depth"]
31
- }
32
- ]
33
- }
package/src/app/main.ts DELETED
@@ -1,29 +0,0 @@
1
- import { NodeContext, NodeRuntime } from "@effect/platform-node"
2
- import { Effect, Layer, pipe } from "effect"
3
-
4
- import { CryptoServiceLive } from "../shell/services/crypto.js"
5
- import { FileSystemLive } from "../shell/services/file-system.js"
6
- import { RuntimeEnvLive } from "../shell/services/runtime-env.js"
7
- import { program } from "./program.js"
8
-
9
- // CHANGE: run the sync program through the Node runtime with all live layers
10
- // WHY: provide platform services and shell dependencies in one place
11
- // QUOTE(TZ): "SHELL: Все эффекты изолированы"
12
- // REF: user-2026-01-19-sync-rewrite
13
- // SOURCE: n/a
14
- // FORMAT THEOREM: forall env: provide(env) -> runMain(program)
15
- // PURITY: SHELL
16
- // EFFECT: Effect<void, never, RuntimeEnv | FileSystemService | CryptoService | Path>
17
- // INVARIANT: program executed with NodeContext + live services
18
- // COMPLEXITY: O(1)/O(1)
19
- const main = pipe(
20
- program,
21
- Effect.provide(
22
- Layer.provideMerge(
23
- Layer.mergeAll(RuntimeEnvLive, FileSystemLive, CryptoServiceLive),
24
- NodeContext.layer
25
- )
26
- )
27
- )
28
-
29
- NodeRuntime.runMain(main)
@@ -1,32 +0,0 @@
1
- import { Effect, pipe } from "effect"
2
-
3
- import { readSyncOptions } from "../shell/cli.js"
4
- import { buildSyncProgram } from "../shell/sync/index.js"
5
-
6
- /**
7
- * Compose the knowledge sync CLI as a single effect.
8
- *
9
- * @returns Effect that runs multi-source sync in sequence.
10
- *
11
- * @pure false - reads argv and performs filesystem IO
12
- * @effect RuntimeEnv, FileSystemService, CryptoService, Path
13
- * @invariant forall opts: buildSyncProgram(opts) runs each source exactly once
14
- * @precondition true
15
- * @postcondition sources are synced or skipped with logs
16
- * @complexity O(n) where n = number of files scanned
17
- * @throws Never - all errors are typed in the Effect error channel
18
- */
19
- // CHANGE: rewire program to knowledge-sync orchestration
20
- // WHY: replace greeting demo with the fully effectful sync pipeline
21
- // QUOTE(TZ): "Возьми прошлый ... код и перепиши его полностью"
22
- // REF: user-2026-01-19-sync-rewrite
23
- // SOURCE: n/a
24
- // FORMAT THEOREM: forall a: parse(a) -> run(sync(a))
25
- // PURITY: SHELL
26
- // EFFECT: Effect<void, never, RuntimeEnv | FileSystemService | CryptoService | Path>
27
- // INVARIANT: sync sources run in deterministic order
28
- // COMPLEXITY: O(n)/O(n)
29
- export const program = pipe(
30
- readSyncOptions,
31
- Effect.flatMap((options) => buildSyncProgram(options))
32
- )
@@ -1,115 +0,0 @@
1
- import { Option, pipe } from "effect"
2
-
3
- export type JsonPrimitive = string | number | boolean | null
4
- export type JsonValue = JsonPrimitive | ReadonlyArray<JsonValue> | JsonRecord
5
- export interface JsonRecord {
6
- readonly [key: string]: JsonValue
7
- }
8
-
9
- export interface ProjectLocator {
10
- readonly normalizedCwd: string
11
- readonly isWithinRoot: (candidate: string) => boolean
12
- }
13
-
14
- interface RecordMetadata {
15
- readonly cwd: Option.Option<string>
16
- }
17
-
18
- const isJsonRecord = (value: JsonValue): value is JsonRecord =>
19
- typeof value === "object" && value !== null && !Array.isArray(value)
20
-
21
- const pickString = (record: JsonRecord, key: string): Option.Option<string> => {
22
- const candidate = record[key]
23
- return Option.fromNullable(typeof candidate === "string" ? candidate : null)
24
- }
25
-
26
- const pickRecord = (record: JsonRecord, key: string): Option.Option<JsonRecord> =>
27
- pipe(
28
- record[key],
29
- Option.fromNullable,
30
- Option.filter((value) => isJsonRecord(value))
31
- )
32
-
33
- const extractCwd = (record: JsonRecord): Option.Option<string> =>
34
- pipe(
35
- pickString(record, "cwd"),
36
- Option.orElse(() =>
37
- pipe(
38
- pickRecord(record, "payload"),
39
- Option.flatMap((payload) => pickString(payload, "cwd"))
40
- )
41
- )
42
- )
43
-
44
- const toMetadata = (value: JsonValue): RecordMetadata => {
45
- if (!isJsonRecord(value)) {
46
- return { cwd: Option.none() }
47
- }
48
-
49
- return {
50
- cwd: extractCwd(value)
51
- }
52
- }
53
-
54
- const cwdMatches = (metadata: RecordMetadata, locator: ProjectLocator): boolean =>
55
- Option.exists(metadata.cwd, (cwdValue) => locator.isWithinRoot(cwdValue))
56
-
57
- const metadataMatches = (
58
- metadata: RecordMetadata,
59
- locator: ProjectLocator
60
- ): boolean => cwdMatches(metadata, locator)
61
-
62
- /**
63
- * Builds a locator for matching project-scoped metadata.
64
- *
65
- * @param normalizedCwd - Pre-normalized cwd of the project root.
66
- * @param isWithinRoot - Pure predicate for checking candidate cwd membership.
67
- * @returns Locator values for project comparisons.
68
- *
69
- * @pure true
70
- * @invariant normalizedCwd is absolute and stable for equal inputs
71
- * @complexity O(1) time / O(1) space
72
- */
73
- // CHANGE: bundle normalized root and pure membership predicate in a locator
74
- // WHY: keep matching invariants in CORE and leave path normalization in SHELL
75
- // QUOTE(TZ): "FUNCTIONAL CORE, IMPERATIVE SHELL"
76
- // REF: user-2026-01-19-sync-rewrite
77
- // SOURCE: n/a
78
- // FORMAT THEOREM: forall c: locator(c) -> membership(c)
79
- // PURITY: CORE
80
- // EFFECT: n/a
81
- // INVARIANT: normalizedCwd is absolute and stable for equal inputs
82
- // COMPLEXITY: O(1)/O(1)
83
- export const buildProjectLocator = (
84
- normalizedCwd: string,
85
- isWithinRoot: (candidate: string) => boolean
86
- ): ProjectLocator => ({
87
- normalizedCwd,
88
- isWithinRoot
89
- })
90
-
91
- /**
92
- * Checks whether a parsed JSON value contains project metadata.
93
- *
94
- * @param value - Parsed JSON value from a .jsonl line.
95
- * @param locator - Normalized project locator.
96
- * @returns True when cwd matches the project root.
97
- *
98
- * @pure true
99
- * @invariant valueMatchesProject implies metadataMatches for extracted metadata
100
- * @complexity O(k) where k = number of metadata fields read
101
- */
102
- // CHANGE: restrict project matching to cwd-only semantics
103
- // WHY: align with project-local path matching requirement
104
- // QUOTE(TZ): "каждая функция — теорема"
105
- // REF: user-2026-01-19-sync-rewrite
106
- // SOURCE: n/a
107
- // FORMAT THEOREM: forall v: JsonValue -> matches(v, locator) <-> metadataMatches(v)
108
- // PURITY: CORE
109
- // EFFECT: n/a
110
- // INVARIANT: metadataMatches uses cwd evidence only
111
- // COMPLEXITY: O(k)/O(1)
112
- export const valueMatchesProject = (
113
- value: JsonValue,
114
- locator: ProjectLocator
115
- ): boolean => metadataMatches(toMetadata(value), locator)
package/src/shell/cli.ts DELETED
@@ -1,75 +0,0 @@
1
- import { Effect } from "effect"
2
-
3
- import { RuntimeEnv } from "./services/runtime-env.js"
4
- import type { SyncOptions } from "./sync/types.js"
5
-
6
- type CliKey = Exclude<keyof SyncOptions, "cwd">
7
-
8
- const flagMap = new Map<string, CliKey>([
9
- ["--project-root", "projectRoot"],
10
- ["-r", "projectRoot"],
11
- ["--source", "sourceDir"],
12
- ["-s", "sourceDir"],
13
- ["--dest", "destinationDir"],
14
- ["-d", "destinationDir"],
15
- ["--project-url", "repositoryUrlOverride"],
16
- ["--project-name", "repositoryUrlOverride"],
17
- ["--meta-root", "metaRoot"],
18
- ["--qwen-source", "qwenSourceDir"],
19
- ["--claude-projects", "claudeProjectsRoot"]
20
- ])
21
-
22
- const parseArgs = (args: ReadonlyArray<string>, cwd: string): SyncOptions => {
23
- let result: SyncOptions = { cwd }
24
-
25
- let index = 0
26
- while (index < args.length) {
27
- const arg = args[index]
28
- if (arg === undefined) {
29
- index += 1
30
- continue
31
- }
32
- const key = flagMap.get(arg)
33
- if (key === undefined) {
34
- index += 1
35
- continue
36
- }
37
-
38
- const value = args[index + 1]
39
- if (value !== undefined) {
40
- result = { ...result, [key]: value }
41
- index += 2
42
- continue
43
- }
44
- index += 1
45
- }
46
-
47
- return result
48
- }
49
-
50
- /**
51
- * Reads CLI arguments and builds SyncOptions.
52
- *
53
- * @returns Effect with resolved SyncOptions.
54
- *
55
- * @pure false - reads process argv/cwd via RuntimeEnv
56
- * @effect RuntimeEnv
57
- * @invariant options.cwd is always defined; projectRoot overrides cwd for matching
58
- * @complexity O(n) where n = |args|
59
- */
60
- // CHANGE: parse CLI flags with optional project root override
61
- // WHY: allow matching against repo root while running from subpackages
62
- // QUOTE(TZ): "передай root-path на основную папку"
63
- // REF: user-2026-01-19-project-root
64
- // SOURCE: n/a
65
- // FORMAT THEOREM: forall a: parse(a) -> SyncOptions
66
- // PURITY: SHELL
67
- // EFFECT: Effect<SyncOptions, never, RuntimeEnv>
68
- // INVARIANT: unknown flags are ignored
69
- // COMPLEXITY: O(n)/O(1)
70
- export const readSyncOptions = Effect.gen(function*(_) {
71
- const env = yield* _(RuntimeEnv)
72
- const argv = yield* _(env.argv)
73
- const cwd = yield* _(env.cwd)
74
- return parseArgs(argv.slice(2), cwd)
75
- })
@@ -1,50 +0,0 @@
1
- import { Context, Effect, Layer, pipe } from "effect"
2
-
3
- export class CryptoService extends Context.Tag("CryptoService")<
4
- CryptoService,
5
- {
6
- readonly sha256: (value: string) => Effect.Effect<string, CryptoError>
7
- }
8
- >() {}
9
-
10
- export interface CryptoError {
11
- readonly _tag: "CryptoError"
12
- readonly reason: string
13
- }
14
-
15
- const cryptoError = (reason: string): CryptoError => ({
16
- _tag: "CryptoError",
17
- reason
18
- })
19
-
20
- const toHex = (buffer: ArrayBuffer): string =>
21
- [...new Uint8Array(buffer)]
22
- .map((byte) => byte.toString(16).padStart(2, "0"))
23
- .join("")
24
-
25
- const digestSha256 = (value: string): Effect.Effect<string, CryptoError> =>
26
- pipe(
27
- Effect.tryPromise({
28
- try: () => {
29
- const crypto = globalThis.crypto
30
- const bytes = new TextEncoder().encode(value)
31
- return crypto.subtle.digest("SHA-256", bytes)
32
- },
33
- catch: (error) => cryptoError(error instanceof Error ? error.message : "Crypto digest failed")
34
- }),
35
- Effect.map((buffer) => toHex(buffer))
36
- )
37
-
38
- // CHANGE: isolate hashing behind a service for deterministic testing
39
- // WHY: avoid direct crypto usage in shell logic
40
- // QUOTE(TZ): "Внешние зависимости: только через типизированные интерфейсы"
41
- // REF: user-2026-01-19-sync-rewrite
42
- // SOURCE: n/a
43
- // FORMAT THEOREM: forall s: sha256(s) -> hex(s)
44
- // PURITY: SHELL
45
- // EFFECT: Effect<CryptoService, never, never>
46
- // INVARIANT: sha256 output length = 64
47
- // COMPLEXITY: O(n)/O(1)
48
- export const CryptoServiceLive = Layer.succeed(CryptoService, {
49
- sha256: (value) => digestSha256(value)
50
- })
@@ -1,142 +0,0 @@
1
- import * as FileSystem from "@effect/platform/FileSystem"
2
- import * as Path from "@effect/platform/Path"
3
- import { Context, Effect, Layer, pipe } from "effect"
4
-
5
- import { type SyncError, syncError } from "../sync/types.js"
6
-
7
- export type DirectoryEntryKind = "file" | "directory" | "other"
8
-
9
- export interface DirectoryEntry {
10
- readonly name: string
11
- readonly path: string
12
- readonly kind: DirectoryEntryKind
13
- }
14
-
15
- export class FileSystemService extends Context.Tag("FileSystemService")<
16
- FileSystemService,
17
- {
18
- readonly readFileString: (pathValue: string) => Effect.Effect<string, SyncError>
19
- readonly readDirectory: (
20
- pathValue: string
21
- ) => Effect.Effect<ReadonlyArray<DirectoryEntry>, SyncError>
22
- readonly makeDirectory: (pathValue: string) => Effect.Effect<void, SyncError>
23
- readonly copyFile: (
24
- sourcePath: string,
25
- destinationPath: string
26
- ) => Effect.Effect<void, SyncError>
27
- readonly exists: (pathValue: string) => Effect.Effect<boolean, SyncError>
28
- }
29
- >() {}
30
-
31
- const forEach = Effect.forEach
32
-
33
- const resolveEntryPath = (
34
- path: Path.Path,
35
- root: string,
36
- entry: string
37
- ): string => (path.isAbsolute(entry) ? entry : path.join(root, entry))
38
-
39
- const entryKindFromInfo = (
40
- info: FileSystem.File.Info
41
- ): DirectoryEntryKind => {
42
- if (info.type === "Directory") {
43
- return "directory"
44
- }
45
- if (info.type === "File") {
46
- return "file"
47
- }
48
- return "other"
49
- }
50
-
51
- const toDirectoryEntry = (
52
- path: Path.Path,
53
- entryPath: string,
54
- info: FileSystem.File.Info
55
- ): DirectoryEntry => ({
56
- name: path.basename(entryPath),
57
- path: entryPath,
58
- kind: entryKindFromInfo(info)
59
- })
60
-
61
- const readEntry = (
62
- fs: FileSystem.FileSystem,
63
- path: Path.Path,
64
- entryPath: string
65
- ): Effect.Effect<DirectoryEntry, SyncError> =>
66
- pipe(
67
- fs.stat(entryPath),
68
- Effect.map((info) => toDirectoryEntry(path, entryPath, info)),
69
- Effect.mapError(() => syncError(entryPath, "Cannot read directory entry"))
70
- )
71
-
72
- const resolveEntry = (
73
- fs: FileSystem.FileSystem,
74
- path: Path.Path,
75
- root: string,
76
- entry: string
77
- ): Effect.Effect<DirectoryEntry, SyncError> => {
78
- const entryPath = resolveEntryPath(path, root, entry)
79
- return readEntry(fs, path, entryPath)
80
- }
81
-
82
- // CHANGE: wrap filesystem access behind a service for typed errors and testing
83
- // WHY: enforce shell boundary and avoid raw fs usage in logic
84
- // QUOTE(TZ): "Внешние зависимости: только через типизированные интерфейсы"
85
- // REF: user-2026-01-19-sync-rewrite
86
- // SOURCE: n/a
87
- // FORMAT THEOREM: forall p: exists(p) -> readable(p)
88
- // PURITY: SHELL
89
- // EFFECT: Effect<FileSystemService, SyncError, never>
90
- // INVARIANT: readDirectory returns absolute entry paths
91
- // COMPLEXITY: O(n)/O(n)
92
- export const FileSystemLive = Layer.effect(
93
- FileSystemService,
94
- Effect.gen(function*(_) {
95
- const fs = yield* _(FileSystem.FileSystem)
96
- const path = yield* _(Path.Path)
97
-
98
- const readFileString = (pathValue: string): Effect.Effect<string, SyncError> =>
99
- pipe(
100
- fs.readFileString(pathValue, "utf8"),
101
- Effect.mapError(() => syncError(pathValue, "Cannot read file"))
102
- )
103
-
104
- const readDirectory = (
105
- pathValue: string
106
- ): Effect.Effect<ReadonlyArray<DirectoryEntry>, SyncError> =>
107
- pipe(
108
- fs.readDirectory(pathValue),
109
- Effect.mapError(() => syncError(pathValue, "Cannot read directory")),
110
- Effect.flatMap((entries) => forEach(entries, (entry) => resolveEntry(fs, path, pathValue, entry)))
111
- )
112
-
113
- const makeDirectory = (pathValue: string): Effect.Effect<void, SyncError> =>
114
- pipe(
115
- fs.makeDirectory(pathValue, { recursive: true }),
116
- Effect.mapError(() => syncError(pathValue, "Cannot create destination directory structure"))
117
- )
118
-
119
- const copyFile = (
120
- sourcePath: string,
121
- destinationPath: string
122
- ): Effect.Effect<void, SyncError> =>
123
- pipe(
124
- fs.copyFile(sourcePath, destinationPath),
125
- Effect.mapError(() => syncError(sourcePath, "Cannot copy file into destination"))
126
- )
127
-
128
- const exists = (pathValue: string): Effect.Effect<boolean, SyncError> =>
129
- pipe(
130
- fs.exists(pathValue),
131
- Effect.mapError(() => syncError(pathValue, "Cannot check path existence"))
132
- )
133
-
134
- return {
135
- readFileString,
136
- readDirectory,
137
- makeDirectory,
138
- copyFile,
139
- exists
140
- }
141
- })
142
- )