traits 0.4.0 → 0.5.0
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/Assessment.ts +14 -0
- package/LICENSE +203 -0
- package/Source.ts +13 -0
- package/Trait.ts +51 -0
- package/TraitConflict.ts +52 -0
- package/TraitDerivation.ts +137 -0
- package/TraitEvaluation.ts +69 -0
- package/TraitStore.ts +88 -0
- package/TraitsConfig.ts +57 -0
- package/TraitsContext.ts +11 -0
- package/_util/Approval.ts +59 -0
- package/_util/Git.ts +25 -0
- package/_util/TraitsConfigProvider.ts +22 -0
- package/_util/TraitsTelemetry.ts +20 -0
- package/_util/words.ts +555 -0
- package/commands/init.ts +73 -0
- package/dist/Assessment.d.ts +12 -0
- package/dist/Assessment.d.ts.map +1 -0
- package/dist/Assessment.js +12 -0
- package/dist/Assessment.js.map +1 -0
- package/dist/Source.d.ts +21 -0
- package/dist/Source.d.ts.map +1 -0
- package/dist/Source.js +10 -0
- package/dist/Source.js.map +1 -0
- package/dist/Trait.d.ts +12 -0
- package/dist/Trait.d.ts.map +1 -0
- package/dist/Trait.js +44 -0
- package/dist/Trait.js.map +1 -0
- package/dist/TraitConflict.d.ts +56 -0
- package/dist/TraitConflict.d.ts.map +1 -0
- package/dist/TraitConflict.js +47 -0
- package/dist/TraitConflict.js.map +1 -0
- package/dist/TraitDerivation.d.ts +99 -0
- package/dist/TraitDerivation.d.ts.map +1 -0
- package/dist/TraitDerivation.js +109 -0
- package/dist/TraitDerivation.js.map +1 -0
- package/dist/TraitEvaluation.d.ts +68 -0
- package/dist/TraitEvaluation.d.ts.map +1 -0
- package/dist/TraitEvaluation.js +61 -0
- package/dist/TraitEvaluation.js.map +1 -0
- package/dist/TraitStore.d.ts +17 -0
- package/dist/TraitStore.d.ts.map +1 -0
- package/dist/TraitStore.js +57 -0
- package/dist/TraitStore.js.map +1 -0
- package/dist/TraitsConfig.d.ts +14 -0
- package/dist/TraitsConfig.d.ts.map +1 -0
- package/dist/TraitsConfig.js +40 -0
- package/dist/TraitsConfig.js.map +1 -0
- package/dist/TraitsContext.d.ts +11 -0
- package/dist/TraitsContext.d.ts.map +1 -0
- package/dist/TraitsContext.js +4 -0
- package/dist/TraitsContext.js.map +1 -0
- package/dist/_util/Approval.d.ts +28 -0
- package/dist/_util/Approval.d.ts.map +1 -0
- package/dist/_util/Approval.js +48 -0
- package/dist/_util/Approval.js.map +1 -0
- package/dist/_util/Git.d.ts +15 -0
- package/dist/_util/Git.d.ts.map +1 -0
- package/dist/_util/Git.js +17 -0
- package/dist/_util/Git.js.map +1 -0
- package/dist/_util/TraitsConfigProvider.d.ts +3 -0
- package/dist/_util/TraitsConfigProvider.d.ts.map +1 -0
- package/dist/_util/TraitsConfigProvider.js +17 -0
- package/dist/_util/TraitsConfigProvider.js.map +1 -0
- package/dist/_util/TraitsTelemetry.d.ts +3 -0
- package/dist/_util/TraitsTelemetry.d.ts.map +1 -0
- package/dist/_util/TraitsTelemetry.js +12 -0
- package/dist/_util/TraitsTelemetry.js.map +1 -0
- package/dist/_util/words.d.ts +2 -0
- package/dist/_util/words.d.ts.map +1 -0
- package/dist/_util/words.js +556 -0
- package/dist/_util/words.js.map +1 -0
- package/dist/commands/init.d.ts +4 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +67 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/main.d.ts +3 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +73 -0
- package/dist/main.js.map +1 -0
- package/dist/package.json +53 -0
- package/dist/toolkit/CheckTraits.d.ts +79 -0
- package/dist/toolkit/CheckTraits.d.ts.map +1 -0
- package/dist/toolkit/CheckTraits.js +110 -0
- package/dist/toolkit/CheckTraits.js.map +1 -0
- package/dist/toolkit/DeriveTrait.d.ts +108 -0
- package/dist/toolkit/DeriveTrait.d.ts.map +1 -0
- package/dist/toolkit/DeriveTrait.js +54 -0
- package/dist/toolkit/DeriveTrait.js.map +1 -0
- package/dist/toolkit/toolkit.d.ts +3 -0
- package/dist/toolkit/toolkit.d.ts.map +1 -0
- package/dist/toolkit/toolkit.js +6 -0
- package/dist/toolkit/toolkit.js.map +1 -0
- package/index.ts +9 -0
- package/main.ts +113 -0
- package/package.json +46 -0
- package/skills/traits/SKILL.md +307 -0
- package/toolkit/CheckTraits.ts +150 -0
- package/toolkit/DeriveTrait.ts +59 -0
- package/toolkit/toolkit.ts +9 -0
- 1289345811335-0.8567837113514543/lib/traits.js +0 -662
- 1289345811335-0.8567837113514543/package.json +0 -7
package/TraitsConfig.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { NodeServices } from "@effect/platform-node"
|
|
2
|
+
import { Array, Effect, Layer, Path, pipe, Schema as S, Semaphore } from "effect"
|
|
3
|
+
import { DecisionModel, LanguageModel } from "effect/ai"
|
|
4
|
+
import { HttpClient } from "effect/http"
|
|
5
|
+
|
|
6
|
+
import { getWorktreeRoot } from "./_util/Git.ts"
|
|
7
|
+
import { TraitsContext } from "./TraitsContext.ts"
|
|
8
|
+
|
|
9
|
+
export type TraitsConfig = typeof TraitsConfig.Type
|
|
10
|
+
export const TraitsConfig = S.Struct({
|
|
11
|
+
excludeGitignored: S.Boolean.pipe(S.optional).annotate({
|
|
12
|
+
description: "Exclude Git-ignored files from checks by default. Set false to disable the Git file-set filter."
|
|
13
|
+
}),
|
|
14
|
+
excludes: S.Array(S.NonEmptyString).pipe(S.optional).annotate({
|
|
15
|
+
description:
|
|
16
|
+
"Filenames or native filesystem glob patterns excluded from trait checks, relative to traits.ts or absolute. Use directory/** to exclude a subtree. Bare directories are not expanded; escape glob characters in literal filenames using native glob syntax."
|
|
17
|
+
}),
|
|
18
|
+
layerDecisionModel: S.declare(
|
|
19
|
+
(
|
|
20
|
+
value
|
|
21
|
+
): value is Layer.Layer<DecisionModel.DecisionModel, any, NodeServices.NodeServices | HttpClient.HttpClient> =>
|
|
22
|
+
Layer.isLayer(value)
|
|
23
|
+
),
|
|
24
|
+
layerLanguageModel: S.declare(
|
|
25
|
+
(
|
|
26
|
+
value
|
|
27
|
+
): value is Layer.Layer<LanguageModel.LanguageModel, any, NodeServices.NodeServices | HttpClient.HttpClient> =>
|
|
28
|
+
Layer.isLayer(value)
|
|
29
|
+
)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
export const layer = Effect.gen(function* () {
|
|
33
|
+
const path = yield* Path.Path
|
|
34
|
+
const configRoot = yield* getWorktreeRoot
|
|
35
|
+
const url = yield* path.toFileUrl(path.join(configRoot, "traits.ts"))
|
|
36
|
+
const module = yield* Effect.promise(() => import(url.href))
|
|
37
|
+
const config = yield* pipe(module.default, S.decodeUnknownEffect(TraitsConfig))
|
|
38
|
+
return Layer.mergeAll(
|
|
39
|
+
// oxlint-disable-next-line effecttsgo/any-unknown-in-error-context
|
|
40
|
+
config.layerDecisionModel.pipe(Layer.orDie),
|
|
41
|
+
// oxlint-disable-next-line effecttsgo/any-unknown-in-error-context
|
|
42
|
+
config.layerLanguageModel.pipe(Layer.orDie),
|
|
43
|
+
Layer.effect(
|
|
44
|
+
TraitsContext,
|
|
45
|
+
Effect.gen(function* () {
|
|
46
|
+
const excludes = Array.appendAll(
|
|
47
|
+
Array.flatMap([".git", ".worktrees"], (directory) => [`**/${directory}`, `**/${directory}/**`]),
|
|
48
|
+
Array.map(config.excludes ?? [], (pattern) =>
|
|
49
|
+
path.isAbsolute(pattern) ? path.relative(configRoot, pattern) : pattern
|
|
50
|
+
)
|
|
51
|
+
)
|
|
52
|
+
const semaphore = yield* Semaphore.make(4)
|
|
53
|
+
return { configRoot, excludeGitignored: config.excludeGitignored ?? true, excludes, semaphore }
|
|
54
|
+
})
|
|
55
|
+
)
|
|
56
|
+
)
|
|
57
|
+
}).pipe(Layer.unwrap)
|
package/TraitsContext.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Context, Semaphore } from "effect"
|
|
2
|
+
|
|
3
|
+
export class TraitsContext extends Context.Service<
|
|
4
|
+
TraitsContext,
|
|
5
|
+
{
|
|
6
|
+
readonly configRoot: string
|
|
7
|
+
readonly excludeGitignored: boolean
|
|
8
|
+
readonly excludes: ReadonlyArray<string>
|
|
9
|
+
readonly semaphore: Semaphore.Semaphore
|
|
10
|
+
}
|
|
11
|
+
>()("traits/TraitsContext") {}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Effect, Option, Schema as S, String } from "effect"
|
|
2
|
+
import { McpSchema, McpServer } from "effect/ai"
|
|
3
|
+
|
|
4
|
+
export type Approval = typeof Approval.Type
|
|
5
|
+
export const Approval = S.TaggedUnion({
|
|
6
|
+
Approved: {},
|
|
7
|
+
ApprovalRequired: { message: S.String },
|
|
8
|
+
RevisionRequired: { feedback: S.String },
|
|
9
|
+
Declined: {}
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
export const request = Effect.fn("Approval.request")(function* (options: {
|
|
13
|
+
readonly message: string
|
|
14
|
+
readonly approved?: boolean | undefined
|
|
15
|
+
}) {
|
|
16
|
+
const client = yield* Effect.serviceOption(McpSchema.McpServerClient).pipe(Effect.map(Option.getOrUndefined))
|
|
17
|
+
const supportsForm =
|
|
18
|
+
client !== undefined &&
|
|
19
|
+
client.clientCapabilities.elicitation !== undefined &&
|
|
20
|
+
(client.clientCapabilities.elicitation.form !== undefined ||
|
|
21
|
+
client.clientCapabilities.elicitation.url === undefined)
|
|
22
|
+
const globalForms =
|
|
23
|
+
client !== undefined &&
|
|
24
|
+
(String.toLowerCase(client.clientInfo.name) === "opencode" ||
|
|
25
|
+
typeof client.requestMetadata?.["ai.opencode/sessionID"] === "string")
|
|
26
|
+
|
|
27
|
+
if (client === undefined || !supportsForm || globalForms) {
|
|
28
|
+
return options.approved === true
|
|
29
|
+
? Approval.cases.Approved.make({})
|
|
30
|
+
: Approval.cases.ApprovalRequired.make({ message: options.message })
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const response = yield* McpServer.elicit({
|
|
34
|
+
message: options.message,
|
|
35
|
+
schema: S.Struct({
|
|
36
|
+
decision: S.Literals(["approve", "revise", "skip"]).annotate({
|
|
37
|
+
description: "Approve this exact proposal, request a revision, or skip it."
|
|
38
|
+
}),
|
|
39
|
+
feedback: S.String.pipe(S.optionalKey).annotate({ description: "Optional revision or alternative wording." })
|
|
40
|
+
})
|
|
41
|
+
}).pipe(
|
|
42
|
+
Effect.provideService(McpSchema.McpServerClient, client),
|
|
43
|
+
Effect.catchTags({
|
|
44
|
+
ElicitationDeclined: (error) => (error.cause === undefined ? Effect.undefined : error)
|
|
45
|
+
})
|
|
46
|
+
)
|
|
47
|
+
if (response === undefined || response.decision === "skip") return Approval.cases.Declined.make({})
|
|
48
|
+
if (response.decision === "revise" || (response.feedback !== undefined && String.trim(response.feedback) !== "")) {
|
|
49
|
+
const feedback = response.feedback === undefined ? "" : String.trim(response.feedback)
|
|
50
|
+
const guidance = String.stripMargin(`
|
|
51
|
+
| Ask the user for revision details using an interactive question with free-text input.
|
|
52
|
+
| Keep this proposal pending; do not infer approval or cancel it because of a related follow-up task.
|
|
53
|
+
`)
|
|
54
|
+
return Approval.cases.RevisionRequired.make({
|
|
55
|
+
feedback: feedback !== "" ? feedback : guidance
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
return Approval.cases.Approved.make({})
|
|
59
|
+
})
|
package/_util/Git.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Data, Effect, Path, String } from "effect"
|
|
2
|
+
import { ChildProcess, ChildProcessSpawner } from "effect/process"
|
|
3
|
+
|
|
4
|
+
export class GitRootError extends Data.TaggedError("GitRootError")<{ readonly message: string }> {}
|
|
5
|
+
|
|
6
|
+
const resolveRoot = Effect.fn("Git.resolveRoot")(function* (command: ChildProcess.Command) {
|
|
7
|
+
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner
|
|
8
|
+
return yield* spawner.string(command).pipe(
|
|
9
|
+
Effect.map(String.trim),
|
|
10
|
+
Effect.filterOrFail(
|
|
11
|
+
String.isNonEmpty,
|
|
12
|
+
() => new GitRootError({ message: "Run from inside a Git repository or worktree" })
|
|
13
|
+
)
|
|
14
|
+
)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
/** The current checkout root, including when running in a linked worktree. */
|
|
18
|
+
export const getWorktreeRoot = resolveRoot(ChildProcess.make`git rev-parse --show-toplevel`)
|
|
19
|
+
|
|
20
|
+
/** The main checkout root, shared by its linked worktrees. */
|
|
21
|
+
export const getGitRoot = Effect.gen(function* () {
|
|
22
|
+
const path = yield* Path.Path
|
|
23
|
+
const gitDir = yield* resolveRoot(ChildProcess.make`git rev-parse --path-format=absolute --git-common-dir`)
|
|
24
|
+
return path.dirname(gitDir)
|
|
25
|
+
})
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Array, ConfigProvider, Effect, FileSystem, Path } from "effect"
|
|
2
|
+
|
|
3
|
+
import { getGitRoot, getWorktreeRoot } from "./Git.ts"
|
|
4
|
+
|
|
5
|
+
export const layer = ConfigProvider.layerAdd(
|
|
6
|
+
Effect.gen(function* () {
|
|
7
|
+
const fs = yield* FileSystem.FileSystem
|
|
8
|
+
const path = yield* Path.Path
|
|
9
|
+
const worktreeRoot = yield* getWorktreeRoot
|
|
10
|
+
const gitRoot = yield* getGitRoot
|
|
11
|
+
const files = Array.dedupe([path.join(worktreeRoot, ".env"), path.join(gitRoot, ".env")])
|
|
12
|
+
let provider = ConfigProvider.fromUnknown({})
|
|
13
|
+
for (const file of files) {
|
|
14
|
+
if (yield* fs.exists(file)) {
|
|
15
|
+
provider = yield* ConfigProvider.fromDotEnv({ path: file }).pipe(
|
|
16
|
+
Effect.map((value) => ConfigProvider.orElse(provider, value))
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return provider
|
|
21
|
+
}).pipe(Effect.withSpan("TraitsConfigProvider.load"))
|
|
22
|
+
)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Layer, pipe, String } from "effect"
|
|
2
|
+
import { Url } from "effect/http"
|
|
3
|
+
import { OtlpLogger, OtlpSerialization, OtlpTracer } from "effect/observability"
|
|
4
|
+
|
|
5
|
+
import PackageJson from "../package.json" with { type: "json" }
|
|
6
|
+
|
|
7
|
+
export const layer = (endpoint: URL | undefined) => {
|
|
8
|
+
if (endpoint === undefined) return Layer.empty
|
|
9
|
+
const url = (signal: string) =>
|
|
10
|
+
pipe(
|
|
11
|
+
endpoint,
|
|
12
|
+
Url.setPathname(`${pipe(endpoint.pathname, String.replace(/\/$/, ""))}/v1/${signal}`),
|
|
13
|
+
Url.setHash("")
|
|
14
|
+
).href
|
|
15
|
+
const resource = { serviceName: "traits", serviceVersion: PackageJson.version }
|
|
16
|
+
return Layer.mergeAll(
|
|
17
|
+
OtlpTracer.layer({ url: url("traces"), resource }),
|
|
18
|
+
OtlpLogger.layer({ url: url("logs"), resource })
|
|
19
|
+
).pipe(Layer.provide(OtlpSerialization.layerJson))
|
|
20
|
+
}
|