traits 0.4.0 → 0.5.1
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/bin.ts +65 -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/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +60 -0
- package/dist/bin.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 +72 -0
- package/dist/main.js.map +1 -0
- package/dist/package.json +52 -0
- package/dist/test/TraitIgnores.test.d.ts +2 -0
- package/dist/test/TraitIgnores.test.d.ts.map +1 -0
- package/dist/test/TraitIgnores.test.js +20 -0
- package/dist/test/TraitIgnores.test.js.map +1 -0
- package/dist/toolkit/CheckTraits.d.ts +79 -0
- package/dist/toolkit/CheckTraits.d.ts.map +1 -0
- package/dist/toolkit/CheckTraits.js +114 -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/dist/traitIgnores.d.ts +3 -0
- package/dist/traitIgnores.d.ts.map +1 -0
- package/dist/traitIgnores.js +17 -0
- package/dist/traitIgnores.js.map +1 -0
- package/index.ts +9 -0
- package/main.ts +111 -0
- package/package.json +45 -0
- package/skills/traits/SKILL.md +339 -0
- package/toolkit/CheckTraits.ts +154 -0
- package/toolkit/DeriveTrait.ts +59 -0
- package/toolkit/toolkit.ts +9 -0
- package/traitIgnores.ts +14 -0
- 1289345811335-0.8567837113514543/lib/traits.js +0 -662
- 1289345811335-0.8567837113514543/package.json +0 -7
package/main.ts
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { NodeRuntime, NodeServices } from "@effect/platform-node"
|
|
3
|
+
import { Effect, Layer, Logger, Option, Schema as S, String } from "effect"
|
|
4
|
+
import { McpProtocol, McpServer } from "effect/ai"
|
|
5
|
+
import { Command, Flag } from "effect/cli"
|
|
6
|
+
import { FetchHttpClient } from "effect/http"
|
|
7
|
+
|
|
8
|
+
import * as TraitsConfigProvider from "./_util/TraitsConfigProvider.ts"
|
|
9
|
+
import * as TraitsTelemetry from "./_util/TraitsTelemetry.ts"
|
|
10
|
+
import PackageJson from "./package.json" with { type: "json" }
|
|
11
|
+
import { toolkit } from "./toolkit/toolkit.ts"
|
|
12
|
+
import * as TraitsConfig from "./TraitsConfig.ts"
|
|
13
|
+
import * as TraitStore from "./TraitStore.ts"
|
|
14
|
+
|
|
15
|
+
Command.make("traits", {
|
|
16
|
+
otlpEndpoint: Flag.String("otlp-endpoint").pipe(
|
|
17
|
+
Flag.withSchema(
|
|
18
|
+
S.URLFromString.check(
|
|
19
|
+
S.makeFilter(
|
|
20
|
+
(url) => url.protocol === "http:" || url.protocol === "https:" || "Use an HTTP(S) collector base URL."
|
|
21
|
+
)
|
|
22
|
+
)
|
|
23
|
+
),
|
|
24
|
+
Flag.withDescription(
|
|
25
|
+
"Export traces and logs as OTLP/HTTP JSON to this collector base URL (for example, http://127.0.0.1:4318). Disabled when omitted."
|
|
26
|
+
),
|
|
27
|
+
Flag.optional
|
|
28
|
+
)
|
|
29
|
+
}).pipe(
|
|
30
|
+
Command.withDescription("Run the traits MCP server over stdio"),
|
|
31
|
+
Command.withHandler((params) =>
|
|
32
|
+
McpServer.layerStdio({
|
|
33
|
+
name: "traits",
|
|
34
|
+
version: PackageJson.version,
|
|
35
|
+
protocols: [McpProtocol.v2025_11_25],
|
|
36
|
+
instructions: String.stripMargin(`
|
|
37
|
+
| Traits are durable, reusable coding preferences stored as .agents/traits/<three_words-uuid>.md in the working directory.
|
|
38
|
+
| The full filename stem is the trait ID; use it in tool calls, for example rose_karate_frog-<uuid>.
|
|
39
|
+
| Before completing a response to a user correction, coding preference, or approved alternative idiom, check whether it expresses a reusable rule.
|
|
40
|
+
| Treat multi-turn exchanges as a whole: asking about a better idiom and then approving it can establish a preference without saying "always" or "trait".
|
|
41
|
+
| Err on the side of proposing a concise trait unless you have read an existing trait that clearly covers the exact rule and its scope.
|
|
42
|
+
| If coverage is uncertain, propose the rule rather than silently assuming a broad trait covers it. If clearer wording is needed, propose a clarification.
|
|
43
|
+
| Call the mutation tool with the drafted proposal first, leaving approved unset. Do not ask separately beforehand: native clients get an approval form.
|
|
44
|
+
| derive_trait uses native MCP forms when usable, or returns ApprovalRequired with the complete proposal for that same interactive question flow.
|
|
45
|
+
| Only on ApprovalRequired, show the full message using the interactive question tool: Save, Revise, Skip, plus its automatic free-text option. Use plain text only if that UI is unavailable.
|
|
46
|
+
| Retry an identical approved proposal with approved: true; do not assert approval for changed wording or targets. Native forms still apply on supported clients.
|
|
47
|
+
| RevisionRequired contains feedback to incorporate before requesting approval again; Declined means stop without writing.
|
|
48
|
+
| Revise without actionable feedback requires an immediate interactive question asking what to change; never guess or treat it as Save.
|
|
49
|
+
| Keep a revision pending across related audits or follow-up tasks. Complete that work, then return to the proposal unless explicitly cancelled.
|
|
50
|
+
| Show the complete revised proposal and obtain fresh approval before saving; a findings summary does not resolve a pending revision.
|
|
51
|
+
| Approval to edit code alone is not approval to persist a trait. Make the requested edit, but also propose the reusable rule before your final response.
|
|
52
|
+
| Also act on explicit requests to remember a convention. Skip one-off requirements, transient facts, and preferences already covered.
|
|
53
|
+
| Read existing trait files before drafting. Keep each trait concise and independently applicable, preserving scope and exceptions.
|
|
54
|
+
| Call derive_trait with the drafted body as proposedTrait. It saves only distinct rules; Covered means reuse an existing trait.
|
|
55
|
+
| A single clarification candidate is selected automatically and shown for approval; supply a complete replacement preserving the existing rule.
|
|
56
|
+
| Multiple candidates return Clarification entries without writing: refine the proposal or clarify intent. Do not supply a target ID.
|
|
57
|
+
| After fallback approval, retry with the same proposedTrait and approved: true; Updated preserves the selected existing ID.
|
|
58
|
+
| Every derive_trait response is one tagged TraitDerivation outcome. Created returns the saved trait and ID; Updated and Resolved return saved replacements.
|
|
59
|
+
| Covered and Clarification return existing traits without writing. Inconclusive returns relationship findings and blocks all writes.
|
|
60
|
+
| Each inconclusive outcome's findings distinguish conflict from inconclusive via reason; clarify ambiguity before retrying.
|
|
61
|
+
| Confirmed conflicts enter reconciliation inside derive_trait, selecting only the conflicting originals for an explicit replacement approval.
|
|
62
|
+
| Approval must cover affected IDs/bodies, removals, the proposal, and generated reconciliation. Nothing is replaced silently.
|
|
63
|
+
| After approval, reconciliation gives the proposal precedence in direct conflicts, saves generated replacements, then removes the conflicting originals.
|
|
64
|
+
| Resolved returns saved replacement bodies and IDs. Review them and check relevant code; do not derive the same proposal again.
|
|
65
|
+
| After generating or changing TypeScript files, call check_traits on those files.
|
|
66
|
+
| check_traits accepts include as file paths or glob strings, for example include: ["traits/**/*.ts"]. Overlapping matches are deduplicated.
|
|
67
|
+
| Restrict assessments to project-owned source and relevant authored configuration/docs. Prefer an explicit project-file manifest for whole-project audits.
|
|
68
|
+
| Never submit dependency/reference code, generated build output or lockfiles, Git metadata, secrets, .env files, credentials, or binary assets for assessment.
|
|
69
|
+
| Unmatched patterns select nothing. Built-in exclusions cover dependencies, Git metadata, and nested worktrees; configure project-specific filenames or native glob patterns through TraitsConfig.excludes.
|
|
70
|
+
| After saving or resolving a trait, check the relevant TypeScript files explicitly; creation does not run that review for you.
|
|
71
|
+
| Omit traitIds for all traits; use specific IDs for focused follow-ups. Before committing or opening a PR, review the changed TypeScript files.
|
|
72
|
+
| Checks read current full files in 200-line chunks, not Git diffs. A finding's startLine locates a chunk, not an exact violation.
|
|
73
|
+
| Each finding includes assessment.classification and probabilities: inconclusive is unresolved context, not a confirmed violation. Probabilities are not calibrated guarantees.
|
|
74
|
+
| Take responsibility for trait compliance whenever a violation is discovered, through tool findings or your own inspection.
|
|
75
|
+
| Inspect each finding against its trait, scope, exceptions, and surrounding source; confirmed violations must be fixed without waiting for another user request.
|
|
76
|
+
| Make minimal behavior-preserving edits to the implementation, not to the trait merely to silence a finding. Do not stop at a findings summary.
|
|
77
|
+
| Rerun check_traits on every corrected file, then run relevant formatting, type, and behavior checks; repeat for remaining confirmed violations.
|
|
78
|
+
| Triage raw findings against the exact rule, scope, exceptions, and source before reporting or editing. Do not blindly change correct code.
|
|
79
|
+
| Routine user summaries should report confirmed issues, meaningful unresolved uncertainty, operational failures, and blockers requiring a decision.
|
|
80
|
+
| Do not repeat rejected, irrelevant false positives as caveats or make them sound actionable. Keep raw results available in tool output and telemetry.
|
|
81
|
+
| Discuss a rejected finding when the user requests audit details or it reveals a new material reliability problem; explain why it was rejected.
|
|
82
|
+
| Never suppress actionable findings based only on confidence, assume a finding is false because similar ones were wrong, or claim an automated clean pass when raw findings remain.
|
|
83
|
+
| If a fix requires a behavior change, broader authorization, or an ambiguous interpretation, ask a focused question and report the blocker.
|
|
84
|
+
| Do not claim compliance while confirmed violations remain. Report model errors as incomplete reviews, not passes.
|
|
85
|
+
| An empty result only means no failures for the selected files and traits; empty selections or an empty store perform no assessments.
|
|
86
|
+
| On mutation errors, inspect the store before retrying: writes and removals are not transactional, and retries may duplicate saved traits.
|
|
87
|
+
`)
|
|
88
|
+
}).pipe(
|
|
89
|
+
Layer.provide(
|
|
90
|
+
toolkit.pipe(
|
|
91
|
+
Layer.provide(TraitStore.layerFileSystem(".agents/traits")),
|
|
92
|
+
Layer.provide(TraitsConfig.layer),
|
|
93
|
+
Layer.provideMerge(McpServer.McpServer.layer)
|
|
94
|
+
)
|
|
95
|
+
),
|
|
96
|
+
Layer.launch,
|
|
97
|
+
Effect.withSpan("traits.server"),
|
|
98
|
+
Effect.provide(
|
|
99
|
+
TraitsTelemetry.layer(Option.getOrUndefined(params.otlpEndpoint)).pipe(
|
|
100
|
+
Layer.provideMerge(TraitsConfigProvider.layer),
|
|
101
|
+
Layer.provideMerge(FetchHttpClient.layer)
|
|
102
|
+
)
|
|
103
|
+
)
|
|
104
|
+
)
|
|
105
|
+
),
|
|
106
|
+
Command.run({ version: PackageJson.version }),
|
|
107
|
+
Effect.provide(NodeServices.layer),
|
|
108
|
+
Effect.tapCause(Effect.logError),
|
|
109
|
+
Effect.provideService(Logger.LogToStderr, true),
|
|
110
|
+
NodeRuntime.runMain({ disableErrorReporting: true })
|
|
111
|
+
)
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "traits",
|
|
3
|
+
"version": "0.5.1",
|
|
4
|
+
"license": "Apache-2.0",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/crosshatch/traits.git",
|
|
8
|
+
"directory": "traits"
|
|
9
|
+
},
|
|
10
|
+
"bin": {
|
|
11
|
+
"traits": "./dist/bin.js"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"*.ts",
|
|
15
|
+
"!vite.config.ts",
|
|
16
|
+
"_util/**/*.ts",
|
|
17
|
+
"skills/**/*.md",
|
|
18
|
+
"toolkit/**/*.ts",
|
|
19
|
+
"dist/package.json",
|
|
20
|
+
"dist/**/*.js",
|
|
21
|
+
"dist/**/*.js.map",
|
|
22
|
+
"dist/**/*.d.ts",
|
|
23
|
+
"dist/**/*.d.ts.map",
|
|
24
|
+
"!dist/vite.config.*"
|
|
25
|
+
],
|
|
26
|
+
"type": "module",
|
|
27
|
+
"exports": {
|
|
28
|
+
"./package.json": "./package.json",
|
|
29
|
+
".": "./dist/index.js",
|
|
30
|
+
"./*": "./dist/*.js",
|
|
31
|
+
"./_util/*": null,
|
|
32
|
+
"./index": null
|
|
33
|
+
},
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@effect/platform-node": "4.0.0",
|
|
39
|
+
"effect": "4.0.0"
|
|
40
|
+
},
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"@effect/platform-node": "4.0.0",
|
|
43
|
+
"effect": "4.0.0"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: traits
|
|
3
|
+
description: >-
|
|
4
|
+
Use when the user corrects an approach, expresses a reusable coding
|
|
5
|
+
preference, approves an alternative idiom, or asks to remember a convention;
|
|
6
|
+
when reconciling conflicting traits; and after generating or editing
|
|
7
|
+
TypeScript files or before committing them.
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Traits
|
|
11
|
+
|
|
12
|
+
Traits are concise, durable coding rules. The filesystem implementation stores
|
|
13
|
+
each body in `.agents/traits/<three_words-uuid>.md`, relative to its working
|
|
14
|
+
directory. The full filename stem is the trait ID, for example
|
|
15
|
+
`rose_karate_frog-2535ceab-5702-4ab4-932a-f87362adf8c6`. Read these files to
|
|
16
|
+
discover existing rules and IDs; pass the full ID, not just the words, to tools.
|
|
17
|
+
|
|
18
|
+
## Capture a reusable insight
|
|
19
|
+
|
|
20
|
+
### Required pre-response preference check
|
|
21
|
+
|
|
22
|
+
Before finishing a response to a correction, preference, or approved alternative
|
|
23
|
+
idiom, evaluate the exchange for a reusable rule. Read the conversation across
|
|
24
|
+
turns: "Can we use the case guard?" followed by "Excellent. Please do"
|
|
25
|
+
establishes a candidate preference even without the words "always", "prefer", or
|
|
26
|
+
"trait".
|
|
27
|
+
|
|
28
|
+
Err on the side of proposing a concise trait unless you have read an existing
|
|
29
|
+
trait that clearly covers the exact rule and scope. Do not silently assume a
|
|
30
|
+
broad convention covers a specific idiom. If coverage is uncertain, propose the
|
|
31
|
+
rule; if an existing rule needs clearer wording, propose a clarification. Skip
|
|
32
|
+
only clearly one-off requests, transient facts, or rules you can identify as
|
|
33
|
+
already covered.
|
|
34
|
+
|
|
35
|
+
Call `derive_trait` with the drafted rule first, leaving `approved` unset. Do
|
|
36
|
+
not ask for approval separately before invoking: capable clients receive a
|
|
37
|
+
native MCP form. Only if `ApprovalRequired` is returned, show the complete
|
|
38
|
+
message in the interactive question tool with **Save trait**, **Revise**, and
|
|
39
|
+
**Skip** and its automatic free-text option. Do not add a duplicate "Other"
|
|
40
|
+
option. Use plain text only if that UI is unavailable. Approval to edit code
|
|
41
|
+
alone does not approve persisting a trait: make the requested edit and propose
|
|
42
|
+
the reusable rule before the final response. Do not stop after implementing an
|
|
43
|
+
idiom and running code checks; preference capture is a separate responsibility.
|
|
44
|
+
|
|
45
|
+
### Shared approval protocol
|
|
46
|
+
|
|
47
|
+
`derive_trait` uses `_util/Approval.request` for creation, clarification, and
|
|
48
|
+
reconciliation. Clients with usable MCP form elicitation receive
|
|
49
|
+
approve/revise/skip choices and an optional feedback field. OpenCode currently
|
|
50
|
+
routes native forms globally, so the helper returns `ApprovalRequired`; clients
|
|
51
|
+
without form support use the same fallback.
|
|
52
|
+
|
|
53
|
+
On `ApprovalRequired`, show its complete message in the interactive question UI
|
|
54
|
+
with Save/Revise/Skip and free text. Save permits retrying the identical body
|
|
55
|
+
and target IDs with `approved: true`. Revision or free-text input requires
|
|
56
|
+
drafting the changed proposal and asking approval again, not silently setting
|
|
57
|
+
approved. Skip means stop. Native `RevisionRequired` and `Declined` have the
|
|
58
|
+
same meanings. Transport errors are failures, never declines or approvals.
|
|
59
|
+
|
|
60
|
+
Selecting **Revise** is not approval and does not cancel the proposal. If no
|
|
61
|
+
specific revision feedback was supplied, immediately ask what wording, scope, or
|
|
62
|
+
exception the user wants changed using the interactive question tool with
|
|
63
|
+
free-text input. Do not guess a revision or retry the mutation with
|
|
64
|
+
`approved: true`. Native `RevisionRequired` without actionable feedback follows
|
|
65
|
+
the same procedure.
|
|
66
|
+
|
|
67
|
+
Keep the original proposal and its pending revision in the conversation until
|
|
68
|
+
the user approves a replacement or explicitly skips/cancels it. A related audit,
|
|
69
|
+
code change, or follow-up question does not silently resolve the pending choice:
|
|
70
|
+
complete that work and then return to the revision request. If that request
|
|
71
|
+
already supplies the desired change, incorporate it, present the complete
|
|
72
|
+
revised proposal through the approval flow, and obtain fresh approval. Do not
|
|
73
|
+
finish by only reporting an audit while forgetting the pending proposal.
|
|
74
|
+
|
|
75
|
+
The fallback approval flag applies to creation, clarification, and resolution.
|
|
76
|
+
It does not bypass relationship checks or suppress native forms on capable
|
|
77
|
+
clients. Pending, revision, and decline outcomes never write files. Resolution
|
|
78
|
+
approval covers the displayed originals, their deletion, the proposal, and
|
|
79
|
+
model-generated reconciliation; inspect returned replacements after saving. The
|
|
80
|
+
flag relies on the agent honoring exact user approval; it is not an
|
|
81
|
+
authenticated consent token.
|
|
82
|
+
|
|
83
|
+
1. Notice user corrections and recurring preferences without waiting for an
|
|
84
|
+
explicit mention of traits. Also handle explicit requests to remember a rule.
|
|
85
|
+
2. Distinguish a durable convention from a one-off task, transient fact, or
|
|
86
|
+
vague reaction. Read existing traits and reuse covered rules rather than
|
|
87
|
+
duplicating them. Clarify ambiguous scope before drafting.
|
|
88
|
+
3. Draft the complete rule as `proposedTrait`. Preserve essential requirements,
|
|
89
|
+
scopes, and exceptions. Keep independently applicable rules separate; omit
|
|
90
|
+
headings, repetition, and unnecessary explanation or examples.
|
|
91
|
+
4. Call `derive_trait({ proposedTrait })` first, without a separate approval
|
|
92
|
+
prompt. This checks existing traits and writes only when the proposal is
|
|
93
|
+
distinct from all of them. It does not independently decide whether the
|
|
94
|
+
conversation contains a generalizable insight.
|
|
95
|
+
5. `Created` means the trait was saved and includes its body and generated ID.
|
|
96
|
+
Do not submit it again. Use that ID for focused checks when needed.
|
|
97
|
+
6. `Inconclusive` means nothing was saved and contains relationship findings;
|
|
98
|
+
each finding's `reason` distinguishes conflict from inconclusive. Any
|
|
99
|
+
inconclusive comparison blocks all writes. Inspect the existing bodies and
|
|
100
|
+
IDs, clarify ambiguity, or revise the proposal. Confirmed conflicts otherwise
|
|
101
|
+
enter approval-gated reconciliation within this same tool.
|
|
102
|
+
|
|
103
|
+
`Covered.traits` means existing traits already express the proposal; reuse them
|
|
104
|
+
without creating a duplicate. When there is exactly one clarification candidate
|
|
105
|
+
and no covered or blocking result, the tool selects it automatically and
|
|
106
|
+
requests approval of its current body and complete replacement. The proposal
|
|
107
|
+
must preserve the existing rule's intent, requirements, scope, and exceptions;
|
|
108
|
+
the tool does not generate a merged replacement from an incremental hint.
|
|
109
|
+
|
|
110
|
+
Multiple candidates return `Clarification` with `traits` and `proposedTrait`
|
|
111
|
+
without writing. Refine the proposal or clarify intent rather than choosing an
|
|
112
|
+
ID arbitrarily. Do not supply a clarification target parameter.
|
|
113
|
+
|
|
114
|
+
Use the interactive question UI only on `ApprovalRequired`, then retry the same
|
|
115
|
+
`proposedTrait` with `approved: true` after Save. The tool rechecks every
|
|
116
|
+
existing trait and preserves the selected ID on update. If the store or selected
|
|
117
|
+
action changes between approval and retry, obtain fresh approval rather than
|
|
118
|
+
reusing the flag. `Updated` returns the saved trait. Never claim approval not
|
|
119
|
+
given by the user or delete a trait merely needing clearer wording.
|
|
120
|
+
|
|
121
|
+
## Reconcile conflicting traits
|
|
122
|
+
|
|
123
|
+
`derive_trait` checks the proposal against every stored trait and automatically
|
|
124
|
+
selects confirmed conflicts for reconciliation. There is no separate resolution
|
|
125
|
+
tool and no ID-selection parameter. Inconclusive comparisons block
|
|
126
|
+
reconciliation rather than authorizing deletion.
|
|
127
|
+
|
|
128
|
+
Before writing, the shared approval flow shows the selected originals with their
|
|
129
|
+
IDs and bodies, the proposal, the removal plan, and the replacement policy.
|
|
130
|
+
Approval authorizes model-generated reconciliation, not silent deletion. On
|
|
131
|
+
`ApprovalRequired`, ask through the interactive UI and retry the same proposal
|
|
132
|
+
with `approved: true` only after Save. If relationships or originals change,
|
|
133
|
+
obtain fresh approval.
|
|
134
|
+
|
|
135
|
+
After approval, the tool evaluates each body and the proposal for decomposition,
|
|
136
|
+
then generates standalone replacements. The proposal takes precedence in direct
|
|
137
|
+
conflicts; unrelated requirements, scopes, and exceptions should be preserved.
|
|
138
|
+
It saves the replacements before deleting the selected originals and returns
|
|
139
|
+
`Resolved` with the saved traits and their new IDs.
|
|
140
|
+
|
|
141
|
+
Inspect those replacements. Do not call `derive_trait` again for the same
|
|
142
|
+
proposal: resolution has already saved its content. The initial comparison
|
|
143
|
+
covers all stored traits, but generated replacements are not automatically
|
|
144
|
+
revalidated against unaffected traits; review the replacements and check the
|
|
145
|
+
relevant code.
|
|
146
|
+
|
|
147
|
+
Writes and removals are not transactional. If resolution fails, inspect the
|
|
148
|
+
store before retrying; some changes may already be present.
|
|
149
|
+
|
|
150
|
+
## Check the implementation
|
|
151
|
+
|
|
152
|
+
### Required review procedure
|
|
153
|
+
|
|
154
|
+
1. Before editing, read the stored trait bodies and identify which apply to the
|
|
155
|
+
planned work. Do not rely on filenames, remembered summaries, or examples
|
|
156
|
+
alone; preserve each rule's scope and exceptions.
|
|
157
|
+
2. Track every TypeScript file you generate or modify, including files changed
|
|
158
|
+
while fixing findings. Review the final on-disk contents, not an earlier
|
|
159
|
+
version or only the initial set of files.
|
|
160
|
+
3. Independently inspect each changed section against every applicable trait. An
|
|
161
|
+
empty tool result does not replace direct inspection against the trait text.
|
|
162
|
+
Formatting, lint, and type checks do not establish trait compliance.
|
|
163
|
+
4. For recognizable patterns, search explicitly and inspect matches. For the
|
|
164
|
+
curried-call rule, look for nested calls such as `fn(args)(value)`, including
|
|
165
|
+
multiline forms and `yield* S.decodeUnknownEffect(schema)(input)`. Check the
|
|
166
|
+
actual signature before deciding whether a match is a violation. Also inspect
|
|
167
|
+
parameter destructuring, manually constructed tagged-union members, wrapped
|
|
168
|
+
schema modifiers, and misplaced imports when their traits apply. Searches
|
|
169
|
+
support review; a lack of matches is not proof that every trait is satisfied.
|
|
170
|
+
5. Call `check_traits` on all changed TypeScript files after the latest edits.
|
|
171
|
+
Before a commit or PR, omit `traitIds` so every stored trait is checked;
|
|
172
|
+
focused checks supplement rather than replace that final review.
|
|
173
|
+
6. Inspect findings, fix confirmed violations, and repeat direct inspection,
|
|
174
|
+
tool checks, and relevant project verification after fixes. If any reviewed
|
|
175
|
+
file changes again, its earlier result is stale and it must be rechecked.
|
|
176
|
+
7. Keep automated results and manual triage distinct. Routine summaries should
|
|
177
|
+
report confirmed issues, meaningful unresolved uncertainty, operational
|
|
178
|
+
failures, and blockers requiring a user decision—not every raw finding.
|
|
179
|
+
Preserve the reviewed scope and raw results for audit without repeating
|
|
180
|
+
rejected false positives as caveats. Never claim an automated clean pass when
|
|
181
|
+
raw findings remain, or turn an empty result into unconditional compliance.
|
|
182
|
+
|
|
183
|
+
### Targeted trait exceptions
|
|
184
|
+
|
|
185
|
+
For a justified file-scoped exception, add a line comment in the leading header,
|
|
186
|
+
before any code:
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
// traits-ignore-file <full-trait-id>: Explain why this trait cannot apply here.
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Each directive requires a nonempty reason and exactly one full trait ID. Repeat
|
|
193
|
+
the comment for additional exceptions. Only the referenced traits are skipped;
|
|
194
|
+
other traits remain checked. Exceptions apply to all file chunks and focused
|
|
195
|
+
checks. Comments after code are ignored.
|
|
196
|
+
|
|
197
|
+
Read the referenced trait and verify the reason before adding or retaining an
|
|
198
|
+
exception. Do not add directives merely to silence findings: fix applicable
|
|
199
|
+
violations, and ask the user before introducing an exception that overrides
|
|
200
|
+
their preference. Respect existing justified exceptions during manual review.
|
|
201
|
+
|
|
202
|
+
### Tool behavior and remediation
|
|
203
|
+
|
|
204
|
+
- After generating or editing TypeScript files, select those files and call
|
|
205
|
+
`check_traits({ include: files })`.
|
|
206
|
+
- After creating or resolving traits, explicitly review relevant TypeScript
|
|
207
|
+
files. Saving traits does not automatically check code.
|
|
208
|
+
- Omit `traitIds` for the full trait set. Supply IDs for focused follow-ups,
|
|
209
|
+
especially the replacements returned by resolution. An empty array selects no
|
|
210
|
+
traits; unknown IDs fail the call.
|
|
211
|
+
- `include` accepts file paths or glob patterns, absolute or relative to the MCP
|
|
212
|
+
working directory. Overlapping matches are deduplicated; unmatched patterns
|
|
213
|
+
select no files. Use `traits/**/*.ts` rather than a bare directory. Glob
|
|
214
|
+
traversal always excludes dependencies, Git metadata, and nested worktrees.
|
|
215
|
+
`TraitsConfig.excludes` adds filenames or native filesystem glob patterns,
|
|
216
|
+
relative to the directory containing `traits.ts` or absolute. Use
|
|
217
|
+
`directory/**` to exclude a subtree; bare directories are not expanded
|
|
218
|
+
automatically. Escape glob characters in literal filenames using native glob
|
|
219
|
+
syntax. This repository configures `repos/**` and `**/dist/**` in `traits.ts`.
|
|
220
|
+
No custom path classification or glob expansion semantics are imposed.
|
|
221
|
+
- Assess only project-owned source and relevant authored configuration or
|
|
222
|
+
documentation. For whole-project audits, prefer an explicit project-file
|
|
223
|
+
manifest. Never submit third-party/reference code, generated output or
|
|
224
|
+
lockfiles, secrets, `.env` files, credentials, or binary assets. Reading a
|
|
225
|
+
dependency's source to verify an API does not make it part of the audit scope.
|
|
226
|
+
- The tool checks current full files in 200-line chunks, including unstaged
|
|
227
|
+
content. It does not inspect the Git index or compute a diff.
|
|
228
|
+
- Each expression finding contains the trait and source excerpt with a file path
|
|
229
|
+
and 1-based chunk starting line. That line is not an exact violation location.
|
|
230
|
+
Its `assessment` includes classification and probabilities. `inconclusive` is
|
|
231
|
+
not a confirmed violation. Inspect the full distribution; probabilities are
|
|
232
|
+
not calibrated guarantees.
|
|
233
|
+
- Inspect the code and trait before fixing a finding. Respect scope and
|
|
234
|
+
exceptions; missing context is not proof of a violation.
|
|
235
|
+
- Own the remediation loop whenever you discover an applicable violation,
|
|
236
|
+
whether from this tool or your own inspection. Automatically make minimal,
|
|
237
|
+
behavior-preserving fixes without waiting for another user request; a findings
|
|
238
|
+
summary alone is not completion. This tool diagnoses; the agent makes edits.
|
|
239
|
+
- Recheck every corrected file and run relevant formatting, type, and behavior
|
|
240
|
+
checks. Repeat for remaining confirmed violations. Do not weaken or rewrite
|
|
241
|
+
traits merely to silence findings, and do not change correct code for false
|
|
242
|
+
positives or inapplicable rules.
|
|
243
|
+
- Do not repeat rejected, irrelevant false positives or already-justified
|
|
244
|
+
exceptions in routine user summaries. Explain them when the user requests an
|
|
245
|
+
audit, needs a decision, or a new material reliability problem is discovered.
|
|
246
|
+
If a fix requires changed behavior, broader authorization, or resolving
|
|
247
|
+
ambiguity, ask a focused question and report the blocker rather than silently
|
|
248
|
+
expanding scope. Do not claim compliance while confirmed violations remain;
|
|
249
|
+
model errors make the review incomplete, not passing.
|
|
250
|
+
|
|
251
|
+
### Reporting triage
|
|
252
|
+
|
|
253
|
+
Before presenting a finding, establish that the rule applies and identify the
|
|
254
|
+
actual offending code. A helper parameter is not a model-visible tool parameter;
|
|
255
|
+
local destructuring is not parameter destructuring. Check the exact scope and
|
|
256
|
+
exceptions rather than treating a matching topic as a violation.
|
|
257
|
+
|
|
258
|
+
Fix confirmed applicable violations within scope and verify the fix. Surface
|
|
259
|
+
remaining actionable issues, material uncertainty, failed/incomplete checks, and
|
|
260
|
+
decisions the user must make. Routine completion messages can state which build,
|
|
261
|
+
lint, and type checks passed without adding distracting checker false positives.
|
|
262
|
+
Do not imply that a rejected finding requires work or that the automated trait
|
|
263
|
+
review returned no findings when it did.
|
|
264
|
+
|
|
265
|
+
Keep unmodified raw assessments available in tool output or configured
|
|
266
|
+
telemetry; do not change tool filtering or introduce probability cutoffs to hide
|
|
267
|
+
inconvenient results. Low confidence alone is not grounds for dismissal, and
|
|
268
|
+
earlier false positives do not justify rejecting a new finding without
|
|
269
|
+
inspecting it. A newly discovered systemic reliability issue remains worth
|
|
270
|
+
reporting. When asked for audit details, distinguish confirmed, unresolved, and
|
|
271
|
+
rejected findings and give the rejection rationale. Quiet reporting is not
|
|
272
|
+
skipped remediation or lost signal.
|
|
273
|
+
|
|
274
|
+
- Before committing or opening a PR, review the changed TypeScript files against
|
|
275
|
+
all traits. An empty result means no failures for the selection; empty
|
|
276
|
+
selections, unmatched patterns, or an empty trait store perform no
|
|
277
|
+
assessments.
|
|
278
|
+
|
|
279
|
+
## Operation
|
|
280
|
+
|
|
281
|
+
The CLI launcher installs project dependencies in its current directory before
|
|
282
|
+
importing the MCP server. Run it from the checkout/workspace root. It selects
|
|
283
|
+
the package manager from `package.json#packageManager` or an unambiguous
|
|
284
|
+
lockfile; the selected executable must be on PATH. Installation preserves
|
|
285
|
+
lockfiles but can execute lifecycle scripts, so only launch in trusted
|
|
286
|
+
checkouts. Allow enough MCP startup time for a cold install and avoid
|
|
287
|
+
simultaneous installations in the same worktree. During development, use
|
|
288
|
+
`node traits/bin.ts` with a Node version supporting native TypeScript execution;
|
|
289
|
+
pass server flags after the filename.
|
|
290
|
+
|
|
291
|
+
Configuration and providers currently initialize at server startup. Startup
|
|
292
|
+
failures go to stderr and require fixing setup and reconnecting; whether the
|
|
293
|
+
agent can see these diagnostics depends on its MCP client. Expected tool
|
|
294
|
+
failures use MCP error results; unexpected defects receive a generic internal
|
|
295
|
+
error rather than implementation details.
|
|
296
|
+
|
|
297
|
+
Git-ignored files are excluded by default, with ignore rules refreshed on every
|
|
298
|
+
check. Untracked files do not need to be added to Git to be evaluated. Tracked
|
|
299
|
+
files stay eligible even if ignore rules match them. Set
|
|
300
|
+
`excludeGitignored: false` in `traits.ts` to disable this filter; explicit
|
|
301
|
+
excludes still apply. Git failures fail checks.
|
|
302
|
+
|
|
303
|
+
For setup, follow the canonical guide at
|
|
304
|
+
https://github.com/crosshatch/traits/blob/main/SETUP.md. Respect the consumer's
|
|
305
|
+
package manager and workspace dependency conventions, ask about providers and
|
|
306
|
+
models before writing configuration, and preserve existing settings. The guide
|
|
307
|
+
covers dependencies, credentials, skill installation, MCP connection, and
|
|
308
|
+
troubleshooting. The agent creates traits.ts using the selected providers; there
|
|
309
|
+
is no init command. Discover the connected server's `derive_trait` and
|
|
310
|
+
`check_traits` tools through the client's tool catalog.
|
|
311
|
+
|
|
312
|
+
The CLI's configuration layer loads and validates the default-exported object
|
|
313
|
+
from repository-root `traits.ts`. Import the `TraitsConfig` type from `traits`
|
|
314
|
+
and use `export default { ... } satisfies TraitsConfig`. Specify
|
|
315
|
+
`layerDecisionModel` and `layerLanguageModel`; `excludes` is optional. The CLI
|
|
316
|
+
supplies platform/HTTP services. Storage remains local and has no source
|
|
317
|
+
configuration. The current configuration uses TypeSafe for decisions and OpenAI
|
|
318
|
+
for generation, requiring `TYPESAFE_API_KEY` and `OPENAI_API_KEY`. Configuration
|
|
319
|
+
precedence is process environment, worktree-root `.env`, then main-checkout
|
|
320
|
+
`.env`. Reconnect the MCP server after changing credentials. A failed model
|
|
321
|
+
request is an incomplete review, not a pass.
|
|
322
|
+
|
|
323
|
+
The project MCP command leaves telemetry disabled. For optional OTLP telemetry,
|
|
324
|
+
launch with `npx traits --otlp-endpoint http://127.0.0.1:4318`. The flag is an
|
|
325
|
+
OTLP/HTTP collector base URL; traces and logs go to `/v1/traces` and `/v1/logs`
|
|
326
|
+
beneath it using JSON, with service name `traits` and the package version.
|
|
327
|
+
Metrics are not exported. Omit the flag to disable exports. URLs must use
|
|
328
|
+
HTTP(S). Base paths and query parameters are preserved; fragments are ignored.
|
|
329
|
+
Avoid credentials in command-line URLs. Stderr logging remains enabled; stdout
|
|
330
|
+
stays reserved for MCP.
|
|
331
|
+
|
|
332
|
+
The CLI does not start a collector or persist telemetry locally. Configure the
|
|
333
|
+
collector's storage, rotation, retention, and access controls separately. Use a
|
|
334
|
+
loopback collector for local debugging and read its configured output files; do
|
|
335
|
+
not assume an output directory exists. Treat telemetry as potentially sensitive:
|
|
336
|
+
spans and logs can include file paths and error text. No full trait bodies,
|
|
337
|
+
source bodies, or authorization headers are intentionally attached by this
|
|
338
|
+
integration, but that is not a blanket redaction guarantee. Do not publish
|
|
339
|
+
collector output without inspection and redaction.
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { Array, Effect, FileSystem, Path, pipe, PlatformError, Schema as S, Stream, String } from "effect"
|
|
2
|
+
import { AiError, DecisionModel, Tool, Toolkit } from "effect/ai"
|
|
3
|
+
import { ChildProcess, ChildProcessSpawner } from "effect/process"
|
|
4
|
+
|
|
5
|
+
import { Source, SourceLocation } from "../Source.ts"
|
|
6
|
+
import { TraitId } from "../Trait.ts"
|
|
7
|
+
import { evaluate, requiresReview, TraitEvaluation } from "../TraitEvaluation.ts"
|
|
8
|
+
import { traitIgnores } from "../traitIgnores.ts"
|
|
9
|
+
import { TraitsContext } from "../TraitsContext.ts"
|
|
10
|
+
import { TraitStore, TraitStoreError } from "../TraitStore.ts"
|
|
11
|
+
|
|
12
|
+
export const CheckTraits = Tool.make("check_traits", {
|
|
13
|
+
description: String.stripMargin(`
|
|
14
|
+
| Use after generating or editing TypeScript files, after saving or resolving a relevant trait, and before committing or opening a PR.
|
|
15
|
+
| Assess only project-owned source files and relevant authored configuration or documentation. Do not audit third-party or generated code.
|
|
16
|
+
| For a whole-project review, prefer an explicit project-file manifest rather than a filesystem-wide glob.
|
|
17
|
+
| Never submit secrets, .env files, credentials, binary assets, generated lockfiles, dependency trees, or reference repositories for assessment.
|
|
18
|
+
| Supply include as TypeScript file paths or glob patterns relevant to the work, relative to the MCP working directory or absolute.
|
|
19
|
+
| Globs are expanded and overlapping matches are checked once. Directories are not reviewed as files; use patterns such as traits/**/*.ts.
|
|
20
|
+
| Built-in exclusions cover dependency trees, Git metadata, and nested worktrees. Configure project-specific exclusions through TraitsConfig.excludes.
|
|
21
|
+
| Configured exclusions are filenames or native filesystem glob patterns, resolved beside traits.ts. Use directory/** to exclude a subtree; bare directories are not expanded.
|
|
22
|
+
| Git-ignored files are excluded by default, with ignore rules refreshed on every call. Untracked files do not need to be added to Git to be evaluated. Tracked files remain eligible even if ignore rules match them. Set excludeGitignored: false in traits.ts to disable this filter; explicit excludes still apply. Git failures fail the check.
|
|
23
|
+
| All entries follow native glob syntax, including escaping filenames with glob characters. No custom directory expansion or literal-path classification is applied.
|
|
24
|
+
| Reads current full file contents, including unstaged edits, and checks each 200-line chunk against the selected traits in parallel.
|
|
25
|
+
| Omit traitIds to check all stored traits; supply trait ids for a focused check of one or more traits. Unknown IDs fail the call.
|
|
26
|
+
| A leading // traits-ignore-file <trait-id>: <reason> comment skips only that trait for the entire file, including focused checks. Directives require a reason and must precede code.
|
|
27
|
+
| Returns findings with the trait, source text, absolute path, 1-based chunk startLine, and assessment classification and probabilities.
|
|
28
|
+
| Classification distinguishes violation from inconclusive; probabilities are not calibrated guarantees. No exact offending lines are provided.
|
|
29
|
+
| Diagnose uncertainty using the full distribution, not the selected label alone; inspect source and trait before changing code.
|
|
30
|
+
| Act on the results: inspect every finding and automatically fix confirmed, applicable violations without waiting for a separate request.
|
|
31
|
+
| Make minimal behavior-preserving code changes, then rerun this tool on the corrected files and run relevant project checks.
|
|
32
|
+
| Repeat for remaining confirmed violations; returning a findings summary alone does not complete the review.
|
|
33
|
+
| Do not weaken a trait to make code pass or change correct code for an inapplicable or inconclusive finding.
|
|
34
|
+
| Triage each finding before reporting it. Routine summaries should surface confirmed issues, meaningful unresolved uncertainty, errors, and decision blockers.
|
|
35
|
+
| Do not distract the user with rejected, irrelevant false positives; retain raw results in tool output for audit and explain them when requested or materially significant.
|
|
36
|
+
| Do not hide actionable findings based only on probability/confidence or claim the automated check was clean when raw findings remain.
|
|
37
|
+
| Ask for clarification when a fix changes behavior or exceeds authorized scope.
|
|
38
|
+
| The tool diagnoses only; the calling agent is responsible for edits, verification, and reporting unresolved blockers.
|
|
39
|
+
| Unmatched patterns select no files. An empty result means no failures for this selection, not a repository-wide guarantee.
|
|
40
|
+
| Empty include/traitIds lists, no matched files, or an empty store check no pairs.
|
|
41
|
+
`),
|
|
42
|
+
parameters: S.Struct({
|
|
43
|
+
include: S.Array(S.String).annotate({
|
|
44
|
+
description:
|
|
45
|
+
"Project-owned source, configuration, or documentation paths/globs, absolute or relative to the MCP working directory. Exclude third-party/generated files, secrets, and binaries. Overlapping matches are deduplicated; unmatched patterns select no files."
|
|
46
|
+
}),
|
|
47
|
+
traitIds: S.Array(TraitId).pipe(S.optional).annotate({
|
|
48
|
+
description: "Optional trait ids for a focused review. Omit for all stored traits; an empty array selects none."
|
|
49
|
+
})
|
|
50
|
+
}),
|
|
51
|
+
success: S.Array(TraitEvaluation),
|
|
52
|
+
failure: S.Union([AiError.AiError, TraitStoreError]),
|
|
53
|
+
dependencies: [
|
|
54
|
+
DecisionModel.DecisionModel,
|
|
55
|
+
TraitStore,
|
|
56
|
+
FileSystem.FileSystem,
|
|
57
|
+
Path.Path,
|
|
58
|
+
ChildProcessSpawner.ChildProcessSpawner,
|
|
59
|
+
TraitsContext
|
|
60
|
+
]
|
|
61
|
+
})
|
|
62
|
+
.annotate(Tool.Title, "Check Traits")
|
|
63
|
+
.annotate(Tool.Readonly, true)
|
|
64
|
+
.annotate(Tool.Destructive, false)
|
|
65
|
+
|
|
66
|
+
export const toolkit = Toolkit.make(CheckTraits)
|
|
67
|
+
|
|
68
|
+
export const layer = toolkit.toLayer({
|
|
69
|
+
check_traits: Effect.fn("CheckTraits.check")(function* (params) {
|
|
70
|
+
const store = yield* TraitStore
|
|
71
|
+
const context = yield* TraitsContext
|
|
72
|
+
const fs = yield* FileSystem.FileSystem
|
|
73
|
+
const path = yield* Path.Path
|
|
74
|
+
const cwd = path.resolve()
|
|
75
|
+
const includedFiles = context.excludeGitignored
|
|
76
|
+
? yield* Effect.scoped(
|
|
77
|
+
Effect.gen(function* () {
|
|
78
|
+
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner
|
|
79
|
+
const process = yield* spawner.spawn(
|
|
80
|
+
ChildProcess.make`git ls-files --cached --others --exclude-standard --full-name -z`.pipe(
|
|
81
|
+
ChildProcess.setCwd(context.configRoot)
|
|
82
|
+
)
|
|
83
|
+
)
|
|
84
|
+
const output = yield* process.stdout.pipe(Stream.decodeText(), Stream.mkString)
|
|
85
|
+
const exitCode = yield* process.exitCode
|
|
86
|
+
if (exitCode !== 0) {
|
|
87
|
+
return yield* PlatformError.systemError({
|
|
88
|
+
_tag: "Unknown",
|
|
89
|
+
module: "ChildProcess",
|
|
90
|
+
method: "git ls-files",
|
|
91
|
+
description: `Process exited with code ${exitCode}`
|
|
92
|
+
})
|
|
93
|
+
}
|
|
94
|
+
return new Set(
|
|
95
|
+
pipe(
|
|
96
|
+
output,
|
|
97
|
+
String.split("\0"),
|
|
98
|
+
Array.filter(String.isNonEmpty),
|
|
99
|
+
Array.map((file) => path.resolve(context.configRoot, file))
|
|
100
|
+
)
|
|
101
|
+
)
|
|
102
|
+
})
|
|
103
|
+
)
|
|
104
|
+
: undefined
|
|
105
|
+
const traits = yield* params.traitIds === undefined
|
|
106
|
+
? store.ls
|
|
107
|
+
: Effect.forEach(Array.dedupe(params.traitIds), store.get, { concurrency: "unbounded" })
|
|
108
|
+
const matches = yield* Effect.forEach(
|
|
109
|
+
params.include,
|
|
110
|
+
(pattern) => {
|
|
111
|
+
const absolute = path.isAbsolute(pattern) ? pattern : `${cwd}${path.sep}${pattern}`
|
|
112
|
+
return fs.glob(absolute, {
|
|
113
|
+
root: context.configRoot,
|
|
114
|
+
exclude: context.excludes
|
|
115
|
+
})
|
|
116
|
+
},
|
|
117
|
+
{ concurrency: "unbounded" }
|
|
118
|
+
)
|
|
119
|
+
const resolved = pipe(
|
|
120
|
+
matches,
|
|
121
|
+
Array.flatten,
|
|
122
|
+
Array.map((file) => path.resolve(context.configRoot, file)),
|
|
123
|
+
Array.dedupe
|
|
124
|
+
)
|
|
125
|
+
const paths = yield* Effect.filter(
|
|
126
|
+
includedFiles === undefined ? resolved : Array.filter(resolved, (file) => includedFiles.has(file)),
|
|
127
|
+
(file) => fs.stat(file).pipe(Effect.map((info) => info.type === "File")),
|
|
128
|
+
{ concurrency: "unbounded" }
|
|
129
|
+
)
|
|
130
|
+
const results = yield* Effect.forEach(
|
|
131
|
+
paths,
|
|
132
|
+
Effect.fn("CheckTraits.file")(function* (file) {
|
|
133
|
+
const text = yield* fs.readFileString(file)
|
|
134
|
+
const ignored = traitIgnores(text)
|
|
135
|
+
const activeTraits = Array.filter(traits, (trait) => !ignored.has(trait.id))
|
|
136
|
+
const chunks = Array.chunksOf(String.split(text, "\n"), 200)
|
|
137
|
+
const sources = Array.map(chunks, (lines, index) =>
|
|
138
|
+
Source.make({
|
|
139
|
+
text: Array.join(lines, "\n"),
|
|
140
|
+
location: SourceLocation.cases.File.make({ path: file, startLine: index * 200 + 1 })
|
|
141
|
+
})
|
|
142
|
+
)
|
|
143
|
+
const results = yield* Effect.forEach(
|
|
144
|
+
sources,
|
|
145
|
+
(source) => Effect.forEach(activeTraits, (trait) => evaluate(trait, source), { concurrency: "unbounded" }),
|
|
146
|
+
{ concurrency: "unbounded" }
|
|
147
|
+
)
|
|
148
|
+
return pipe(results, Array.flatten, Array.filter(requiresReview))
|
|
149
|
+
}),
|
|
150
|
+
{ concurrency: "unbounded" }
|
|
151
|
+
)
|
|
152
|
+
return Array.flatten(results)
|
|
153
|
+
})
|
|
154
|
+
})
|