Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 17 additions & 4 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 37 additions & 0 deletions eslint.effect-ts-shared.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
export const effectMigrationWarnings = [
{
selector: "SwitchStatement",
message: "Effect migration blocker: use Match.exhaustive instead of switch."
},
{
selector: "TryStatement",
message: "Effect migration blocker: use Effect.try / Effect.catch* instead of try/catch."
},
{
selector: "AwaitExpression",
message: "Effect migration blocker: use Effect.gen / Effect.flatMap instead of await."
},
{
selector: "FunctionDeclaration[async=true], FunctionExpression[async=true], ArrowFunctionExpression[async=true]",
message: "Effect migration blocker: use Effect.gen / Effect.tryPromise instead of async functions."
},
{
selector: "NewExpression[callee.name='Promise']",
message: "Effect migration blocker: use Effect.async / Effect.tryPromise instead of new Promise."
},
{
selector: "CallExpression[callee.object.name='Promise']",
message: "Effect migration blocker: use Effect combinators instead of Promise.*."
}
]

export const effectPromiseRestrictedTypes = {
types: {
Promise: {
message: "Effect migration blocker: avoid Promise in public types. Use Effect.Effect<A, E, R>."
},
"Promise<*>": {
message: "Effect migration blocker: avoid Promise<T>. Use Effect.Effect<T, E, R>."
}
}
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
"web:serve": "bun run --cwd packages/app serve:web",
"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",
"lint:tests": "bun run --filter @prover-coder-ai/docker-git lint:tests",
"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",
"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",
"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",
"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",
"start": "bun run --cwd packages/app build:docker-git && bun ./packages/app/dist/src/docker-git/main.js"
Expand Down
44 changes: 44 additions & 0 deletions packages/api/eslint.effect-ts-check.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// CHANGE: add API-local Effect-TS compliance lint profile.
// WHY: expose Effect migration blockers in the API package, which previously only ran baseline ESLint.
// QUOTE(TZ): "packages/api не защищён lint:effect"
// REF: user-request-2026-06-17-effect-compliance-api
// SOURCE: n/a
// FORMAT THEOREM: lint(api) = hard unsafe bypass errors + visible Effect migration blockers
// PURITY: SHELL
// EFFECT: eslint config
// INVARIANT: config does not alter runtime behavior.
// COMPLEXITY: O(1)/O(1)
import eslintComments from "@eslint-community/eslint-plugin-eslint-comments"
import globals from "globals"
import tseslint from "typescript-eslint"

import { effectMigrationWarnings, effectPromiseRestrictedTypes } from "../../eslint.effect-ts-shared.mjs"

export default tseslint.config({
name: "api-effect-ts-compliance",
files: ["src/**/*.ts", "tests/**/*.ts"],
languageOptions: {
parser: tseslint.parser,
globals: { ...globals.node }
},
plugins: {
"@typescript-eslint": tseslint.plugin,
"eslint-comments": eslintComments
},
rules: {
"@typescript-eslint/ban-ts-comment": ["error", {
"ts-check": false,
"ts-expect-error": true,
"ts-ignore": true,
"ts-nocheck": true
}],
"@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/no-restricted-types": ["warn", effectPromiseRestrictedTypes],
"eslint-comments/disable-enable-pair": "error",
"eslint-comments/no-unlimited-disable": "error",
"eslint-comments/no-unused-disable": "error",
"eslint-comments/no-use": "error",
"no-console": "error",
"no-restricted-syntax": ["warn", ...effectMigrationWarnings]
}
})
3 changes: 3 additions & 0 deletions packages/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"pretypecheck": "bun run --cwd ../terminal build && bun run --cwd ../container build && bun run --cwd ../lib build",
"typecheck": "tsc --noEmit -p tsconfig.json",
"lint": "eslint .",
"lint:effect": "eslint --config eslint.effect-ts-check.config.mjs .",
"pretest": "bun run --cwd ../terminal build && bun run --cwd ../container build && bun run --cwd ../lib build",
"test": "vitest run"
},
Expand All @@ -41,6 +42,7 @@
"homepage": "https://github.com/ProverCoderAI/docker-git#readme",
"devDependencies": {
"@effect/vitest": "^0.29.0",
"@eslint-community/eslint-plugin-eslint-comments": "^4.7.2",
"@eslint/js": "10.0.1",
"@types/node": "^25.9.3",
"@types/ws": "^8.18.1",
Expand All @@ -50,6 +52,7 @@
"fast-check": "4.8.0",
"globals": "^17.6.0",
"typescript": "^6.0.3",
"typescript-eslint": "^8.61.1",
"vitest": "^4.1.9"
}
}
30 changes: 29 additions & 1 deletion packages/api/src/api/contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,17 @@ export type ProjectSummary = {
readonly sshSessions: number
readonly startedAtIso: string | null
readonly startedAtEpochMs: number | null
readonly clonedOnHostname?: string | undefined
/**
* Hostname of the machine where the project was originally cloned, when known.
*
* @pure true - immutable API DTO field.
* @effect none
* @invariant if present, the value was decoded by the API/config hostname schema.
* @precondition producers omit the field when clone host identity is unknown.
* @postcondition consumers can group projects by clone-origin host without reading OS state.
* @complexity O(1)/O(1)
*/
readonly clonedOnHostname?: string
}

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

export type AgentEnvVar = {
Expand Down
8 changes: 7 additions & 1 deletion packages/api/src/api/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ export {
const OptionalString = Schema.optional(Schema.String)
const OptionalBoolean = Schema.optional(Schema.Boolean)
const OptionalNullableString = Schema.optional(Schema.NullOr(Schema.String))
const HostnameSchema = Schema.String.pipe(
Schema.minLength(1),
Schema.maxLength(253),
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)
)

export const CreateProjectRequestSchema = Schema.Struct({
repoUrl: OptionalString,
Expand Down Expand Up @@ -51,7 +56,8 @@ export const CreateProjectRequestSchema = Schema.Struct({
force: OptionalBoolean,
forceEnv: OptionalBoolean,
waitForClone: OptionalBoolean,
async: OptionalBoolean
async: OptionalBoolean,
clonedOnHostname: Schema.optional(HostnameSchema)
})
Comment thread
coderabbitai[bot] marked this conversation as resolved.

export const GithubAuthLoginRequestSchema = Schema.Struct({
Expand Down
6 changes: 5 additions & 1 deletion packages/api/src/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1426,7 +1426,11 @@ export const makeRouter = () => {
"/projects",
Effect.gen(function*(_) {
const request = yield* _(readCreateProjectRequest())
const result = yield* _(createProjectFromRequest(request))
const { clonedOnHostname, ...requestWithoutCloneHost } = request
const result = yield* _(createProjectFromRequest({
...requestWithoutCloneHost,
...(clonedOnHostname === undefined ? {} : { clonedOnHostname })
}))
return yield* _(
"accepted" in result && result.accepted === true
? jsonResponse(result, 202)
Expand Down
Loading