Skip to content

Commit 737df2c

Browse files
authored
Merge pull request #420 from skulidropek/codex/effect-ts-compliance
[codex] align API and session-sync compliance boundaries
2 parents 11a9745 + 18b068f commit 737df2c

39 files changed

Lines changed: 1277 additions & 282 deletions

bun.lock

Lines changed: 17 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

eslint.effect-ts-shared.mjs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
export const effectMigrationWarnings = [
2+
{
3+
selector: "SwitchStatement",
4+
message: "Effect migration blocker: use Match.exhaustive instead of switch."
5+
},
6+
{
7+
selector: "TryStatement",
8+
message: "Effect migration blocker: use Effect.try / Effect.catch* instead of try/catch."
9+
},
10+
{
11+
selector: "AwaitExpression",
12+
message: "Effect migration blocker: use Effect.gen / Effect.flatMap instead of await."
13+
},
14+
{
15+
selector: "FunctionDeclaration[async=true], FunctionExpression[async=true], ArrowFunctionExpression[async=true]",
16+
message: "Effect migration blocker: use Effect.gen / Effect.tryPromise instead of async functions."
17+
},
18+
{
19+
selector: "NewExpression[callee.name='Promise']",
20+
message: "Effect migration blocker: use Effect.async / Effect.tryPromise instead of new Promise."
21+
},
22+
{
23+
selector: "CallExpression[callee.object.name='Promise']",
24+
message: "Effect migration blocker: use Effect combinators instead of Promise.*."
25+
}
26+
]
27+
28+
export const effectPromiseRestrictedTypes = {
29+
types: {
30+
Promise: {
31+
message: "Effect migration blocker: avoid Promise in public types. Use Effect.Effect<A, E, R>."
32+
},
33+
"Promise<*>": {
34+
message: "Effect migration blocker: avoid Promise<T>. Use Effect.Effect<T, E, R>."
35+
}
36+
}
37+
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
"web:serve": "bun run --cwd packages/app serve:web",
4747
"lint": "bun run --filter @prover-coder-ai/docker-git-terminal lint && bun run --filter @prover-coder-ai/docker-git lint && bun run --filter @effect-template/lib lint",
4848
"lint:tests": "bun run --filter @prover-coder-ai/docker-git lint:tests",
49-
"lint:effect": "bun run --filter @prover-coder-ai/docker-git-terminal lint:effect && bun run --filter @prover-coder-ai/docker-git lint:effect && bun run --filter @effect-template/lib lint:effect",
49+
"lint:effect": "bun run --filter @prover-coder-ai/docker-git-session-sync lint:effect && bun run --filter @prover-coder-ai/docker-git-terminal lint:effect && bun run --filter @prover-coder-ai/docker-git lint:effect && bun run --filter @prover-coder-ai/docker-git-container lint:effect && bun run --filter @effect-template/lib lint:effect && bun run --filter @effect-template/api lint:effect",
5050
"test": "bun run --filter @prover-coder-ai/docker-git-session-sync test && bun run --filter @prover-coder-ai/docker-git-terminal test && bun run --filter @prover-coder-ai/docker-git test && bun run --filter @effect-template/lib test",
5151
"typecheck": "bun run --filter @prover-coder-ai/docker-git-session-sync typecheck && bun run --filter @prover-coder-ai/docker-git-terminal typecheck && bun run --filter @prover-coder-ai/docker-git typecheck && bun run --filter @effect-template/lib typecheck",
5252
"start": "bun run --cwd packages/app build:docker-git && bun ./packages/app/dist/src/docker-git/main.js"
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// CHANGE: add API-local Effect-TS compliance lint profile.
2+
// WHY: expose Effect migration blockers in the API package, which previously only ran baseline ESLint.
3+
// QUOTE(TZ): "packages/api не защищён lint:effect"
4+
// REF: user-request-2026-06-17-effect-compliance-api
5+
// SOURCE: n/a
6+
// FORMAT THEOREM: lint(api) = hard unsafe bypass errors + visible Effect migration blockers
7+
// PURITY: SHELL
8+
// EFFECT: eslint config
9+
// INVARIANT: config does not alter runtime behavior.
10+
// COMPLEXITY: O(1)/O(1)
11+
import eslintComments from "@eslint-community/eslint-plugin-eslint-comments"
12+
import globals from "globals"
13+
import tseslint from "typescript-eslint"
14+
15+
import { effectMigrationWarnings, effectPromiseRestrictedTypes } from "../../eslint.effect-ts-shared.mjs"
16+
17+
export default tseslint.config({
18+
name: "api-effect-ts-compliance",
19+
files: ["src/**/*.ts", "tests/**/*.ts"],
20+
languageOptions: {
21+
parser: tseslint.parser,
22+
globals: { ...globals.node }
23+
},
24+
plugins: {
25+
"@typescript-eslint": tseslint.plugin,
26+
"eslint-comments": eslintComments
27+
},
28+
rules: {
29+
"@typescript-eslint/ban-ts-comment": ["error", {
30+
"ts-check": false,
31+
"ts-expect-error": true,
32+
"ts-ignore": true,
33+
"ts-nocheck": true
34+
}],
35+
"@typescript-eslint/no-explicit-any": "error",
36+
"@typescript-eslint/no-restricted-types": ["warn", effectPromiseRestrictedTypes],
37+
"eslint-comments/disable-enable-pair": "error",
38+
"eslint-comments/no-unlimited-disable": "error",
39+
"eslint-comments/no-unused-disable": "error",
40+
"eslint-comments/no-use": "error",
41+
"no-console": "error",
42+
"no-restricted-syntax": ["warn", ...effectMigrationWarnings]
43+
}
44+
})

packages/api/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"pretypecheck": "bun run --cwd ../terminal build && bun run --cwd ../container build && bun run --cwd ../lib build",
1616
"typecheck": "tsc --noEmit -p tsconfig.json",
1717
"lint": "eslint .",
18+
"lint:effect": "eslint --config eslint.effect-ts-check.config.mjs .",
1819
"pretest": "bun run --cwd ../terminal build && bun run --cwd ../container build && bun run --cwd ../lib build",
1920
"test": "vitest run"
2021
},
@@ -41,6 +42,7 @@
4142
"homepage": "https://github.com/ProverCoderAI/docker-git#readme",
4243
"devDependencies": {
4344
"@effect/vitest": "^0.29.0",
45+
"@eslint-community/eslint-plugin-eslint-comments": "^4.7.2",
4446
"@eslint/js": "10.0.1",
4547
"@types/node": "^25.9.3",
4648
"@types/ws": "^8.18.1",
@@ -50,6 +52,7 @@
5052
"fast-check": "4.8.0",
5153
"globals": "^17.6.0",
5254
"typescript": "^6.0.3",
55+
"typescript-eslint": "^8.61.1",
5356
"vitest": "^4.1.9"
5457
}
5558
}

packages/api/src/api/contracts.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,17 @@ export type ProjectSummary = {
2626
readonly sshSessions: number
2727
readonly startedAtIso: string | null
2828
readonly startedAtEpochMs: number | null
29-
readonly clonedOnHostname?: string | undefined
29+
/**
30+
* Hostname of the machine where the project was originally cloned, when known.
31+
*
32+
* @pure true - immutable API DTO field.
33+
* @effect none
34+
* @invariant if present, the value was decoded by the API/config hostname schema.
35+
* @precondition producers omit the field when clone host identity is unknown.
36+
* @postcondition consumers can group projects by clone-origin host without reading OS state.
37+
* @complexity O(1)/O(1)
38+
*/
39+
readonly clonedOnHostname?: string
3040
}
3141

3242
export type ProjectDetails = ProjectSummary & {
@@ -480,6 +490,24 @@ export type CreateProjectRequest = {
480490
readonly forceEnv?: boolean | undefined
481491
readonly waitForClone?: boolean | undefined
482492
readonly async?: boolean | undefined
493+
/**
494+
* Hostname of the machine where the project was cloned.
495+
*
496+
* CHANGE: add explicit clone-origin hostname to the create-project contract.
497+
* WHY: keeps command builders pure by passing host identity as immutable input instead of reading OS state.
498+
* QUOTE(ТЗ): "CORE: Исключительно чистые функции, неизменяемые данные, математические операции"
499+
* REF: pr-420-coderabbit-review-4518791377
500+
* SOURCE: n/a
501+
* FORMAT THEOREM: ∀h ∈ Hostname: request(h) -> build(request).config.clonedOnHostname = h
502+
* PURITY: CORE - immutable request data.
503+
* @pure true - immutable API request DTO field.
504+
* @effect none
505+
* INVARIANT: if present, value satisfies API HostnameSchema.
506+
* @precondition callers omit the field when clone host identity is unknown.
507+
* @postcondition command builders receive host identity as data, not by reading OS state.
508+
* COMPLEXITY: O(1)/O(1)
509+
*/
510+
readonly clonedOnHostname?: string
483511
}
484512

485513
export type AgentEnvVar = {

packages/api/src/api/schema.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ export {
1212
const OptionalString = Schema.optional(Schema.String)
1313
const OptionalBoolean = Schema.optional(Schema.Boolean)
1414
const OptionalNullableString = Schema.optional(Schema.NullOr(Schema.String))
15+
const HostnameSchema = Schema.String.pipe(
16+
Schema.minLength(1),
17+
Schema.maxLength(253),
18+
Schema.pattern(/^(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)(?:\.[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$/u)
19+
)
1520

1621
export const CreateProjectRequestSchema = Schema.Struct({
1722
repoUrl: OptionalString,
@@ -51,7 +56,8 @@ export const CreateProjectRequestSchema = Schema.Struct({
5156
force: OptionalBoolean,
5257
forceEnv: OptionalBoolean,
5358
waitForClone: OptionalBoolean,
54-
async: OptionalBoolean
59+
async: OptionalBoolean,
60+
clonedOnHostname: Schema.optional(HostnameSchema)
5561
})
5662

5763
export const GithubAuthLoginRequestSchema = Schema.Struct({

packages/api/src/http.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1426,7 +1426,11 @@ export const makeRouter = () => {
14261426
"/projects",
14271427
Effect.gen(function*(_) {
14281428
const request = yield* _(readCreateProjectRequest())
1429-
const result = yield* _(createProjectFromRequest(request))
1429+
const { clonedOnHostname, ...requestWithoutCloneHost } = request
1430+
const result = yield* _(createProjectFromRequest({
1431+
...requestWithoutCloneHost,
1432+
...(clonedOnHostname === undefined ? {} : { clonedOnHostname })
1433+
}))
14301434
return yield* _(
14311435
"accepted" in result && result.accepted === true
14321436
? jsonResponse(result, 202)

0 commit comments

Comments
 (0)