@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.
- package/CHANGELOG.md +36 -0
- package/LICENSE +29 -0
- package/README.md +920 -0
- package/dist/__test-helpers__.d.ts +95 -0
- package/dist/__test-helpers__.d.ts.map +1 -0
- package/dist/__test-helpers__.js +174 -0
- package/dist/__test-helpers__.js.map +1 -0
- package/dist/bin/pi-steering.d.ts +9 -0
- package/dist/bin/pi-steering.d.ts.map +1 -0
- package/dist/bin/pi-steering.js +506 -0
- package/dist/bin/pi-steering.js.map +1 -0
- package/dist/compat.d.ts +50 -0
- package/dist/compat.d.ts.map +1 -0
- package/dist/compat.js +180 -0
- package/dist/compat.js.map +1 -0
- package/dist/defaults.d.ts +61 -0
- package/dist/defaults.d.ts.map +1 -0
- package/dist/defaults.js +106 -0
- package/dist/defaults.js.map +1 -0
- package/dist/define-config.d.ts +235 -0
- package/dist/define-config.d.ts.map +1 -0
- package/dist/define-config.js +118 -0
- package/dist/define-config.js.map +1 -0
- package/dist/define-predicate.d.ts +49 -0
- package/dist/define-predicate.d.ts.map +1 -0
- package/dist/define-predicate.js +35 -0
- package/dist/define-predicate.js.map +1 -0
- package/dist/evaluator-internals/context.d.ts +186 -0
- package/dist/evaluator-internals/context.d.ts.map +1 -0
- package/dist/evaluator-internals/context.js +219 -0
- package/dist/evaluator-internals/context.js.map +1 -0
- package/dist/evaluator-internals/override.d.ts +19 -0
- package/dist/evaluator-internals/override.d.ts.map +1 -0
- package/dist/evaluator-internals/override.js +79 -0
- package/dist/evaluator-internals/override.js.map +1 -0
- package/dist/evaluator-internals/predicates.d.ts +152 -0
- package/dist/evaluator-internals/predicates.d.ts.map +1 -0
- package/dist/evaluator-internals/predicates.js +849 -0
- package/dist/evaluator-internals/predicates.js.map +1 -0
- package/dist/evaluator-internals/speculative-synthesis.d.ts +102 -0
- package/dist/evaluator-internals/speculative-synthesis.d.ts.map +1 -0
- package/dist/evaluator-internals/speculative-synthesis.js +101 -0
- package/dist/evaluator-internals/speculative-synthesis.js.map +1 -0
- package/dist/evaluator.d.ts +101 -0
- package/dist/evaluator.d.ts.map +1 -0
- package/dist/evaluator.js +714 -0
- package/dist/evaluator.js.map +1 -0
- package/dist/helpers/walker-unknown-cwd-reason.d.ts +68 -0
- package/dist/helpers/walker-unknown-cwd-reason.d.ts.map +1 -0
- package/dist/helpers/walker-unknown-cwd-reason.js +73 -0
- package/dist/helpers/walker-unknown-cwd-reason.js.map +1 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +93 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/drop-unused-observers.d.ts +43 -0
- package/dist/internal/drop-unused-observers.d.ts.map +1 -0
- package/dist/internal/drop-unused-observers.js +50 -0
- package/dist/internal/drop-unused-observers.js.map +1 -0
- package/dist/internal/finalize-plugin-state.d.ts +22 -0
- package/dist/internal/finalize-plugin-state.d.ts.map +1 -0
- package/dist/internal/finalize-plugin-state.js +14 -0
- package/dist/internal/finalize-plugin-state.js.map +1 -0
- package/dist/internal/merge-observers.d.ts +30 -0
- package/dist/internal/merge-observers.d.ts.map +1 -0
- package/dist/internal/merge-observers.js +27 -0
- package/dist/internal/merge-observers.js.map +1 -0
- package/dist/internal/pattern-utils.d.ts +33 -0
- package/dist/internal/pattern-utils.d.ts.map +1 -0
- package/dist/internal/pattern-utils.js +20 -0
- package/dist/internal/pattern-utils.js.map +1 -0
- package/dist/internal/ref-text.d.ts +11 -0
- package/dist/internal/ref-text.d.ts.map +1 -0
- package/dist/internal/ref-text.js +15 -0
- package/dist/internal/ref-text.js.map +1 -0
- package/dist/internal/session-runtime.d.ts +44 -0
- package/dist/internal/session-runtime.d.ts.map +1 -0
- package/dist/internal/session-runtime.js +143 -0
- package/dist/internal/session-runtime.js.map +1 -0
- package/dist/internal/watch-matcher.d.ts +56 -0
- package/dist/internal/watch-matcher.d.ts.map +1 -0
- package/dist/internal/watch-matcher.js +179 -0
- package/dist/internal/watch-matcher.js.map +1 -0
- package/dist/loader.d.ts +97 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +527 -0
- package/dist/loader.js.map +1 -0
- package/dist/observer-dispatcher.d.ts +69 -0
- package/dist/observer-dispatcher.d.ts.map +1 -0
- package/dist/observer-dispatcher.js +184 -0
- package/dist/observer-dispatcher.js.map +1 -0
- package/dist/plugin-merger.d.ts +175 -0
- package/dist/plugin-merger.d.ts.map +1 -0
- package/dist/plugin-merger.js +450 -0
- package/dist/plugin-merger.js.map +1 -0
- package/dist/plugins/git/branch-tracker.d.ts +85 -0
- package/dist/plugins/git/branch-tracker.d.ts.map +1 -0
- package/dist/plugins/git/branch-tracker.js +181 -0
- package/dist/plugins/git/branch-tracker.js.map +1 -0
- package/dist/plugins/git/cwd-extensions.d.ts +10 -0
- package/dist/plugins/git/cwd-extensions.d.ts.map +1 -0
- package/dist/plugins/git/cwd-extensions.js +155 -0
- package/dist/plugins/git/cwd-extensions.js.map +1 -0
- package/dist/plugins/git/git-ops.d.ts +89 -0
- package/dist/plugins/git/git-ops.d.ts.map +1 -0
- package/dist/plugins/git/git-ops.js +107 -0
- package/dist/plugins/git/git-ops.js.map +1 -0
- package/dist/plugins/git/index.d.ts +225 -0
- package/dist/plugins/git/index.d.ts.map +1 -0
- package/dist/plugins/git/index.js +52 -0
- package/dist/plugins/git/index.js.map +1 -0
- package/dist/plugins/git/predicates.d.ts +312 -0
- package/dist/plugins/git/predicates.d.ts.map +1 -0
- package/dist/plugins/git/predicates.js +580 -0
- package/dist/plugins/git/predicates.js.map +1 -0
- package/dist/plugins/git/rules.d.ts +209 -0
- package/dist/plugins/git/rules.d.ts.map +1 -0
- package/dist/plugins/git/rules.js +267 -0
- package/dist/plugins/git/rules.js.map +1 -0
- package/dist/schema.d.ts +1799 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +4 -0
- package/dist/schema.js.map +1 -0
- package/dist/testing/index.d.ts +621 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +747 -0
- package/dist/testing/index.js.map +1 -0
- package/examples/README.md +83 -0
- package/examples/combined-git-discipline/README.md +52 -0
- package/examples/combined-git-discipline/node_modules/.bin/pi-steering +43 -0
- package/examples/combined-git-discipline/package.json +18 -0
- package/examples/combined-git-discipline/steering.json +27 -0
- package/examples/combined-git-discipline/steering.test.ts +42 -0
- package/examples/combined-git-discipline/steering.ts +64 -0
- package/examples/combined-git-discipline/tsconfig.json +7 -0
- package/examples/draft-prs-only/README.md +42 -0
- package/examples/draft-prs-only/node_modules/.bin/pi-steering +43 -0
- package/examples/draft-prs-only/package.json +18 -0
- package/examples/draft-prs-only/steering.json +12 -0
- package/examples/draft-prs-only/steering.test.ts +48 -0
- package/examples/draft-prs-only/steering.ts +36 -0
- package/examples/draft-prs-only/tsconfig.json +7 -0
- package/examples/dynamic-reason-runtime-cwd/README.md +148 -0
- package/examples/dynamic-reason-runtime-cwd/node_modules/.bin/pi-steering +43 -0
- package/examples/dynamic-reason-runtime-cwd/package.json +18 -0
- package/examples/dynamic-reason-runtime-cwd/steering.test.ts +130 -0
- package/examples/dynamic-reason-runtime-cwd/steering.ts +82 -0
- package/examples/dynamic-reason-runtime-cwd/tsconfig.json +7 -0
- package/examples/force-push-strict/README.md +75 -0
- package/examples/force-push-strict/node_modules/.bin/pi-steering +43 -0
- package/examples/force-push-strict/package.json +18 -0
- package/examples/force-push-strict/steering.json +12 -0
- package/examples/force-push-strict/steering.test.ts +51 -0
- package/examples/force-push-strict/steering.ts +54 -0
- package/examples/force-push-strict/tsconfig.json +7 -0
- package/examples/no-amend/README.md +52 -0
- package/examples/no-amend/node_modules/.bin/pi-steering +43 -0
- package/examples/no-amend/package.json +18 -0
- package/examples/no-amend/steering.cwd-scoped.json +12 -0
- package/examples/no-amend/steering.json +11 -0
- package/examples/no-amend/steering.test.ts +49 -0
- package/examples/no-amend/steering.ts +38 -0
- package/examples/no-amend/tsconfig.json +7 -0
- package/examples/work-item-plugin/README.md +103 -0
- package/examples/work-item-plugin/node_modules/.bin/pi-steering +43 -0
- package/examples/work-item-plugin/package.json +19 -0
- package/examples/work-item-plugin/src/index.test.ts +168 -0
- package/examples/work-item-plugin/src/index.ts +122 -0
- package/examples/work-item-plugin/src/observers/npm-test-tracker.test.ts +95 -0
- package/examples/work-item-plugin/src/observers/npm-test-tracker.ts +88 -0
- package/examples/work-item-plugin/src/observers/retest-required-tracker.test.ts +81 -0
- package/examples/work-item-plugin/src/observers/retest-required-tracker.ts +81 -0
- package/examples/work-item-plugin/src/predicates/work-item-format.test.ts +157 -0
- package/examples/work-item-plugin/src/predicates/work-item-format.ts +99 -0
- package/examples/work-item-plugin/src/rules/commit-description-check.test.ts +107 -0
- package/examples/work-item-plugin/src/rules/commit-description-check.ts +83 -0
- package/examples/work-item-plugin/src/rules/commit-requires-work-item.test.ts +79 -0
- package/examples/work-item-plugin/src/rules/commit-requires-work-item.ts +56 -0
- package/examples/work-item-plugin/src/rules/push-requires-tests.test.ts +245 -0
- package/examples/work-item-plugin/src/rules/push-requires-tests.ts +62 -0
- package/examples/work-item-plugin/tsconfig.json +8 -0
- package/package.json +88 -0
- package/skills/steering-authoring/SKILL.md +102 -0
- package/src/INVARIANTS.md +116 -0
- package/src/__test-helpers__.ts +243 -0
- package/src/bin/pi-steering.test.ts +920 -0
- package/src/bin/pi-steering.ts +584 -0
- package/src/compat.test.ts +431 -0
- package/src/compat.ts +282 -0
- package/src/defaults.test.ts +585 -0
- package/src/defaults.ts +148 -0
- package/src/define-config.test.ts +888 -0
- package/src/define-config.ts +375 -0
- package/src/define-predicate.test.ts +70 -0
- package/src/define-predicate.ts +57 -0
- package/src/evaluator-internals/context.ts +290 -0
- package/src/evaluator-internals/override.test.ts +99 -0
- package/src/evaluator-internals/override.ts +83 -0
- package/src/evaluator-internals/predicates.ts +1037 -0
- package/src/evaluator-internals/speculative-synthesis.test.ts +411 -0
- package/src/evaluator-internals/speculative-synthesis.ts +199 -0
- package/src/evaluator.test.ts +6296 -0
- package/src/evaluator.ts +1106 -0
- package/src/factory-time-load.test.ts +630 -0
- package/src/helpers/walker-unknown-cwd-reason.test.ts +73 -0
- package/src/helpers/walker-unknown-cwd-reason.ts +83 -0
- package/src/index.test.ts +836 -0
- package/src/index.ts +229 -0
- package/src/internal/drop-unused-observers.test.ts +141 -0
- package/src/internal/drop-unused-observers.ts +81 -0
- package/src/internal/finalize-plugin-state.ts +43 -0
- package/src/internal/merge-observers.ts +51 -0
- package/src/internal/pattern-utils.test.ts +52 -0
- package/src/internal/pattern-utils.ts +38 -0
- package/src/internal/ref-text.test.ts +54 -0
- package/src/internal/ref-text.ts +17 -0
- package/src/internal/session-runtime.test.ts +629 -0
- package/src/internal/session-runtime.ts +201 -0
- package/src/internal/watch-matcher.test.ts +326 -0
- package/src/internal/watch-matcher.ts +200 -0
- package/src/jsdoc-propagation.test.ts +277 -0
- package/src/loader.test.ts +1098 -0
- package/src/loader.ts +581 -0
- package/src/not-block-onunknown.test.ts +554 -0
- package/src/observer-dispatcher.test.ts +939 -0
- package/src/observer-dispatcher.ts +290 -0
- package/src/per-predicate-typing.test.ts +361 -0
- package/src/plugin-merger.test.ts +790 -0
- package/src/plugin-merger.ts +598 -0
- package/src/plugins/git/README.md +485 -0
- package/src/plugins/git/branch-tracker.test.ts +281 -0
- package/src/plugins/git/branch-tracker.ts +187 -0
- package/src/plugins/git/cwd-extensions.test.ts +230 -0
- package/src/plugins/git/cwd-extensions.ts +160 -0
- package/src/plugins/git/git-ops.test.ts +397 -0
- package/src/plugins/git/git-ops.ts +173 -0
- package/src/plugins/git/index.ts +253 -0
- package/src/plugins/git/integration.test.ts +705 -0
- package/src/plugins/git/predicates.test.ts +1401 -0
- package/src/plugins/git/predicates.ts +693 -0
- package/src/plugins/git/rules.test.ts +1001 -0
- package/src/plugins/git/rules.ts +307 -0
- package/src/schema.test.ts +259 -0
- package/src/schema.ts +1963 -0
- package/src/testing/index.test.ts +1696 -0
- 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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"}
|
package/dist/loader.d.ts
ADDED
|
@@ -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"}
|