@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
package/dist/loader.js ADDED
@@ -0,0 +1,527 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Part of pi-steering.
3
+ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
4
+ if (typeof path === "string" && /^\.\.?\//.test(path)) {
5
+ return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
6
+ return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
7
+ });
8
+ }
9
+ return path;
10
+ };
11
+ /**
12
+ * TS config loader. Walk up cwd → `$HOME`, find `.pi/steering/index.ts`
13
+ * or `.pi/steering.ts` per layer, dynamic-import each, merge
14
+ * inner-first. Per-symbol JSDoc carries the contract; see also the
15
+ * {@link SteeringDiagnostic} / {@link SteeringDiagnosticKind} JSDoc
16
+ * for the diagnostic stream.
17
+ */
18
+ import { existsSync, readdirSync, statSync } from "node:fs";
19
+ import { dirname, join, resolve } from "node:path";
20
+ import { pathToFileURL } from "node:url";
21
+ import { EVALUATOR_BUILTIN_TRACKERS } from "./evaluator.js";
22
+ import { runMergerPipeline } from "./internal/session-runtime.js";
23
+ import { formatTrackerNameCollisionMessage } from "./plugin-merger.js";
24
+ /**
25
+ * Minimum Node version that supports native `.ts` import via type
26
+ * stripping (without a `--experimental-strip-types` flag). Shipped
27
+ * stable in Node 22.6+. We require 22.x outright to keep the error
28
+ * message simple.
29
+ */
30
+ const MIN_NODE_MAJOR = 22;
31
+ /**
32
+ * Runtime check: throws when Node is older than the minimum required
33
+ * for native `.ts` import. See README §Install for the supported
34
+ * Node range.
35
+ */
36
+ function assertNodeVersion() {
37
+ const raw = process.versions.node;
38
+ const major = Number.parseInt(raw.split(".")[0] ?? "0", 10);
39
+ if (Number.isNaN(major) || major < MIN_NODE_MAJOR) {
40
+ throw new Error(`pi-steering requires Node >= ${MIN_NODE_MAJOR} ` +
41
+ `for native .ts loading (found ${raw}). ` +
42
+ `Upgrade Node, or stay on v1 JSON configs (\`.pi/steering.json\`).`);
43
+ }
44
+ }
45
+ // ---------------------------------------------------------------------------
46
+ // Discovery
47
+ // ---------------------------------------------------------------------------
48
+ /**
49
+ * Candidate file paths for a given directory's `.pi/steering/...` slot,
50
+ * in priority order. First existing file wins.
51
+ *
52
+ * Exported for tests — not part of the library's public API.
53
+ */
54
+ export function configCandidates(dir) {
55
+ return [
56
+ join(dir, ".pi", "steering", "index.ts"),
57
+ join(dir, ".pi", "steering.ts"),
58
+ ];
59
+ }
60
+ /**
61
+ * Return the non-`.ts` files that exist under `<dir>/.pi/steering/` so
62
+ * callers can warn about them. Uses a best-effort fs read: a missing
63
+ * directory returns an empty list.
64
+ */
65
+ function unexpectedFilesUnderSteering(dir) {
66
+ const steeringDir = join(dir, ".pi", "steering");
67
+ if (!existsSync(steeringDir))
68
+ return [];
69
+ try {
70
+ const entries = readdirSync(steeringDir);
71
+ const out = [];
72
+ for (const name of entries) {
73
+ const full = join(steeringDir, name);
74
+ try {
75
+ const st = statSync(full);
76
+ if (!st.isFile())
77
+ continue;
78
+ if (name === "index.ts")
79
+ continue;
80
+ if (name.endsWith(".ts"))
81
+ continue; // allow helpers like `rules.ts`
82
+ out.push(full);
83
+ }
84
+ catch {
85
+ // skip unreadable entry
86
+ }
87
+ }
88
+ return out;
89
+ }
90
+ catch {
91
+ return [];
92
+ }
93
+ }
94
+ /**
95
+ * Walk up from `cwd` to `$HOME` (inclusive, or to the filesystem root
96
+ * if HOME is unset / outside the cwd's ancestry), returning the list
97
+ * of directories INNER-FIRST — so `[cwd, cwd/parent, ..., HOME]`.
98
+ *
99
+ * Exported for tests.
100
+ */
101
+ export function ancestorChain(cwd) {
102
+ const home = process.env["HOME"] ?? "";
103
+ const out = [];
104
+ const seen = new Set();
105
+ let current = resolve(cwd);
106
+ while (true) {
107
+ if (seen.has(current))
108
+ break; // symlink-loop guard
109
+ seen.add(current);
110
+ out.push(current);
111
+ if (current === home || current === "/")
112
+ break;
113
+ const parent = dirname(current);
114
+ if (parent === current)
115
+ break; // filesystem root
116
+ current = parent;
117
+ }
118
+ return out;
119
+ }
120
+ /**
121
+ * Find the config file (if any) for a single layer. Returns the
122
+ * resolved file path and a `layer-form-coexistence` diagnostic when
123
+ * both `.pi/steering/index.ts` and `.pi/steering.ts` coexist in the
124
+ * same directory (the directory form wins).
125
+ *
126
+ * Exported for tests.
127
+ */
128
+ export function findConfigFile(dir) {
129
+ const [indexForm, flatForm] = configCandidates(dir);
130
+ const indexExists = indexForm !== undefined && existsSync(indexForm);
131
+ const flatExists = flatForm !== undefined && existsSync(flatForm);
132
+ let diagnostic = null;
133
+ if (indexExists && flatExists && indexForm !== undefined) {
134
+ // `path` points at the directory holding the conflict so the
135
+ // renderer's `${path}: ${message}` prefix surfaces the parent
136
+ // once and the message names which two forms coexist. The winning
137
+ // file path is implicit (the directory form always wins).
138
+ diagnostic = {
139
+ type: "warning",
140
+ kind: "layer-form-coexistence",
141
+ path: dir,
142
+ message: "both .pi/steering.ts and .pi/steering/index.ts exist; using " +
143
+ "directory form. Delete .pi/steering.ts to remove this warning.",
144
+ };
145
+ }
146
+ if (indexExists)
147
+ return { file: indexForm ?? null, diagnostic };
148
+ if (flatExists)
149
+ return { file: flatForm ?? null, diagnostic };
150
+ return { file: null, diagnostic };
151
+ }
152
+ // ---------------------------------------------------------------------------
153
+ // Loading
154
+ // ---------------------------------------------------------------------------
155
+ /**
156
+ * Dynamic-import a single config file and return its default export.
157
+ * The module MUST `export default` a {@link SteeringConfig} object —
158
+ * the loader does not accept module-namespace imports as a fallback,
159
+ * to keep the authoring contract unambiguous.
160
+ *
161
+ * Throws a scoped error when the import fails, when the module has no
162
+ * default export, or when the default export isn't a plain object —
163
+ * the caller surfaces these per-layer without bringing the whole
164
+ * session down (a single bad layer shouldn't nuke the engine).
165
+ *
166
+ * Each call appends a unique `?t=<timestamp>` query string to the
167
+ * import URL. Node's ESM module map is keyed on URL and persists for
168
+ * the lifetime of the process, so a plain `await import(url)` returns
169
+ * the cached module forever — even after `/reload` and even after
170
+ * the file's content has changed (or, worse, after a previous load
171
+ * threw, since failed loads are also cached). The cache-bust forces
172
+ * Node to re-fetch and re-evaluate the file each call. See the
173
+ * "Hot-reload" section in the README for the limits of this approach
174
+ * (it does NOT bust transitively-imported plugin packages, which the
175
+ * user config reaches via static `import` of bare specifiers like
176
+ * `"pi-steering"` — those still require a full pi restart).
177
+ */
178
+ async function importConfigFile(path) {
179
+ const url = pathToFileURL(path).href;
180
+ /**
181
+ * Cache-bust query string. `process.hrtime.bigint()` returns
182
+ * monotonic nanoseconds since an arbitrary process-relative
183
+ * origin — each call returns a strictly greater value, even
184
+ * back-to-back within the same millisecond, so no two
185
+ * `importConfigFile` invocations ever share a URL. If a future
186
+ * Node version changes the cache-bust contract (e.g. ignores
187
+ * query strings on `file:` URLs), this will silently revert to
188
+ * the cached-forever behavior, and the integration tests in
189
+ * `loader.test.ts` will fail.
190
+ */
191
+ const bust = `${url.includes("?") ? "&" : "?"}t=${process.hrtime.bigint()}`;
192
+ const mod = (await import(__rewriteRelativeImportExtension(url + bust)));
193
+ if (mod.default === undefined) {
194
+ // Path is intentionally omitted from the message; the caller wraps
195
+ // this throw in a `layer-import-failed` diagnostic whose own `path`
196
+ // field is the single source of truth for the file location.
197
+ throw new Error("config file must have a default export. Use " +
198
+ "`export default { ... } satisfies SteeringConfig` or " +
199
+ "`export default defineConfig({ ... })`.");
200
+ }
201
+ const candidate = mod.default;
202
+ if (candidate === null ||
203
+ typeof candidate !== "object" ||
204
+ Array.isArray(candidate)) {
205
+ throw new Error(`config file default export must be a SteeringConfig object, ` +
206
+ `got ${Array.isArray(candidate) ? "array" : typeof candidate}.`);
207
+ }
208
+ return candidate;
209
+ }
210
+ /**
211
+ * Walk up from `cwd` collecting config layers. Returns INNER-FIRST
212
+ * (caller passes to {@link buildConfig}, which expects inner-first so
213
+ * early entries take precedence on collisions).
214
+ *
215
+ * Issues encountered along the way (per-layer import failure, dual
216
+ * form coexistence, stray non-`.ts` file under `.pi/steering/`)
217
+ * surface as structured {@link SteeringDiagnostic} entries on the
218
+ * returned object. The loader does not log to `console.warn` directly
219
+ * — the bridge runtime owns the policy decision (throw vs. log) once
220
+ * it has collected diagnostics from every source.
221
+ *
222
+ * @throws when Node is older than {@link MIN_NODE_MAJOR}.
223
+ */
224
+ export async function loadConfigs(cwd) {
225
+ assertNodeVersion();
226
+ const dirs = ancestorChain(cwd);
227
+ const layers = [];
228
+ const diagnostics = [];
229
+ for (const dir of dirs) {
230
+ const { file, diagnostic } = findConfigFile(dir);
231
+ if (diagnostic !== null)
232
+ diagnostics.push(diagnostic);
233
+ if (file === null) {
234
+ // Surface stray files under `.pi/steering/` that the loader
235
+ // won't pick up. Only check when the directory exists but has
236
+ // no `index.ts` — otherwise a project without any steering
237
+ // directory would emit noise.
238
+ const steeringDir = join(dir, ".pi", "steering");
239
+ if (existsSync(steeringDir)) {
240
+ for (const stray of unexpectedFilesUnderSteering(dir)) {
241
+ diagnostics.push({
242
+ type: "warning",
243
+ kind: "layer-stray-file",
244
+ path: stray,
245
+ message: "ignoring non-.ts file under .pi/steering/",
246
+ });
247
+ }
248
+ }
249
+ continue;
250
+ }
251
+ try {
252
+ layers.push(await importConfigFile(file));
253
+ }
254
+ catch (err) {
255
+ // Use err.message to drop the `Error: ` class prefix; native
256
+ // runtime errors (jiti syntax errors) may embed their path inside
257
+ // the message and we accept that duplication.
258
+ const body = err instanceof Error ? err.message : String(err);
259
+ diagnostics.push({
260
+ type: "warning",
261
+ kind: "layer-import-failed",
262
+ path: file,
263
+ message: `failed to import: ${body}`,
264
+ });
265
+ }
266
+ }
267
+ return { layers, diagnostics };
268
+ }
269
+ // ---------------------------------------------------------------------------
270
+ // Merging
271
+ // ---------------------------------------------------------------------------
272
+ /**
273
+ * Collect plugins across layers, recording a diagnostic for each
274
+ * cross-layer duplicate plugin name. First-registered wins (inner
275
+ * layer is first — matches pi's project-local → global convention).
276
+ *
277
+ * The caller is responsible for unioning `config.disabledPlugins`
278
+ * across layers and passing the result as `disabledPlugins` (see
279
+ * `buildConfig`). Plugins whose name appears in the supplied set are
280
+ * still merged into the output (so downstream surfaces like
281
+ * `pi-steering list` can render them tagged as disabled), but they're
282
+ * EXEMPT from collision detection. A user resolving a duplicate-plugin
283
+ * warning by adding the plugin to `disabledPlugins` should see the
284
+ * warning go away in the same config edit; detect-then-disable would
285
+ * still surface the warning even though the disable already settled
286
+ * the conflict.
287
+ */
288
+ function mergePlugins(layers, disabledPlugins, diagnostics) {
289
+ const seen = new Set();
290
+ const out = [];
291
+ for (const layer of layers) {
292
+ if (!layer.plugins)
293
+ continue;
294
+ for (const plugin of layer.plugins) {
295
+ if (seen.has(plugin.name)) {
296
+ if (!disabledPlugins.has(plugin.name)) {
297
+ diagnostics.push({
298
+ type: "warning",
299
+ kind: "plugin-name-collision",
300
+ message: `duplicate plugin "${plugin.name}"; keeping first-registered entry.`,
301
+ });
302
+ }
303
+ continue;
304
+ }
305
+ seen.add(plugin.name);
306
+ out.push(plugin);
307
+ }
308
+ }
309
+ return out;
310
+ }
311
+ /**
312
+ * Merge rules across layers — inner layer's rule name overrides outer.
313
+ * Declaration order within a layer is preserved; cross-layer order is
314
+ * "first layer that mentions a given rule name wins for its slot".
315
+ *
316
+ * Records a diagnostic on duplicate names WITHIN a single layer
317
+ * (authoring mistake) — mirrors {@link mergeObservers}. Cross-layer
318
+ * collisions stay silent: overriding a rule by name is the documented
319
+ * customization path.
320
+ *
321
+ * Disabled rules are merged but exempt from collision detection (see
322
+ * {@link mergePlugins} for rationale).
323
+ */
324
+ function mergeRules(layers, disabledRules, diagnostics) {
325
+ const byName = new Map();
326
+ for (const layer of layers) {
327
+ if (!layer.rules)
328
+ continue;
329
+ const seenInLayer = new Set();
330
+ for (const rule of layer.rules) {
331
+ if (seenInLayer.has(rule.name)) {
332
+ if (!disabledRules.has(rule.name)) {
333
+ diagnostics.push({
334
+ type: "warning",
335
+ kind: "rule-name-collision",
336
+ message: `duplicate rule "${rule.name}" within single config layer; ` +
337
+ "keeping first, dropping subsequent",
338
+ });
339
+ }
340
+ continue;
341
+ }
342
+ seenInLayer.add(rule.name);
343
+ if (!byName.has(rule.name)) {
344
+ byName.set(rule.name, rule);
345
+ }
346
+ }
347
+ }
348
+ return [...byName.values()];
349
+ }
350
+ /**
351
+ * Merge observers across layers — inner layer's observer name
352
+ * overrides outer. Records a diagnostic on duplicate names WITHIN a
353
+ * single layer (authoring mistake); cross-layer overrides are silent
354
+ * (intentional customization).
355
+ */
356
+ function mergeObservers(layers, diagnostics) {
357
+ const byName = new Map();
358
+ for (const layer of layers) {
359
+ if (!layer.observers)
360
+ continue;
361
+ const seenInLayer = new Set();
362
+ for (const obs of layer.observers) {
363
+ if (seenInLayer.has(obs.name)) {
364
+ diagnostics.push({
365
+ type: "warning",
366
+ kind: "observer-name-collision",
367
+ message: `duplicate observer "${obs.name}"; keeping first-registered entry.`,
368
+ });
369
+ continue;
370
+ }
371
+ seenInLayer.add(obs.name);
372
+ if (!byName.has(obs.name)) {
373
+ byName.set(obs.name, obs);
374
+ }
375
+ }
376
+ }
377
+ return [...byName.values()];
378
+ }
379
+ /**
380
+ * Merge simple string-list fields (`disabledRules`, `disabledPlugins`)
381
+ * as a union across layers. Preserves first-seen order for deterministic
382
+ * output in tests.
383
+ */
384
+ function mergeStringUnion(layers, key) {
385
+ const seen = new Set();
386
+ let any = false;
387
+ for (const layer of layers) {
388
+ const list = layer[key];
389
+ if (list === undefined)
390
+ continue;
391
+ any = true;
392
+ for (const item of list)
393
+ seen.add(item);
394
+ }
395
+ return any ? [...seen] : undefined;
396
+ }
397
+ /**
398
+ * Inner-wins boolean merge over walked-up layers. Walks left-to-right
399
+ * (inner-first); returns the first explicit boolean or `undefined`.
400
+ * Used by `buildConfig` and the session runtime for the inner-wins
401
+ * boolean fields. Internal — not in the package's `exports` surface.
402
+ */
403
+ export function mergeBool(layers, key) {
404
+ for (const layer of layers) {
405
+ const v = layer[key];
406
+ if (typeof v === "boolean")
407
+ return v;
408
+ }
409
+ return undefined;
410
+ }
411
+ /**
412
+ * Detect plugins registering a tracker under the same name and push
413
+ * an error-class diagnostic for each collision. Two plugins claiming
414
+ * the same state dimension is always a bug; the runtime escalates
415
+ * these to a thrown error regardless of the user's strict-mode
416
+ * preference.
417
+ *
418
+ * Plugins whose name appears in `disabledPlugins` are skipped before
419
+ * collision detection (mirrors {@link mergePlugins}).
420
+ */
421
+ function detectTrackerNameCollisions(plugins, disabledPlugins, diagnostics) {
422
+ const seen = new Map(); // trackerName -> pluginName
423
+ for (const plugin of plugins) {
424
+ if (disabledPlugins.has(plugin.name))
425
+ continue;
426
+ if (!plugin.trackers)
427
+ continue;
428
+ for (const trackerName of Object.keys(plugin.trackers)) {
429
+ const prior = seen.get(trackerName);
430
+ if (prior !== undefined) {
431
+ diagnostics.push({
432
+ type: "error",
433
+ kind: "tracker-name-collision",
434
+ message: formatTrackerNameCollisionMessage(prior, plugin.name, trackerName),
435
+ });
436
+ continue;
437
+ }
438
+ seen.set(trackerName, plugin.name);
439
+ }
440
+ }
441
+ }
442
+ /**
443
+ * Merge `layers` (inner-first) into a single effective
444
+ * {@link SteeringConfig}. An optional `defaults` config is treated as
445
+ * the OUTERMOST layer — its fields apply when no real layer specifies
446
+ * them, otherwise real layers override.
447
+ *
448
+ * Cross-layer plugin name collisions, within-layer rule + observer
449
+ * name collisions, and cross-layer tracker name collisions surface
450
+ * as structured {@link SteeringDiagnostic} entries on the returned
451
+ * object. Predicate-key + tracker-extension collisions are detected
452
+ * in `resolvePlugins`, not here — buildConfig handles cross-layer and
453
+ * within-layer name-collision shapes only.
454
+ */
455
+ export function buildConfig(layers, defaults) {
456
+ // Build the effective inner-first layer list. `defaults` goes at
457
+ // the END (outermost position) so inner real layers override it.
458
+ const effective = [...layers];
459
+ if (defaults !== undefined)
460
+ effective.push(defaults);
461
+ const diagnostics = [];
462
+ const disabledPluginsList = mergeStringUnion(effective, "disabledPlugins");
463
+ const disabledRulesList = mergeStringUnion(effective, "disabledRules");
464
+ const disabledPluginsSet = new Set(disabledPluginsList ?? []);
465
+ const disabledRulesSet = new Set(disabledRulesList ?? []);
466
+ // Predicate + tracker-extension collisions are detected in
467
+ // resolvePlugins, not here. buildConfig handles cross-layer and
468
+ // within-layer name-collision shapes only.
469
+ const plugins = mergePlugins(effective, disabledPluginsSet, diagnostics);
470
+ detectTrackerNameCollisions(plugins, disabledPluginsSet, diagnostics);
471
+ const rules = mergeRules(effective, disabledRulesSet, diagnostics);
472
+ const observers = mergeObservers(effective, diagnostics);
473
+ const out = {};
474
+ if (plugins.length > 0)
475
+ out.plugins = plugins;
476
+ if (rules.length > 0)
477
+ out.rules = rules;
478
+ if (observers.length > 0)
479
+ out.observers = observers;
480
+ if (disabledRulesList !== undefined)
481
+ out.disabledRules = disabledRulesList;
482
+ if (disabledPluginsList !== undefined) {
483
+ out.disabledPlugins = disabledPluginsList;
484
+ }
485
+ const defaultNoOverride = mergeBool(effective, "defaultNoOverride");
486
+ if (defaultNoOverride !== undefined) {
487
+ out.defaultNoOverride = defaultNoOverride;
488
+ }
489
+ const disableDefaults = mergeBool(effective, "disableDefaults");
490
+ if (disableDefaults !== undefined)
491
+ out.disableDefaults = disableDefaults;
492
+ const failOnWarnings = mergeBool(effective, "failOnWarnings");
493
+ if (failOnWarnings !== undefined)
494
+ out.failOnWarnings = failOnWarnings;
495
+ return { config: out, diagnostics };
496
+ }
497
+ /**
498
+ * Convenience: load all layers for `cwd`, run the loader-side merge
499
+ * (`buildConfig`), then the plugin merger (`resolvePlugins`) with
500
+ * user-config rule + observer name validation between the two passes.
501
+ * Diagnostics from every surface flow into a single returned array,
502
+ * so an external embedder writing their own bridge or pre-flight
503
+ * check sees the SAME diagnostic stream the production runtime sees
504
+ * — no surface is silently skipped.
505
+ *
506
+ * Diagnostics return in declaration order; merge-side errors
507
+ * short-circuit `resolvePlugins` before its diagnostics are added.
508
+ *
509
+ * Production-strictness divergence: `loadSteeringConfig` does NOT
510
+ * apply the strict-mode `failOnWarnings` throw policy that
511
+ * `buildSessionRuntime` does. The function never throws on
512
+ * diagnostics; embedders apply their own throw + warning policy.
513
+ * See `failOnWarnings` on {@link SteeringConfig} for production-
514
+ * faithful pre-flight semantics.
515
+ *
516
+ * @throws when Node < {@link MIN_NODE_MAJOR} (propagated from
517
+ * `loadConfigs`).
518
+ */
519
+ export async function loadSteeringConfig(cwd, defaults) {
520
+ const { layers, diagnostics: loaderDiagnostics } = await loadConfigs(cwd);
521
+ const { merged, diagnostics: mergeAndResolveDiagnostics } = runMergerPipeline(layers, defaults, EVALUATOR_BUILTIN_TRACKERS);
522
+ return {
523
+ config: merged,
524
+ diagnostics: [...loaderDiagnostics, ...mergeAndResolveDiagnostics],
525
+ };
526
+ }
527
+ //# sourceMappingURL=loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.js","sourceRoot":"","sources":["../src/loader.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,69 @@
1
+ /**
2
+ * v2 observer dispatcher.
3
+ *
4
+ * Observers are the `tool_result` side of the steering engine: rules
5
+ * decide "can this run" pre-execution; observers record "what happened"
6
+ * post-execution. Typical use is to `appendEntry` into pi's session
7
+ * JSONL so later predicates can gate on prior turn state (the
8
+ * "description was read in a PRIOR turn" idiom from the ADR).
9
+ *
10
+ * This module merges user-declared observers (from
11
+ * {@link SteeringConfig.observers}) with plugin-shipped ones (from
12
+ * {@link ResolvedPluginState.observers}), then on every `tool_result`:
13
+ *
14
+ * 1. Applies the observer's `watch` filter (toolName + inputMatches
15
+ * + exitCode). No `watch` means "fire on every tool_result".
16
+ * 2. Calls `observer.onResult(event, observerCtx)`. Awaits if it
17
+ * returns a promise.
18
+ * 3. Catches thrown errors per-observer — one buggy observer does
19
+ * NOT prevent the rest from running.
20
+ *
21
+ * Observer context (`ObserverContext`) is built fresh per event, using
22
+ * `appendEntry` and `findEntries` closures shared with the evaluator.
23
+ * `exec` is deliberately NOT exposed — observers are expected to be
24
+ * lightweight state-recording hooks. Complex tool_result analysis that
25
+ * needs to shell out belongs in a separate pi extension hook or in a
26
+ * rule's `when.condition` pre-execution, not in an observer.
27
+ *
28
+ * Wiring (Phase 3c): the extension runtime subscribes to `tool_result`
29
+ * and forwards the event + current `agentLoopIndex` into `dispatch`.
30
+ */
31
+ import type { ExtensionContext, ToolResultEvent as PiToolResultEvent } from "@earendil-works/pi-coding-agent";
32
+ import { type EvaluatorHost } from "./evaluator-internals/context.ts";
33
+ import type { ResolvedPluginState } from "./plugin-merger.ts";
34
+ import type { Observer } from "./schema.ts";
35
+ export { matchesWatch } from "./internal/watch-matcher.ts";
36
+ /**
37
+ * Runtime-facing dispatcher handle. Phase 3c holds an instance per
38
+ * session and calls {@link dispatch} from the pi `tool_result` listener.
39
+ */
40
+ export interface ObserverDispatcher {
41
+ /**
42
+ * Dispatch a single `tool_result` to every matching observer.
43
+ *
44
+ * Resolves when all observer handlers have settled. Handlers that
45
+ * throw are caught + logged via `console.warn` and do not prevent
46
+ * subsequent observers from running.
47
+ */
48
+ dispatch(event: PiToolResultEvent, ctx: ExtensionContext, agentLoopIndex: number): Promise<void>;
49
+ }
50
+ /**
51
+ * Construct an {@link ObserverDispatcher}.
52
+ *
53
+ * Arguments:
54
+ * - `resolved` — merged plugin state from {@link resolvePlugins}.
55
+ * Source of plugin-shipped observers and the
56
+ * registry used by the evaluator at the same
57
+ * level.
58
+ * - `userObservers` — the user's `config.observers` list (already
59
+ * deduped at the loader level in Phase 2). User
60
+ * observers fire BEFORE plugin observers on the
61
+ * same event; within each group, registration
62
+ * order decides.
63
+ * - `host` — narrow surface exposing pi's `exec` +
64
+ * `appendEntry`. Passed straight through to the
65
+ * per-event observer context.
66
+ */
67
+ export declare function buildObserverDispatcher(resolved: ResolvedPluginState, userObservers: readonly Observer[], host: EvaluatorHost): ObserverDispatcher;
68
+ export type { EvaluatorHost } from "./evaluator-internals/context.ts";
69
+ //# sourceMappingURL=observer-dispatcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"observer-dispatcher.d.ts","sourceRoot":"","sources":["../src/observer-dispatcher.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,KAAK,EACV,gBAAgB,EAChB,eAAe,IAAI,iBAAiB,EACrC,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAIL,KAAK,aAAa,EACnB,MAAM,kCAAkC,CAAC;AAM1C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAE9D,OAAO,KAAK,EACV,QAAQ,EAGT,MAAM,aAAa,CAAC;AAOrB,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAM3D;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC;;;;;;OAMG;IACH,QAAQ,CACN,KAAK,EAAE,iBAAiB,EACxB,GAAG,EAAE,gBAAgB,EACrB,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,IAAI,CAAC,CAAC;CAClB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,mBAAmB,EAC7B,aAAa,EAAE,SAAS,QAAQ,EAAE,EAClC,IAAI,EAAE,aAAa,GAClB,kBAAkB,CAwBpB;AA6JD,YAAY,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC"}