traits 0.5.1 → 0.5.2

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.
Files changed (103) hide show
  1. package/Source.ts +1 -1
  2. package/Trait.ts +109 -4
  3. package/TraitConflict.ts +43 -4
  4. package/TraitDerivation.ts +104 -32
  5. package/TraitEvaluation.ts +104 -49
  6. package/TraitStore.ts +128 -44
  7. package/TraitsConfig.ts +16 -9
  8. package/TraitsContext.ts +3 -1
  9. package/_util/Frontmatter.ts +78 -0
  10. package/_util/Git.ts +6 -5
  11. package/bin.ts +3 -1
  12. package/dist/Source.js +1 -1
  13. package/dist/Source.js.map +1 -1
  14. package/dist/Trait.d.ts +39 -5
  15. package/dist/Trait.d.ts.map +1 -1
  16. package/dist/Trait.js +66 -3
  17. package/dist/Trait.js.map +1 -1
  18. package/dist/TraitConflict.d.ts +47 -15
  19. package/dist/TraitConflict.d.ts.map +1 -1
  20. package/dist/TraitConflict.js +38 -4
  21. package/dist/TraitConflict.js.map +1 -1
  22. package/dist/TraitDerivation.d.ts +85 -28
  23. package/dist/TraitDerivation.d.ts.map +1 -1
  24. package/dist/TraitDerivation.js +85 -23
  25. package/dist/TraitDerivation.js.map +1 -1
  26. package/dist/TraitEvaluation.d.ts +38 -8
  27. package/dist/TraitEvaluation.d.ts.map +1 -1
  28. package/dist/TraitEvaluation.js +84 -47
  29. package/dist/TraitEvaluation.js.map +1 -1
  30. package/dist/TraitStore.d.ts +60 -10
  31. package/dist/TraitStore.d.ts.map +1 -1
  32. package/dist/TraitStore.js +57 -37
  33. package/dist/TraitStore.js.map +1 -1
  34. package/dist/TraitsConfig.d.ts +4 -3
  35. package/dist/TraitsConfig.d.ts.map +1 -1
  36. package/dist/TraitsConfig.js +12 -5
  37. package/dist/TraitsConfig.js.map +1 -1
  38. package/dist/TraitsContext.d.ts +2 -1
  39. package/dist/TraitsContext.d.ts.map +1 -1
  40. package/dist/TraitsContext.js.map +1 -1
  41. package/dist/_util/Frontmatter.d.ts +37 -0
  42. package/dist/_util/Frontmatter.d.ts.map +1 -0
  43. package/dist/_util/Frontmatter.js +52 -0
  44. package/dist/_util/Frontmatter.js.map +1 -0
  45. package/dist/_util/Git.d.ts +2 -3
  46. package/dist/_util/Git.d.ts.map +1 -1
  47. package/dist/_util/Git.js +4 -1
  48. package/dist/_util/Git.js.map +1 -1
  49. package/dist/bin.js +3 -1
  50. package/dist/bin.js.map +1 -1
  51. package/dist/getIncludedFiles.d.ts +13 -0
  52. package/dist/getIncludedFiles.d.ts.map +1 -0
  53. package/dist/getIncludedFiles.js +26 -0
  54. package/dist/getIncludedFiles.js.map +1 -0
  55. package/dist/main.js +14 -11
  56. package/dist/main.js.map +1 -1
  57. package/dist/package.json +7 -2
  58. package/dist/test/Frontmatter.test.d.ts +2 -0
  59. package/dist/test/Frontmatter.test.d.ts.map +1 -0
  60. package/dist/test/Frontmatter.test.js +29 -0
  61. package/dist/test/Frontmatter.test.js.map +1 -0
  62. package/dist/test/Trait.test.d.ts +2 -0
  63. package/dist/test/Trait.test.d.ts.map +1 -0
  64. package/dist/test/Trait.test.js +144 -0
  65. package/dist/test/Trait.test.js.map +1 -0
  66. package/dist/test/TraitDerivation.test.d.ts +2 -0
  67. package/dist/test/TraitDerivation.test.d.ts.map +1 -0
  68. package/dist/test/TraitDerivation.test.js +173 -0
  69. package/dist/test/TraitDerivation.test.js.map +1 -0
  70. package/dist/test/TraitEvaluation.test.d.ts +2 -0
  71. package/dist/test/TraitEvaluation.test.d.ts.map +1 -0
  72. package/dist/test/TraitEvaluation.test.js +116 -0
  73. package/dist/test/TraitEvaluation.test.js.map +1 -0
  74. package/dist/test/TraitIgnores.test.js +29 -5
  75. package/dist/test/TraitIgnores.test.js.map +1 -1
  76. package/dist/test/TraitStore.test.d.ts +2 -0
  77. package/dist/test/TraitStore.test.d.ts.map +1 -0
  78. package/dist/test/TraitStore.test.js +94 -0
  79. package/dist/test/TraitStore.test.js.map +1 -0
  80. package/dist/toolkit/CheckTraits.d.ts +121 -13
  81. package/dist/toolkit/CheckTraits.d.ts.map +1 -1
  82. package/dist/toolkit/CheckTraits.js +28 -40
  83. package/dist/toolkit/CheckTraits.js.map +1 -1
  84. package/dist/toolkit/DeriveTrait.d.ts +161 -45
  85. package/dist/toolkit/DeriveTrait.d.ts.map +1 -1
  86. package/dist/toolkit/DeriveTrait.js +26 -14
  87. package/dist/toolkit/DeriveTrait.js.map +1 -1
  88. package/dist/traitIgnores.d.ts +18 -0
  89. package/dist/traitIgnores.d.ts.map +1 -1
  90. package/dist/traitIgnores.js +10 -7
  91. package/dist/traitIgnores.js.map +1 -1
  92. package/getIncludedFiles.ts +42 -0
  93. package/main.ts +14 -11
  94. package/package.json +7 -2
  95. package/skills/traits/SKILL.md +92 -46
  96. package/toolkit/CheckTraits.ts +34 -55
  97. package/toolkit/DeriveTrait.ts +27 -14
  98. package/traitIgnores.ts +14 -6
  99. package/_util/words.ts +0 -555
  100. package/dist/_util/words.d.ts +0 -2
  101. package/dist/_util/words.d.ts.map +0 -1
  102. package/dist/_util/words.js +0 -556
  103. package/dist/_util/words.js.map +0 -1
@@ -1,6 +1,7 @@
1
1
  import { Schema as S, String } from "effect";
2
- import { AiError, DecisionModel, LanguageModel, McpSchema, Tool, Toolkit } from "effect/ai";
3
- import { derive, TraitDerivation } from "../TraitDerivation.js";
2
+ import { DecisionModel, LanguageModel, McpSchema, Tool, Toolkit } from "effect/ai";
3
+ import * as Trait from "../Trait.js";
4
+ import * as Derivation from "../TraitDerivation.js";
4
5
  import { TraitsContext } from "../TraitsContext.js";
5
6
  import { TraitStore, TraitStoreError } from "../TraitStore.js";
6
7
  export const DeriveTrait = Tool.make("derive_trait", {
@@ -15,40 +16,51 @@ export const DeriveTrait = Tool.make("derive_trait", {
15
16
  | If Revise supplies no concrete feedback, ask for revision details with the interactive question UI and free text. Do not guess or set approved: true.
16
17
  | Keep the proposal pending through related work and return to it afterward unless cancelled; obtain fresh approval of the complete revised body.
17
18
  | First decide whether the feedback generalizes beyond the current task and inspect existing trait files for coverage.
18
- | Supply a concise, standalone rule as proposedTrait, preserving its scope and exceptions; omit headings and unnecessary examples.
19
+ | Supply a concise, standalone rule as proposedTrait, preserving its scope and exceptions; omit headings.
20
+ | Include a separate diff showing BAD (-) versus GOOD (+) code whenever a useful example applies. Omit it only when a code example is not meaningful. Approval covers the exact diff as well as the body.
21
+ | Supply a unique descriptive kebab-case name without a redundant trait- prefix for a newly created trait; it is the canonical identity, frontmatter name, and Markdown filename stem. Duplicate names are rejected.
22
+ | Avoid repeated requirements and duplicate traits. Keep bodies standalone; do not strip essential scope or exceptions just to shorten them.
23
+ | When authoring trait frontmatter, let mime restrict file types and when define positive semantic scope. Include skip only for an independent exception within that scope, never its negation or a repetition of another field.
24
+ | Write when values without a leading When, for example when: editing React state.
19
25
  | Compare against all stored traits. Only a distinct proposal is saved as a new trait.
20
26
  | Inconclusive returns relationship findings and blocks all writes; clarify ambiguity or revise the proposal before retrying.
21
- | Confirmed conflicts enter reconciliation within this tool. Approval shows affected trait IDs/bodies, removals, the proposal, and replacement policy.
22
- | Nothing is replaced before approval. After approval, generate compatible standalone replacements, save them, then delete only the conflicting originals.
27
+ | Confirmed conflicts and independently verified coverage gaps enter reconciliation within this tool. Approval shows affected trait names, bodies, applicability metadata, removals, the proposal, and replacement policy.
28
+ | Nothing is replaced before approval. After approval, validate unique replacement names against unaffected traits, update reused selected names, save new names, then delete only selected originals whose names were not reused.
23
29
  | The proposal takes precedence only in direct conflicts; preserve unrelated requirements, scopes, and exceptions. Resolved returns saved replacements.
24
30
  | Do not submit the same proposal again after Resolved: it is already incorporated. Inspect replacements and check relevant TypeScript files.
25
31
  | Reconciliation is not transactional; on an error, inspect the store before retrying because some writes or removals may have completed.
26
- | Covered returns the existing traits representing the proposal; reuse them without writing a duplicate.
32
+ | Covered requires a second independent decision-model assessment of the saved body and when/mime/skip metadata against every proposed requirement. Similar intent or partial overlap is insufficient. Coverage gaps require approval-gated revision; inconclusive verification blocks writes.
33
+ | Covered returns only independently verified existing traits; reuse them without writing a duplicate.
27
34
  | A single clarification candidate is selected automatically for approval; provide a complete replacement body preserving existing requirements and exceptions.
28
35
  | Multiple clarification candidates return Clarification with those traits and the proposal without writing. Refine the proposal or clarify intent.
29
36
  | The shared approval flow shows the existing and proposed bodies; ask in the client UI only if ApprovalRequired is returned.
30
- | The tool rechecks all traits before updating, preserving the selected ID. Updated returns the saved trait.
37
+ | The tool rechecks all traits before updating, preserving the selected name on clarification. Updated returns the saved trait.
31
38
  | Never use approved: true for an unapproved replacement, and never resolve a mere clarification by deleting traits.
32
- | Created returns the saved trait including its generated ID. Every response is one tagged TraitDerivation outcome, never a bare array or void.
33
- | Then call check_traits on relevant TypeScript files, using returned trait IDs for focused reviews when appropriate.
39
+ | Created returns the saved trait including its unique name. Every response is one tagged TraitDerivation outcome, never a bare array or void.
40
+ | Then call check_traits on relevant TypeScript files, using returned trait names for focused reviews when appropriate.
34
41
  `),
35
42
  parameters: S.Struct({
43
+ diff: Trait.TraitDiff.pipe(S.optional),
44
+ name: Trait.TraitName.annotate({
45
+ description: "Unique descriptive kebab-case identity and Markdown filename stem without a redundant trait- prefix for a newly created trait. Existing names are preserved on clarification."
46
+ }),
36
47
  proposedTrait: S.String.annotate({
37
- description: "The complete reusable rule to save, including scope and exceptions, rather than raw conversation text."
48
+ description: "A concise, standalone reusable rule with its essential scope and exceptions, not raw conversation text or repeated restatements of the same requirement."
38
49
  }),
39
50
  approved: S.Boolean.pipe(S.optional).annotate({
40
51
  description: "Fallback approval for the exact proposal and automatically selected action shown to the user, including any existing trait being updated. Set true only after approval; native clients still receive a form. Does not bypass relationship checks."
41
52
  })
42
53
  }),
43
- success: TraitDerivation,
44
- failure: S.Union([AiError.AiError, TraitStoreError, McpSchema.ElicitationDeclined]),
45
- dependencies: [DecisionModel.DecisionModel, LanguageModel.LanguageModel, TraitStore, TraitsContext]
54
+ success: Derivation.TraitDerivation,
55
+ failure: S.Union([S.TaggedStruct("AiError", { message: S.String }), TraitStoreError, McpSchema.ElicitationDeclined]),
56
+ failureMode: "return",
57
+ dependencies: [DecisionModel.DecisionModel, LanguageModel.LanguageModel, TraitsContext, TraitStore]
46
58
  })
47
59
  .annotate(Tool.Title, "Derive Trait")
48
60
  .annotate(Tool.Readonly, false)
49
61
  .annotate(Tool.Destructive, true);
50
62
  export const toolkit = Toolkit.make(DeriveTrait);
51
63
  export const layer = toolkit.toLayer({
52
- derive_trait: derive
64
+ derive_trait: Derivation.derive
53
65
  });
54
66
  //# sourceMappingURL=DeriveTrait.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"DeriveTrait.js","sourceRoot":"","sources":["../../toolkit/DeriveTrait.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAC5C,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAE3F,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAC/D,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACnD,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAE9D,MAAM,CAAC,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE;IACnD,WAAW,EAAE,MAAM,CAAC,WAAW,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2B/B,CAAC;IACF,UAAU,EAAE,CAAC,CAAC,MAAM,CAAC;QACnB,aAAa,EAAE,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC;YAC/B,WAAW,EACT,wGAAwG;SAC3G,CAAC;QACF,QAAQ,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,QAAQ,CAAC;YAC5C,WAAW,EACT,mPAAmP;SACtP,CAAC;KACH,CAAC;IACF,OAAO,EAAE,eAAe;IACxB,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,eAAe,EAAE,SAAS,CAAC,mBAAmB,CAAC,CAAC;IACnF,YAAY,EAAE,CAAC,aAAa,CAAC,aAAa,EAAE,aAAa,CAAC,aAAa,EAAE,UAAU,EAAE,aAAa,CAAC;CACpG,CAAC;KACC,QAAQ,CAAC,IAAI,CAAC,KAAK,EAAE,cAAc,CAAC;KACpC,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC;KAC9B,QAAQ,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,CAAA;AAEnC,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;AAEhD,MAAM,CAAC,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC;IACnC,YAAY,EAAE,MAAM;CACrB,CAAC,CAAA"}
1
+ {"version":3,"file":"DeriveTrait.js","sourceRoot":"","sources":["../../toolkit/DeriveTrait.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAC5C,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAElF,OAAO,KAAK,KAAK,MAAM,aAAa,CAAA;AACpC,OAAO,KAAK,UAAU,MAAM,uBAAuB,CAAA;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACnD,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAE9D,MAAM,CAAC,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE;IACnD,WAAW,EAAE,MAAM,CAAC,WAAW,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiC/B,CAAC;IACF,UAAU,EAAE,CAAC,CAAC,MAAM,CAAC;QACnB,IAAI,EAAE,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC;QACtC,IAAI,EAAE,KAAK,CAAC,SAAS,CAAC,QAAQ,CAAC;YAC7B,WAAW,EACT,+KAA+K;SAClL,CAAC;QACF,aAAa,EAAE,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC;YAC/B,WAAW,EACT,0JAA0J;SAC7J,CAAC;QACF,QAAQ,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,QAAQ,CAAC;YAC5C,WAAW,EACT,mPAAmP;SACtP,CAAC;KACH,CAAC;IACF,OAAO,EAAE,UAAU,CAAC,eAAe;IACnC,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,SAAS,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,eAAe,EAAE,SAAS,CAAC,mBAAmB,CAAC,CAAC;IACpH,WAAW,EAAE,QAAQ;IACrB,YAAY,EAAE,CAAC,aAAa,CAAC,aAAa,EAAE,aAAa,CAAC,aAAa,EAAE,aAAa,EAAE,UAAU,CAAC;CACpG,CAAC;KACC,QAAQ,CAAC,IAAI,CAAC,KAAK,EAAE,cAAc,CAAC;KACpC,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC;KAC9B,QAAQ,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,CAAA;AAEnC,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;AAEhD,MAAM,CAAC,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC;IACnC,YAAY,EAAE,UAAU,CAAC,MAAM;CAChC,CAAC,CAAA"}
@@ -1,3 +1,21 @@
1
1
  /** File-scoped exceptions must appear in the leading line-comment header and include a reason. */
2
2
  export declare const traitIgnores: (text: string) => ReadonlySet<string>;
3
+ /** References match unique trait names exactly. */
4
+ export declare const excludeIgnored: (text: string) => (self: Iterable<{
5
+ readonly [x: string]: import("effect/Schema").Json;
6
+ readonly body: string;
7
+ readonly diff?: string | undefined;
8
+ readonly mime?: string | readonly string[] | undefined;
9
+ readonly name: string & import("effect/Brand").Brand<"traits/TraitName">;
10
+ readonly skip?: string | undefined;
11
+ readonly when?: string | undefined;
12
+ }>) => {
13
+ readonly [x: string]: import("effect/Schema").Json;
14
+ readonly body: string;
15
+ readonly diff?: string | undefined;
16
+ readonly mime?: string | readonly string[] | undefined;
17
+ readonly name: string & import("effect/Brand").Brand<"traits/TraitName">;
18
+ readonly skip?: string | undefined;
19
+ readonly when?: string | undefined;
20
+ }[];
3
21
  //# sourceMappingURL=traitIgnores.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"traitIgnores.d.ts","sourceRoot":"","sources":["../traitIgnores.ts"],"names":[],"mappings":"AAEA,kGAAkG;AAClG,eAAO,MAAM,YAAY,SAAU,MAAM,KAAG,WAAW,CAAC,MAAM,CAU7D,CAAA"}
1
+ {"version":3,"file":"traitIgnores.d.ts","sourceRoot":"","sources":["../traitIgnores.ts"],"names":[],"mappings":"AAIA,kGAAkG;AAClG,eAAO,MAAM,YAAY,SAAU,MAAM,KAAG,WAAW,CAAC,MAAM,CAW7D,CAAA;AAED,mDAAmD;AACnD,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;GAE1B,CAAA"}
@@ -1,17 +1,20 @@
1
- import { pipe, String } from "effect";
1
+ import { Array, flow, String } from "effect";
2
2
  /** File-scoped exceptions must appear in the leading line-comment header and include a reason. */
3
3
  export const traitIgnores = (text) => {
4
- const ids = new Set();
4
+ const references = new Set();
5
5
  for (const line of String.split(text, "\n")) {
6
6
  const trimmed = String.trim(line);
7
- if (trimmed === "" || pipe(trimmed, String.startsWith("#!")))
7
+ if (trimmed === "" || trimmed.startsWith("#!"))
8
8
  continue;
9
- if (!pipe(trimmed, String.startsWith("//")))
9
+ if (!trimmed.startsWith("//"))
10
10
  break;
11
11
  const match = /^\/\/\s*traits-ignore-file\s+(\S+)\s*:\s*(\S.*)$/.exec(trimmed);
12
- if (match?.[1] !== undefined)
13
- ids.add(match[1]);
12
+ const reference = match?.[1];
13
+ if (reference !== undefined)
14
+ references.add(reference);
14
15
  }
15
- return ids;
16
+ return references;
16
17
  };
18
+ /** References match unique trait names exactly. */
19
+ export const excludeIgnored = flow(traitIgnores, (references) => Array.filter((trait) => !references.has(trait.name)));
17
20
  //# sourceMappingURL=traitIgnores.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"traitIgnores.js","sourceRoot":"","sources":["../traitIgnores.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAErC,kGAAkG;AAClG,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,IAAY,EAAuB,EAAE;IAChE,MAAM,GAAG,GAAG,IAAI,GAAG,EAAU,CAAA;IAC7B,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC;QAC5C,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACjC,IAAI,OAAO,KAAK,EAAE,IAAI,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;YAAE,SAAQ;QACtE,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;YAAE,MAAK;QAClD,MAAM,KAAK,GAAG,kDAAkD,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QAC9E,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,SAAS;YAAE,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;IACjD,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC,CAAA"}
1
+ {"version":3,"file":"traitIgnores.js","sourceRoot":"","sources":["../traitIgnores.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAI5C,kGAAkG;AAClG,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,IAAY,EAAuB,EAAE;IAChE,MAAM,UAAU,GAAG,IAAI,GAAG,EAAU,CAAA;IACpC,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC;QAC5C,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACjC,IAAI,OAAO,KAAK,EAAE,IAAI,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,SAAQ;QACxD,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,MAAK;QACpC,MAAM,KAAK,GAAG,kDAAkD,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QAC9E,MAAM,SAAS,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,CAAA;QAC5B,IAAI,SAAS,KAAK,SAAS;YAAE,UAAU,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;IACxD,CAAC;IACD,OAAO,UAAU,CAAA;AACnB,CAAC,CAAA;AAED,mDAAmD;AACnD,MAAM,CAAC,MAAM,cAAc,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC,UAAU,EAAE,EAAE,CAC9D,KAAK,CAAC,MAAM,CAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAC5D,CAAA"}
@@ -0,0 +1,42 @@
1
+ import { Array, Effect, Equal, Path, pipe, Schema as S, Stream, String } from "effect"
2
+ import { ChildProcess, ChildProcessSpawner } from "effect/process"
3
+
4
+ import { TraitsContext } from "./TraitsContext.ts"
5
+
6
+ export class GitFileListingError extends S.TaggedError<GitFileListingError>()("GitFileListingError", {
7
+ directory: S.String,
8
+ exitCode: S.Int.check(S.isGreaterThan(0))
9
+ }) {
10
+ override get message() {
11
+ return `git ls-files exited with code ${this.exitCode}`
12
+ }
13
+ }
14
+
15
+ export const getIncludedFiles = Effect.gen(function* () {
16
+ const context = yield* TraitsContext
17
+ if (!context.config.excludeGitignored) return undefined
18
+
19
+ const path = yield* Path.Path
20
+ const spawner = yield* ChildProcessSpawner.ChildProcessSpawner
21
+ const process = yield* spawner.spawn(
22
+ ChildProcess.make`git ls-files --cached --others --exclude-standard --full-name -z`.pipe(
23
+ ChildProcess.setCwd(context.configRoot)
24
+ )
25
+ )
26
+ const output = yield* process.stdout.pipe(Stream.decodeText(), Stream.mkString)
27
+ yield* process.exitCode.pipe(
28
+ Effect.filterOrFail(Equal.equals(0), (exitCode) =>
29
+ GitFileListingError.make({
30
+ directory: context.configRoot,
31
+ exitCode
32
+ })
33
+ )
34
+ )
35
+ return pipe(
36
+ output,
37
+ String.split("\0"),
38
+ Array.filter(String.isNonEmpty),
39
+ Array.map((file) => path.resolve(context.configRoot, file)),
40
+ (files) => new Set(files)
41
+ )
42
+ }).pipe(Effect.scoped, Effect.withSpan("getIncludedFiles"))
package/main.ts CHANGED
@@ -34,8 +34,8 @@ Command.make("traits", {
34
34
  version: PackageJson.version,
35
35
  protocols: [McpProtocol.v2025_11_25],
36
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>.
37
+ | Traits are durable, reusable coding preferences. The filesystem adapter stores each as .agents/traits/<name>.md, with a unique descriptive kebab-case name (no redundant trait- prefix) in frontmatter and no separate id.
38
+ | The unique name is the canonical identity across storage backends. Use exact names for focused traitNames selections and ignore directives, not filenames. Update references after renaming. Duplicate names are rejected.
39
39
  | Before completing a response to a user correction, coding preference, or approved alternative idiom, check whether it expresses a reusable rule.
40
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
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.
@@ -51,24 +51,27 @@ Command.make("traits", {
51
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
52
  | Also act on explicit requests to remember a convention. Skip one-off requirements, transient facts, and preferences already covered.
53
53
  | Read existing trait files before drafting. Keep each trait concise and independently applicable, preserving scope and exceptions.
54
+ | Keep frontmatter complementary: mime restricts file types, when defines positive semantic scope, and skip is only an independent exception within that scope. Omit redundant fields; never repeat when's negation as skip. Keep bodies standalone without repeated requirements or duplicate traits.
55
+ | Write when values as conditions without a redundant leading When, for example when: editing React state.
54
56
  | Call derive_trait with the drafted body as proposedTrait. It saves only distinct rules; Covered means reuse an existing trait.
55
57
  | 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.
58
+ | Multiple candidates return Clarification entries without writing: refine the proposal or clarify intent. Do not supply a clarification target.
59
+ | After fallback approval, retry with the same name, proposedTrait and approved: true; Updated preserves the selected existing name.
60
+ | Every derive_trait response is one tagged TraitDerivation outcome. Created returns the saved trait with its unique name; Updated and Resolved return saved replacements.
59
61
  | 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.
62
+ | Inconclusive outcome findings use reason to distinguish conflict, coverage_gap, and inconclusive; clarify ambiguity before retrying.
63
+ | Covered requires independent second-pass verification of the saved body and applicability metadata against the complete proposal. Similar intent is insufficient; coverage gaps enter approval-gated reconciliation and inconclusive verification blocks writes.
64
+ | Confirmed conflicts and verified coverage gaps enter reconciliation inside derive_trait, selecting only the affected originals for an explicit replacement approval.
65
+ | Approval must cover affected names, bodies, removals, the proposal, and generated reconciliation. Nothing is replaced silently.
66
+ | After approval, reconciliation gives the proposal precedence in direct conflicts, updates selected names reused by replacements, saves new names, then removes only originals whose names were not reused. Unaffected names and duplicate generated names cannot be overwritten.
67
+ | Resolved returns saved replacement bodies and unique names. Review them and check relevant code; do not derive the same proposal again.
65
68
  | After generating or changing TypeScript files, call check_traits on those files.
66
69
  | check_traits accepts include as file paths or glob strings, for example include: ["traits/**/*.ts"]. Overlapping matches are deduplicated.
67
70
  | Restrict assessments to project-owned source and relevant authored configuration/docs. Prefer an explicit project-file manifest for whole-project audits.
68
71
  | Never submit dependency/reference code, generated build output or lockfiles, Git metadata, secrets, .env files, credentials, or binary assets for assessment.
69
72
  | 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
73
  | 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.
74
+ | Omit traitNames for all traits; use specific names for focused follow-ups. Before committing or opening a PR, review the changed TypeScript files.
72
75
  | Checks read current full files in 200-line chunks, not Git diffs. A finding's startLine locates a chunk, not an exact violation.
73
76
  | Each finding includes assessment.classification and probabilities: inconclusive is unresolved context, not a confirmed violation. Probabilities are not calibrated guarantees.
74
77
  | Take responsibility for trait compliance whenever a violation is discovered, through tool findings or your own inspection.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "traits",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
4
4
  "license": "Apache-2.0",
5
5
  "repository": {
6
6
  "type": "git",
@@ -21,7 +21,8 @@
21
21
  "dist/**/*.js.map",
22
22
  "dist/**/*.d.ts",
23
23
  "dist/**/*.d.ts.map",
24
- "!dist/vite.config.*"
24
+ "!dist/vite.config.*",
25
+ "!dist/integration/**"
25
26
  ],
26
27
  "type": "module",
27
28
  "exports": {
@@ -34,8 +35,12 @@
34
35
  "publishConfig": {
35
36
  "access": "public"
36
37
  },
38
+ "dependencies": {
39
+ "yaml": "^2.9.1"
40
+ },
37
41
  "devDependencies": {
38
42
  "@effect/platform-node": "4.0.0",
43
+ "@effect/vitest": "4.0.0",
39
44
  "effect": "4.0.0"
40
45
  },
41
46
  "peerDependencies": {
@@ -10,10 +10,39 @@ description: >-
10
10
  # Traits
11
11
 
12
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.
13
+ each body in `.agents/traits/<name>.md`, relative to its working directory.
14
+ Frontmatter contains a unique descriptive kebab-case `name` without a redundant
15
+ `trait-` prefix and no separate `id`. The name is the canonical identity across
16
+ backends: use exact names for focused `check_traits({ traitNames: [...] })`
17
+ selections and ignore directives, not filenames. Duplicate names are rejected.
18
+ Renaming changes identity and requires updating references. Construct complete
19
+ records with the schema's `Trait.Trait.make`. `TraitStore` persists complete
20
+ traits and uses names for get, update, and remove. Updates retain the name; a
21
+ rename requires adding the new name and removing the old one after saving. The
22
+ `TraitStore` service class declares its shape inline; use
23
+ `TraitStore["Service"]` for its implementation type. Its filesystem layer is
24
+ colocated in the same module. Only the filesystem adapter builds `<name>.md`
25
+ paths.
26
+
27
+ Include a concise BAD-to-GOOD code example whenever meaningful. Pass its raw
28
+ unified diff as `diff` to `derive_trait` (removed `-` lines are BAD, added `+`
29
+ lines are GOOD), separately from `proposedTrait`. Omit it only when a code
30
+ example does not apply. Approval and retries cover the exact diff too. On disk,
31
+ `diff` is not frontmatter: put it in a Markdown `diff` fence directly after the
32
+ frontmatter, followed by a blank line, `---`, and the standalone body. Keep
33
+ examples out of the body and preserve them when updating a trait unless the
34
+ updated rule needs a changed example.
35
+
36
+ Keep frontmatter fields complementary. `mime` restricts file types; `when`
37
+ defines positive semantic scope. Write `when` values without a leading "When",
38
+ such as `when: editing React state`. Add `skip` only for an independent
39
+ exception inside that scope, not for its negation or a restriction already
40
+ expressed by another field. For example,
41
+ `when: importing from local project modules` already excludes external packages;
42
+ do not repeat that as a skip. Leave unnecessary optional fields absent. Keep the
43
+ body concise and standalone, preserving essential scope and exceptions without
44
+ repeated restatements. Reuse existing traits that already cover the same rule
45
+ rather than creating duplicates.
17
46
 
18
47
  ## Capture a reusable insight
19
48
 
@@ -32,15 +61,23 @@ rule; if an existing rule needs clearer wording, propose a clarification. Skip
32
61
  only clearly one-off requests, transient facts, or rules you can identify as
33
62
  already covered.
34
63
 
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.
64
+ When coverage is uncertain, call `derive_trait` with the specific preference; do
65
+ not skip capture because a broad trait or a combination of general traits seems
66
+ related. Let the tool assess coverage or clarification. For example, “prefer
67
+ Effect utilities” and “pass callbacks directly” are not sufficient grounds to
68
+ skip proposing “prefer native inline checks and use Effect predicate helpers
69
+ directly in callbacks or existing pipelines.”
70
+
71
+ Call `derive_trait` with the drafted rule and a descriptive kebab-case `name`
72
+ first, leaving `approved` unset. Do not ask for approval separately before
73
+ invoking: capable clients receive a native MCP form. Only if `ApprovalRequired`
74
+ is returned, show the complete message in the interactive question tool with
75
+ **Save trait**, **Revise**, and **Skip** and its automatic free-text option. Do
76
+ not add a duplicate "Other" option. Use plain text only if that UI is
77
+ unavailable. Approval to edit code alone does not approve persisting a trait:
78
+ make the requested edit and propose the reusable rule before the final response.
79
+ Do not stop after implementing an idiom and running code checks; preference
80
+ capture is a separate responsibility.
44
81
 
45
82
  ### Shared approval protocol
46
83
 
@@ -52,7 +89,7 @@ without form support use the same fallback.
52
89
 
53
90
  On `ApprovalRequired`, show its complete message in the interactive question UI
54
91
  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
92
+ and target names with `approved: true`. Revision or free-text input requires
56
93
  drafting the changed proposal and asking approval again, not silently setting
57
94
  approved. Skip means stop. Native `RevisionRequired` and `Declined` have the
58
95
  same meanings. Transport errors are failures, never declines or approvals.
@@ -88,45 +125,50 @@ authenticated consent token.
88
125
  3. Draft the complete rule as `proposedTrait`. Preserve essential requirements,
89
126
  scopes, and exceptions. Keep independently applicable rules separate; omit
90
127
  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.
128
+ 4. Call `derive_trait({ name, proposedTrait })` first, without a separate
129
+ approval prompt. This checks existing traits and writes only when the
130
+ proposal is distinct from all of them. It does not independently decide
131
+ whether the conversation contains a generalizable insight.
132
+ 5. `Created` means the trait was saved and includes its body and unique name. Do
133
+ not submit it again. Use that name for focused checks when needed.
97
134
  6. `Inconclusive` means nothing was saved and contains relationship findings;
98
135
  each finding's `reason` distinguishes conflict from inconclusive. Any
99
136
  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.
137
+ names, clarify ambiguity, or revise the proposal. Confirmed conflicts
138
+ otherwise enter approval-gated reconciliation within this same tool.
139
+
140
+ `Covered.traits` requires an independent second decision-model check of the
141
+ saved body and when, mime, and skip metadata against the entire proposal.
142
+ Partial overlap or similar intent is insufficient. Verified coverage gaps enter
143
+ approval-gated reconciliation, including needed applicability updates;
144
+ inconclusive verification blocks writes. `Covered.traits` means existing traits
145
+ already express the proposal; reuse them without creating a duplicate. When
146
+ there is exactly one clarification candidate and no covered or blocking result,
147
+ the tool selects it automatically and requests approval of its current body and
148
+ complete replacement. The proposal must preserve the existing rule's intent,
149
+ requirements, scope, and exceptions; the tool does not generate a merged
150
+ replacement from an incremental hint.
109
151
 
110
152
  Multiple candidates return `Clarification` with `traits` and `proposedTrait`
111
153
  without writing. Refine the proposal or clarify intent rather than choosing an
112
- ID arbitrarily. Do not supply a clarification target parameter.
154
+ name arbitrarily. Do not supply a clarification target parameter.
113
155
 
114
156
  Use the interactive question UI only on `ApprovalRequired`, then retry the same
115
157
  `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.
158
+ existing trait and preserves the selected name on clarification. If the store or
159
+ selected action changes between approval and retry, obtain fresh approval rather
160
+ than reusing the flag. `Updated` returns the saved trait. Never claim approval
161
+ not given by the user or delete a trait merely needing clearer wording.
120
162
 
121
163
  ## Reconcile conflicting traits
122
164
 
123
165
  `derive_trait` checks the proposal against every stored trait and automatically
124
166
  selects confirmed conflicts for reconciliation. There is no separate resolution
125
- tool and no ID-selection parameter. Inconclusive comparisons block
167
+ tool and no trait-selection parameter. Inconclusive comparisons block
126
168
  reconciliation rather than authorizing deletion.
127
169
 
128
170
  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.
171
+ names and bodies, the proposal, the removal plan, and the replacement policy.
130
172
  Approval authorizes model-generated reconciliation, not silent deletion. On
131
173
  `ApprovalRequired`, ask through the interactive UI and retry the same proposal
132
174
  with `approved: true` only after Save. If relationships or originals change,
@@ -135,8 +177,10 @@ obtain fresh approval.
135
177
  After approval, the tool evaluates each body and the proposal for decomposition,
136
178
  then generates standalone replacements. The proposal takes precedence in direct
137
179
  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.
180
+ It validates replacement names against each other and unaffected traits before
181
+ writing. It updates selected names reused by replacements, adds new names, then
182
+ deletes only selected originals whose names were not reused. It returns
183
+ `Resolved` with the saved traits and their unique names.
140
184
 
141
185
  Inspect those replacements. Do not call `derive_trait` again for the same
142
186
  proposal: resolution has already saved its content. The initial comparison
@@ -168,7 +212,7 @@ store before retrying; some changes may already be present.
168
212
  schema modifiers, and misplaced imports when their traits apply. Searches
169
213
  support review; a lack of matches is not proof that every trait is satisfied.
170
214
  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;
215
+ Before a commit or PR, omit `traitNames` so every stored trait is checked;
172
216
  focused checks supplement rather than replace that final review.
173
217
  6. Inspect findings, fix confirmed violations, and repeat direct inspection,
174
218
  tool checks, and relevant project verification after fixes. If any reviewed
@@ -186,13 +230,15 @@ For a justified file-scoped exception, add a line comment in the leading header,
186
230
  before any code:
187
231
 
188
232
  ```ts
189
- // traits-ignore-file <full-trait-id>: Explain why this trait cannot apply here.
233
+ // traits-ignore-file <trait-name>: Explain why this trait cannot apply here.
190
234
  ```
191
235
 
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.
236
+ Each directive requires a nonempty reason and exactly one unique trait name.
237
+ Names are case-sensitive; update directives after renaming a trait. Filenames
238
+ and partial names are not selectors. Repeat the comment for additional
239
+ exceptions. Only the referenced traits are skipped; other traits remain checked.
240
+ Exceptions apply to all file chunks and focused checks. Comments after code are
241
+ ignored.
196
242
 
197
243
  Read the referenced trait and verify the reason before adding or retaining an
198
244
  exception. Do not add directives merely to silence findings: fix applicable
@@ -205,9 +251,9 @@ their preference. Respect existing justified exceptions during manual review.
205
251
  `check_traits({ include: files })`.
206
252
  - After creating or resolving traits, explicitly review relevant TypeScript
207
253
  files. Saving traits does not automatically check code.
208
- - Omit `traitIds` for the full trait set. Supply IDs for focused follow-ups,
254
+ - Omit `traitNames` for the full trait set. Supply names for focused follow-ups,
209
255
  especially the replacements returned by resolution. An empty array selects no
210
- traits; unknown IDs fail the call.
256
+ traits; unknown names fail the call.
211
257
  - `include` accepts file paths or glob patterns, absolute or relative to the MCP
212
258
  working directory. Overlapping matches are deduplicated; unmatched patterns
213
259
  select no files. Use `traits/**/*.ts` rather than a bare directory. Glob