@cad0p/pi-steering 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (246) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/LICENSE +29 -0
  3. package/README.md +920 -0
  4. package/dist/__test-helpers__.d.ts +95 -0
  5. package/dist/__test-helpers__.d.ts.map +1 -0
  6. package/dist/__test-helpers__.js +174 -0
  7. package/dist/__test-helpers__.js.map +1 -0
  8. package/dist/bin/pi-steering.d.ts +9 -0
  9. package/dist/bin/pi-steering.d.ts.map +1 -0
  10. package/dist/bin/pi-steering.js +506 -0
  11. package/dist/bin/pi-steering.js.map +1 -0
  12. package/dist/compat.d.ts +50 -0
  13. package/dist/compat.d.ts.map +1 -0
  14. package/dist/compat.js +180 -0
  15. package/dist/compat.js.map +1 -0
  16. package/dist/defaults.d.ts +61 -0
  17. package/dist/defaults.d.ts.map +1 -0
  18. package/dist/defaults.js +106 -0
  19. package/dist/defaults.js.map +1 -0
  20. package/dist/define-config.d.ts +235 -0
  21. package/dist/define-config.d.ts.map +1 -0
  22. package/dist/define-config.js +118 -0
  23. package/dist/define-config.js.map +1 -0
  24. package/dist/define-predicate.d.ts +49 -0
  25. package/dist/define-predicate.d.ts.map +1 -0
  26. package/dist/define-predicate.js +35 -0
  27. package/dist/define-predicate.js.map +1 -0
  28. package/dist/evaluator-internals/context.d.ts +186 -0
  29. package/dist/evaluator-internals/context.d.ts.map +1 -0
  30. package/dist/evaluator-internals/context.js +219 -0
  31. package/dist/evaluator-internals/context.js.map +1 -0
  32. package/dist/evaluator-internals/override.d.ts +19 -0
  33. package/dist/evaluator-internals/override.d.ts.map +1 -0
  34. package/dist/evaluator-internals/override.js +79 -0
  35. package/dist/evaluator-internals/override.js.map +1 -0
  36. package/dist/evaluator-internals/predicates.d.ts +152 -0
  37. package/dist/evaluator-internals/predicates.d.ts.map +1 -0
  38. package/dist/evaluator-internals/predicates.js +849 -0
  39. package/dist/evaluator-internals/predicates.js.map +1 -0
  40. package/dist/evaluator-internals/speculative-synthesis.d.ts +102 -0
  41. package/dist/evaluator-internals/speculative-synthesis.d.ts.map +1 -0
  42. package/dist/evaluator-internals/speculative-synthesis.js +101 -0
  43. package/dist/evaluator-internals/speculative-synthesis.js.map +1 -0
  44. package/dist/evaluator.d.ts +101 -0
  45. package/dist/evaluator.d.ts.map +1 -0
  46. package/dist/evaluator.js +714 -0
  47. package/dist/evaluator.js.map +1 -0
  48. package/dist/helpers/walker-unknown-cwd-reason.d.ts +68 -0
  49. package/dist/helpers/walker-unknown-cwd-reason.d.ts.map +1 -0
  50. package/dist/helpers/walker-unknown-cwd-reason.js +73 -0
  51. package/dist/helpers/walker-unknown-cwd-reason.js.map +1 -0
  52. package/dist/index.d.ts +42 -0
  53. package/dist/index.d.ts.map +1 -0
  54. package/dist/index.js +93 -0
  55. package/dist/index.js.map +1 -0
  56. package/dist/internal/drop-unused-observers.d.ts +43 -0
  57. package/dist/internal/drop-unused-observers.d.ts.map +1 -0
  58. package/dist/internal/drop-unused-observers.js +50 -0
  59. package/dist/internal/drop-unused-observers.js.map +1 -0
  60. package/dist/internal/finalize-plugin-state.d.ts +22 -0
  61. package/dist/internal/finalize-plugin-state.d.ts.map +1 -0
  62. package/dist/internal/finalize-plugin-state.js +14 -0
  63. package/dist/internal/finalize-plugin-state.js.map +1 -0
  64. package/dist/internal/merge-observers.d.ts +30 -0
  65. package/dist/internal/merge-observers.d.ts.map +1 -0
  66. package/dist/internal/merge-observers.js +27 -0
  67. package/dist/internal/merge-observers.js.map +1 -0
  68. package/dist/internal/pattern-utils.d.ts +33 -0
  69. package/dist/internal/pattern-utils.d.ts.map +1 -0
  70. package/dist/internal/pattern-utils.js +20 -0
  71. package/dist/internal/pattern-utils.js.map +1 -0
  72. package/dist/internal/ref-text.d.ts +11 -0
  73. package/dist/internal/ref-text.d.ts.map +1 -0
  74. package/dist/internal/ref-text.js +15 -0
  75. package/dist/internal/ref-text.js.map +1 -0
  76. package/dist/internal/session-runtime.d.ts +44 -0
  77. package/dist/internal/session-runtime.d.ts.map +1 -0
  78. package/dist/internal/session-runtime.js +143 -0
  79. package/dist/internal/session-runtime.js.map +1 -0
  80. package/dist/internal/watch-matcher.d.ts +56 -0
  81. package/dist/internal/watch-matcher.d.ts.map +1 -0
  82. package/dist/internal/watch-matcher.js +179 -0
  83. package/dist/internal/watch-matcher.js.map +1 -0
  84. package/dist/loader.d.ts +97 -0
  85. package/dist/loader.d.ts.map +1 -0
  86. package/dist/loader.js +527 -0
  87. package/dist/loader.js.map +1 -0
  88. package/dist/observer-dispatcher.d.ts +69 -0
  89. package/dist/observer-dispatcher.d.ts.map +1 -0
  90. package/dist/observer-dispatcher.js +184 -0
  91. package/dist/observer-dispatcher.js.map +1 -0
  92. package/dist/plugin-merger.d.ts +175 -0
  93. package/dist/plugin-merger.d.ts.map +1 -0
  94. package/dist/plugin-merger.js +450 -0
  95. package/dist/plugin-merger.js.map +1 -0
  96. package/dist/plugins/git/branch-tracker.d.ts +85 -0
  97. package/dist/plugins/git/branch-tracker.d.ts.map +1 -0
  98. package/dist/plugins/git/branch-tracker.js +181 -0
  99. package/dist/plugins/git/branch-tracker.js.map +1 -0
  100. package/dist/plugins/git/cwd-extensions.d.ts +10 -0
  101. package/dist/plugins/git/cwd-extensions.d.ts.map +1 -0
  102. package/dist/plugins/git/cwd-extensions.js +155 -0
  103. package/dist/plugins/git/cwd-extensions.js.map +1 -0
  104. package/dist/plugins/git/git-ops.d.ts +89 -0
  105. package/dist/plugins/git/git-ops.d.ts.map +1 -0
  106. package/dist/plugins/git/git-ops.js +107 -0
  107. package/dist/plugins/git/git-ops.js.map +1 -0
  108. package/dist/plugins/git/index.d.ts +225 -0
  109. package/dist/plugins/git/index.d.ts.map +1 -0
  110. package/dist/plugins/git/index.js +52 -0
  111. package/dist/plugins/git/index.js.map +1 -0
  112. package/dist/plugins/git/predicates.d.ts +312 -0
  113. package/dist/plugins/git/predicates.d.ts.map +1 -0
  114. package/dist/plugins/git/predicates.js +580 -0
  115. package/dist/plugins/git/predicates.js.map +1 -0
  116. package/dist/plugins/git/rules.d.ts +209 -0
  117. package/dist/plugins/git/rules.d.ts.map +1 -0
  118. package/dist/plugins/git/rules.js +267 -0
  119. package/dist/plugins/git/rules.js.map +1 -0
  120. package/dist/schema.d.ts +1799 -0
  121. package/dist/schema.d.ts.map +1 -0
  122. package/dist/schema.js +4 -0
  123. package/dist/schema.js.map +1 -0
  124. package/dist/testing/index.d.ts +621 -0
  125. package/dist/testing/index.d.ts.map +1 -0
  126. package/dist/testing/index.js +747 -0
  127. package/dist/testing/index.js.map +1 -0
  128. package/examples/README.md +83 -0
  129. package/examples/combined-git-discipline/README.md +52 -0
  130. package/examples/combined-git-discipline/node_modules/.bin/pi-steering +43 -0
  131. package/examples/combined-git-discipline/package.json +18 -0
  132. package/examples/combined-git-discipline/steering.json +27 -0
  133. package/examples/combined-git-discipline/steering.test.ts +42 -0
  134. package/examples/combined-git-discipline/steering.ts +64 -0
  135. package/examples/combined-git-discipline/tsconfig.json +7 -0
  136. package/examples/draft-prs-only/README.md +42 -0
  137. package/examples/draft-prs-only/node_modules/.bin/pi-steering +43 -0
  138. package/examples/draft-prs-only/package.json +18 -0
  139. package/examples/draft-prs-only/steering.json +12 -0
  140. package/examples/draft-prs-only/steering.test.ts +48 -0
  141. package/examples/draft-prs-only/steering.ts +36 -0
  142. package/examples/draft-prs-only/tsconfig.json +7 -0
  143. package/examples/dynamic-reason-runtime-cwd/README.md +148 -0
  144. package/examples/dynamic-reason-runtime-cwd/node_modules/.bin/pi-steering +43 -0
  145. package/examples/dynamic-reason-runtime-cwd/package.json +18 -0
  146. package/examples/dynamic-reason-runtime-cwd/steering.test.ts +130 -0
  147. package/examples/dynamic-reason-runtime-cwd/steering.ts +82 -0
  148. package/examples/dynamic-reason-runtime-cwd/tsconfig.json +7 -0
  149. package/examples/force-push-strict/README.md +75 -0
  150. package/examples/force-push-strict/node_modules/.bin/pi-steering +43 -0
  151. package/examples/force-push-strict/package.json +18 -0
  152. package/examples/force-push-strict/steering.json +12 -0
  153. package/examples/force-push-strict/steering.test.ts +51 -0
  154. package/examples/force-push-strict/steering.ts +54 -0
  155. package/examples/force-push-strict/tsconfig.json +7 -0
  156. package/examples/no-amend/README.md +52 -0
  157. package/examples/no-amend/node_modules/.bin/pi-steering +43 -0
  158. package/examples/no-amend/package.json +18 -0
  159. package/examples/no-amend/steering.cwd-scoped.json +12 -0
  160. package/examples/no-amend/steering.json +11 -0
  161. package/examples/no-amend/steering.test.ts +49 -0
  162. package/examples/no-amend/steering.ts +38 -0
  163. package/examples/no-amend/tsconfig.json +7 -0
  164. package/examples/work-item-plugin/README.md +103 -0
  165. package/examples/work-item-plugin/node_modules/.bin/pi-steering +43 -0
  166. package/examples/work-item-plugin/package.json +19 -0
  167. package/examples/work-item-plugin/src/index.test.ts +168 -0
  168. package/examples/work-item-plugin/src/index.ts +122 -0
  169. package/examples/work-item-plugin/src/observers/npm-test-tracker.test.ts +95 -0
  170. package/examples/work-item-plugin/src/observers/npm-test-tracker.ts +88 -0
  171. package/examples/work-item-plugin/src/observers/retest-required-tracker.test.ts +81 -0
  172. package/examples/work-item-plugin/src/observers/retest-required-tracker.ts +81 -0
  173. package/examples/work-item-plugin/src/predicates/work-item-format.test.ts +157 -0
  174. package/examples/work-item-plugin/src/predicates/work-item-format.ts +99 -0
  175. package/examples/work-item-plugin/src/rules/commit-description-check.test.ts +107 -0
  176. package/examples/work-item-plugin/src/rules/commit-description-check.ts +83 -0
  177. package/examples/work-item-plugin/src/rules/commit-requires-work-item.test.ts +79 -0
  178. package/examples/work-item-plugin/src/rules/commit-requires-work-item.ts +56 -0
  179. package/examples/work-item-plugin/src/rules/push-requires-tests.test.ts +245 -0
  180. package/examples/work-item-plugin/src/rules/push-requires-tests.ts +62 -0
  181. package/examples/work-item-plugin/tsconfig.json +8 -0
  182. package/package.json +88 -0
  183. package/skills/steering-authoring/SKILL.md +102 -0
  184. package/src/INVARIANTS.md +116 -0
  185. package/src/__test-helpers__.ts +243 -0
  186. package/src/bin/pi-steering.test.ts +920 -0
  187. package/src/bin/pi-steering.ts +584 -0
  188. package/src/compat.test.ts +431 -0
  189. package/src/compat.ts +282 -0
  190. package/src/defaults.test.ts +585 -0
  191. package/src/defaults.ts +148 -0
  192. package/src/define-config.test.ts +888 -0
  193. package/src/define-config.ts +375 -0
  194. package/src/define-predicate.test.ts +70 -0
  195. package/src/define-predicate.ts +57 -0
  196. package/src/evaluator-internals/context.ts +290 -0
  197. package/src/evaluator-internals/override.test.ts +99 -0
  198. package/src/evaluator-internals/override.ts +83 -0
  199. package/src/evaluator-internals/predicates.ts +1037 -0
  200. package/src/evaluator-internals/speculative-synthesis.test.ts +411 -0
  201. package/src/evaluator-internals/speculative-synthesis.ts +199 -0
  202. package/src/evaluator.test.ts +6296 -0
  203. package/src/evaluator.ts +1106 -0
  204. package/src/factory-time-load.test.ts +630 -0
  205. package/src/helpers/walker-unknown-cwd-reason.test.ts +73 -0
  206. package/src/helpers/walker-unknown-cwd-reason.ts +83 -0
  207. package/src/index.test.ts +836 -0
  208. package/src/index.ts +229 -0
  209. package/src/internal/drop-unused-observers.test.ts +141 -0
  210. package/src/internal/drop-unused-observers.ts +81 -0
  211. package/src/internal/finalize-plugin-state.ts +43 -0
  212. package/src/internal/merge-observers.ts +51 -0
  213. package/src/internal/pattern-utils.test.ts +52 -0
  214. package/src/internal/pattern-utils.ts +38 -0
  215. package/src/internal/ref-text.test.ts +54 -0
  216. package/src/internal/ref-text.ts +17 -0
  217. package/src/internal/session-runtime.test.ts +629 -0
  218. package/src/internal/session-runtime.ts +201 -0
  219. package/src/internal/watch-matcher.test.ts +326 -0
  220. package/src/internal/watch-matcher.ts +200 -0
  221. package/src/jsdoc-propagation.test.ts +277 -0
  222. package/src/loader.test.ts +1098 -0
  223. package/src/loader.ts +581 -0
  224. package/src/not-block-onunknown.test.ts +554 -0
  225. package/src/observer-dispatcher.test.ts +939 -0
  226. package/src/observer-dispatcher.ts +290 -0
  227. package/src/per-predicate-typing.test.ts +361 -0
  228. package/src/plugin-merger.test.ts +790 -0
  229. package/src/plugin-merger.ts +598 -0
  230. package/src/plugins/git/README.md +485 -0
  231. package/src/plugins/git/branch-tracker.test.ts +281 -0
  232. package/src/plugins/git/branch-tracker.ts +187 -0
  233. package/src/plugins/git/cwd-extensions.test.ts +230 -0
  234. package/src/plugins/git/cwd-extensions.ts +160 -0
  235. package/src/plugins/git/git-ops.test.ts +397 -0
  236. package/src/plugins/git/git-ops.ts +173 -0
  237. package/src/plugins/git/index.ts +253 -0
  238. package/src/plugins/git/integration.test.ts +705 -0
  239. package/src/plugins/git/predicates.test.ts +1401 -0
  240. package/src/plugins/git/predicates.ts +693 -0
  241. package/src/plugins/git/rules.test.ts +1001 -0
  242. package/src/plugins/git/rules.ts +307 -0
  243. package/src/schema.test.ts +259 -0
  244. package/src/schema.ts +1963 -0
  245. package/src/testing/index.test.ts +1696 -0
  246. package/src/testing/index.ts +1472 -0
@@ -0,0 +1,118 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Part of pi-steering.
3
+ /**
4
+ * Build a {@link SteeringConfig} with cross-reference name checking.
5
+ *
6
+ * Observer references in {@link Rule.observer} are typed against the
7
+ * union of observer names gathered from `plugins[*].observers` AND the
8
+ * top-level `observers` array — a typo produces a compile error.
9
+ *
10
+ * The `disabledRules` / `disabledPlugins` arrays are typed against the unions
11
+ * of registered rule / plugin names — typos rejected.
12
+ *
13
+ * `rules[].when.happened.event` and `rules[].when.happened.since` are
14
+ * both typed against the union of all `writes` declarations across
15
+ * plugin rules, plugin observers, user rules, and user observers —
16
+ * typos rejected. (The `since` field on the `Writes` union enforces
17
+ * the same contract as `event`: the sentinel event must be known to
18
+ * the config, not a free-form string.)
19
+ *
20
+ * Runtime behavior: returns a shallow copy of the input with optional
21
+ * fields normalized from `readonly` arrays to mutable arrays (the
22
+ * {@link SteeringConfig} shape doesn't constrain mutability). The
23
+ * return value is safe to pass to the loader / buildConfig.
24
+ *
25
+ * ## Authoring pattern — preserving observer/plugin names for inference
26
+ *
27
+ * For compile-time typo detection on rule `observer` references, declare
28
+ * your observers and plugins with `as const satisfies` so TypeScript
29
+ * preserves the literal `name` values through to `AllObserverNames`:
30
+ *
31
+ * const myObs = {
32
+ * name: "description-read",
33
+ * onResult: (event, ctx) => { ... },
34
+ * } as const satisfies Observer;
35
+ *
36
+ * const myPlugin = {
37
+ * name: "my-plugin",
38
+ * observers: [{ name: "sync-done", onResult: ... }],
39
+ * } as const satisfies Plugin;
40
+ *
41
+ * Authors who prefer type annotations (`const myObs: Observer = ...`)
42
+ * get widened `name: string`, which collapses `AllObserverNames` to
43
+ * `string` and silently disables typo detection. Use `as const satisfies`
44
+ * to keep the inference.
45
+ *
46
+ * ## Behavior with no observers declared
47
+ *
48
+ * When no plugins contribute observers AND no inline `observers[]` is
49
+ * passed, `AllObserverNames` resolves to `never`, which causes ANY
50
+ * string `observer` reference on a Rule to be a compile error. This is
51
+ * deliberate — fail-closed on unknown observer names. For configs that
52
+ * deliberately reference observers by name without registering them
53
+ * inline (e.g., deferred to runtime), use `satisfies SteeringConfig`
54
+ * as a fallback; you lose typo detection but regain flexibility.
55
+ *
56
+ * ## Hover ergonomics for plugin-predicate JSDoc
57
+ *
58
+ * The `const R extends readonly Rule[]` signature narrows the
59
+ * contextual type of inline rule literals to their `const`-inferred
60
+ * shape, bypassing the homomorphic mapped-type linkage that surfaces
61
+ * source-declared JSDoc on hover (e.g. on `when.isClean:`). Factor
62
+ * rules out into `const myRule = { ... } as const satisfies Rule`
63
+ * bindings before passing them to `defineConfig` to keep the
64
+ * hover-rich shape; see the
65
+ * `examples/dynamic-reason-runtime-cwd/steering.ts` example. The
66
+ * `as const` modifier on the binding (and the `const R` modifier on
67
+ * the signature) preserves each rule's literal `name` so
68
+ * `disabledRules` typo detection fires — the alternatives `: Rule`
69
+ * and bare `satisfies Rule` restore hover but widen the inferred
70
+ * type and collapse typo detection (and `when.happened.event`
71
+ * narrowing across declared `writes`).
72
+ *
73
+ * @example
74
+ * export default defineConfig({
75
+ * plugins: [gitPlugin],
76
+ * observers: [descriptionReadObserver],
77
+ * rules: [
78
+ * { name: "must-read-docs", ..., observer: "description-read" },
79
+ * ],
80
+ * });
81
+ */
82
+ export function defineConfig(config) {
83
+ // Runtime work is minimal: copy the supplied config, widening the
84
+ // `readonly` tuple slots back to plain arrays for downstream
85
+ // consumers (loader, evaluator) that don't care about the tuple
86
+ // literal types. The generic machinery's job is done at the call
87
+ // site — once we return, we return plain SteeringConfig.
88
+ const out = {};
89
+ if (config.defaultNoOverride !== undefined) {
90
+ out.defaultNoOverride = config.defaultNoOverride;
91
+ }
92
+ if (config.disabledRules !== undefined) {
93
+ out.disabledRules = [...config.disabledRules];
94
+ }
95
+ if (config.disabledPlugins !== undefined) {
96
+ out.disabledPlugins = [...config.disabledPlugins];
97
+ }
98
+ if (config.disableDefaults !== undefined) {
99
+ out.disableDefaults = config.disableDefaults;
100
+ }
101
+ if (config.failOnWarnings !== undefined) {
102
+ out.failOnWarnings = config.failOnWarnings;
103
+ }
104
+ if (config.plugins !== undefined) {
105
+ // Cast: `readonly Plugin[]` → `Plugin[]` (shape is identical;
106
+ // the loader never mutates the array, but SteeringConfig
107
+ // doesn't require readonly).
108
+ out.plugins = [...config.plugins];
109
+ }
110
+ if (config.rules !== undefined) {
111
+ out.rules = [...config.rules];
112
+ }
113
+ if (config.observers !== undefined) {
114
+ out.observers = [...config.observers];
115
+ }
116
+ return out;
117
+ }
118
+ //# sourceMappingURL=define-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define-config.js","sourceRoot":"","sources":["../src/define-config.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,uBAAuB;AA4PvB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8EG;AACH,MAAM,UAAU,YAAY,CAO1B,MAAuC;IACvC,kEAAkE;IAClE,6DAA6D;IAC7D,gEAAgE;IAChE,iEAAiE;IACjE,yDAAyD;IACzD,MAAM,GAAG,GAAmB,EAAE,CAAC;IAC/B,IAAI,MAAM,CAAC,iBAAiB,KAAK,SAAS,EAAE,CAAC;QAC3C,GAAG,CAAC,iBAAiB,GAAG,MAAM,CAAC,iBAAiB,CAAC;IACnD,CAAC;IACD,IAAI,MAAM,CAAC,aAAa,KAAK,SAAS,EAAE,CAAC;QACvC,GAAG,CAAC,aAAa,GAAG,CAAC,GAAG,MAAM,CAAC,aAAa,CAAC,CAAC;IAChD,CAAC;IACD,IAAI,MAAM,CAAC,eAAe,KAAK,SAAS,EAAE,CAAC;QACzC,GAAG,CAAC,eAAe,GAAG,CAAC,GAAG,MAAM,CAAC,eAAe,CAAC,CAAC;IACpD,CAAC;IACD,IAAI,MAAM,CAAC,eAAe,KAAK,SAAS,EAAE,CAAC;QACzC,GAAG,CAAC,eAAe,GAAG,MAAM,CAAC,eAAe,CAAC;IAC/C,CAAC;IACD,IAAI,MAAM,CAAC,cAAc,KAAK,SAAS,EAAE,CAAC;QACxC,GAAG,CAAC,cAAc,GAAG,MAAM,CAAC,cAAc,CAAC;IAC7C,CAAC;IACD,IAAI,MAAM,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;QACjC,8DAA8D;QAC9D,yDAAyD;QACzD,6BAA6B;QAC7B,GAAG,CAAC,OAAO,GAAG,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IACpC,CAAC;IACD,IAAI,MAAM,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;QAC/B,GAAG,CAAC,KAAK,GAAG,CAAC,GAAG,MAAM,CAAC,KAAK,CAAW,CAAC;IAC1C,CAAC;IACD,IAAI,MAAM,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;QACnC,GAAG,CAAC,SAAS,GAAG,CAAC,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC;IACxC,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * `definePredicate<T>` — ~5-LOC helper for declaring typed plugin
3
+ * predicate handlers.
4
+ *
5
+ * Parallels {@link defineConfig} — pure pass-through at runtime, all
6
+ * the value is in the type signature. Narrows the handler's first
7
+ * argument to the supplied type parameter `T` so the body reads like
8
+ * a plain function on typed args, without the author needing to
9
+ * write the annotation twice (once on the declaration, once on the
10
+ * handler's `args` parameter). The `PredicateHandler<T>` return
11
+ * preserves that narrowing when the result is assigned to a local
12
+ * variable; the {@link Plugin.predicates} registry slot then accepts
13
+ * it cast-free via {@link AnyPredicateHandler} (= `PredicateHandler<any>`),
14
+ * which uses TS bivariance to admit typed handlers directly.
15
+ *
16
+ * See ADR §10 for the motivating usage.
17
+ */
18
+ import type { PredicateHandler } from "./schema.ts";
19
+ /**
20
+ * Sugar for declaring a typed {@link PredicateHandler}. The handler
21
+ * is returned unchanged at runtime; the generic parameter `T`
22
+ * narrows the handler's `args` parameter to the author's intended
23
+ * shape. Return type is `PredicateHandler<T>`, so authors threading
24
+ * the result through local variables keep the narrowed arg type.
25
+ * The registry slot at {@link Plugin.predicates} accepts the result
26
+ * cast-free via {@link AnyPredicateHandler}.
27
+ *
28
+ * @example
29
+ * interface CommitFormatArgs {
30
+ * pattern: RegExp;
31
+ * onUnknown?: "allow" | "block";
32
+ * }
33
+ *
34
+ * export const commitFormat = definePredicate<CommitFormatArgs>(
35
+ * (args, ctx) => {
36
+ * // `args` is narrowed to CommitFormatArgs here.
37
+ * const msg = extractCommitMessage(ctx.input.args ?? []);
38
+ * return args.pattern.test(msg);
39
+ * },
40
+ * );
41
+ *
42
+ * // Plug into a plugin:
43
+ * export const gitPlugin: Plugin = {
44
+ * name: "git",
45
+ * predicates: { commitFormat },
46
+ * };
47
+ */
48
+ export declare function definePredicate<T>(handler: PredicateHandler<T>): PredicateHandler<T>;
49
+ //# sourceMappingURL=define-predicate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define-predicate.d.ts","sourceRoot":"","sources":["../src/define-predicate.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAC/B,OAAO,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAC3B,gBAAgB,CAAC,CAAC,CAAC,CAErB"}
@@ -0,0 +1,35 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Part of pi-steering.
3
+ /**
4
+ * Sugar for declaring a typed {@link PredicateHandler}. The handler
5
+ * is returned unchanged at runtime; the generic parameter `T`
6
+ * narrows the handler's `args` parameter to the author's intended
7
+ * shape. Return type is `PredicateHandler<T>`, so authors threading
8
+ * the result through local variables keep the narrowed arg type.
9
+ * The registry slot at {@link Plugin.predicates} accepts the result
10
+ * cast-free via {@link AnyPredicateHandler}.
11
+ *
12
+ * @example
13
+ * interface CommitFormatArgs {
14
+ * pattern: RegExp;
15
+ * onUnknown?: "allow" | "block";
16
+ * }
17
+ *
18
+ * export const commitFormat = definePredicate<CommitFormatArgs>(
19
+ * (args, ctx) => {
20
+ * // `args` is narrowed to CommitFormatArgs here.
21
+ * const msg = extractCommitMessage(ctx.input.args ?? []);
22
+ * return args.pattern.test(msg);
23
+ * },
24
+ * );
25
+ *
26
+ * // Plug into a plugin:
27
+ * export const gitPlugin: Plugin = {
28
+ * name: "git",
29
+ * predicates: { commitFormat },
30
+ * };
31
+ */
32
+ export function definePredicate(handler) {
33
+ return handler;
34
+ }
35
+ //# sourceMappingURL=define-predicate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define-predicate.js","sourceRoot":"","sources":["../src/define-predicate.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,uBAAuB;AAsBvB;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,UAAU,eAAe,CAC7B,OAA4B;IAE5B,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -0,0 +1,186 @@
1
+ /**
2
+ * Predicate context construction for the v2 evaluator.
3
+ *
4
+ * Two concerns live here because they collaborate tightly:
5
+ *
6
+ * 1. `createExecCache` — memoizes `exec(cmd, args, opts)` by
7
+ * `(cmd, args, cwd)` so every rule evaluated for ONE tool_call
8
+ * sees the same result for the same query, without re-running the
9
+ * underlying child process. A fresh cache is created per
10
+ * tool_call; cross-call results are never shared.
11
+ * 2. `createFindEntries` — wraps pi's `sessionManager.getEntries()`
12
+ * into the {@link PredicateContext.findEntries} shape, filtering
13
+ * to `type: "custom"` entries by `customType` and flattening to
14
+ * `{ data, timestamp }` (timestamps normalized from ISO strings to
15
+ * epoch ms, matching what observers producing entries can rely on).
16
+ *
17
+ * The evaluator itself assembles the final {@link PredicateContext}
18
+ * from these closures plus per-candidate fields (cwd / tool / input /
19
+ * agentLoopIndex) as an object literal — no helper needed once the shape
20
+ * is shared across bash and write/edit code paths.
21
+ *
22
+ * Kept internal (under `evaluator-internals/`) so consumers can swap
23
+ * the evaluator without inheriting its helper surface. The only
24
+ * re-export is through `../evaluator.ts`.
25
+ */
26
+ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
27
+ import type { ExecOpts, PredicateContext } from "../schema.ts";
28
+ /**
29
+ * Narrow host surface the evaluator needs from the pi runtime. Lets
30
+ * tests pass a stub without building a full fake `ExtensionAPI`, and
31
+ * keeps the evaluator decoupled from the unrelated parts of pi's API
32
+ * (tool registration, slash commands, OAuth, …).
33
+ */
34
+ export interface EvaluatorHost {
35
+ /** See {@link ExtensionAPI.exec}. */
36
+ exec: ExtensionAPI["exec"];
37
+ /** See {@link ExtensionAPI.appendEntry}. */
38
+ appendEntry: ExtensionAPI["appendEntry"];
39
+ }
40
+ /**
41
+ * Create a tool_call-scoped exec function that memoizes by
42
+ * `(cmd, args, cwd)`. Caches only the PROMISE so concurrent
43
+ * predicate evaluations that hit the same key await the same
44
+ * in-flight child process — not N redundant ones.
45
+ *
46
+ * `opts.cwd` defaults to `sessionCwd` (mirroring how predicates see
47
+ * their "current cwd" via {@link PredicateContext.cwd}). `timeoutMs`
48
+ * is forwarded as `timeout`.
49
+ */
50
+ export declare function createExecCache(host: EvaluatorHost, sessionCwd: string): PredicateContext["exec"];
51
+ /**
52
+ * Key under which the engine auto-injects the current `agentLoopIndex`
53
+ * into every entry written via `PredicateContext.appendEntry` or
54
+ * `ObserverContext.appendEntry`. Rules using
55
+ * `when.happened: { in: "agent_loop" }` filter session entries by
56
+ * comparing this key against `ctx.agentLoopIndex`.
57
+ *
58
+ * Part of the on-disk session-JSONL format, exposed as a public
59
+ * module-level constant. Re-exported from the package root
60
+ * so plugin authors who manually inspect entries via
61
+ * `findEntries` can import the constant by name rather than
62
+ * hardcoding the string — a future rename would then break at
63
+ * import time instead of silently producing un-filtered entries.
64
+ */
65
+ export declare const AGENT_LOOP_INDEX_KEY: "_agentLoopIndex";
66
+ /**
67
+ * Narrow "is plain object" guard used to distinguish payloads that
68
+ * are safe to merge into (spread) from payloads that must be wrapped
69
+ * as `{ value, _agentLoopIndex }`.
70
+ *
71
+ * Anything that is NOT a plain object (arrays, Date, Map, Set, Error,
72
+ * class instances, functions, null, undefined, primitives) falls into
73
+ * the wrap branch. Direct `{...}` or `Object.create(null)` shapes fall
74
+ * into the merge branch.
75
+ *
76
+ * Two-stage detection:
77
+ * 1. `Object.prototype.toString.call(x)` returns `"[object Object]"`
78
+ * only for plain objects and for class instances of user-defined
79
+ * classes. It correctly excludes arrays, Date, Map, Set, Error,
80
+ * etc.
81
+ * 2. A prototype check then rejects user-class instances: plain
82
+ * objects have `Object.prototype` (or `null` for
83
+ * `Object.create(null)`) as their prototype; `new Box(...)` has
84
+ * `Box.prototype`, which is neither.
85
+ *
86
+ * The explicit `Array.isArray` check is belt-and-suspenders — some
87
+ * runtimes have historically misreported arrays via `toString`, and
88
+ * the array case is the one most likely to hit this wrapper (an
89
+ * observer appending a list of watched paths). Cheap to check twice.
90
+ */
91
+ export declare function isPlainObject(x: unknown): x is Record<string, unknown>;
92
+ /**
93
+ * Wrap a raw `host.appendEntry` so every write auto-injects the
94
+ * current `agentLoopIndex` into the payload. Plain-object payloads
95
+ * get the field merged in; everything else — primitives, arrays,
96
+ * `Date`, `Map`, `Set`, `Error`, class instances, functions, null,
97
+ * undefined — is wrapped as `{ value, _agentLoopIndex }` so
98
+ * downstream consumers always see a consistent object shape.
99
+ *
100
+ * The "everything else" branch exists because the naive spread
101
+ * (`{ ...data, ... }`) silently corrupts non-plain objects: arrays
102
+ * become pseudo-objects with string-indexed keys, Date / Map / Set /
103
+ * Error instances lose their internal state entirely, etc. Wrapping
104
+ * under `value` preserves the original reference unchanged.
105
+ *
106
+ * The returned closure matches both {@link PredicateContext.appendEntry}
107
+ * and {@link ObserverContext.appendEntry} so the evaluator and the
108
+ * observer dispatcher share one wrapper.
109
+ *
110
+ * `findEntriesCache` (optional) is a cache map shared with a sibling
111
+ * {@link createFindEntries} closure. When supplied, every `appendEntry`
112
+ * call invalidates the cache entry for the written `customType` so
113
+ * the next `findEntries(customType)` re-reads the session JSONL and
114
+ * sees the newly-written entry (S2/E1; see ../INVARIANTS.md for the
115
+ * S/E tag glossary). Omit the parameter to keep
116
+ * the pre-S2 behaviour (no invalidation) — handy for tests or callers
117
+ * that don't pair the two closures.
118
+ */
119
+ export declare function createAppendEntry(host: EvaluatorHost, agentLoopIndex: number, findEntriesCache?: Map<string, Array<{
120
+ data: unknown;
121
+ timestamp: number;
122
+ }>>): PredicateContext["appendEntry"];
123
+ /**
124
+ * Adapt pi's `sessionManager.getEntries()` into the typed-and-filtered
125
+ * view predicates (and observers) expect.
126
+ *
127
+ * Strategy:
128
+ * - pick only `type: "custom"` entries (the shape `pi.appendEntry`
129
+ * produces — see `CustomEntry` in pi's session-manager),
130
+ * - filter by `customType`,
131
+ * - project to `{ data, timestamp }` where `timestamp` is epoch-ms
132
+ * (parsed from the entry's ISO string). Epoch-ms is what turn-state
133
+ * checks want for chronological comparisons without having to
134
+ * re-parse.
135
+ *
136
+ * Results are memoized PER invocation of `createFindEntries` by
137
+ * customType. The evaluator rebuilds the closure on every tool_call;
138
+ * the observer dispatcher rebuilds on every tool_result. So each phase
139
+ * sees a consistent snapshot across reads.
140
+ *
141
+ * Cross-rule write visibility (S2/E1): when the same phase also uses
142
+ * a paired {@link createAppendEntry} with the SAME cache map, a write
143
+ * during rule A's `onFire` invalidates the cached read for that
144
+ * customType so rule B's `when.happened` predicate sees the fresh
145
+ * entry. Callers that want this consistency pass in a shared cache
146
+ * via the optional `cache` parameter; callers that omit it get the
147
+ * old per-closure snapshot behaviour (pre-S2), which is sound only
148
+ * when the closure never interleaves reads with writes.
149
+ *
150
+ * The `ctx` argument is the pi `ExtensionContext` — we re-read
151
+ * `getEntries()` only on a cache miss. Cache keys are per-closure (or
152
+ * per shared cache) so cross-tool_call or cross-tool_result reads
153
+ * always see the freshest state (a new closure = a new cache).
154
+ */
155
+ export declare function createFindEntries(ctx: ExtensionContext, cache?: Map<string, Array<{
156
+ data: unknown;
157
+ timestamp: number;
158
+ }>>): PredicateContext["findEntries"];
159
+ /**
160
+ * Allocate a fresh session-entry cache shared between a paired
161
+ * {@link createFindEntries} + {@link createAppendEntry} for the same
162
+ * tool_call (evaluator) or tool_result (observer dispatcher) phase.
163
+ *
164
+ * Using a shared cache gives two guarantees the evaluator + dispatcher
165
+ * rely on:
166
+ *
167
+ * 1. Consistent reads: N calls to `findEntries(type)` within one
168
+ * phase materialize the entry list ONCE per type.
169
+ * 2. Write-through-reads (S2/E1): a write via the paired
170
+ * `appendEntry` invalidates that type's cached list, so the next
171
+ * read re-scans the session JSONL and observes the write. Without
172
+ * this, a rule's `onFire` appending X followed by a later rule's
173
+ * `when.happened: { event: X }` would read a stale pre-write
174
+ * snapshot.
175
+ *
176
+ * Consumers who don't need write-through-reads (tests, one-shot
177
+ * `findEntries` calls) can pass a fresh cache or omit the parameter
178
+ * on both constructors — the closures then each get their own cache
179
+ * map and behave like the pre-S2 implementation.
180
+ */
181
+ export declare function createSessionEntryCache(): Map<string, Array<{
182
+ data: unknown;
183
+ timestamp: number;
184
+ }>>;
185
+ export type { ExecOpts };
186
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/evaluator-internals/context.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,EACV,YAAY,EACZ,gBAAgB,EAGjB,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EAAE,QAAQ,EAAc,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAE3E;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,qCAAqC;IACrC,IAAI,EAAE,YAAY,CAAC,MAAM,CAAC,CAAC;IAC3B,4CAA4C;IAC5C,WAAW,EAAE,YAAY,CAAC,aAAa,CAAC,CAAC;CAC1C;AAyBD;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,aAAa,EACnB,UAAU,EAAE,MAAM,GACjB,gBAAgB,CAAC,MAAM,CAAC,CAa1B;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,oBAAoB,EAAG,iBAA0B,CAAC;AAE/D;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,OAAO,GAAG,CAAC,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAOtE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,aAAa,EACnB,cAAc,EAAE,MAAM,EACtB,gBAAgB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,GAC1E,gBAAgB,CAAC,aAAa,CAAC,CAajC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,gBAAgB,EACrB,KAAK,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,GAC/D,gBAAgB,CAAC,aAAa,CAAC,CAwBjC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,uBAAuB,IAAI,GAAG,CAC5C,MAAM,EACN,KAAK,CAAC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,CAC5C,CAEA;AAID,YAAY,EAAE,QAAQ,EAAE,CAAC"}
@@ -0,0 +1,219 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Part of pi-steering.
3
+ /**
4
+ * Key used by the per-tool-call exec cache. Null-byte separator is safe
5
+ * because neither a command path nor POSIX argv can legitimately contain
6
+ * a NUL byte; collisions are impossible in practice.
7
+ */
8
+ function execCacheKey(cmd, args, cwd) {
9
+ return `${cmd}\x00${args.join("\x00")}\x00${cwd}`;
10
+ }
11
+ /**
12
+ * Bridge pi's `ExecResult` (uses `code`) to the schema's `ExecResult`
13
+ * (uses `exitCode`). Dropping `killed` is intentional — predicate
14
+ * authors don't need to distinguish "timed out" from "exited
15
+ * non-zero"; both surface as a non-zero exit for guardrail purposes.
16
+ */
17
+ function toSchemaExecResult(r) {
18
+ return { stdout: r.stdout, stderr: r.stderr, exitCode: r.code };
19
+ }
20
+ /**
21
+ * Create a tool_call-scoped exec function that memoizes by
22
+ * `(cmd, args, cwd)`. Caches only the PROMISE so concurrent
23
+ * predicate evaluations that hit the same key await the same
24
+ * in-flight child process — not N redundant ones.
25
+ *
26
+ * `opts.cwd` defaults to `sessionCwd` (mirroring how predicates see
27
+ * their "current cwd" via {@link PredicateContext.cwd}). `timeoutMs`
28
+ * is forwarded as `timeout`.
29
+ */
30
+ export function createExecCache(host, sessionCwd) {
31
+ const cache = new Map();
32
+ return (cmd, args, opts) => {
33
+ const cwd = opts?.cwd ?? sessionCwd;
34
+ const key = execCacheKey(cmd, args, cwd);
35
+ const hit = cache.get(key);
36
+ if (hit !== undefined)
37
+ return hit;
38
+ const piOpts = { cwd };
39
+ if (opts?.timeoutMs !== undefined)
40
+ piOpts.timeout = opts.timeoutMs;
41
+ const p = host.exec(cmd, args, piOpts).then(toSchemaExecResult);
42
+ cache.set(key, p);
43
+ return p;
44
+ };
45
+ }
46
+ /**
47
+ * Key under which the engine auto-injects the current `agentLoopIndex`
48
+ * into every entry written via `PredicateContext.appendEntry` or
49
+ * `ObserverContext.appendEntry`. Rules using
50
+ * `when.happened: { in: "agent_loop" }` filter session entries by
51
+ * comparing this key against `ctx.agentLoopIndex`.
52
+ *
53
+ * Part of the on-disk session-JSONL format, exposed as a public
54
+ * module-level constant. Re-exported from the package root
55
+ * so plugin authors who manually inspect entries via
56
+ * `findEntries` can import the constant by name rather than
57
+ * hardcoding the string — a future rename would then break at
58
+ * import time instead of silently producing un-filtered entries.
59
+ */
60
+ export const AGENT_LOOP_INDEX_KEY = "_agentLoopIndex";
61
+ /**
62
+ * Narrow "is plain object" guard used to distinguish payloads that
63
+ * are safe to merge into (spread) from payloads that must be wrapped
64
+ * as `{ value, _agentLoopIndex }`.
65
+ *
66
+ * Anything that is NOT a plain object (arrays, Date, Map, Set, Error,
67
+ * class instances, functions, null, undefined, primitives) falls into
68
+ * the wrap branch. Direct `{...}` or `Object.create(null)` shapes fall
69
+ * into the merge branch.
70
+ *
71
+ * Two-stage detection:
72
+ * 1. `Object.prototype.toString.call(x)` returns `"[object Object]"`
73
+ * only for plain objects and for class instances of user-defined
74
+ * classes. It correctly excludes arrays, Date, Map, Set, Error,
75
+ * etc.
76
+ * 2. A prototype check then rejects user-class instances: plain
77
+ * objects have `Object.prototype` (or `null` for
78
+ * `Object.create(null)`) as their prototype; `new Box(...)` has
79
+ * `Box.prototype`, which is neither.
80
+ *
81
+ * The explicit `Array.isArray` check is belt-and-suspenders — some
82
+ * runtimes have historically misreported arrays via `toString`, and
83
+ * the array case is the one most likely to hit this wrapper (an
84
+ * observer appending a list of watched paths). Cheap to check twice.
85
+ */
86
+ export function isPlainObject(x) {
87
+ if (x === null || typeof x !== "object")
88
+ return false;
89
+ if (Array.isArray(x))
90
+ return false;
91
+ if (Object.prototype.toString.call(x) !== "[object Object]")
92
+ return false;
93
+ const proto = Object.getPrototypeOf(x);
94
+ if (proto !== null && proto !== Object.prototype)
95
+ return false;
96
+ return true;
97
+ }
98
+ /**
99
+ * Wrap a raw `host.appendEntry` so every write auto-injects the
100
+ * current `agentLoopIndex` into the payload. Plain-object payloads
101
+ * get the field merged in; everything else — primitives, arrays,
102
+ * `Date`, `Map`, `Set`, `Error`, class instances, functions, null,
103
+ * undefined — is wrapped as `{ value, _agentLoopIndex }` so
104
+ * downstream consumers always see a consistent object shape.
105
+ *
106
+ * The "everything else" branch exists because the naive spread
107
+ * (`{ ...data, ... }`) silently corrupts non-plain objects: arrays
108
+ * become pseudo-objects with string-indexed keys, Date / Map / Set /
109
+ * Error instances lose their internal state entirely, etc. Wrapping
110
+ * under `value` preserves the original reference unchanged.
111
+ *
112
+ * The returned closure matches both {@link PredicateContext.appendEntry}
113
+ * and {@link ObserverContext.appendEntry} so the evaluator and the
114
+ * observer dispatcher share one wrapper.
115
+ *
116
+ * `findEntriesCache` (optional) is a cache map shared with a sibling
117
+ * {@link createFindEntries} closure. When supplied, every `appendEntry`
118
+ * call invalidates the cache entry for the written `customType` so
119
+ * the next `findEntries(customType)` re-reads the session JSONL and
120
+ * sees the newly-written entry (S2/E1; see ../INVARIANTS.md for the
121
+ * S/E tag glossary). Omit the parameter to keep
122
+ * the pre-S2 behaviour (no invalidation) — handy for tests or callers
123
+ * that don't pair the two closures.
124
+ */
125
+ export function createAppendEntry(host, agentLoopIndex, findEntriesCache) {
126
+ return (customType, data) => {
127
+ const tagged = isPlainObject(data)
128
+ ? { ...data, [AGENT_LOOP_INDEX_KEY]: agentLoopIndex }
129
+ : { value: data, [AGENT_LOOP_INDEX_KEY]: agentLoopIndex };
130
+ host.appendEntry(customType, tagged);
131
+ // S2/E1: drop the cached read for this customType so a later
132
+ // `findEntries(customType)` call from the same phase re-materializes
133
+ // the list and sees the write we just made. Without this, a rule's
134
+ // `onFire` that writes + a later rule's `when.happened` that reads
135
+ // see inconsistent snapshots within one tool_call.
136
+ findEntriesCache?.delete(customType);
137
+ };
138
+ }
139
+ /**
140
+ * Adapt pi's `sessionManager.getEntries()` into the typed-and-filtered
141
+ * view predicates (and observers) expect.
142
+ *
143
+ * Strategy:
144
+ * - pick only `type: "custom"` entries (the shape `pi.appendEntry`
145
+ * produces — see `CustomEntry` in pi's session-manager),
146
+ * - filter by `customType`,
147
+ * - project to `{ data, timestamp }` where `timestamp` is epoch-ms
148
+ * (parsed from the entry's ISO string). Epoch-ms is what turn-state
149
+ * checks want for chronological comparisons without having to
150
+ * re-parse.
151
+ *
152
+ * Results are memoized PER invocation of `createFindEntries` by
153
+ * customType. The evaluator rebuilds the closure on every tool_call;
154
+ * the observer dispatcher rebuilds on every tool_result. So each phase
155
+ * sees a consistent snapshot across reads.
156
+ *
157
+ * Cross-rule write visibility (S2/E1): when the same phase also uses
158
+ * a paired {@link createAppendEntry} with the SAME cache map, a write
159
+ * during rule A's `onFire` invalidates the cached read for that
160
+ * customType so rule B's `when.happened` predicate sees the fresh
161
+ * entry. Callers that want this consistency pass in a shared cache
162
+ * via the optional `cache` parameter; callers that omit it get the
163
+ * old per-closure snapshot behaviour (pre-S2), which is sound only
164
+ * when the closure never interleaves reads with writes.
165
+ *
166
+ * The `ctx` argument is the pi `ExtensionContext` — we re-read
167
+ * `getEntries()` only on a cache miss. Cache keys are per-closure (or
168
+ * per shared cache) so cross-tool_call or cross-tool_result reads
169
+ * always see the freshest state (a new closure = a new cache).
170
+ */
171
+ export function createFindEntries(ctx, cache) {
172
+ const entryCache = cache ?? new Map();
173
+ return (customType) => {
174
+ const hit = entryCache.get(customType);
175
+ if (hit !== undefined) {
176
+ return hit;
177
+ }
178
+ const out = [];
179
+ for (const entry of ctx.sessionManager.getEntries()) {
180
+ if (entry.type !== "custom")
181
+ continue;
182
+ if (entry.customType !== customType)
183
+ continue;
184
+ const ts = Date.parse(entry.timestamp);
185
+ out.push({
186
+ data: entry.data,
187
+ timestamp: Number.isNaN(ts) ? 0 : ts,
188
+ });
189
+ }
190
+ entryCache.set(customType, out);
191
+ return out;
192
+ };
193
+ }
194
+ /**
195
+ * Allocate a fresh session-entry cache shared between a paired
196
+ * {@link createFindEntries} + {@link createAppendEntry} for the same
197
+ * tool_call (evaluator) or tool_result (observer dispatcher) phase.
198
+ *
199
+ * Using a shared cache gives two guarantees the evaluator + dispatcher
200
+ * rely on:
201
+ *
202
+ * 1. Consistent reads: N calls to `findEntries(type)` within one
203
+ * phase materialize the entry list ONCE per type.
204
+ * 2. Write-through-reads (S2/E1): a write via the paired
205
+ * `appendEntry` invalidates that type's cached list, so the next
206
+ * read re-scans the session JSONL and observes the write. Without
207
+ * this, a rule's `onFire` appending X followed by a later rule's
208
+ * `when.happened: { event: X }` would read a stale pre-write
209
+ * snapshot.
210
+ *
211
+ * Consumers who don't need write-through-reads (tests, one-shot
212
+ * `findEntries` calls) can pass a fresh cache or omit the parameter
213
+ * on both constructors — the closures then each get their own cache
214
+ * map and behave like the pre-S2 implementation.
215
+ */
216
+ export function createSessionEntryCache() {
217
+ return new Map();
218
+ }
219
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/evaluator-internals/context.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Extract an inline override-comment's reason that targets a specific
3
+ * rule name. Returns the trimmed reason, or null when no override
4
+ * addressed to `ruleName` is present (empty reasons are treated as
5
+ * "no override" — users must supply an explicit justification).
6
+ *
7
+ * Behaviour parity with v1 `extractOverride`:
8
+ *
9
+ * - case-sensitive rule-name match,
10
+ * - supports stacked overrides on one line (e.g.
11
+ * `cmd # steering-override: a — r1 # steering-override: b — r2`),
12
+ * - first-match-wins across the scanned comments,
13
+ * - all leader styles from v1 accepted.
14
+ *
15
+ * The v1 implementation is the authoritative reference for edge cases
16
+ * — see `evaluator.ts` in this package's v1 tree.
17
+ */
18
+ export declare function extractOverride(text: string, ruleName: string): string | null;
19
+ //# sourceMappingURL=override.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"override.d.ts","sourceRoot":"","sources":["../../src/evaluator-internals/override.ts"],"names":[],"mappings":"AAoCA;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA6B7E"}