@prover-coder-ai/context-doc 1.0.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/README.md ADDED
@@ -0,0 +1,19 @@
1
+ # Knowledge Sync CLI
2
+
3
+ Command: `npm run sync:knowledge` (or `tsx src/shell/syncKnowledge.ts`) — copies only dialogs that belong to the current project into `.knowledge/.codex`.
4
+
5
+ ## Flags
6
+ - `--source, -s <path>` — explicit path to `.codex`.
7
+ - `--dest, -d <path>` — destination root (defaults to `.knowledge/.codex` in the project).
8
+ - `--project-url` / `--project-name <url>` — override repository URL (otherwise read from `package.json`).
9
+ - `--meta-root <path>` — path to a meta folder; if it’s not already `.codex`, the tool looks for `<meta-root>/.codex`.
10
+
11
+ ## `.codex` lookup order
12
+ 1) `--source` (if provided)
13
+ 2) env `CODEX_SOURCE_DIR`
14
+ 3) `--meta-root` (either the `.codex` itself or `<meta-root>/.codex`)
15
+ 4) project-local `.codex`
16
+ 5) home `~/.codex`
17
+
18
+ Only `.jsonl` files whose `git.repository_url` or `cwd` match this project are copied.\
19
+ Copies are placed under `.knowledge/.codex` preserving the directory structure.
@@ -0,0 +1,25 @@
1
+ import { Option } from "effect";
2
+ const normalize = (value) => value.trim();
3
+ const isNonEmpty = (value) => value.length > 0;
4
+ /**
5
+ * CHANGE: Introduce pure greeting synthesis for console entrypoint
6
+ * WHY: Provide mathematically checkable construction of startup banner without side effects
7
+ * QUOTE(ТЗ): "Каждая функция — это теорема."
8
+ * REF: REQ-GREETING-CORE
9
+ * SOURCE: AGENTS.md – функциональное ядро без побочных эффектов
10
+ * FORMAT THEOREM: ∀p ∈ AppProfile: valid(p) → nonEmpty(buildGreeting(p).message)
11
+ * PURITY: CORE
12
+ * EFFECT: None (pure)
13
+ * INVARIANT: output.message.length > 0 when Option is Some
14
+ * COMPLEXITY: O(1)/O(1)
15
+ */
16
+ export const buildGreeting = (profile) => {
17
+ const trimmedName = normalize(profile.name);
18
+ const trimmedMission = normalize(profile.mission);
19
+ if (!isNonEmpty(trimmedName) || !isNonEmpty(trimmedMission)) {
20
+ return Option.none();
21
+ }
22
+ return Option.some({
23
+ message: `${trimmedName}: ${trimmedMission}`,
24
+ });
25
+ };
@@ -0,0 +1,51 @@
1
+ import path from "node:path";
2
+ import { Option, pipe } from "effect";
3
+ const isJsonRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4
+ const normalizeRepositoryUrl = (value) => pipe(value.trim(), (trimmed) => trimmed.replace(/^git\+/, ""), (withoutPrefix) => withoutPrefix.replace(/\.git$/, ""), (withoutGitSuffix) => withoutGitSuffix.replace(/^git@github\.com:/, "https://github.com/"), (withoutSsh) => withoutSsh.replace(/^ssh:\/\/git@github\.com\//, "https://github.com/"), (normalized) => normalized.toLowerCase());
5
+ const normalizeCwd = (value) => path.resolve(value);
6
+ const pickString = (record, key) => {
7
+ const candidate = record[key];
8
+ return typeof candidate === "string" ? Option.some(candidate) : Option.none();
9
+ };
10
+ const pickRecord = (record, key) => pipe(record[key], Option.fromNullable, Option.filter(isJsonRecord));
11
+ const pickGitRepository = (record) => pipe(pickString(record, "repository_url"), Option.orElse(() => pickString(record, "repositoryUrl")));
12
+ const extractRepository = (record) => pipe(pickRecord(record, "git"), Option.flatMap(pickGitRepository), Option.orElse(() => pipe(pickRecord(record, "payload"), Option.flatMap((payload) => pipe(pickRecord(payload, "git"), Option.flatMap(pickGitRepository))))));
13
+ const extractCwd = (record) => pipe(pickString(record, "cwd"), Option.orElse(() => pipe(pickRecord(record, "payload"), Option.flatMap((payload) => pickString(payload, "cwd")))));
14
+ const toMetadata = (value) => {
15
+ if (!isJsonRecord(value)) {
16
+ return { repositoryUrl: Option.none(), cwd: Option.none() };
17
+ }
18
+ return {
19
+ repositoryUrl: extractRepository(value),
20
+ cwd: extractCwd(value),
21
+ };
22
+ };
23
+ const normalizeLocator = (repositoryUrl, cwd) => ({
24
+ normalizedRepositoryUrl: normalizeRepositoryUrl(repositoryUrl),
25
+ normalizedCwd: normalizeCwd(cwd),
26
+ });
27
+ const safeParseJson = (line) => {
28
+ try {
29
+ return Option.some(JSON.parse(line));
30
+ }
31
+ catch {
32
+ return Option.none();
33
+ }
34
+ };
35
+ const metadataMatches = (metadata, locator) => {
36
+ const repoMatches = Option.exists(metadata.repositoryUrl, (repositoryUrl) => normalizeRepositoryUrl(repositoryUrl) === locator.normalizedRepositoryUrl);
37
+ const cwdMatches = Option.exists(metadata.cwd, (cwdValue) => {
38
+ const normalized = normalizeCwd(cwdValue);
39
+ return (locator.normalizedCwd.startsWith(normalized) ||
40
+ normalized.startsWith(locator.normalizedCwd));
41
+ });
42
+ return repoMatches || cwdMatches;
43
+ };
44
+ export const buildProjectLocator = (repositoryUrl, cwd) => normalizeLocator(repositoryUrl, cwd);
45
+ export const linesMatchProject = (lines, locator) => lines.some((line) => {
46
+ const trimmed = line.trim();
47
+ if (trimmed.length === 0) {
48
+ return false;
49
+ }
50
+ return pipe(safeParseJson(trimmed), Option.map(toMetadata), Option.exists((metadata) => metadataMatches(metadata, locator)));
51
+ });
package/dist/main.js ADDED
@@ -0,0 +1,27 @@
1
+ import { Console, Effect, Option, pipe } from "effect";
2
+ import { match } from "ts-pattern";
3
+ import { buildGreeting } from "./core/greeting.js";
4
+ const appProfile = {
5
+ name: "Context Console",
6
+ mission: "Functional core ready for verifiable effects",
7
+ };
8
+ const toStartupError = (reason) => ({
9
+ _tag: "StartupError",
10
+ reason,
11
+ });
12
+ /**
13
+ * CHANGE: Compose shell-level startup program around pure greeting builder
14
+ * WHY: Isolate side effects (console IO) while delegating computation to functional core
15
+ * QUOTE(ТЗ): "FUNCTIONAL CORE, IMPERATIVE SHELL"
16
+ * REF: REQ-SHELL-STARTUP
17
+ * SOURCE: AGENTS.md — эффекты только в SHELL
18
+ * FORMAT THEOREM: ∀profile: valid(profile) → logs(buildGreeting(profile))
19
+ * PURITY: SHELL
20
+ * EFFECT: Effect<void, StartupError, Console>
21
+ * INVARIANT: Console side-effects occur once and only after successful greeting synthesis
22
+ * COMPLEXITY: O(1)/O(1)
23
+ */
24
+ const program = pipe(Effect.succeed(buildGreeting(appProfile)), Effect.filterOrFail(Option.isSome, () => toStartupError("Profile must not be empty")), Effect.map((option) => option.value), Effect.flatMap((greeting) => Console.log(greeting.message)), Effect.catchAll((error) => match(error)
25
+ .with({ _tag: "StartupError" }, (startupError) => Console.error(`StartupError: ${startupError.reason}`))
26
+ .exhaustive()));
27
+ Effect.runSync(program);
@@ -0,0 +1,119 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import * as S from "@effect/schema/Schema";
5
+ import { Console, Effect, pipe } from "effect";
6
+ import { buildProjectLocator, linesMatchProject } from "../core/knowledge.js";
7
+ import { ensureDirectory, syncError } from "./syncShared.js";
8
+ const PackageRepositorySchema = S.Struct({
9
+ url: S.String,
10
+ });
11
+ const PackageFileSchema = S.Struct({
12
+ repository: S.optional(PackageRepositorySchema),
13
+ });
14
+ const PackageFileFromJson = S.compose(S.parseJson(), PackageFileSchema);
15
+ const decodePackageFile = S.decodeUnknownSync(PackageFileFromJson);
16
+ const readRepositoryUrl = (cwd, repositoryUrlOverride) => Effect.try({
17
+ try: () => {
18
+ if (repositoryUrlOverride !== undefined) {
19
+ return repositoryUrlOverride;
20
+ }
21
+ const raw = fs.readFileSync(path.join(cwd, "package.json"), "utf8");
22
+ const parsed = decodePackageFile(raw);
23
+ const repositoryUrl = parsed.repository?.url;
24
+ if (repositoryUrl === undefined) {
25
+ throw new Error("repository url is missing");
26
+ }
27
+ return repositoryUrl;
28
+ },
29
+ catch: () => syncError("package.json", "Cannot read repository url"),
30
+ });
31
+ const resolveSourceDir = (cwd, override, metaRoot) => Effect.try({
32
+ try: () => {
33
+ const envSource = process.env.CODEX_SOURCE_DIR;
34
+ const metaCandidate = metaRoot === undefined
35
+ ? undefined
36
+ : metaRoot.endsWith(".codex")
37
+ ? metaRoot
38
+ : path.join(metaRoot, ".codex");
39
+ const localSource = path.join(cwd, ".codex");
40
+ const homeSource = path.join(os.homedir(), ".codex");
41
+ const candidates = [
42
+ override,
43
+ envSource,
44
+ metaCandidate,
45
+ localSource,
46
+ homeSource,
47
+ ];
48
+ const existing = candidates.find((candidate) => candidate !== undefined && fs.existsSync(candidate));
49
+ if (existing === undefined) {
50
+ throw new Error("source .codex not found");
51
+ }
52
+ return existing;
53
+ },
54
+ catch: () => syncError(".codex", "Source .codex directory is missing"),
55
+ });
56
+ const collectJsonlFiles = (root) => Effect.try({
57
+ try: () => {
58
+ const collected = [];
59
+ const walk = (current) => {
60
+ const entries = fs.readdirSync(current, { withFileTypes: true });
61
+ for (const entry of entries) {
62
+ const fullPath = path.join(current, entry.name);
63
+ if (entry.isDirectory()) {
64
+ walk(fullPath);
65
+ }
66
+ else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
67
+ collected.push(fullPath);
68
+ }
69
+ }
70
+ };
71
+ walk(root);
72
+ return collected;
73
+ },
74
+ catch: () => syncError(root, "Cannot traverse .codex"),
75
+ });
76
+ const isFileRelevant = (filePath, locator) => Effect.try({
77
+ try: () => {
78
+ const content = fs.readFileSync(filePath, "utf8");
79
+ const lines = content.split("\n");
80
+ return linesMatchProject(lines, locator);
81
+ },
82
+ catch: () => syncError(filePath, "Cannot read jsonl file"),
83
+ });
84
+ const copyRelevantFile = (sourceRoot, destinationRoot, filePath) => Effect.try({
85
+ try: () => {
86
+ const relative = path.relative(sourceRoot, filePath);
87
+ const targetPath = path.join(destinationRoot, relative);
88
+ fs.mkdirSync(path.dirname(targetPath), { recursive: true });
89
+ fs.copyFileSync(filePath, targetPath);
90
+ },
91
+ catch: () => syncError(filePath, "Cannot copy file into .knowledge/.codex"),
92
+ });
93
+ const selectRelevantFiles = (files, locator) => Effect.reduce(files, [], (acc, filePath) => pipe(isFileRelevant(filePath, locator), Effect.map((isRelevant) => {
94
+ if (isRelevant) {
95
+ acc.push(filePath);
96
+ }
97
+ return acc;
98
+ })));
99
+ // CHANGE: Extract Codex dialog sync into dedicated module for clarity.
100
+ // WHY: Separate Codex-specific shell effects from other sync flows.
101
+ // QUOTE(ТЗ): "вынеси в отдельный файл"
102
+ // REF: user request 2025-11-26
103
+ // SOURCE: internal requirement
104
+ // FORMAT THEOREM: ∀f ∈ Files: relevant(f, locator) → copied(f, destination)
105
+ // PURITY: SHELL
106
+ // EFFECT: Effect<void, SyncError, never>
107
+ // INVARIANT: ∀f ∈ copiedFiles: linesMatchProject(f, locator)
108
+ // COMPLEXITY: O(n) time / O(n) space, n = |files|
109
+ export const syncCodex = (options) => Effect.gen(function* (_) {
110
+ const repositoryUrl = yield* _(readRepositoryUrl(options.cwd, options.repositoryUrlOverride));
111
+ const sourceDir = yield* _(resolveSourceDir(options.cwd, options.sourceDir, options.metaRoot));
112
+ const destinationDir = options.destinationDir ?? path.join(options.cwd, ".knowledge", ".codex");
113
+ yield* _(ensureDirectory(destinationDir));
114
+ const locator = buildProjectLocator(repositoryUrl, options.cwd);
115
+ const allJsonlFiles = yield* _(collectJsonlFiles(sourceDir));
116
+ const relevantFiles = yield* _(selectRelevantFiles(allJsonlFiles, locator));
117
+ yield* _(Effect.forEach(relevantFiles, (filePath) => copyRelevantFile(sourceDir, destinationDir, filePath)));
118
+ yield* _(Console.log(`Synced ${relevantFiles.length} dialog files into .knowledge/.codex`));
119
+ });
@@ -0,0 +1,49 @@
1
+ import { createHash } from "node:crypto";
2
+ import fs from "node:fs";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { Console, Effect, pipe } from "effect";
6
+ import { ensureDirectory, syncError } from "./syncShared.js";
7
+ const copyDirectoryJsonOnly = (sourceRoot, destinationRoot) => Effect.try({
8
+ try: () => {
9
+ let copied = 0;
10
+ fs.cpSync(sourceRoot, destinationRoot, {
11
+ recursive: true,
12
+ filter: (src) => {
13
+ const stat = fs.statSync(src);
14
+ if (stat.isDirectory()) {
15
+ return true;
16
+ }
17
+ if (src.endsWith(".json")) {
18
+ copied += 1;
19
+ return true;
20
+ }
21
+ return false;
22
+ },
23
+ });
24
+ return copied;
25
+ },
26
+ catch: () => syncError(sourceRoot, "Cannot traverse Qwen directory"),
27
+ });
28
+ const qwenHashFromPath = (projectPath) => createHash("sha256").update(projectPath).digest("hex");
29
+ const resolveQwenSourceDir = (cwd, override, metaRoot) => pipe(Effect.sync(() => {
30
+ const hash = qwenHashFromPath(cwd);
31
+ const envSource = process.env.QWEN_SOURCE_DIR;
32
+ const baseFromMeta = metaRoot === undefined
33
+ ? undefined
34
+ : metaRoot.endsWith(".qwen")
35
+ ? metaRoot
36
+ : path.join(metaRoot, ".qwen");
37
+ const homeBase = path.join(os.homedir(), ".qwen");
38
+ const candidates = [
39
+ override,
40
+ envSource,
41
+ baseFromMeta ? path.join(baseFromMeta, "tmp", hash) : undefined,
42
+ path.join(cwd, ".qwen", "tmp", hash),
43
+ path.join(homeBase, "tmp", hash),
44
+ ];
45
+ return candidates.find((candidate) => candidate !== undefined && fs.existsSync(candidate));
46
+ }), Effect.flatMap((found) => found === undefined
47
+ ? Effect.fail(syncError(".qwen", "Qwen source directory is missing"))
48
+ : Effect.succeed(found)));
49
+ export const syncQwen = (options) => pipe(resolveQwenSourceDir(options.cwd, options.qwenSourceDir, options.metaRoot), Effect.flatMap((qwenSource) => pipe(ensureDirectory(path.join(options.cwd, ".knowledge", ".qwen")), Effect.flatMap(() => copyDirectoryJsonOnly(qwenSource, path.join(options.cwd, ".knowledge", ".qwen"))), Effect.flatMap((copiedCount) => Console.log(`Synced ${copiedCount} Qwen dialog files into .knowledge/.qwen`)))), Effect.catchAll(() => Console.log("Qwen source not found; skipped syncing Qwen dialog files")));
@@ -0,0 +1,47 @@
1
+ import { pathToFileURL } from "node:url";
2
+ import { Effect } from "effect";
3
+ import { syncCodex } from "./codexSync.js";
4
+ import { syncQwen } from "./qwenSync.js";
5
+ // PURPOSE: Sync project-scoped dialogs (Codex + Qwen) into .knowledge storage.
6
+ export const buildSyncProgram = (options) => Effect.gen(function* (_) {
7
+ yield* _(syncCodex(options));
8
+ yield* _(syncQwen(options));
9
+ });
10
+ const isMainModule = () => {
11
+ const entry = process.argv[1];
12
+ if (entry === undefined) {
13
+ return false;
14
+ }
15
+ return import.meta.url === pathToFileURL(entry).href;
16
+ };
17
+ const parseArgs = () => {
18
+ const argv = process.argv.slice(2);
19
+ let result = { cwd: process.cwd() };
20
+ const mapping = {
21
+ "--source": "sourceDir",
22
+ "-s": "sourceDir",
23
+ "--dest": "destinationDir",
24
+ "-d": "destinationDir",
25
+ "--project-url": "repositoryUrlOverride",
26
+ "--project-name": "repositoryUrlOverride",
27
+ "--meta-root": "metaRoot",
28
+ "--qwen-source": "qwenSourceDir",
29
+ };
30
+ for (let i = 0; i < argv.length; i++) {
31
+ const arg = argv[i];
32
+ const key = mapping[arg];
33
+ if (key === undefined) {
34
+ continue;
35
+ }
36
+ const value = argv[i + 1];
37
+ if (value !== undefined) {
38
+ result = { ...result, [key]: value };
39
+ i++;
40
+ }
41
+ }
42
+ return result;
43
+ };
44
+ if (isMainModule()) {
45
+ const program = buildSyncProgram(parseArgs());
46
+ Effect.runSync(program);
47
+ }
@@ -0,0 +1,13 @@
1
+ import fs from "node:fs";
2
+ import { Effect } from "effect";
3
+ export const syncError = (pathValue, reason) => ({
4
+ _tag: "SyncError",
5
+ path: pathValue,
6
+ reason,
7
+ });
8
+ export const ensureDirectory = (directory) => Effect.try({
9
+ try: () => {
10
+ fs.mkdirSync(directory, { recursive: true });
11
+ },
12
+ catch: () => syncError(directory, "Cannot create destination directory structure"),
13
+ });
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@prover-coder-ai/context-doc",
3
+ "version": "1.0.4",
4
+ "description": "",
5
+ "type": "module",
6
+ "scripts": {
7
+ "start": "tsx src/main.ts",
8
+ "build": "tsc -p tsconfig.build.json",
9
+ "lint": "npx --no-install @ton-ai-core/vibecode-linter src/",
10
+ "test": "npx --no-install @ton-ai-core/vibecode-linter tests/ && vitest run --passWithNoTests",
11
+ "sync:knowledge": "tsx src/shell/syncKnowledge.ts",
12
+ "release": "npm run build && npm run lint && npm version patch && npm publish --access public"
13
+ },
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/prover-coder-ai/context-doc.git"
17
+ },
18
+ "keywords": [],
19
+ "author": "",
20
+ "license": "ISC",
21
+ "bugs": {
22
+ "url": "https://github.com/prover-coder-ai/context-doc/issues"
23
+ },
24
+ "homepage": "https://github.com/prover-coder-ai/context-doc#readme",
25
+ "files": [
26
+ "dist",
27
+ "src",
28
+ "doc",
29
+ "README.md",
30
+ "tsconfig.build.json",
31
+ "vitest.config.ts"
32
+ ],
33
+ "publishConfig": {
34
+ "access": "public"
35
+ },
36
+ "dependencies": {
37
+ "@effect/schema": "^0.75.5",
38
+ "effect": "^3.19.6",
39
+ "ts-morph": "^27.0.2",
40
+ "ts-pattern": "^5.9.0",
41
+ "vite-tsconfig-paths": "^5.1.4"
42
+ },
43
+ "devDependencies": {
44
+ "@biomejs/biome": "^2.3.7",
45
+ "@eslint-community/eslint-plugin-eslint-comments": "^4.5.0",
46
+ "@eslint/js": "^9.39.1",
47
+ "@ton-ai-core/eslint-plugin-suggest-members": "^1.6.17",
48
+ "@ton-ai-core/vibecode-linter": "^1.0.6",
49
+ "@types/node": "^24.10.1",
50
+ "eslint": "^9.39.1",
51
+ "eslint-plugin-vitest": "^0.5.4",
52
+ "globals": "^16.5.0",
53
+ "tsx": "^4.20.6",
54
+ "typescript": "^5.9.3",
55
+ "typescript-eslint": "^8.48.0",
56
+ "vitest": "^4.0.14"
57
+ }
58
+ }
@@ -0,0 +1,39 @@
1
+ import { Option } from "effect";
2
+
3
+ export interface AppProfile {
4
+ readonly name: string;
5
+ readonly mission: string;
6
+ }
7
+
8
+ export interface Greeting {
9
+ readonly message: string;
10
+ }
11
+
12
+ const normalize = (value: string): string => value.trim();
13
+
14
+ const isNonEmpty = (value: string): boolean => value.length > 0;
15
+
16
+ /**
17
+ * CHANGE: Introduce pure greeting synthesis for console entrypoint
18
+ * WHY: Provide mathematically checkable construction of startup banner without side effects
19
+ * QUOTE(ТЗ): "Каждая функция — это теорема."
20
+ * REF: REQ-GREETING-CORE
21
+ * SOURCE: AGENTS.md – функциональное ядро без побочных эффектов
22
+ * FORMAT THEOREM: ∀p ∈ AppProfile: valid(p) → nonEmpty(buildGreeting(p).message)
23
+ * PURITY: CORE
24
+ * EFFECT: None (pure)
25
+ * INVARIANT: output.message.length > 0 when Option is Some
26
+ * COMPLEXITY: O(1)/O(1)
27
+ */
28
+ export const buildGreeting = (profile: AppProfile): Option.Option<Greeting> => {
29
+ const trimmedName = normalize(profile.name);
30
+ const trimmedMission = normalize(profile.mission);
31
+
32
+ if (!isNonEmpty(trimmedName) || !isNonEmpty(trimmedMission)) {
33
+ return Option.none();
34
+ }
35
+
36
+ return Option.some({
37
+ message: `${trimmedName}: ${trimmedMission}`,
38
+ });
39
+ };
@@ -0,0 +1,148 @@
1
+ import path from "node:path";
2
+ import { Option, pipe } from "effect";
3
+
4
+ type JsonPrimitive = string | number | boolean | null;
5
+ type JsonValue = JsonPrimitive | readonly JsonValue[] | JsonRecord;
6
+ interface JsonRecord {
7
+ readonly [key: string]: JsonValue;
8
+ }
9
+
10
+ export interface ProjectLocator {
11
+ readonly normalizedRepositoryUrl: string;
12
+ readonly normalizedCwd: string;
13
+ }
14
+
15
+ interface RecordMetadata {
16
+ readonly repositoryUrl: Option.Option<string>;
17
+ readonly cwd: Option.Option<string>;
18
+ }
19
+
20
+ const isJsonRecord = (value: JsonValue): value is JsonRecord =>
21
+ typeof value === "object" && value !== null && !Array.isArray(value);
22
+
23
+ const normalizeRepositoryUrl = (value: string): string =>
24
+ pipe(
25
+ value.trim(),
26
+ (trimmed) => trimmed.replace(/^git\+/, ""),
27
+ (withoutPrefix) => withoutPrefix.replace(/\.git$/, ""),
28
+ (withoutGitSuffix) =>
29
+ withoutGitSuffix.replace(/^git@github\.com:/, "https://github.com/"),
30
+ (withoutSsh) =>
31
+ withoutSsh.replace(/^ssh:\/\/git@github\.com\//, "https://github.com/"),
32
+ (normalized) => normalized.toLowerCase(),
33
+ );
34
+
35
+ const normalizeCwd = (value: string): string => path.resolve(value);
36
+
37
+ const pickString = (record: JsonRecord, key: string): Option.Option<string> => {
38
+ const candidate = record[key];
39
+ return typeof candidate === "string" ? Option.some(candidate) : Option.none();
40
+ };
41
+
42
+ const pickRecord = (
43
+ record: JsonRecord,
44
+ key: string,
45
+ ): Option.Option<JsonRecord> =>
46
+ pipe(record[key], Option.fromNullable, Option.filter(isJsonRecord));
47
+
48
+ const pickGitRepository = (record: JsonRecord): Option.Option<string> =>
49
+ pipe(
50
+ pickString(record, "repository_url"),
51
+ Option.orElse(() => pickString(record, "repositoryUrl")),
52
+ );
53
+
54
+ const extractRepository = (record: JsonRecord): Option.Option<string> =>
55
+ pipe(
56
+ pickRecord(record, "git"),
57
+ Option.flatMap(pickGitRepository),
58
+ Option.orElse(() =>
59
+ pipe(
60
+ pickRecord(record, "payload"),
61
+ Option.flatMap((payload) =>
62
+ pipe(pickRecord(payload, "git"), Option.flatMap(pickGitRepository)),
63
+ ),
64
+ ),
65
+ ),
66
+ );
67
+
68
+ const extractCwd = (record: JsonRecord): Option.Option<string> =>
69
+ pipe(
70
+ pickString(record, "cwd"),
71
+ Option.orElse(() =>
72
+ pipe(
73
+ pickRecord(record, "payload"),
74
+ Option.flatMap((payload) => pickString(payload, "cwd")),
75
+ ),
76
+ ),
77
+ );
78
+
79
+ const toMetadata = (value: JsonValue): RecordMetadata => {
80
+ if (!isJsonRecord(value)) {
81
+ return { repositoryUrl: Option.none(), cwd: Option.none() };
82
+ }
83
+
84
+ return {
85
+ repositoryUrl: extractRepository(value),
86
+ cwd: extractCwd(value),
87
+ };
88
+ };
89
+
90
+ const normalizeLocator = (
91
+ repositoryUrl: string,
92
+ cwd: string,
93
+ ): ProjectLocator => ({
94
+ normalizedRepositoryUrl: normalizeRepositoryUrl(repositoryUrl),
95
+ normalizedCwd: normalizeCwd(cwd),
96
+ });
97
+
98
+ const safeParseJson = (line: string): Option.Option<JsonValue> => {
99
+ try {
100
+ return Option.some(JSON.parse(line));
101
+ } catch {
102
+ return Option.none();
103
+ }
104
+ };
105
+
106
+ const metadataMatches = (
107
+ metadata: RecordMetadata,
108
+ locator: ProjectLocator,
109
+ ): boolean => {
110
+ const repoMatches = Option.exists(
111
+ metadata.repositoryUrl,
112
+ (repositoryUrl) =>
113
+ normalizeRepositoryUrl(repositoryUrl) === locator.normalizedRepositoryUrl,
114
+ );
115
+
116
+ const cwdMatches = Option.exists(metadata.cwd, (cwdValue) => {
117
+ const normalized = normalizeCwd(cwdValue);
118
+ return (
119
+ locator.normalizedCwd.startsWith(normalized) ||
120
+ normalized.startsWith(locator.normalizedCwd)
121
+ );
122
+ });
123
+
124
+ return repoMatches || cwdMatches;
125
+ };
126
+
127
+ export const buildProjectLocator = (
128
+ repositoryUrl: string,
129
+ cwd: string,
130
+ ): ProjectLocator => normalizeLocator(repositoryUrl, cwd);
131
+
132
+ export const linesMatchProject = (
133
+ lines: readonly string[],
134
+ locator: ProjectLocator,
135
+ ): boolean =>
136
+ lines.some((line) => {
137
+ const trimmed = line.trim();
138
+
139
+ if (trimmed.length === 0) {
140
+ return false;
141
+ }
142
+
143
+ return pipe(
144
+ safeParseJson(trimmed),
145
+ Option.map(toMetadata),
146
+ Option.exists((metadata) => metadataMatches(metadata, locator)),
147
+ );
148
+ });
package/src/main.ts ADDED
@@ -0,0 +1,49 @@
1
+ import { Console, Effect, Option, pipe } from "effect";
2
+ import { match } from "ts-pattern";
3
+ import type { AppProfile } from "./core/greeting.js";
4
+ import { buildGreeting } from "./core/greeting.js";
5
+
6
+ interface StartupError {
7
+ readonly _tag: "StartupError";
8
+ readonly reason: string;
9
+ }
10
+
11
+ const appProfile: AppProfile = {
12
+ name: "Context Console",
13
+ mission: "Functional core ready for verifiable effects",
14
+ };
15
+
16
+ const toStartupError = (reason: string): StartupError => ({
17
+ _tag: "StartupError",
18
+ reason,
19
+ });
20
+
21
+ /**
22
+ * CHANGE: Compose shell-level startup program around pure greeting builder
23
+ * WHY: Isolate side effects (console IO) while delegating computation to functional core
24
+ * QUOTE(ТЗ): "FUNCTIONAL CORE, IMPERATIVE SHELL"
25
+ * REF: REQ-SHELL-STARTUP
26
+ * SOURCE: AGENTS.md — эффекты только в SHELL
27
+ * FORMAT THEOREM: ∀profile: valid(profile) → logs(buildGreeting(profile))
28
+ * PURITY: SHELL
29
+ * EFFECT: Effect<void, StartupError, Console>
30
+ * INVARIANT: Console side-effects occur once and only after successful greeting synthesis
31
+ * COMPLEXITY: O(1)/O(1)
32
+ */
33
+ const program = pipe(
34
+ Effect.succeed(buildGreeting(appProfile)),
35
+ Effect.filterOrFail(Option.isSome, () =>
36
+ toStartupError("Profile must not be empty"),
37
+ ),
38
+ Effect.map((option) => option.value),
39
+ Effect.flatMap((greeting) => Console.log(greeting.message)),
40
+ Effect.catchAll((error) =>
41
+ match(error)
42
+ .with({ _tag: "StartupError" }, (startupError) =>
43
+ Console.error(`StartupError: ${startupError.reason}`),
44
+ )
45
+ .exhaustive(),
46
+ ),
47
+ );
48
+
49
+ Effect.runSync(program);
@@ -0,0 +1,189 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import * as S from "@effect/schema/Schema";
5
+ import { Console, Effect, pipe } from "effect";
6
+ import type { ProjectLocator } from "../core/knowledge.js";
7
+ import { buildProjectLocator, linesMatchProject } from "../core/knowledge.js";
8
+ import { ensureDirectory, syncError } from "./syncShared.js";
9
+ import type { SyncError, SyncOptions } from "./syncTypes.js";
10
+
11
+ const PackageRepositorySchema = S.Struct({
12
+ url: S.String,
13
+ });
14
+
15
+ const PackageFileSchema = S.Struct({
16
+ repository: S.optional(PackageRepositorySchema),
17
+ });
18
+ const PackageFileFromJson = S.compose(S.parseJson(), PackageFileSchema);
19
+ const decodePackageFile = S.decodeUnknownSync(PackageFileFromJson);
20
+
21
+ const readRepositoryUrl = (
22
+ cwd: string,
23
+ repositoryUrlOverride?: string,
24
+ ): Effect.Effect<string, SyncError> =>
25
+ Effect.try({
26
+ try: () => {
27
+ if (repositoryUrlOverride !== undefined) {
28
+ return repositoryUrlOverride;
29
+ }
30
+
31
+ const raw = fs.readFileSync(path.join(cwd, "package.json"), "utf8");
32
+ const parsed = decodePackageFile(raw);
33
+ const repositoryUrl = parsed.repository?.url;
34
+
35
+ if (repositoryUrl === undefined) {
36
+ throw new Error("repository url is missing");
37
+ }
38
+
39
+ return repositoryUrl;
40
+ },
41
+ catch: () => syncError("package.json", "Cannot read repository url"),
42
+ });
43
+
44
+ const resolveSourceDir = (
45
+ cwd: string,
46
+ override?: string,
47
+ metaRoot?: string,
48
+ ): Effect.Effect<string, SyncError> =>
49
+ Effect.try({
50
+ try: () => {
51
+ const envSource = process.env.CODEX_SOURCE_DIR;
52
+ const metaCandidate =
53
+ metaRoot === undefined
54
+ ? undefined
55
+ : metaRoot.endsWith(".codex")
56
+ ? metaRoot
57
+ : path.join(metaRoot, ".codex");
58
+ const localSource = path.join(cwd, ".codex");
59
+ const homeSource = path.join(os.homedir(), ".codex");
60
+ const candidates = [
61
+ override,
62
+ envSource,
63
+ metaCandidate,
64
+ localSource,
65
+ homeSource,
66
+ ];
67
+ const existing = candidates.find(
68
+ (candidate) => candidate !== undefined && fs.existsSync(candidate),
69
+ );
70
+
71
+ if (existing === undefined) {
72
+ throw new Error("source .codex not found");
73
+ }
74
+
75
+ return existing;
76
+ },
77
+ catch: () => syncError(".codex", "Source .codex directory is missing"),
78
+ });
79
+
80
+ const collectJsonlFiles = (
81
+ root: string,
82
+ ): Effect.Effect<ReadonlyArray<string>, SyncError> =>
83
+ Effect.try({
84
+ try: () => {
85
+ const collected: string[] = [];
86
+ const walk = (current: string): void => {
87
+ const entries = fs.readdirSync(current, { withFileTypes: true });
88
+ for (const entry of entries) {
89
+ const fullPath = path.join(current, entry.name);
90
+ if (entry.isDirectory()) {
91
+ walk(fullPath);
92
+ } else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
93
+ collected.push(fullPath);
94
+ }
95
+ }
96
+ };
97
+
98
+ walk(root);
99
+ return collected;
100
+ },
101
+ catch: () => syncError(root, "Cannot traverse .codex"),
102
+ });
103
+
104
+ const isFileRelevant = (
105
+ filePath: string,
106
+ locator: ProjectLocator,
107
+ ): Effect.Effect<boolean, SyncError> =>
108
+ Effect.try({
109
+ try: () => {
110
+ const content = fs.readFileSync(filePath, "utf8");
111
+ const lines = content.split("\n");
112
+ return linesMatchProject(lines, locator);
113
+ },
114
+ catch: () => syncError(filePath, "Cannot read jsonl file"),
115
+ });
116
+
117
+ const copyRelevantFile = (
118
+ sourceRoot: string,
119
+ destinationRoot: string,
120
+ filePath: string,
121
+ ): Effect.Effect<void, SyncError> =>
122
+ Effect.try({
123
+ try: () => {
124
+ const relative = path.relative(sourceRoot, filePath);
125
+ const targetPath = path.join(destinationRoot, relative);
126
+ fs.mkdirSync(path.dirname(targetPath), { recursive: true });
127
+ fs.copyFileSync(filePath, targetPath);
128
+ },
129
+ catch: () => syncError(filePath, "Cannot copy file into .knowledge/.codex"),
130
+ });
131
+
132
+ const selectRelevantFiles = (
133
+ files: ReadonlyArray<string>,
134
+ locator: ProjectLocator,
135
+ ): Effect.Effect<ReadonlyArray<string>, SyncError> =>
136
+ Effect.reduce(files, [] as string[], (acc, filePath) =>
137
+ pipe(
138
+ isFileRelevant(filePath, locator),
139
+ Effect.map((isRelevant) => {
140
+ if (isRelevant) {
141
+ acc.push(filePath);
142
+ }
143
+
144
+ return acc;
145
+ }),
146
+ ),
147
+ );
148
+
149
+ // CHANGE: Extract Codex dialog sync into dedicated module for clarity.
150
+ // WHY: Separate Codex-specific shell effects from other sync flows.
151
+ // QUOTE(ТЗ): "вынеси в отдельный файл"
152
+ // REF: user request 2025-11-26
153
+ // SOURCE: internal requirement
154
+ // FORMAT THEOREM: ∀f ∈ Files: relevant(f, locator) → copied(f, destination)
155
+ // PURITY: SHELL
156
+ // EFFECT: Effect<void, SyncError, never>
157
+ // INVARIANT: ∀f ∈ copiedFiles: linesMatchProject(f, locator)
158
+ // COMPLEXITY: O(n) time / O(n) space, n = |files|
159
+ export const syncCodex = (
160
+ options: SyncOptions,
161
+ ): Effect.Effect<void, SyncError> =>
162
+ Effect.gen(function* (_) {
163
+ const repositoryUrl = yield* _(
164
+ readRepositoryUrl(options.cwd, options.repositoryUrlOverride),
165
+ );
166
+ const sourceDir = yield* _(
167
+ resolveSourceDir(options.cwd, options.sourceDir, options.metaRoot),
168
+ );
169
+ const destinationDir =
170
+ options.destinationDir ?? path.join(options.cwd, ".knowledge", ".codex");
171
+
172
+ yield* _(ensureDirectory(destinationDir));
173
+
174
+ const locator = buildProjectLocator(repositoryUrl, options.cwd);
175
+ const allJsonlFiles = yield* _(collectJsonlFiles(sourceDir));
176
+ const relevantFiles = yield* _(selectRelevantFiles(allJsonlFiles, locator));
177
+
178
+ yield* _(
179
+ Effect.forEach(relevantFiles, (filePath) =>
180
+ copyRelevantFile(sourceDir, destinationDir, filePath),
181
+ ),
182
+ );
183
+
184
+ yield* _(
185
+ Console.log(
186
+ `Synced ${relevantFiles.length} dialog files into .knowledge/.codex`,
187
+ ),
188
+ );
189
+ });
@@ -0,0 +1,98 @@
1
+ import { createHash } from "node:crypto";
2
+ import fs from "node:fs";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { Console, Effect, pipe } from "effect";
6
+ import { ensureDirectory, syncError } from "./syncShared.js";
7
+ import type { SyncError, SyncOptions } from "./syncTypes.js";
8
+
9
+ const copyDirectoryJsonOnly = (
10
+ sourceRoot: string,
11
+ destinationRoot: string,
12
+ ): Effect.Effect<number, SyncError> =>
13
+ Effect.try({
14
+ try: () => {
15
+ let copied = 0;
16
+ fs.cpSync(sourceRoot, destinationRoot, {
17
+ recursive: true,
18
+ filter: (src) => {
19
+ const stat = fs.statSync(src);
20
+ if (stat.isDirectory()) {
21
+ return true;
22
+ }
23
+ if (src.endsWith(".json")) {
24
+ copied += 1;
25
+ return true;
26
+ }
27
+ return false;
28
+ },
29
+ });
30
+ return copied;
31
+ },
32
+ catch: () => syncError(sourceRoot, "Cannot traverse Qwen directory"),
33
+ });
34
+
35
+ const qwenHashFromPath = (projectPath: string): string =>
36
+ createHash("sha256").update(projectPath).digest("hex");
37
+
38
+ const resolveQwenSourceDir = (
39
+ cwd: string,
40
+ override?: string,
41
+ metaRoot?: string,
42
+ ): Effect.Effect<string, SyncError> =>
43
+ pipe(
44
+ Effect.sync(() => {
45
+ const hash = qwenHashFromPath(cwd);
46
+ const envSource = process.env.QWEN_SOURCE_DIR;
47
+ const baseFromMeta =
48
+ metaRoot === undefined
49
+ ? undefined
50
+ : metaRoot.endsWith(".qwen")
51
+ ? metaRoot
52
+ : path.join(metaRoot, ".qwen");
53
+ const homeBase = path.join(os.homedir(), ".qwen");
54
+
55
+ const candidates = [
56
+ override,
57
+ envSource,
58
+ baseFromMeta ? path.join(baseFromMeta, "tmp", hash) : undefined,
59
+ path.join(cwd, ".qwen", "tmp", hash),
60
+ path.join(homeBase, "tmp", hash),
61
+ ];
62
+
63
+ return candidates.find(
64
+ (candidate) => candidate !== undefined && fs.existsSync(candidate),
65
+ );
66
+ }),
67
+ Effect.flatMap((found) =>
68
+ found === undefined
69
+ ? Effect.fail(syncError(".qwen", "Qwen source directory is missing"))
70
+ : Effect.succeed(found),
71
+ ),
72
+ );
73
+
74
+ export const syncQwen = (
75
+ options: SyncOptions,
76
+ ): Effect.Effect<void, SyncError> =>
77
+ pipe(
78
+ resolveQwenSourceDir(options.cwd, options.qwenSourceDir, options.metaRoot),
79
+ Effect.flatMap((qwenSource) =>
80
+ pipe(
81
+ ensureDirectory(path.join(options.cwd, ".knowledge", ".qwen")),
82
+ Effect.flatMap(() =>
83
+ copyDirectoryJsonOnly(
84
+ qwenSource,
85
+ path.join(options.cwd, ".knowledge", ".qwen"),
86
+ ),
87
+ ),
88
+ Effect.flatMap((copiedCount) =>
89
+ Console.log(
90
+ `Synced ${copiedCount} Qwen dialog files into .knowledge/.qwen`,
91
+ ),
92
+ ),
93
+ ),
94
+ ),
95
+ Effect.catchAll(() =>
96
+ Console.log("Qwen source not found; skipped syncing Qwen dialog files"),
97
+ ),
98
+ );
@@ -0,0 +1,62 @@
1
+ import { pathToFileURL } from "node:url";
2
+ import { Effect } from "effect";
3
+ import { syncCodex } from "./codexSync.js";
4
+ import { syncQwen } from "./qwenSync.js";
5
+ import type { SyncError, SyncOptions } from "./syncTypes.js";
6
+
7
+ // PURPOSE: Sync project-scoped dialogs (Codex + Qwen) into .knowledge storage.
8
+ export const buildSyncProgram = (
9
+ options: SyncOptions,
10
+ ): Effect.Effect<void, SyncError> =>
11
+ Effect.gen(function* (_) {
12
+ yield* _(syncCodex(options));
13
+ yield* _(syncQwen(options));
14
+ });
15
+
16
+ const isMainModule = (): boolean => {
17
+ const entry = process.argv[1];
18
+
19
+ if (entry === undefined) {
20
+ return false;
21
+ }
22
+
23
+ return import.meta.url === pathToFileURL(entry).href;
24
+ };
25
+
26
+ const parseArgs = (): SyncOptions => {
27
+ const argv = process.argv.slice(2);
28
+ let result: SyncOptions = { cwd: process.cwd() };
29
+
30
+ const mapping: Readonly<Record<string, keyof SyncOptions>> = {
31
+ "--source": "sourceDir",
32
+ "-s": "sourceDir",
33
+ "--dest": "destinationDir",
34
+ "-d": "destinationDir",
35
+ "--project-url": "repositoryUrlOverride",
36
+ "--project-name": "repositoryUrlOverride",
37
+ "--meta-root": "metaRoot",
38
+ "--qwen-source": "qwenSourceDir",
39
+ };
40
+
41
+ for (let i = 0; i < argv.length; i++) {
42
+ const arg = argv[i];
43
+ const key = mapping[arg as keyof typeof mapping];
44
+ if (key === undefined) {
45
+ continue;
46
+ }
47
+
48
+ const value = argv[i + 1];
49
+ if (value !== undefined) {
50
+ result = { ...result, [key]: value };
51
+ i++;
52
+ }
53
+ }
54
+
55
+ return result;
56
+ };
57
+
58
+ if (isMainModule()) {
59
+ const program = buildSyncProgram(parseArgs());
60
+
61
+ Effect.runSync(program);
62
+ }
@@ -0,0 +1,20 @@
1
+ import fs from "node:fs";
2
+ import { Effect } from "effect";
3
+ import type { SyncError } from "./syncTypes.js";
4
+
5
+ export const syncError = (pathValue: string, reason: string): SyncError => ({
6
+ _tag: "SyncError",
7
+ path: pathValue,
8
+ reason,
9
+ });
10
+
11
+ export const ensureDirectory = (
12
+ directory: string,
13
+ ): Effect.Effect<void, SyncError> =>
14
+ Effect.try({
15
+ try: () => {
16
+ fs.mkdirSync(directory, { recursive: true });
17
+ },
18
+ catch: () =>
19
+ syncError(directory, "Cannot create destination directory structure"),
20
+ });
@@ -0,0 +1,14 @@
1
+ export interface SyncError {
2
+ readonly _tag: "SyncError";
3
+ readonly path: string;
4
+ readonly reason: string;
5
+ }
6
+
7
+ export interface SyncOptions {
8
+ readonly cwd: string;
9
+ readonly sourceDir?: string;
10
+ readonly destinationDir?: string;
11
+ readonly repositoryUrlOverride?: string;
12
+ readonly metaRoot?: string;
13
+ readonly qwenSourceDir?: string;
14
+ }
@@ -0,0 +1,10 @@
1
+ declare global {
2
+ namespace NodeJS {
3
+ interface ProcessEnv {
4
+ readonly CODEX_SOURCE_DIR?: string;
5
+ readonly QWEN_SOURCE_DIR?: string;
6
+ }
7
+ }
8
+ }
9
+
10
+ export {};
@@ -0,0 +1,18 @@
1
+ {
2
+ // CHANGE: Emit compiled artifacts into dist instead of source tree
3
+ // WHY: Keep repository free of generated .js while still supporting production builds
4
+ // QUOTE(ТЗ): "FUNCTIONAL CORE, IMPERATIVE SHELL" — сборка отделена от исходников
5
+ // PURITY: SHELL (build configuration)
6
+ "extends": "./tsconfig.json",
7
+ "compilerOptions": {
8
+ // ═══════════════════════════════════════════════════════════════════════════
9
+ // EMIT OVERRIDES (build-only)
10
+ // ═══════════════════════════════════════════════════════════════════════════
11
+ "noEmit": false,
12
+ "outDir": "dist",
13
+ "rootDir": "src",
14
+ "tsBuildInfoFile": "dist/.tsbuildinfo"
15
+ },
16
+ "include": ["src/**/*"],
17
+ "exclude": ["tests/**/*", "node_modules", "dist"]
18
+ }
@@ -0,0 +1,81 @@
1
+ // CHANGE: Migrate from Jest to Vitest with mathematical equivalence
2
+ // WHY: Faster execution, native ESM, Effect integration via @effect/vitest
3
+ // QUOTE(ТЗ): "Проект использует Effect + функциональную парадигму"
4
+ // REF: Migration from jest.config.mjs
5
+ // PURITY: SHELL (configuration only)
6
+ // INVARIANT: ∀ test: behavior_jest ≡ behavior_vitest
7
+ // EFFECT: Effect<TestReport, never, TestEnvironment>
8
+ // COMPLEXITY: O(n) test execution where n = |test_files|
9
+
10
+ import { defineConfig } from "vitest/config";
11
+ import tsconfigPaths from "vite-tsconfig-paths";
12
+
13
+ export default defineConfig({
14
+ plugins: [tsconfigPaths()], // Resolves @/* paths from tsconfig
15
+ test: {
16
+ // CHANGE: Native ESM support without experimental flags
17
+ // WHY: Vitest designed for ESM, no need for --experimental-vm-modules
18
+ // INVARIANT: Deterministic test execution without side effects
19
+ globals: false, // IMPORTANT: Use explicit imports for type safety
20
+ environment: "node",
21
+
22
+ // CHANGE: Match Jest's test file patterns
23
+ // INVARIANT: Same test discovery as Jest
24
+ include: ["tests/**/*.{test,spec}.ts"],
25
+ exclude: ["node_modules", "dist", "dist-test"],
26
+
27
+ // CHANGE: Coverage with 100% threshold for CORE (same as Jest)
28
+ // WHY: CORE must maintain mathematical guarantees via complete coverage
29
+ // INVARIANT: coverage_vitest ≥ coverage_jest ∧ ∀ f ∈ CORE: coverage(f) = 100%
30
+ coverage: {
31
+ provider: "v8", // Faster than babel (istanbul), native V8 coverage
32
+ reporter: ["text", "json", "html"],
33
+ include: ["src/**/*.ts"],
34
+ exclude: [
35
+ "src/**/*.test.ts",
36
+ "src/**/*.spec.ts",
37
+ "src/**/__tests__/**",
38
+ "scripts/**/*.ts",
39
+ ],
40
+ // CHANGE: Maintain exact same thresholds as Jest
41
+ // WHY: Enforce 100% coverage for CORE, 10% minimum for SHELL
42
+ // INVARIANT: ∀ f ∈ src/core/**/*.ts: all_metrics(f) = 100%
43
+ // NOTE: Vitest v8 provider collects coverage for all matched files by default
44
+ thresholds: {
45
+ "src/core/**/*.ts": {
46
+ branches: 100,
47
+ functions: 100,
48
+ lines: 100,
49
+ statements: 100,
50
+ },
51
+ global: {
52
+ branches: 10,
53
+ functions: 10,
54
+ lines: 10,
55
+ statements: 10,
56
+ },
57
+ },
58
+ },
59
+
60
+ // CHANGE: Faster test execution via thread pooling
61
+ // WHY: Vitest uses worker threads by default (faster than Jest's processes)
62
+ // COMPLEXITY: O(n/k) where n = tests, k = worker_count
63
+ // NOTE: Vitest runs tests in parallel by default, no additional config needed
64
+
65
+ // CHANGE: Clear mocks between tests (Jest equivalence)
66
+ // WHY: Prevent test contamination, ensure test independence
67
+ // INVARIANT: ∀ test_i, test_j: independent(test_i, test_j) ⇒ no_shared_state
68
+ clearMocks: true,
69
+ mockReset: true,
70
+ restoreMocks: true,
71
+
72
+ // CHANGE: Disable globals to enforce explicit imports
73
+ // WHY: Type safety, explicit dependencies, functional purity
74
+ // NOTE: Tests must import { describe, it, expect } from "vitest"
75
+ },
76
+ resolve: {
77
+ alias: {
78
+ "@": "/src",
79
+ },
80
+ },
81
+ });