@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,15 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Part of pi-steering.
3
+ /**
4
+ * Canonical stringification of a bash {@link CommandRef} for observer
5
+ * watch matching. Every call site must use this one implementation to
6
+ * avoid drift across observer-dispatch + speculative-entry synthesis.
7
+ *
8
+ * @internal — not part of the public pi-steering surface.
9
+ */
10
+ import { getBasename, getCommandArgs } from "@cad0p/unbash-walker";
11
+ /** Render a ref as `"{basename} {args joined by space}"`, trimmed. */
12
+ export function refToText(ref) {
13
+ return `${getBasename(ref)} ${getCommandArgs(ref).join(" ")}`.trim();
14
+ }
15
+ //# sourceMappingURL=ref-text.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ref-text.js","sourceRoot":"","sources":["../../src/internal/ref-text.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,uBAAuB;AAEvB;;;;;;GAMG;AAEH,OAAO,EAAmB,WAAW,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEpF,sEAAsE;AACtE,MAAM,UAAU,SAAS,CAAC,GAAe;IACvC,OAAO,GAAG,WAAW,CAAC,GAAG,CAAC,IAAI,cAAc,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC;AACvE,CAAC"}
@@ -0,0 +1,44 @@
1
+ import { type EvaluatorHost, type EvaluatorRuntime } from "../evaluator.ts";
2
+ import { type ObserverDispatcher } from "../observer-dispatcher.ts";
3
+ import { type ResolvedPluginState } from "../plugin-merger.ts";
4
+ import type { SteeringConfig, SteeringDiagnostic } from "../schema.ts";
5
+ /**
6
+ * Run `buildConfig` then `resolvePlugins` over the raw layer list,
7
+ * short-circuiting before `resolvePlugins` if any merge-side
8
+ * diagnostic is error-class. Avoids double-emitting
9
+ * `tracker-name-collision` (O2 in INVARIANTS.md).
10
+ * `validateUserConfigNames` runs unconditionally so user-config name
11
+ * issues surface alongside merge errors.
12
+ */
13
+ export declare function runMergerPipeline(layers: readonly SteeringConfig[], defaults: SteeringConfig | undefined, builtinTrackers: readonly string[]): {
14
+ merged: SteeringConfig;
15
+ resolved: ResolvedPluginState | null;
16
+ diagnostics: SteeringDiagnostic[];
17
+ };
18
+ /**
19
+ * Render a diagnostics array into a single multi-line message
20
+ * suitable for use as a thrown Error's `message`. See {@link
21
+ * SteeringDiagnostic} render-format matrix for the canonical shape;
22
+ * the `formatAggregatedDiagnostics: rule-based spec` describe block in
23
+ * `internal/session-runtime.test.ts` pins the rules.
24
+ */
25
+ export declare function formatAggregatedDiagnostics(diagnostics: readonly SteeringDiagnostic[]): string;
26
+ /**
27
+ * Single-line render of one diagnostic; see {@link SteeringDiagnostic}
28
+ * render-format matrix for the canonical contract.
29
+ */
30
+ export declare function formatSingleLineDiagnostic(d: SteeringDiagnostic): string;
31
+ /**
32
+ * Build the per-session evaluator + observer dispatcher from the walk-
33
+ * up config rooted at `cwd`. Honors `disableDefaults` via inner-wins
34
+ * peek before injecting `DEFAULT_*`. Throws on any error-class
35
+ * diagnostic and on warning-class diagnostics when
36
+ * `failOnWarnings !== false`; otherwise emits surviving warnings via
37
+ * `console.warn`. See {@link runMergerPipeline} for the merge contract
38
+ * and `finalizePluginState` for observer-drop.
39
+ */
40
+ export declare function buildSessionRuntime(cwd: string, host: EvaluatorHost): Promise<{
41
+ evaluator: EvaluatorRuntime;
42
+ dispatcher: ObserverDispatcher;
43
+ }>;
44
+ //# sourceMappingURL=session-runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-runtime.d.ts","sourceRoot":"","sources":["../../src/internal/session-runtime.ts"],"names":[],"mappings":"AA+BA,OAAO,EAGL,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACtB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAEL,KAAK,kBAAkB,EACxB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,KAAK,mBAAmB,EAGzB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAGvE;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,SAAS,cAAc,EAAE,EACjC,QAAQ,EAAE,cAAc,GAAG,SAAS,EACpC,eAAe,EAAE,SAAS,MAAM,EAAE,GACjC;IACD,MAAM,EAAE,cAAc,CAAC;IACvB,QAAQ,EAAE,mBAAmB,GAAG,IAAI,CAAC;IACrC,WAAW,EAAE,kBAAkB,EAAE,CAAC;CACnC,CA2BA;AAED;;;;;;GAMG;AACH,wBAAgB,2BAA2B,CACzC,WAAW,EAAE,SAAS,kBAAkB,EAAE,GACzC,MAAM,CAWR;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,CAAC,EAAE,kBAAkB,GAAG,MAAM,CAGxE;AAED;;;;;;;;GAQG;AACH,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,aAAa,GAClB,OAAO,CAAC;IACT,SAAS,EAAE,gBAAgB,CAAC;IAC5B,UAAU,EAAE,kBAAkB,CAAC;CAChC,CAAC,CA2DD"}
@@ -0,0 +1,143 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Part of pi-steering.
3
+ /**
4
+ * Internal module — not part of the package's public API.
5
+ *
6
+ * This module holds the wiring that the bridge factory in `index.ts`
7
+ * uses to spin up an evaluator + observer dispatcher from a walk-up
8
+ * steering config. It is intentionally NOT re-exported from
9
+ * `index.ts` or any other public entry point; consumers building
10
+ * their own extensions should go through `loadHarness` (subpath
11
+ * `@cad0p/pi-steering/testing`) or call `buildEvaluator` /
12
+ * `buildObserverDispatcher` directly.
13
+ *
14
+ * The runtime owns the strict-mode contract: diagnostics produced
15
+ * by the loader (per-layer import failures, dual-form coexistence,
16
+ * stray files, cross-layer + within-layer collisions) and by the
17
+ * plugin merger (predicate / observer / rule / extension-orphan /
18
+ * reserved-name / invalid-name diagnostics) are aggregated here.
19
+ * Any error-class diagnostic always escalates to a thrown error;
20
+ * warning-class diagnostics escalate when `failOnWarnings !== false`
21
+ * on the merged config (default: true). Otherwise warnings are
22
+ * emitted to `console.warn` for legacy fail-soft semantics.
23
+ *
24
+ * The bridge calls `buildSessionRuntime` once at extension factory
25
+ * time. A thrown factory propagates through pi's extension loader
26
+ * into pi's `[Extension issues]` diagnostic block (which survives
27
+ * `/reload`); the bridge does not catch.
28
+ */
29
+ import { DEFAULT_PLUGINS, DEFAULT_RULES } from "../defaults.js";
30
+ import { buildEvaluator, EVALUATOR_BUILTIN_TRACKERS, } from "../evaluator.js";
31
+ import { buildConfig, loadConfigs, mergeBool } from "../loader.js";
32
+ import { buildObserverDispatcher, } from "../observer-dispatcher.js";
33
+ import { resolvePlugins, validateUserConfigNames, } from "../plugin-merger.js";
34
+ import { finalizePluginState } from "./finalize-plugin-state.js";
35
+ /**
36
+ * Run `buildConfig` then `resolvePlugins` over the raw layer list,
37
+ * short-circuiting before `resolvePlugins` if any merge-side
38
+ * diagnostic is error-class. Avoids double-emitting
39
+ * `tracker-name-collision` (O2 in INVARIANTS.md).
40
+ * `validateUserConfigNames` runs unconditionally so user-config name
41
+ * issues surface alongside merge errors.
42
+ */
43
+ export function runMergerPipeline(layers, defaults, builtinTrackers) {
44
+ const { config: merged, diagnostics: mergeDiagnostics } = buildConfig(layers, defaults);
45
+ const userConfigNameDiagnostics = validateUserConfigNames(layers);
46
+ if (mergeDiagnostics.some((d) => d.type === "error")) {
47
+ return {
48
+ merged,
49
+ resolved: null,
50
+ diagnostics: [...mergeDiagnostics, ...userConfigNameDiagnostics],
51
+ };
52
+ }
53
+ const resolved = resolvePlugins(merged.plugins ?? [], merged, builtinTrackers);
54
+ return {
55
+ merged,
56
+ resolved,
57
+ diagnostics: [
58
+ ...mergeDiagnostics,
59
+ ...userConfigNameDiagnostics,
60
+ ...resolved.diagnostics,
61
+ ],
62
+ };
63
+ }
64
+ /**
65
+ * Render a diagnostics array into a single multi-line message
66
+ * suitable for use as a thrown Error's `message`. See {@link
67
+ * SteeringDiagnostic} render-format matrix for the canonical shape;
68
+ * the `formatAggregatedDiagnostics: rule-based spec` describe block in
69
+ * `internal/session-runtime.test.ts` pins the rules.
70
+ */
71
+ export function formatAggregatedDiagnostics(diagnostics) {
72
+ const errors = diagnostics.filter((d) => d.type === "error");
73
+ const warnings = diagnostics.filter((d) => d.type === "warning");
74
+ const ordered = [...errors, ...warnings];
75
+ const count = ordered.length;
76
+ const noun = count === 1 ? "issue" : "issues";
77
+ const lines = ordered.map((d) => {
78
+ const pathPrefix = d.path !== undefined ? `${d.path}: ` : "";
79
+ return ` - [${d.type}] ${pathPrefix}${d.message}`;
80
+ });
81
+ return `${count} config ${noun}:\n${lines.join("\n")}`;
82
+ }
83
+ /**
84
+ * Single-line render of one diagnostic; see {@link SteeringDiagnostic}
85
+ * render-format matrix for the canonical contract.
86
+ */
87
+ export function formatSingleLineDiagnostic(d) {
88
+ const pathPrefix = d.path !== undefined ? `${d.path}: ` : "";
89
+ return `[pi-steering] [${d.type}] ${pathPrefix}${d.message}`;
90
+ }
91
+ /**
92
+ * Build the per-session evaluator + observer dispatcher from the walk-
93
+ * up config rooted at `cwd`. Honors `disableDefaults` via inner-wins
94
+ * peek before injecting `DEFAULT_*`. Throws on any error-class
95
+ * diagnostic and on warning-class diagnostics when
96
+ * `failOnWarnings !== false`; otherwise emits surviving warnings via
97
+ * `console.warn`. See {@link runMergerPipeline} for the merge contract
98
+ * and `finalizePluginState` for observer-drop.
99
+ */
100
+ export async function buildSessionRuntime(cwd, host) {
101
+ const aggregated = [];
102
+ const { layers: rawLayers, diagnostics: loaderDiagnostics } = await loadConfigs(cwd);
103
+ aggregated.push(...loaderDiagnostics);
104
+ const disableDefaults = mergeBool(rawLayers, "disableDefaults") === true;
105
+ const defaults = disableDefaults
106
+ ? undefined
107
+ : { rules: DEFAULT_RULES, plugins: DEFAULT_PLUGINS };
108
+ const { merged, resolved, diagnostics: mergeAndResolveDiagnostics, } = runMergerPipeline(rawLayers, defaults, EVALUATOR_BUILTIN_TRACKERS);
109
+ aggregated.push(...mergeAndResolveDiagnostics);
110
+ const failOnWarnings = merged.failOnWarnings;
111
+ const treatWarningsAsErrors = failOnWarnings !== false;
112
+ const hasError = aggregated.some((d) => d.type === "error");
113
+ const hasWarning = aggregated.some((d) => d.type === "warning");
114
+ if (hasError || (treatWarningsAsErrors && hasWarning)) {
115
+ throw new Error(formatAggregatedDiagnostics(aggregated));
116
+ }
117
+ if (hasWarning) {
118
+ for (const d of aggregated) {
119
+ console.warn(formatSingleLineDiagnostic(d));
120
+ }
121
+ }
122
+ if (resolved === null) {
123
+ throw new Error("internal: resolved null without error diagnostic");
124
+ }
125
+ const disabled = new Set(merged.disabledRules ?? []);
126
+ const filteredConfig = { ...merged };
127
+ if (merged.rules !== undefined) {
128
+ const kept = merged.rules.filter((r) => !disabled.has(r.name));
129
+ if (kept.length > 0)
130
+ filteredConfig.rules = kept;
131
+ else
132
+ delete filteredConfig.rules;
133
+ }
134
+ const { pluginKept, userKept } = finalizePluginState(filteredConfig.rules ?? [], resolved.rules, filteredConfig.observers ?? [], resolved.observers);
135
+ const filteredResolved = {
136
+ ...resolved,
137
+ observers: [...pluginKept],
138
+ };
139
+ const evaluator = buildEvaluator(filteredConfig, filteredResolved, host);
140
+ const dispatcher = buildObserverDispatcher(filteredResolved, userKept, host);
141
+ return { evaluator, dispatcher };
142
+ }
143
+ //# sourceMappingURL=session-runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-runtime.js","sourceRoot":"","sources":["../../src/internal/session-runtime.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ import type { ObserverWatch, ToolResultEvent } from "../schema.ts";
2
+ /**
3
+ * True if the observer's `watch` filter accepts this event. No watch
4
+ * → matches everything. Semantics per ADR "Observer schema":
5
+ *
6
+ * - `toolName` — exact match against `event.toolName`.
7
+ * - `inputMatches` — every declared key's Pattern must match against
8
+ * `event.input[key]` if that key exists AND the value is a string.
9
+ * Keys absent from the event's input (or non-string values) make
10
+ * the whole filter fail — documented fail-closed choice: subset
11
+ * checks don't silently pass when the expected field isn't present.
12
+ * - `exitCode` — `"success"` → 0, `"failure"` → non-zero,
13
+ * `"any"`/omitted → pass, numeric → exact match. `exitCode` is
14
+ * sourced from the event's `exitCode` field (bash only via pi's
15
+ * `details.exitCode` after projection to the schema shape); other
16
+ * tool results leave it `undefined` and satisfy everything except
17
+ * a numeric `exitCode:` (treated as "no match" — bash-specific
18
+ * filter).
19
+ *
20
+ * Wrapper-aware command matching (ADR §12): when `inputMatches.command`
21
+ * is set AND the event is a bash event, the pattern matches if EITHER
22
+ * the raw outer `event.input.command` OR any extracted command ref
23
+ * text matches. So `sh -c 'brazil ws sync'` with pattern
24
+ * `/^brazil\s+ws\s+sync$/` fires the observer — the outer raw command
25
+ * starts with `sh`, but the walker-extracted ref `brazil ws sync` does
26
+ * hit the anchored pattern.
27
+ *
28
+ * Performance: when multiple observers share the same event (the
29
+ * production dispatch path), pass a memoizing `refTextsProvider` to
30
+ * parse the bash command once across observers. Standalone callers
31
+ * (e.g. `testObserver` evaluating one observer in isolation, or the
32
+ * evaluator's speculative-allow synthesizing one event per prior
33
+ * `&&` ref) can omit it — the default provider parses on demand.
34
+ */
35
+ export declare function matchesWatch(watch: ObserverWatch | undefined, event: ToolResultEvent, refTextsProvider?: () => readonly string[] | null): boolean;
36
+ /**
37
+ * Extract per-ref flattened text (basename + args joined with spaces)
38
+ * from a bash tool_result's outer command, mirroring the evaluator's
39
+ * `prepareBashState` text projection so observer watch patterns match
40
+ * the same strings rule patterns see for the same command.
41
+ *
42
+ * Returns `null` when the event isn't a bash tool_result, the raw
43
+ * command is missing/non-string, or the walker throws while parsing
44
+ * (hard-to-parse command — fall back to raw-only matching without
45
+ * blowing up dispatch). Unlike the evaluator we don't walk trackers:
46
+ * observers don't receive `walkerState`, so the parse+extract+expand
47
+ * stages suffice.
48
+ *
49
+ * Exported so the production dispatcher can memoize the parse across
50
+ * observers on the same event (see `dispatchEventInner`'s
51
+ * `getRefTexts` cache). Chain-aware speculative-allow already
52
+ * synthesizes one event per prior ref and doesn't need memoization —
53
+ * it calls {@link matchesWatch} without a provider.
54
+ */
55
+ export declare function extractRefTextsForBash(event: ToolResultEvent): readonly string[] | null;
56
+ //# sourceMappingURL=watch-matcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watch-matcher.d.ts","sourceRoot":"","sources":["../../src/internal/watch-matcher.ts"],"names":[],"mappings":"AAwCA,OAAO,KAAK,EAAE,aAAa,EAAW,eAAe,EAAE,MAAM,cAAc,CAAC;AAG5E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,aAAa,GAAG,SAAS,EAChC,KAAK,EAAE,eAAe,EACtB,gBAAgB,CAAC,EAAE,MAAM,SAAS,MAAM,EAAE,GAAG,IAAI,GAChD,OAAO,CA4BT;AAqCD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,eAAe,GACrB,SAAS,MAAM,EAAE,GAAG,IAAI,CAmB1B"}
@@ -0,0 +1,179 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Part of pi-steering.
3
+ /**
4
+ * Shared observer-watch filter contract. Single source of truth for
5
+ * "does this observer's `watch` accept this tool_result event?"
6
+ *
7
+ * Used by BOTH:
8
+ *
9
+ * - the observer-dispatcher (production fire path — decides which
10
+ * observers see a concrete tool_result).
11
+ * - the evaluator's `when.happened` tool_call-scope speculative-
12
+ * allow (synthesizes a minimal successful bash event representing
13
+ * "this prior `&&` ref is about to run and succeed", then asks
14
+ * the same question).
15
+ *
16
+ * Co-locating the contract here retires a structural fragility PR #4
17
+ * reviewers caught three times: the speculative-allow path used to
18
+ * hand-roll a SUBSET of the watch filter (command-pattern only, then
19
+ * patched to also check toolName + exitCode). Each new `watch` field
20
+ * the dispatcher grew would create a fresh drift opportunity.
21
+ *
22
+ * Keeping both callers on this one function guarantees the two paths
23
+ * agree by construction. If the speculative-allow path wants to
24
+ * impose a STRICTER gate on top (e.g. "observer must declare
25
+ * `inputMatches.command`" — an authoring requirement to keep
26
+ * speculative-allow safe), it layers that gate before delegating to
27
+ * {@link matchesWatch} rather than re-implementing the filter body.
28
+ *
29
+ * Merge-observers helper lives in {@link ./merge-observers.ts} for the
30
+ * same single-source-of-truth reason. This file is its sibling on the
31
+ * watch-filter axis.
32
+ */
33
+ import { expandWrapperCommands, extractAllCommandsFromAST, parse as parseBash, } from "@cad0p/unbash-walker";
34
+ import { matchesPattern } from "../evaluator-internals/predicates.js";
35
+ import { refToText } from "./ref-text.js";
36
+ /**
37
+ * True if the observer's `watch` filter accepts this event. No watch
38
+ * → matches everything. Semantics per ADR "Observer schema":
39
+ *
40
+ * - `toolName` — exact match against `event.toolName`.
41
+ * - `inputMatches` — every declared key's Pattern must match against
42
+ * `event.input[key]` if that key exists AND the value is a string.
43
+ * Keys absent from the event's input (or non-string values) make
44
+ * the whole filter fail — documented fail-closed choice: subset
45
+ * checks don't silently pass when the expected field isn't present.
46
+ * - `exitCode` — `"success"` → 0, `"failure"` → non-zero,
47
+ * `"any"`/omitted → pass, numeric → exact match. `exitCode` is
48
+ * sourced from the event's `exitCode` field (bash only via pi's
49
+ * `details.exitCode` after projection to the schema shape); other
50
+ * tool results leave it `undefined` and satisfy everything except
51
+ * a numeric `exitCode:` (treated as "no match" — bash-specific
52
+ * filter).
53
+ *
54
+ * Wrapper-aware command matching (ADR §12): when `inputMatches.command`
55
+ * is set AND the event is a bash event, the pattern matches if EITHER
56
+ * the raw outer `event.input.command` OR any extracted command ref
57
+ * text matches. So `sh -c 'brazil ws sync'` with pattern
58
+ * `/^brazil\s+ws\s+sync$/` fires the observer — the outer raw command
59
+ * starts with `sh`, but the walker-extracted ref `brazil ws sync` does
60
+ * hit the anchored pattern.
61
+ *
62
+ * Performance: when multiple observers share the same event (the
63
+ * production dispatch path), pass a memoizing `refTextsProvider` to
64
+ * parse the bash command once across observers. Standalone callers
65
+ * (e.g. `testObserver` evaluating one observer in isolation, or the
66
+ * evaluator's speculative-allow synthesizing one event per prior
67
+ * `&&` ref) can omit it — the default provider parses on demand.
68
+ */
69
+ export function matchesWatch(watch, event, refTextsProvider) {
70
+ if (!watch)
71
+ return true;
72
+ if (watch.toolName !== undefined && watch.toolName !== event.toolName) {
73
+ return false;
74
+ }
75
+ if (watch.inputMatches) {
76
+ const rawInput = event.input;
77
+ const input = typeof rawInput === "object" && rawInput !== null
78
+ ? rawInput
79
+ : {};
80
+ const getRefTexts = refTextsProvider ?? (() => extractRefTextsForBash(event));
81
+ for (const [key, pat] of Object.entries(watch.inputMatches)) {
82
+ const value = input[key];
83
+ if (typeof value !== "string")
84
+ return false;
85
+ if (!matchesInputField(key, pat, value, event, getRefTexts)) {
86
+ return false;
87
+ }
88
+ }
89
+ }
90
+ if (watch.exitCode !== undefined && watch.exitCode !== "any") {
91
+ if (!matchesExitCode(event.exitCode, watch.exitCode))
92
+ return false;
93
+ }
94
+ return true;
95
+ }
96
+ /**
97
+ * Match a single `inputMatches` key/value against the event. `command`
98
+ * on a bash event is wrapper-aware per ADR §12 — the raw outer command
99
+ * OR any extracted ref text matches. All other keys (and `command` on
100
+ * non-bash events) keep the straight raw-string match the v0.0 engine
101
+ * shipped with.
102
+ *
103
+ * Share the evaluator's regex cache (module-scoped in `predicates.ts`)
104
+ * so observer `inputMatches` reuse the same compiled `RegExp` as
105
+ * equivalent rule patterns.
106
+ */
107
+ function matchesInputField(key, pat, value, event, getRefTexts) {
108
+ if (matchesPattern(pat, value))
109
+ return true;
110
+ // Wrapper-aware fallback: only for `command` on bash events. Other
111
+ // fields (path, content, …) don't have wrapper analogues — a
112
+ // file-path pattern has nothing to do with bash AST refs, so
113
+ // leaving them on the raw-string path is both correct and a perf
114
+ // guard against needless parsing on non-bash events.
115
+ if (key !== "command" || event.toolName !== "bash")
116
+ return false;
117
+ const refTexts = getRefTexts();
118
+ if (refTexts === null)
119
+ return false;
120
+ for (const text of refTexts) {
121
+ if (matchesPattern(pat, text))
122
+ return true;
123
+ }
124
+ return false;
125
+ }
126
+ /**
127
+ * Extract per-ref flattened text (basename + args joined with spaces)
128
+ * from a bash tool_result's outer command, mirroring the evaluator's
129
+ * `prepareBashState` text projection so observer watch patterns match
130
+ * the same strings rule patterns see for the same command.
131
+ *
132
+ * Returns `null` when the event isn't a bash tool_result, the raw
133
+ * command is missing/non-string, or the walker throws while parsing
134
+ * (hard-to-parse command — fall back to raw-only matching without
135
+ * blowing up dispatch). Unlike the evaluator we don't walk trackers:
136
+ * observers don't receive `walkerState`, so the parse+extract+expand
137
+ * stages suffice.
138
+ *
139
+ * Exported so the production dispatcher can memoize the parse across
140
+ * observers on the same event (see `dispatchEventInner`'s
141
+ * `getRefTexts` cache). Chain-aware speculative-allow already
142
+ * synthesizes one event per prior ref and doesn't need memoization —
143
+ * it calls {@link matchesWatch} without a provider.
144
+ */
145
+ export function extractRefTextsForBash(event) {
146
+ if (event.toolName !== "bash")
147
+ return null;
148
+ const input = typeof event.input === "object" && event.input !== null
149
+ ? event.input
150
+ : undefined;
151
+ const command = input?.command;
152
+ if (typeof command !== "string" || command.length === 0)
153
+ return null;
154
+ try {
155
+ const script = parseBash(command);
156
+ const extracted = extractAllCommandsFromAST(script, command);
157
+ const { commands: refs } = expandWrapperCommands(extracted);
158
+ return refs.map(refToText);
159
+ }
160
+ catch {
161
+ // Don't let a parse error take down dispatch — a malformed
162
+ // command still deserves a raw-match chance. Returning null
163
+ // (as opposed to []) skips ref matching entirely for this event.
164
+ return null;
165
+ }
166
+ }
167
+ function matchesExitCode(code, filter) {
168
+ if (typeof filter === "number") {
169
+ // Numeric filter requires a concrete code; no-code events (non-bash)
170
+ // never match a numeric filter.
171
+ return code === filter;
172
+ }
173
+ if (filter === "success")
174
+ return code === 0;
175
+ if (filter === "failure")
176
+ return code !== undefined && code !== 0;
177
+ return true;
178
+ }
179
+ //# sourceMappingURL=watch-matcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watch-matcher.js","sourceRoot":"","sources":["../../src/internal/watch-matcher.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,97 @@
1
+ import type { SteeringConfig, SteeringDiagnostic } from "./schema.ts";
2
+ /**
3
+ * Candidate file paths for a given directory's `.pi/steering/...` slot,
4
+ * in priority order. First existing file wins.
5
+ *
6
+ * Exported for tests — not part of the library's public API.
7
+ */
8
+ export declare function configCandidates(dir: string): string[];
9
+ /**
10
+ * Walk up from `cwd` to `$HOME` (inclusive, or to the filesystem root
11
+ * if HOME is unset / outside the cwd's ancestry), returning the list
12
+ * of directories INNER-FIRST — so `[cwd, cwd/parent, ..., HOME]`.
13
+ *
14
+ * Exported for tests.
15
+ */
16
+ export declare function ancestorChain(cwd: string): string[];
17
+ /**
18
+ * Find the config file (if any) for a single layer. Returns the
19
+ * resolved file path and a `layer-form-coexistence` diagnostic when
20
+ * both `.pi/steering/index.ts` and `.pi/steering.ts` coexist in the
21
+ * same directory (the directory form wins).
22
+ *
23
+ * Exported for tests.
24
+ */
25
+ export declare function findConfigFile(dir: string): {
26
+ file: string | null;
27
+ diagnostic: SteeringDiagnostic | null;
28
+ };
29
+ /**
30
+ * Walk up from `cwd` collecting config layers. Returns INNER-FIRST
31
+ * (caller passes to {@link buildConfig}, which expects inner-first so
32
+ * early entries take precedence on collisions).
33
+ *
34
+ * Issues encountered along the way (per-layer import failure, dual
35
+ * form coexistence, stray non-`.ts` file under `.pi/steering/`)
36
+ * surface as structured {@link SteeringDiagnostic} entries on the
37
+ * returned object. The loader does not log to `console.warn` directly
38
+ * — the bridge runtime owns the policy decision (throw vs. log) once
39
+ * it has collected diagnostics from every source.
40
+ *
41
+ * @throws when Node is older than {@link MIN_NODE_MAJOR}.
42
+ */
43
+ export declare function loadConfigs(cwd: string): Promise<{
44
+ layers: SteeringConfig[];
45
+ diagnostics: SteeringDiagnostic[];
46
+ }>;
47
+ /**
48
+ * Inner-wins boolean merge over walked-up layers. Walks left-to-right
49
+ * (inner-first); returns the first explicit boolean or `undefined`.
50
+ * Used by `buildConfig` and the session runtime for the inner-wins
51
+ * boolean fields. Internal — not in the package's `exports` surface.
52
+ */
53
+ export declare function mergeBool(layers: readonly SteeringConfig[], key: "defaultNoOverride" | "disableDefaults" | "failOnWarnings"): boolean | undefined;
54
+ /**
55
+ * Merge `layers` (inner-first) into a single effective
56
+ * {@link SteeringConfig}. An optional `defaults` config is treated as
57
+ * the OUTERMOST layer — its fields apply when no real layer specifies
58
+ * them, otherwise real layers override.
59
+ *
60
+ * Cross-layer plugin name collisions, within-layer rule + observer
61
+ * name collisions, and cross-layer tracker name collisions surface
62
+ * as structured {@link SteeringDiagnostic} entries on the returned
63
+ * object. Predicate-key + tracker-extension collisions are detected
64
+ * in `resolvePlugins`, not here — buildConfig handles cross-layer and
65
+ * within-layer name-collision shapes only.
66
+ */
67
+ export declare function buildConfig(layers: readonly SteeringConfig[], defaults?: SteeringConfig): {
68
+ config: SteeringConfig;
69
+ diagnostics: SteeringDiagnostic[];
70
+ };
71
+ /**
72
+ * Convenience: load all layers for `cwd`, run the loader-side merge
73
+ * (`buildConfig`), then the plugin merger (`resolvePlugins`) with
74
+ * user-config rule + observer name validation between the two passes.
75
+ * Diagnostics from every surface flow into a single returned array,
76
+ * so an external embedder writing their own bridge or pre-flight
77
+ * check sees the SAME diagnostic stream the production runtime sees
78
+ * — no surface is silently skipped.
79
+ *
80
+ * Diagnostics return in declaration order; merge-side errors
81
+ * short-circuit `resolvePlugins` before its diagnostics are added.
82
+ *
83
+ * Production-strictness divergence: `loadSteeringConfig` does NOT
84
+ * apply the strict-mode `failOnWarnings` throw policy that
85
+ * `buildSessionRuntime` does. The function never throws on
86
+ * diagnostics; embedders apply their own throw + warning policy.
87
+ * See `failOnWarnings` on {@link SteeringConfig} for production-
88
+ * faithful pre-flight semantics.
89
+ *
90
+ * @throws when Node < {@link MIN_NODE_MAJOR} (propagated from
91
+ * `loadConfigs`).
92
+ */
93
+ export declare function loadSteeringConfig(cwd: string, defaults?: SteeringConfig): Promise<{
94
+ config: SteeringConfig;
95
+ diagnostics: SteeringDiagnostic[];
96
+ }>;
97
+ //# sourceMappingURL=loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../src/loader.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAIV,cAAc,EACd,kBAAkB,EACnB,MAAM,aAAa,CAAC;AA+BrB;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAKtD;AA+BD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAenD;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG;IAC3C,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,UAAU,EAAE,kBAAkB,GAAG,IAAI,CAAC;CACvC,CAsBA;AAuED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IACtD,MAAM,EAAE,cAAc,EAAE,CAAC;IACzB,WAAW,EAAE,kBAAkB,EAAE,CAAC;CACnC,CAAC,CA2CD;AAiJD;;;;;GAKG;AACH,wBAAgB,SAAS,CACvB,MAAM,EAAE,SAAS,cAAc,EAAE,EACjC,GAAG,EAAE,mBAAmB,GAAG,iBAAiB,GAAG,gBAAgB,GAC9D,OAAO,GAAG,SAAS,CAMrB;AAwCD;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CACzB,MAAM,EAAE,SAAS,cAAc,EAAE,EACjC,QAAQ,CAAC,EAAE,cAAc,GACxB;IAAE,MAAM,EAAE,cAAc,CAAC;IAAC,WAAW,EAAE,kBAAkB,EAAE,CAAA;CAAE,CA0C/D;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,MAAM,EACX,QAAQ,CAAC,EAAE,cAAc,GACxB,OAAO,CAAC;IAAE,MAAM,EAAE,cAAc,CAAC;IAAC,WAAW,EAAE,kBAAkB,EAAE,CAAA;CAAE,CAAC,CAWxE"}