@wrongstack/techstack 0.308.0 → 0.308.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/policy/resolver.d.ts +186 -0
- package/dist/policy/rulebook.d.ts +213 -0
- package/package.json +3 -3
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TechStack — Rulebook Resolver (implementation).
|
|
3
|
+
*
|
|
4
|
+
* Pure functions that combine registry evidence (the facts) with a project
|
|
5
|
+
* rulebook (the policy) into a single `ResolvedTarget` per dependency. The
|
|
6
|
+
* resolver is called by the engine after Inventory + Enrichment but before
|
|
7
|
+
* the Analyzer. It does NOT call into the LLM — it only manipulates typed
|
|
8
|
+
* data. The LLM (advisor) reads the resolver's output as input.
|
|
9
|
+
*
|
|
10
|
+
* Determinism: same inputs + same `now` → same output. No I/O and no clock
|
|
11
|
+
* reads (the caller passes `now`), so tests pin the full decision matrix.
|
|
12
|
+
*
|
|
13
|
+
* Decision order (per SDD §1 "deterministic engines for facts"):
|
|
14
|
+
* 1. `latestStable` missing → `registry_unavailable` (never fabricate)
|
|
15
|
+
* 2. `banned` match → `banned_violated` / `banned_unresolved_replacement`
|
|
16
|
+
* 3. `pinned` match → `pinned_within_max` / `pinned_above_max`
|
|
17
|
+
* 4. `deferred` match → `deferred_until_future` / `deferred_overdue`
|
|
18
|
+
* 5. `preferred` match → `preferred_no_status_change`
|
|
19
|
+
* 6. nothing matched → `unconstrained`
|
|
20
|
+
*
|
|
21
|
+
* @see packages/techstack/src/policy/rulebook.ts — the input shape.
|
|
22
|
+
* @see packages/techstack/src/policy/status.ts — `compareVersions` is the ONE
|
|
23
|
+
* semver comparison primitive reused by `satisfiesRange`.
|
|
24
|
+
*/
|
|
25
|
+
import type { DependencyObservation } from '../types.js';
|
|
26
|
+
import type { BanEntry, DeferEntry, PackageSelector, PinEntry, PreferEntry, Rulebook, VersionRange } from './rulebook.js';
|
|
27
|
+
/**
|
|
28
|
+
* Why the resolver decided what it decided. One per affected dependency.
|
|
29
|
+
* The `reason` is a stable identifier — used by tests and by the UI as a
|
|
30
|
+
* translation key. The `detail` is a human-readable string.
|
|
31
|
+
*/
|
|
32
|
+
export type ResolveReason = 'registry_unavailable' | 'pinned_within_max' | 'pinned_above_max' | 'banned_violated' | 'banned_unresolved_replacement' | 'deferred_until_future' | 'deferred_overdue' | 'preferred_no_status_change' | 'unconstrained';
|
|
33
|
+
/**
|
|
34
|
+
* Provenance for the rule that drove a decision. Kept separate from the
|
|
35
|
+
* engine-wide `Evidence` union on purpose: a rulebook hit is policy, not
|
|
36
|
+
* registry/manifest/audit evidence, and must never be presented as such.
|
|
37
|
+
*/
|
|
38
|
+
export interface RuleEvidence {
|
|
39
|
+
readonly rule: 'pinned' | 'banned' | 'deferred' | 'preferred';
|
|
40
|
+
readonly detail: string;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The single resolved target for one dependency. The resolver produces one
|
|
44
|
+
* of these per `(dependency, rulebook)` pair. The engine then merges this
|
|
45
|
+
* into the analyzer's finding stream.
|
|
46
|
+
*/
|
|
47
|
+
export interface ResolvedTarget {
|
|
48
|
+
readonly dependencyId: string;
|
|
49
|
+
readonly selectorHits: {
|
|
50
|
+
readonly pinned: readonly PinEntry[];
|
|
51
|
+
readonly banned: readonly BanEntry[];
|
|
52
|
+
readonly deferred: readonly DeferEntry[];
|
|
53
|
+
readonly preferred: readonly PreferEntry[];
|
|
54
|
+
};
|
|
55
|
+
/** The version the rulebook wants, or `undefined` if no rule constrains it. */
|
|
56
|
+
readonly targetVersion: VersionRange | undefined;
|
|
57
|
+
/** Whether the evaluated version satisfies the rulebook. */
|
|
58
|
+
readonly satisfies: boolean;
|
|
59
|
+
/** Reason for the decision — stable identifier + human detail. */
|
|
60
|
+
readonly reason: ResolveReason;
|
|
61
|
+
readonly detail: string;
|
|
62
|
+
/** Which rule fired and why. Empty for `unconstrained`/`registry_unavailable`. */
|
|
63
|
+
readonly ruleEvidence: readonly RuleEvidence[];
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Validate a `PackageSelector` against a `(ecosystem, name)` pair.
|
|
67
|
+
*
|
|
68
|
+
* Wildcard semantics:
|
|
69
|
+
* - `namePattern` uses `*` (any non-`/` sequence) and `**` (any sequence).
|
|
70
|
+
* - `selector.ecosystem` is exact match (case-insensitive, trimmed).
|
|
71
|
+
* - `selector.name` is exact match (case-insensitive, trimmed).
|
|
72
|
+
* - At least one of `name`, `namePattern`, `ecosystem` must be present.
|
|
73
|
+
* - An empty selector matches nothing.
|
|
74
|
+
* - When both `name` and `namePattern` are present, either may match.
|
|
75
|
+
* - An `ecosystem`-only selector matches every package in that ecosystem.
|
|
76
|
+
*/
|
|
77
|
+
export declare function matchesSelector(selector: PackageSelector, ecosystem: string, name: string): boolean;
|
|
78
|
+
/**
|
|
79
|
+
* Find all rulebook entries that match a `(ecosystem, name)` pair.
|
|
80
|
+
* Pure function. Used by the advisor to filter the rulebook by context.
|
|
81
|
+
*/
|
|
82
|
+
export declare function findRuleMatches(rulebook: Rulebook, ecosystem: string, name: string): {
|
|
83
|
+
readonly pinned: readonly PinEntry[];
|
|
84
|
+
readonly banned: readonly BanEntry[];
|
|
85
|
+
readonly deferred: readonly DeferEntry[];
|
|
86
|
+
readonly preferred: readonly PreferEntry[];
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* The structured form of a rulebook `VersionRange`. The grammar is the
|
|
90
|
+
* semver-range subset the engine already understands — it is intentionally
|
|
91
|
+
* NOT a generic semver-range parser.
|
|
92
|
+
*/
|
|
93
|
+
export type ParsedRange = {
|
|
94
|
+
readonly kind: 'exact';
|
|
95
|
+
readonly version: string;
|
|
96
|
+
} | {
|
|
97
|
+
readonly kind: 'caret';
|
|
98
|
+
readonly version: string;
|
|
99
|
+
} | {
|
|
100
|
+
readonly kind: 'tilde';
|
|
101
|
+
readonly version: string;
|
|
102
|
+
} | {
|
|
103
|
+
readonly kind: 'comparator';
|
|
104
|
+
readonly op: '>=' | '<=' | '>' | '<' | '=';
|
|
105
|
+
readonly version: string;
|
|
106
|
+
} | {
|
|
107
|
+
readonly kind: 'and';
|
|
108
|
+
readonly ranges: readonly ParsedRange[];
|
|
109
|
+
} | {
|
|
110
|
+
readonly kind: 'or';
|
|
111
|
+
readonly ranges: readonly ParsedRange[];
|
|
112
|
+
};
|
|
113
|
+
export declare class RangeParseError extends Error {
|
|
114
|
+
readonly input: string;
|
|
115
|
+
readonly position: number;
|
|
116
|
+
constructor(input: string, position: number, message: string);
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Parse a `VersionRange` string into a structured range.
|
|
120
|
+
*
|
|
121
|
+
* Grammar:
|
|
122
|
+
* - exact: `1.2.3`
|
|
123
|
+
* - caret: `^1.2.3`
|
|
124
|
+
* - tilde: `~1.2.3`
|
|
125
|
+
* - comparator: `>=1.2.3` (also `<=`, `>`, `<`, `=`)
|
|
126
|
+
* - AND: `>=1.2.3 <2.0.0` (whitespace-separated)
|
|
127
|
+
* - OR: `^1.2.3 || ^2.0.0`
|
|
128
|
+
*
|
|
129
|
+
* Invalid inputs throw `RangeParseError`.
|
|
130
|
+
*/
|
|
131
|
+
export declare function parseRange(input: string): ParsedRange;
|
|
132
|
+
/**
|
|
133
|
+
* Check whether `version` satisfies `range`. Pure function.
|
|
134
|
+
* Re-uses `compareVersions` from `policy/status.ts` as the single semver
|
|
135
|
+
* comparison primitive — including its prerelease tie-break semantics.
|
|
136
|
+
*/
|
|
137
|
+
export declare function satisfiesRange(version: string, range: ParsedRange): boolean;
|
|
138
|
+
/**
|
|
139
|
+
* Resolve a single dependency against the rulebook. Pure: same inputs +
|
|
140
|
+
* same `now` → same output. See the decision order in the module header.
|
|
141
|
+
*
|
|
142
|
+
* @param dep Enriched dependency. A missing `latestStable` short-circuits
|
|
143
|
+
* to `registry_unavailable` — the resolver never fabricates.
|
|
144
|
+
* @param rulebook Optional rulebook. `undefined` → `unconstrained`.
|
|
145
|
+
* @param now Reference time for `deferred.until`; the caller passes it
|
|
146
|
+
* to keep this function deterministic.
|
|
147
|
+
*/
|
|
148
|
+
export declare function resolveDependency(dep: DependencyObservation, rulebook: Rulebook | undefined, now: Date): ResolvedTarget;
|
|
149
|
+
/**
|
|
150
|
+
* Resolve an entire observation list. Non-registry sources (path, git,
|
|
151
|
+
* system) are always `unconstrained` — the rulebook only addresses registry
|
|
152
|
+
* packages.
|
|
153
|
+
*/
|
|
154
|
+
export declare function resolveDependencies(dependencies: readonly DependencyObservation[], rulebook: Rulebook | undefined, now: Date): readonly ResolvedTarget[];
|
|
155
|
+
/**
|
|
156
|
+
* A "summary" of a rulebook across the project. The advisor (LLM) reads
|
|
157
|
+
* this to know what to recommend; the analyzer adds these as evidence
|
|
158
|
+
* to its findings. The resolver is the only writer.
|
|
159
|
+
*/
|
|
160
|
+
export interface RulebookSummary {
|
|
161
|
+
readonly totalPinned: number;
|
|
162
|
+
readonly totalBanned: number;
|
|
163
|
+
readonly totalDeferred: number;
|
|
164
|
+
readonly totalPreferred: number;
|
|
165
|
+
readonly packageManagerPreference: {
|
|
166
|
+
readonly ecosystem: string;
|
|
167
|
+
readonly manager: string;
|
|
168
|
+
}[];
|
|
169
|
+
readonly violations: readonly {
|
|
170
|
+
readonly dependencyId: string;
|
|
171
|
+
readonly reason: ResolveReason;
|
|
172
|
+
readonly detail: string;
|
|
173
|
+
}[];
|
|
174
|
+
/** Human-readable snapshot of the rulebook, ≤ 4 KiB. */
|
|
175
|
+
readonly digest: string;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Build a `RulebookSummary` from a list of resolved targets + the rulebook.
|
|
179
|
+
* Pure function. The digest is the only string the LLM is allowed to read
|
|
180
|
+
* verbatim; it MUST NOT contain `latestStable`/version numbers from the
|
|
181
|
+
* registry (those would be a fabrication risk if the rulebook is stale).
|
|
182
|
+
*
|
|
183
|
+
* Contract only — not implemented yet; nothing depends on it today.
|
|
184
|
+
*/
|
|
185
|
+
export type SummarizeRulebook = (rulebook: Rulebook, resolved: readonly ResolvedTarget[]) => RulebookSummary;
|
|
186
|
+
//# sourceMappingURL=resolver.d.ts.map
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TechStack — Project Rulebook.
|
|
3
|
+
*
|
|
4
|
+
* A Rulebook is a deterministic, project-local policy document that overrides
|
|
5
|
+
* or supplements the registry-driven defaults produced by the TechStack engine.
|
|
6
|
+
* It exists so that the LLM (`advisor/`) never has to invent policies like
|
|
7
|
+
* "we pin React 18 because of the EOL fork" — that intent lives in this file,
|
|
8
|
+
* authored by humans, and consumed by the Resolver as ground truth.
|
|
9
|
+
*
|
|
10
|
+
* Design principles (mirrors SDD §1: "deterministic engines for facts, agents for interpretation"):
|
|
11
|
+
* - The rulebook is **data**, not code. It is a JSON file under the target project.
|
|
12
|
+
* - The rulebook is **read-only** to the engine. The engine proposes changes; humans apply them.
|
|
13
|
+
* - The rulebook never sets `latestStable` — that field is always registry evidence.
|
|
14
|
+
* - The rulebook only constrains: pin, ban, prefer, defer, and provides an
|
|
15
|
+
* `advisorHints` bag for the LLM to read (not write).
|
|
16
|
+
*
|
|
17
|
+
* File location (resolution order, first hit wins):
|
|
18
|
+
* 1. Path passed to `loadRulebook(targetRoot, overridePath)`
|
|
19
|
+
* 2. `<targetRoot>/.wrongstack/techstack.rulebook.json`
|
|
20
|
+
* 3. `<targetRoot>/.wrongstack/techstack.rulebook.yaml`
|
|
21
|
+
* 4. No rulebook → Resolver falls back to "no overrides" mode.
|
|
22
|
+
*
|
|
23
|
+
* @see docs/specs/techstack-sdd.md §1, §7
|
|
24
|
+
* @see packages/techstack/src/policy/resolver.ts — the consumer of this contract.
|
|
25
|
+
*/
|
|
26
|
+
/** A semver-range string. Resolved by `policy/resolver.ts#parseRange`. */
|
|
27
|
+
export type VersionRange = string & {
|
|
28
|
+
readonly __brand: 'VersionRange';
|
|
29
|
+
};
|
|
30
|
+
/** ISO 8601 timestamp. Used for `defer.until`, `banned.since`, etc. */
|
|
31
|
+
export type IsoDateTime = string & {
|
|
32
|
+
readonly __brand: 'IsoDateTime';
|
|
33
|
+
};
|
|
34
|
+
/** Wildcard matcher over `(ecosystem, name)` pairs. */
|
|
35
|
+
export interface PackageSelector {
|
|
36
|
+
readonly ecosystem?: string | undefined;
|
|
37
|
+
readonly name?: string | undefined;
|
|
38
|
+
/**
|
|
39
|
+
* Wildcard glob over the package name. `*` matches any sequence of characters
|
|
40
|
+
* EXCEPT `/`; `**` matches across path separators. The resolver pins this
|
|
41
|
+
* down to a concrete matching function.
|
|
42
|
+
*/
|
|
43
|
+
readonly namePattern?: string | undefined;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Pin a package to a maximum version (or version range). Updates beyond
|
|
47
|
+
* `max` are flagged as `blocked_by_constraints` (not `update_available_*`).
|
|
48
|
+
* The reason is surfaced in advisor output and analyzer rationale.
|
|
49
|
+
*/
|
|
50
|
+
export interface PinEntry {
|
|
51
|
+
readonly selector: PackageSelector;
|
|
52
|
+
readonly max: VersionRange;
|
|
53
|
+
readonly reason: string;
|
|
54
|
+
/** Review-after date. After this date the rulebook emits a `investigate` finding. */
|
|
55
|
+
readonly reviewAfter?: IsoDateTime | undefined;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Ban a package entirely. The resolver emits a `replace` finding using
|
|
59
|
+
* `replacement` as the migration target when present.
|
|
60
|
+
*/
|
|
61
|
+
export interface BanEntry {
|
|
62
|
+
readonly selector: PackageSelector;
|
|
63
|
+
readonly reason: string;
|
|
64
|
+
readonly replacement?: ReplacementEntry | undefined;
|
|
65
|
+
/** When the ban took effect; advisory findings are not emitted for usages before this date. */
|
|
66
|
+
readonly since?: IsoDateTime | undefined;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Replacement target for a banned package. The Resolver emits a `replace`
|
|
70
|
+
* finding with `action: 'replace'` and `breakingRisk` derived from the
|
|
71
|
+
* major-version gap between current and replacement.
|
|
72
|
+
*/
|
|
73
|
+
export interface ReplacementEntry {
|
|
74
|
+
readonly ecosystem: string;
|
|
75
|
+
readonly name: string;
|
|
76
|
+
readonly minVersion?: VersionRange | undefined;
|
|
77
|
+
readonly notes?: string | undefined;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Defer an upgrade — the package is intentionally out-of-date and the
|
|
81
|
+
* upgrade is scheduled. The Resolver tags affected dependencies as
|
|
82
|
+
* `deferred_intentional` (a new finding kind) and suppresses
|
|
83
|
+
* `update_available_*` findings until `until`.
|
|
84
|
+
*/
|
|
85
|
+
export interface DeferEntry {
|
|
86
|
+
readonly selector: PackageSelector;
|
|
87
|
+
readonly until: IsoDateTime;
|
|
88
|
+
readonly reason: string;
|
|
89
|
+
/** Reference key (e.g. "JIRA-1234") for traceability. */
|
|
90
|
+
readonly ticket?: string | undefined;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Preferred package — when multiple implementations are valid, the advisor
|
|
94
|
+
* prefers this one. The Resolver does not change `current` status; only the
|
|
95
|
+
* advisor's ranking function reads this.
|
|
96
|
+
*/
|
|
97
|
+
export interface PreferEntry {
|
|
98
|
+
readonly selector: PackageSelector;
|
|
99
|
+
readonly context: string;
|
|
100
|
+
/** Optional replacement for a sibling package — e.g. "prefer `vitest` over `jest`". */
|
|
101
|
+
readonly deprecates?: PackageSelector | undefined;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Top-level package manager preference. The Resolver cross-references this
|
|
105
|
+
* with `Workspace.packageManager` and emits a finding when a workspace
|
|
106
|
+
* disagrees with the rulebook.
|
|
107
|
+
*/
|
|
108
|
+
export interface PackageManagerPreference {
|
|
109
|
+
readonly npm?: string | undefined;
|
|
110
|
+
readonly python?: string | undefined;
|
|
111
|
+
readonly rust?: string | undefined;
|
|
112
|
+
readonly go?: string | undefined;
|
|
113
|
+
readonly dotnet?: string | undefined;
|
|
114
|
+
readonly php?: string | undefined;
|
|
115
|
+
readonly dart?: string | undefined;
|
|
116
|
+
readonly java?: string | undefined;
|
|
117
|
+
readonly ruby?: string | undefined;
|
|
118
|
+
readonly swift?: string | undefined;
|
|
119
|
+
readonly elixir?: string | undefined;
|
|
120
|
+
}
|
|
121
|
+
/** Schema version of the rulebook on disk. Bump on breaking changes. */
|
|
122
|
+
export type RulebookSchemaVersion = '1';
|
|
123
|
+
export interface Rulebook {
|
|
124
|
+
readonly schemaVersion: RulebookSchemaVersion;
|
|
125
|
+
readonly projectId?: string | undefined;
|
|
126
|
+
readonly pinned: readonly PinEntry[];
|
|
127
|
+
readonly banned: readonly BanEntry[];
|
|
128
|
+
readonly deferred: readonly DeferEntry[];
|
|
129
|
+
readonly preferred: readonly PreferEntry[];
|
|
130
|
+
readonly packageManager?: PackageManagerPreference | undefined;
|
|
131
|
+
/**
|
|
132
|
+
* Free-form context for the LLM advisor. The Resolver never reads this;
|
|
133
|
+
* it is exposed verbatim to the advisor at suggestion time.
|
|
134
|
+
* Constraints: max 8 KiB total, no execution on read.
|
|
135
|
+
*/
|
|
136
|
+
readonly advisorHints?: string | undefined;
|
|
137
|
+
}
|
|
138
|
+
/** Result of a rulebook load attempt. Never throws for "missing" — only for "malformed". */
|
|
139
|
+
export type LoadRulebookResult = {
|
|
140
|
+
readonly kind: 'absent';
|
|
141
|
+
} | {
|
|
142
|
+
readonly kind: 'loaded';
|
|
143
|
+
readonly rulebook: Rulebook;
|
|
144
|
+
readonly source: string;
|
|
145
|
+
} | {
|
|
146
|
+
readonly kind: 'malformed';
|
|
147
|
+
readonly source: string;
|
|
148
|
+
readonly errors: readonly string[];
|
|
149
|
+
};
|
|
150
|
+
/**
|
|
151
|
+
* In-memory file system abstract for the rulebook loader. Tests inject a
|
|
152
|
+
* deterministic map; production uses `node:fs/promises`.
|
|
153
|
+
*/
|
|
154
|
+
export interface RulebookFileSystem {
|
|
155
|
+
exists(path: string): Promise<boolean>;
|
|
156
|
+
readFile(path: string): Promise<string>;
|
|
157
|
+
stat(path: string): Promise<{
|
|
158
|
+
readonly size: number;
|
|
159
|
+
}>;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Locate and parse a rulebook from the target project tree.
|
|
163
|
+
*
|
|
164
|
+
* Resolution order:
|
|
165
|
+
* 1. `overridePath` (if provided and exists)
|
|
166
|
+
* 2. `<targetRoot>/.wrongstack/techstack.rulebook.json`
|
|
167
|
+
* 3. `<targetRoot>/.wrongstack/techstack.rulebook.yaml`
|
|
168
|
+
* 4. `absent`
|
|
169
|
+
*
|
|
170
|
+
* JSON is fully supported. YAML files are detected but reported as
|
|
171
|
+
* `malformed` with an explanatory error — the vendored minimal YAML parser
|
|
172
|
+
* is scheduled for P3.1 (`adapters/parse-utils.ts`), not yet available.
|
|
173
|
+
*
|
|
174
|
+
* @param targetRoot Absolute path to the analyzed project root.
|
|
175
|
+
* @param overridePath Optional explicit path to a rulebook file.
|
|
176
|
+
* @param io File system abstraction; default uses real fs. Tests inject a mock.
|
|
177
|
+
*/
|
|
178
|
+
export declare function loadRulebook(targetRoot: string, overridePath?: string, io?: RulebookFileSystem): Promise<LoadRulebookResult>;
|
|
179
|
+
/**
|
|
180
|
+
* Validate a parsed rulebook object against `Rulebook`. Returns the list of
|
|
181
|
+
* human-readable errors. Empty list means valid.
|
|
182
|
+
*
|
|
183
|
+
* Schema invariants (any violation is an error):
|
|
184
|
+
* - `schemaVersion === '1'`
|
|
185
|
+
* - `pinned[].max` is a non-empty version range
|
|
186
|
+
* - `pinned[].reason` is non-empty
|
|
187
|
+
* - `banned[].reason` is non-empty
|
|
188
|
+
* - `deferred[].until` is a valid ISO 8601 timestamp and in the future
|
|
189
|
+
* - `advisorHints` ≤ 8192 bytes when serialized
|
|
190
|
+
* - Each `selector` reaches at least one of `name`, `namePattern`, `ecosystem`
|
|
191
|
+
* - At least one of `pinned`, `banned`, `deferred`, `preferred`,
|
|
192
|
+
* `packageManager`, `advisorHints` must be present (a totally empty
|
|
193
|
+
* rulebook is treated as "no rulebook" — see `LoadRulebookResult.kind`).
|
|
194
|
+
*/
|
|
195
|
+
export declare function validateRulebook(input: unknown): readonly string[];
|
|
196
|
+
/**
|
|
197
|
+
* Stable JSON Schema for the rulebook. Authored as a string literal so the
|
|
198
|
+
* schema travels with the package and can be emitted by tooling.
|
|
199
|
+
* Implementation: emitted by `scripts/check-rulebook.mjs` at CI time.
|
|
200
|
+
*/
|
|
201
|
+
export declare const RULEBOOK_JSON_SCHEMA: string;
|
|
202
|
+
/**
|
|
203
|
+
* Match a `(ecosystem, name)` pair against a PackageSelector.
|
|
204
|
+
* Pure function. Symmetric: `matchesSelector(selector, ecosystem, name)`.
|
|
205
|
+
*/
|
|
206
|
+
export type MatchesSelector = (selector: PackageSelector, ecosystem: string, name: string) => boolean;
|
|
207
|
+
/**
|
|
208
|
+
* Brand-construction helper for the runtime. Tests that build fixtures
|
|
209
|
+
* call this so branded types stay opaque.
|
|
210
|
+
*/
|
|
211
|
+
export declare const asVersionRange: (s: string) => VersionRange;
|
|
212
|
+
export declare const asIsoDateTime: (s: string) => IsoDateTime;
|
|
213
|
+
//# sourceMappingURL=rulebook.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wrongstack/techstack",
|
|
3
|
-
"version": "0.308.
|
|
3
|
+
"version": "0.308.2",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "WrongStack TechStack — cross-language dependency intelligence for the active target project: discover, inventory, enrich, analyze, and report.",
|
|
6
6
|
"repository": {
|
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
"!dist/**/*.map"
|
|
27
27
|
],
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@wrongstack/core": "0.308.
|
|
30
|
-
"@wrongstack/tools": "0.308.
|
|
29
|
+
"@wrongstack/core": "0.308.2",
|
|
30
|
+
"@wrongstack/tools": "0.308.2"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@types/node": "^26.2.0",
|