polydeukes 0.6.1 → 0.8.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/README.ko.md +16 -28
- package/README.md +16 -28
- package/dist/baseline.d.ts +37 -1
- package/dist/baseline.js +68 -1
- package/dist/bin.d.ts +3 -4
- package/dist/bin.js +73 -90
- package/dist/covenant/bash-line.d.ts +130 -0
- package/dist/covenant/bash-line.js +566 -0
- package/dist/covenant/declaration-engine.d.ts +108 -0
- package/dist/covenant/declaration-engine.js +415 -0
- package/dist/covenant/discipline.d.ts +109 -0
- package/dist/covenant/discipline.js +667 -0
- package/dist/covenant/dispatch.d.ts +156 -0
- package/dist/covenant/dispatch.js +250 -0
- package/dist/covenant/extract-steps.d.ts +122 -0
- package/dist/covenant/extract-steps.js +0 -0
- package/dist/covenant/mention.d.ts +117 -0
- package/dist/covenant/mention.js +209 -0
- package/dist/covenant/module.d.ts +25 -0
- package/dist/covenant/module.js +23 -0
- package/dist/covenant/mutation-rules.d.ts +40 -0
- package/dist/covenant/mutation-rules.js +146 -0
- package/dist/covenant/relations.d.ts +46 -0
- package/dist/covenant/relations.js +68 -0
- package/dist/covenant/run-covenant.d.ts +95 -0
- package/dist/covenant/run-covenant.js +164 -0
- package/dist/covenant/self-mod.d.ts +55 -0
- package/dist/covenant/self-mod.js +84 -0
- package/dist/covenant/shell-evidence.d.ts +38 -0
- package/dist/covenant/shell-evidence.js +247 -0
- package/dist/covenant/shell-mod.d.ts +84 -0
- package/dist/covenant/shell-mod.js +213 -0
- package/dist/covenant/supply.d.ts +54 -0
- package/dist/covenant/supply.js +50 -0
- package/dist/covenant/transcript-mod.d.ts +60 -0
- package/dist/covenant/transcript-mod.js +254 -0
- package/dist/covenant/ttl-witness.d.ts +47 -0
- package/dist/covenant/ttl-witness.js +80 -0
- package/dist/covenant-check.d.ts +74 -41
- package/dist/covenant-check.js +372 -129
- package/dist/diff-ir.d.ts +28 -0
- package/dist/diff-ir.js +310 -0
- package/dist/docs/README.ko.md +10 -9
- package/dist/docs/README.md +10 -9
- package/dist/docs/catalog.json +76 -28
- package/dist/docs/concepts/judgment.ko.md +2 -2
- package/dist/docs/concepts/judgment.md +2 -2
- package/dist/docs/how-to/configure-project.ko.md +16 -16
- package/dist/docs/how-to/configure-project.md +15 -12
- package/dist/docs/how-to/connect-surfaces.ko.md +67 -34
- package/dist/docs/how-to/connect-surfaces.md +67 -34
- package/dist/docs/how-to/write-disciplines.ko.md +64 -7
- package/dist/docs/how-to/write-disciplines.md +65 -7
- package/dist/docs/index.json +744 -362
- package/dist/docs/reference/cli/covenant-check.ko.md +112 -65
- package/dist/docs/reference/cli/covenant-check.md +114 -59
- package/dist/docs/reference/cli/docs.ko.md +2 -2
- package/dist/docs/reference/cli/docs.md +2 -2
- package/dist/docs/reference/cli/explain.ko.md +20 -15
- package/dist/docs/reference/cli/explain.md +22 -15
- package/dist/docs/reference/cli/init.ko.md +91 -52
- package/dist/docs/reference/cli/init.md +81 -55
- package/dist/docs/reference/configuration/index.ko.md +182 -71
- package/dist/docs/reference/configuration/index.md +188 -76
- package/dist/docs/reference/packages/adapter-claude-code.ko.md +39 -11
- package/dist/docs/reference/packages/adapter-claude-code.md +40 -14
- package/dist/docs/reference/packages/adapter-codex.ko.md +99 -0
- package/dist/docs/reference/packages/adapter-codex.md +100 -0
- package/dist/docs/reference/packages/adapter-grok.ko.md +81 -0
- package/dist/docs/reference/packages/adapter-grok.md +83 -0
- package/dist/docs/reference/packages/core.ko.md +50 -4
- package/dist/docs/reference/packages/core.md +55 -6
- package/dist/docs/reference/packages/polydeukes.ko.md +139 -77
- package/dist/docs/reference/packages/polydeukes.md +147 -81
- package/dist/docs/reference/packages/sdk-ts.ko.md +164 -0
- package/dist/docs/reference/packages/sdk-ts.md +167 -0
- package/dist/docs/troubleshooting.ko.md +51 -19
- package/dist/docs/troubleshooting.md +53 -17
- package/dist/docs/tutorials/first-judgment.ko.md +6 -4
- package/dist/docs/tutorials/first-judgment.md +6 -4
- package/dist/explain.d.ts +1 -1
- package/dist/explain.js +37 -22
- package/dist/load-config.d.ts +25 -1
- package/dist/load-config.js +21 -3
- package/dist/pre-state-reader.d.ts +1 -1
- package/dist/pre-state-reader.js +1 -1
- package/dist/scaffold-project.d.ts +2 -2
- package/dist/scaffold-project.js +12 -4
- package/dist/schema/polydeukes.schema.json +12 -0
- package/dist/worktree-reader.d.ts +19 -0
- package/dist/worktree-reader.js +30 -0
- package/package.json +5 -18
- package/dist/claude-code-hook.d.ts +0 -79
- package/dist/claude-code-hook.js +0 -372
- package/dist/claude-code.d.ts +0 -6
- package/dist/claude-code.js +0 -6
- package/dist/covenant-module.d.ts +0 -25
- package/dist/covenant-module.js +0 -42
- package/dist/docs/reference/packages/adapter-git.ko.md +0 -101
- package/dist/docs/reference/packages/adapter-git.md +0 -108
- package/dist/docs/reference/packages/covenant.ko.md +0 -115
- package/dist/docs/reference/packages/covenant.md +0 -123
- package/dist/index.d.ts +0 -22
- package/dist/index.js +0 -21
- package/dist/init-claude-code.d.ts +0 -52
- package/dist/init-claude-code.js +0 -586
- package/dist/init-grok.d.ts +0 -51
- package/dist/init-grok.js +0 -242
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path-routing dispatcher — routes an input to the covenant bodies whose protected paths
|
|
3
|
+
* it mentions.
|
|
4
|
+
*
|
|
5
|
+
* Matching is a pure path-mention core (zero I/O); execution reuses {@link runCovenant}
|
|
6
|
+
* (the sole judgment wrapper) and {@link appendRecordFailOpen} (the sole log seam). The
|
|
7
|
+
* dispatcher parses the payload once, to decide routing and to hand the judge thunks the
|
|
8
|
+
* one parsed call set they judge.
|
|
9
|
+
*/
|
|
10
|
+
import { type CanonicalTranscript, type CovenantInput, type DispatchOutcome, type EnforceLevel, type SkipReason } from '@polydeukes/core';
|
|
11
|
+
import { type JudgeOutcome } from './run-covenant.ts';
|
|
12
|
+
/**
|
|
13
|
+
* `WitnessPredicate` — the valve a registration offers, asked after a blocking judgment:
|
|
14
|
+
* the observation, the transcript, and the registration's own label with the matched subject.
|
|
15
|
+
*/
|
|
16
|
+
export type WitnessPredicate = (input: CovenantInput, transcript: CanonicalTranscript, context: {
|
|
17
|
+
label: string;
|
|
18
|
+
subject: string;
|
|
19
|
+
}) => boolean;
|
|
20
|
+
/**
|
|
21
|
+
* `CovenantRegistration` — one registered covenant.
|
|
22
|
+
*
|
|
23
|
+
* `protectedPaths` are literal path strings (the output shape of normalization, not
|
|
24
|
+
* globs); an empty array never matches, and empty-string entries are ignored (an empty
|
|
25
|
+
* `''` would match every input). `body` is the in-process judge: assembly binds its
|
|
26
|
+
* options into it, and the dispatcher supplies the parsed call set as its argument when a
|
|
27
|
+
* protected path is mentioned. Assembly therefore never needs the payload, so one compiled
|
|
28
|
+
* registration set judges every call.
|
|
29
|
+
*
|
|
30
|
+
* `witness`, when present, is consulted only after a *matched* registration's body has run
|
|
31
|
+
* and broken, receiving the injected transcript seam as its second argument and a
|
|
32
|
+
* `{ label, subject }` context naming what broke as its third: a `true` return relaxes
|
|
33
|
+
* that block (measured as `witnessed`).
|
|
34
|
+
*
|
|
35
|
+
* `matches`, when present, replaces path-mention routing with a content predicate: a
|
|
36
|
+
* non-null return routes (the string becomes the telemetry subject), null does not, and a
|
|
37
|
+
* throw is a fail-closed match with subject `'-'`.
|
|
38
|
+
*
|
|
39
|
+
* A registration carries EITHER a `body` or a `skip`. `skip` means assembly could not
|
|
40
|
+
* produce a judgeable body — the evidence channel is absent, or the declared evidence
|
|
41
|
+
* vocabulary could not be resolved — so a match records one `skipped` and upholds instead
|
|
42
|
+
* of judging. Judging it anyway would block every matched input with no legitimate pass
|
|
43
|
+
* path; throwing at assembly would take down every sibling registration and the witness
|
|
44
|
+
* valve with it. `reason` is the sentence the author reads; `kind` is the token the row
|
|
45
|
+
* carries, so the measurement separates an environment fact from a config fault.
|
|
46
|
+
*
|
|
47
|
+
* `enforce` is the AUTHOR's level for this one registration, distinct from the observer's
|
|
48
|
+
* dispatch-wide level; absence means the registration inherits whatever the dispatch
|
|
49
|
+
* carries.
|
|
50
|
+
*
|
|
51
|
+
* `sources` are what outside the target this registration's declaration names, each element
|
|
52
|
+
* carrying its own kind — a repo-relative file, a channel the surface supplies, or the
|
|
53
|
+
* session's conversation history. They are the supply layer's only input; a family that
|
|
54
|
+
* names nothing carries no key.
|
|
55
|
+
*/
|
|
56
|
+
export type CovenantRegistration = {
|
|
57
|
+
label: string;
|
|
58
|
+
protectedPaths: string[];
|
|
59
|
+
enforce?: EnforceLevel;
|
|
60
|
+
sources?: readonly ({
|
|
61
|
+
name: string;
|
|
62
|
+
file: string;
|
|
63
|
+
} | {
|
|
64
|
+
name: string;
|
|
65
|
+
sidecar: true;
|
|
66
|
+
} | {
|
|
67
|
+
name: string;
|
|
68
|
+
transcript: true;
|
|
69
|
+
})[];
|
|
70
|
+
witness?: WitnessPredicate;
|
|
71
|
+
matches?: (input: CovenantInput) => string | null;
|
|
72
|
+
} & ({
|
|
73
|
+
body: (input: CovenantInput) => Promise<JudgeOutcome>;
|
|
74
|
+
skip?: never;
|
|
75
|
+
} | {
|
|
76
|
+
body?: never;
|
|
77
|
+
skip: {
|
|
78
|
+
reason: string;
|
|
79
|
+
kind: SkipReason;
|
|
80
|
+
};
|
|
81
|
+
});
|
|
82
|
+
/**
|
|
83
|
+
* Match registrations against a {@link CovenantInput} by path mention (pure).
|
|
84
|
+
*
|
|
85
|
+
* A registration matches when any of its `protectedPaths` is an ancestor/descendant/equal of
|
|
86
|
+
* a path candidate. Candidates come from two places: every string value reachable at any
|
|
87
|
+
* depth inside `input.toolCalls[].args`, read as quote-aware tokenizer words so a
|
|
88
|
+
* quote-split write still routes, and each call's proven `fileChange` path, taken whole
|
|
89
|
+
* because an adapter already resolved it. A call carrying only the latter is the ordinary
|
|
90
|
+
* shape where a host names its target in a form the adapter must parse — the patch text of
|
|
91
|
+
* one edit rather than a path argument. An unread span with a non-empty `protectedPaths`
|
|
92
|
+
* routes fail-closed (the registration matches on its first protected path) rather than
|
|
93
|
+
* silently miss. `subagentSpawns` and `userMessages` never participate. `mentionedPath` is
|
|
94
|
+
* the first protected path (in array order) that mentions. Result preserves registration
|
|
95
|
+
* order, at most one entry per registration.
|
|
96
|
+
*
|
|
97
|
+
* A registration carrying a `matches` predicate routes on it exclusively, path mention
|
|
98
|
+
* skipped for it: non-null return → included with that string as `mentionedPath`; null →
|
|
99
|
+
* skipped; a throw → fail-closed inclusion with `'-'` (caught per registration, never
|
|
100
|
+
* bubbling to the dispatcher-level catch).
|
|
101
|
+
*/
|
|
102
|
+
export declare function matchRegistrations(input: CovenantInput, registrations: CovenantRegistration[]): {
|
|
103
|
+
registration: CovenantRegistration;
|
|
104
|
+
mentionedPath: string;
|
|
105
|
+
routingFailed?: boolean;
|
|
106
|
+
}[];
|
|
107
|
+
/** A registration assembled by a meta-covenant: one that always carries a judgeable body. */
|
|
108
|
+
export type MetaCovenantRegistration = CovenantRegistration & {
|
|
109
|
+
body: NonNullable<CovenantRegistration['body']>;
|
|
110
|
+
};
|
|
111
|
+
/** `dispatchCovenants` input — the payload, the registration table, and the dispatch posture. */
|
|
112
|
+
export type DispatchCovenantsSpec = {
|
|
113
|
+
stdinPayload: string;
|
|
114
|
+
registrations: CovenantRegistration[];
|
|
115
|
+
telemetryPath: string;
|
|
116
|
+
dispatcherLabel?: string;
|
|
117
|
+
transcript?: CanonicalTranscript;
|
|
118
|
+
enforce?: EnforceLevel;
|
|
119
|
+
world?: CovenantInput['world'];
|
|
120
|
+
};
|
|
121
|
+
/**
|
|
122
|
+
* Dispatch covenants for a stdin payload.
|
|
123
|
+
*
|
|
124
|
+
* Fail-closed: an unjudgeable payload — unparseable JSON, or a parseable one whose
|
|
125
|
+
* structure defeats the matching traversal (a null toolCalls element, adversarially deep
|
|
126
|
+
* nesting) — yields exitCode 2, judges nothing, and appends exactly one `blocked` record
|
|
127
|
+
* for the dispatcher itself. "Cannot judge" means block; it never means throw, because an
|
|
128
|
+
* uncaught rejection exits the hook with a non-blocking code and becomes a bypass vector.
|
|
129
|
+
* On matches, every matched registration runs sequentially via {@link runCovenant}
|
|
130
|
+
* (run-all, no short-circuit); the verdict is `2` if any body blocks, else `0`. No matches
|
|
131
|
+
* passes vacuously with zero judgments and zero telemetry.
|
|
132
|
+
*
|
|
133
|
+
* World: `spec.world` is the supply layer's result, attached to the parsed input before
|
|
134
|
+
* routing so the body, the routing predicate, and the valve all judge one world. A world
|
|
135
|
+
* the payload itself carries stands only when the spec names none.
|
|
136
|
+
*
|
|
137
|
+
* Witness: the dispatcher only BINDS the witness's arguments — the parsed input, the
|
|
138
|
+
* injected `spec.transcript` (`noopTranscript` when omitted), and a `{ label, subject }`
|
|
139
|
+
* context naming the registration and its matched path — and hands the thunk to
|
|
140
|
+
* {@link runCovenant}, which consults it after the judgment and only when the body's
|
|
141
|
+
* outcome translated to `blocked`. So the body always runs: a matched registration that
|
|
142
|
+
* upholds is never witnessed, and a `true` return relaxes a real break into
|
|
143
|
+
* `0` / `witnessed`. A predicate that throws opens nothing: an uncertain witness never
|
|
144
|
+
* leaks toward fail-open.
|
|
145
|
+
*
|
|
146
|
+
* Enforce: the level has two owners — `spec.enforce` is the observer's posture for the
|
|
147
|
+
* whole dispatch, `registration.enforce` the author's for one entry — and the dispatcher
|
|
148
|
+
* composes them per registration with the lenient side winning, then threads the effective
|
|
149
|
+
* level into {@link runCovenant}, where the translation table lives. Lenient-wins keeps an
|
|
150
|
+
* explicit `block` entry from raising a surface the observer lowered, and lets one entry
|
|
151
|
+
* lower itself under a block surface. The dispatcher's own fail-closed is outside that axis
|
|
152
|
+
* and outside the valve too (nothing judged, so no verdict to relax). Each results entry
|
|
153
|
+
* surfaces the telemetry `event` the wrapper recorded, never a recomputed one — the valve
|
|
154
|
+
* is impure, and a recompute would consult it twice for one verdict.
|
|
155
|
+
*/
|
|
156
|
+
export declare function dispatchCovenants(spec: DispatchCovenantsSpec): Promise<DispatchOutcome>;
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path-routing dispatcher — routes an input to the covenant bodies whose protected paths
|
|
3
|
+
* it mentions.
|
|
4
|
+
*
|
|
5
|
+
* Matching is a pure path-mention core (zero I/O); execution reuses {@link runCovenant}
|
|
6
|
+
* (the sole judgment wrapper) and {@link appendRecordFailOpen} (the sole log seam). The
|
|
7
|
+
* dispatcher parses the payload once, to decide routing and to hand the judge thunks the
|
|
8
|
+
* one parsed call set they judge.
|
|
9
|
+
*/
|
|
10
|
+
import { appendRecordFailOpen, EXIT_BREAK_BLOCKING, EXIT_UPHOLD, noopTranscript, parseInput, } from '@polydeukes/core';
|
|
11
|
+
import { tokenizeCommandLine } from './bash-line.js';
|
|
12
|
+
import { pathCandidates, pathMatchesProtected, provenChangePath } from './mention.js';
|
|
13
|
+
import { runCovenant } from './run-covenant.js';
|
|
14
|
+
/**
|
|
15
|
+
* Collect path candidates from every string value inside `value`. Each string is tokenized
|
|
16
|
+
* quote-aware (via the shared tokenizer) so quote/escape splits collapse to the word the
|
|
17
|
+
* shell would see; each resulting word text is a candidate. An unread span surfaces as
|
|
18
|
+
* `failed = true` so the caller can route fail-closed rather than fall back to a
|
|
19
|
+
* raw-substring scan — the candidates the same line's read commands contribute add routing
|
|
20
|
+
* precision, they never withdraw that flag.
|
|
21
|
+
*/
|
|
22
|
+
function collectPathCandidates(value) {
|
|
23
|
+
const candidates = [];
|
|
24
|
+
let failed = false;
|
|
25
|
+
const walk = (node) => {
|
|
26
|
+
if (typeof node === 'string') {
|
|
27
|
+
const result = tokenizeCommandLine(node);
|
|
28
|
+
if (result.unread.length > 0)
|
|
29
|
+
failed = true;
|
|
30
|
+
for (const command of result.commands) {
|
|
31
|
+
// Split each tokenized word the same way mentionsPath does, so a path fused to
|
|
32
|
+
// another lexeme (`--dest=path`) still surfaces as its own candidate.
|
|
33
|
+
for (const word of command.words)
|
|
34
|
+
candidates.push(...pathCandidates(word.text));
|
|
35
|
+
for (const redirect of command.redirects) {
|
|
36
|
+
candidates.push(...pathCandidates(redirect.target.text));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (Array.isArray(node)) {
|
|
42
|
+
for (const item of node)
|
|
43
|
+
walk(item);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (typeof node === 'object' && node !== null) {
|
|
47
|
+
for (const item of Object.values(node))
|
|
48
|
+
walk(item);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
walk(value);
|
|
52
|
+
return { candidates, failed };
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Match registrations against a {@link CovenantInput} by path mention (pure).
|
|
56
|
+
*
|
|
57
|
+
* A registration matches when any of its `protectedPaths` is an ancestor/descendant/equal of
|
|
58
|
+
* a path candidate. Candidates come from two places: every string value reachable at any
|
|
59
|
+
* depth inside `input.toolCalls[].args`, read as quote-aware tokenizer words so a
|
|
60
|
+
* quote-split write still routes, and each call's proven `fileChange` path, taken whole
|
|
61
|
+
* because an adapter already resolved it. A call carrying only the latter is the ordinary
|
|
62
|
+
* shape where a host names its target in a form the adapter must parse — the patch text of
|
|
63
|
+
* one edit rather than a path argument. An unread span with a non-empty `protectedPaths`
|
|
64
|
+
* routes fail-closed (the registration matches on its first protected path) rather than
|
|
65
|
+
* silently miss. `subagentSpawns` and `userMessages` never participate. `mentionedPath` is
|
|
66
|
+
* the first protected path (in array order) that mentions. Result preserves registration
|
|
67
|
+
* order, at most one entry per registration.
|
|
68
|
+
*
|
|
69
|
+
* A registration carrying a `matches` predicate routes on it exclusively, path mention
|
|
70
|
+
* skipped for it: non-null return → included with that string as `mentionedPath`; null →
|
|
71
|
+
* skipped; a throw → fail-closed inclusion with `'-'` (caught per registration, never
|
|
72
|
+
* bubbling to the dispatcher-level catch).
|
|
73
|
+
*/
|
|
74
|
+
export function matchRegistrations(input, registrations) {
|
|
75
|
+
const { candidates, failed } = collectPathCandidates(input.toolCalls.map((call) => call.args));
|
|
76
|
+
// A proven change path joins the candidates directly, untokenized: it is a path the
|
|
77
|
+
// adapter already resolved, not a line to be read for one, and tokenizing it would split
|
|
78
|
+
// a path containing a space into words that name nothing. Without this a call whose only
|
|
79
|
+
// evidence is its `fileChange` routes nowhere — the judge bodies read that evidence, so
|
|
80
|
+
// the input would be refused before the layer that can settle it.
|
|
81
|
+
for (const call of input.toolCalls) {
|
|
82
|
+
const proven = provenChangePath(call);
|
|
83
|
+
if (proven !== null)
|
|
84
|
+
candidates.push(proven);
|
|
85
|
+
}
|
|
86
|
+
const matches = [];
|
|
87
|
+
for (const registration of registrations) {
|
|
88
|
+
if (registration.matches !== undefined) {
|
|
89
|
+
let subject;
|
|
90
|
+
let routingFailed = false;
|
|
91
|
+
try {
|
|
92
|
+
subject = registration.matches(input);
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
// An uncertain predicate must not leak fail-open — route with subject '-'. The
|
|
96
|
+
// flag travels with the match because a skip registration has no body to carry
|
|
97
|
+
// that verdict out, and answering `skipped` there would turn the fail-closed
|
|
98
|
+
// routing into a pass.
|
|
99
|
+
subject = '-';
|
|
100
|
+
routingFailed = true;
|
|
101
|
+
}
|
|
102
|
+
if (subject !== null) {
|
|
103
|
+
matches.push({ registration, mentionedPath: subject, routingFailed });
|
|
104
|
+
}
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
const paths = registration.protectedPaths.filter((path) => path !== '');
|
|
108
|
+
const mentionedPath = paths.find((path) => candidates.some((candidate) => pathMatchesProtected(candidate, path))) ??
|
|
109
|
+
(failed ? paths[0] : undefined);
|
|
110
|
+
if (mentionedPath !== undefined) {
|
|
111
|
+
matches.push({ registration, mentionedPath });
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return matches;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Dispatch covenants for a stdin payload.
|
|
118
|
+
*
|
|
119
|
+
* Fail-closed: an unjudgeable payload — unparseable JSON, or a parseable one whose
|
|
120
|
+
* structure defeats the matching traversal (a null toolCalls element, adversarially deep
|
|
121
|
+
* nesting) — yields exitCode 2, judges nothing, and appends exactly one `blocked` record
|
|
122
|
+
* for the dispatcher itself. "Cannot judge" means block; it never means throw, because an
|
|
123
|
+
* uncaught rejection exits the hook with a non-blocking code and becomes a bypass vector.
|
|
124
|
+
* On matches, every matched registration runs sequentially via {@link runCovenant}
|
|
125
|
+
* (run-all, no short-circuit); the verdict is `2` if any body blocks, else `0`. No matches
|
|
126
|
+
* passes vacuously with zero judgments and zero telemetry.
|
|
127
|
+
*
|
|
128
|
+
* World: `spec.world` is the supply layer's result, attached to the parsed input before
|
|
129
|
+
* routing so the body, the routing predicate, and the valve all judge one world. A world
|
|
130
|
+
* the payload itself carries stands only when the spec names none.
|
|
131
|
+
*
|
|
132
|
+
* Witness: the dispatcher only BINDS the witness's arguments — the parsed input, the
|
|
133
|
+
* injected `spec.transcript` (`noopTranscript` when omitted), and a `{ label, subject }`
|
|
134
|
+
* context naming the registration and its matched path — and hands the thunk to
|
|
135
|
+
* {@link runCovenant}, which consults it after the judgment and only when the body's
|
|
136
|
+
* outcome translated to `blocked`. So the body always runs: a matched registration that
|
|
137
|
+
* upholds is never witnessed, and a `true` return relaxes a real break into
|
|
138
|
+
* `0` / `witnessed`. A predicate that throws opens nothing: an uncertain witness never
|
|
139
|
+
* leaks toward fail-open.
|
|
140
|
+
*
|
|
141
|
+
* Enforce: the level has two owners — `spec.enforce` is the observer's posture for the
|
|
142
|
+
* whole dispatch, `registration.enforce` the author's for one entry — and the dispatcher
|
|
143
|
+
* composes them per registration with the lenient side winning, then threads the effective
|
|
144
|
+
* level into {@link runCovenant}, where the translation table lives. Lenient-wins keeps an
|
|
145
|
+
* explicit `block` entry from raising a surface the observer lowered, and lets one entry
|
|
146
|
+
* lower itself under a block surface. The dispatcher's own fail-closed is outside that axis
|
|
147
|
+
* and outside the valve too (nothing judged, so no verdict to relax). Each results entry
|
|
148
|
+
* surfaces the telemetry `event` the wrapper recorded, never a recomputed one — the valve
|
|
149
|
+
* is impure, and a recompute would consult it twice for one verdict.
|
|
150
|
+
*/
|
|
151
|
+
export async function dispatchCovenants(spec) {
|
|
152
|
+
const blockedByDispatcher = () => {
|
|
153
|
+
appendRecordFailOpen(spec.telemetryPath, {
|
|
154
|
+
event: 'blocked',
|
|
155
|
+
label: spec.dispatcherLabel ?? 'dispatcher',
|
|
156
|
+
subject: '-',
|
|
157
|
+
});
|
|
158
|
+
return { exitCode: EXIT_BREAK_BLOCKING, results: [] };
|
|
159
|
+
};
|
|
160
|
+
const parsed = parseInput(spec.stdinPayload);
|
|
161
|
+
if (!parsed.ok) {
|
|
162
|
+
return blockedByDispatcher();
|
|
163
|
+
}
|
|
164
|
+
// The world the supply layer built, spliced onto the parsed input here so a composition
|
|
165
|
+
// root never reopens the payload string its adapter path produced. Without one the key
|
|
166
|
+
// stays absent rather than holding `undefined`: the judge derives the change set from
|
|
167
|
+
// the field's absence, and an empty object would read as "supplied nothing".
|
|
168
|
+
const input = spec.world === undefined ? parsed.value : { ...parsed.value, world: spec.world };
|
|
169
|
+
let matches;
|
|
170
|
+
try {
|
|
171
|
+
matches = matchRegistrations(input, spec.registrations);
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
// Structurally unjudgeable input (parseInput validates the collection shapes, not the
|
|
175
|
+
// element ones) — fail-closed, same as an unparseable payload.
|
|
176
|
+
return blockedByDispatcher();
|
|
177
|
+
}
|
|
178
|
+
const transcript = spec.transcript ?? noopTranscript;
|
|
179
|
+
const results = [];
|
|
180
|
+
for (const { registration, mentionedPath, routingFailed } of matches) {
|
|
181
|
+
if (registration.skip !== undefined) {
|
|
182
|
+
if (routingFailed === true) {
|
|
183
|
+
// The routing predicate could not answer, which matchRegistrations already
|
|
184
|
+
// resolved fail-closed. A body-bearing registration would carry that verdict out
|
|
185
|
+
// by judging and blocking; a skip has no body, so the block is recorded here
|
|
186
|
+
// rather than softened into a pass. Outside the enforce axis, like every
|
|
187
|
+
// unjudgeable outcome.
|
|
188
|
+
appendRecordFailOpen(spec.telemetryPath, {
|
|
189
|
+
event: 'blocked',
|
|
190
|
+
label: registration.label,
|
|
191
|
+
subject: mentionedPath,
|
|
192
|
+
});
|
|
193
|
+
results.push({
|
|
194
|
+
label: registration.label,
|
|
195
|
+
exitCode: EXIT_BREAK_BLOCKING,
|
|
196
|
+
event: 'blocked',
|
|
197
|
+
});
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
// Nothing to judge and nothing to witness — the valve exists for a verdict, and a
|
|
201
|
+
// skip has none. Recording it keeps the no-op visible in `gain`.
|
|
202
|
+
appendRecordFailOpen(spec.telemetryPath, {
|
|
203
|
+
event: 'skipped',
|
|
204
|
+
label: registration.label,
|
|
205
|
+
subject: mentionedPath,
|
|
206
|
+
reason: registration.skip.kind,
|
|
207
|
+
});
|
|
208
|
+
results.push({ label: registration.label, exitCode: EXIT_UPHOLD, event: 'skipped' });
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
// Bound here, consulted in the wrapper: the context is what the umbrella prompt
|
|
212
|
+
// needs to name what broke. The local alias exists for TypeScript narrowing — a
|
|
213
|
+
// property access cannot stay narrowed inside the closure below.
|
|
214
|
+
const witness = registration.witness;
|
|
215
|
+
// Absence stays absent: the block default lives in the wrapper, not restated here.
|
|
216
|
+
// A routing that could not answer is outside the level axis on this arm too (the skip
|
|
217
|
+
// arm above already is): the body judges against subject '-' and its break must land
|
|
218
|
+
// blocked whatever level the entry or the surface declared. Every compiled entry
|
|
219
|
+
// carries a level, so without this the unjudgeable call would advise.
|
|
220
|
+
const effectiveEnforce = routingFailed === true
|
|
221
|
+
? undefined
|
|
222
|
+
: registration.enforce === 'advise'
|
|
223
|
+
? 'advise'
|
|
224
|
+
: spec.enforce;
|
|
225
|
+
// The parsed call set is handed to the judge HERE rather than baked in at assembly:
|
|
226
|
+
// the dispatcher is the one place that has it, so assembly stays payload-free and one
|
|
227
|
+
// compiled registration set serves every payload.
|
|
228
|
+
const body = registration.body;
|
|
229
|
+
const { exitCode, event } = await runCovenant({
|
|
230
|
+
body: () => body(input),
|
|
231
|
+
label: registration.label,
|
|
232
|
+
subject: mentionedPath,
|
|
233
|
+
telemetryPath: spec.telemetryPath,
|
|
234
|
+
enforce: effectiveEnforce,
|
|
235
|
+
...(witness !== undefined
|
|
236
|
+
? {
|
|
237
|
+
witness: () => witness(input, transcript, {
|
|
238
|
+
label: registration.label,
|
|
239
|
+
subject: mentionedPath,
|
|
240
|
+
}),
|
|
241
|
+
}
|
|
242
|
+
: {}),
|
|
243
|
+
});
|
|
244
|
+
results.push({ label: registration.label, exitCode, event });
|
|
245
|
+
}
|
|
246
|
+
const exitCode = results.some((result) => result.exitCode === EXIT_BREAK_BLOCKING)
|
|
247
|
+
? EXIT_BREAK_BLOCKING
|
|
248
|
+
: EXIT_UPHOLD;
|
|
249
|
+
return { exitCode, results };
|
|
250
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `EXTRACT_STEPS` — the registry of unary extract steps, the open half of the extraction
|
|
3
|
+
* vocabulary. A declaration can name a step only if this table carries it; anything else is
|
|
4
|
+
* refused when the declaration is compiled, before any value is produced.
|
|
5
|
+
*
|
|
6
|
+
* Every entry is a triple: `args` is the closed set of argument keys it admits, `validate`
|
|
7
|
+
* turns a wrong key or a wrong type into a config fault, and `run` maps `Items` to `Items`
|
|
8
|
+
* and to nothing else — no step answers a boolean, because the last comparison of a
|
|
9
|
+
* judgment belongs to the relation position.
|
|
10
|
+
*
|
|
11
|
+
* The seventeen steps:
|
|
12
|
+
*
|
|
13
|
+
* - `source` — lift `World[of]` into a single item keyed `'0'`.
|
|
14
|
+
* - `json` — parse each item's string value; an unparseable value fails supply.
|
|
15
|
+
* - `select` — project a dot path, spreading an array result into position-keyed items; a
|
|
16
|
+
* path the object does not carry projects to nothing.
|
|
17
|
+
* - `items` — spread each array value one level into position-keyed items; a non-array value
|
|
18
|
+
* is dropped, and an element that is itself an array stays one item. The keys are
|
|
19
|
+
* positions within their own array, so two items each holding one collide under `'0'`,
|
|
20
|
+
* and a relation that compares keys reads them as positions and nothing else. Pair this
|
|
21
|
+
* step with `keyBy` when the elements are objects carrying the key the relation needs.
|
|
22
|
+
* - `keyBy` — re-index each element by one of its fields, as a string.
|
|
23
|
+
* - `keyByPattern` — re-index each item by capture group 1 of a constant regular expression
|
|
24
|
+
* over its stringified value, dropping what does not match or leaves the group unbound.
|
|
25
|
+
* The pair of `keyBy`: one re-keys by an object field, the other by a pattern over the
|
|
26
|
+
* value.
|
|
27
|
+
* - `field` — keep the key, replace the value with one property of it.
|
|
28
|
+
* - `filter` — keep the items every constant predicate admits.
|
|
29
|
+
* - `flattenKeys` — enumerate a nested object's leaves as dot paths, key and value alike.
|
|
30
|
+
* - `sort` — stable sort by value: numbers numerically, strings by code point.
|
|
31
|
+
* - `lines` — split a string into trimmed non-empty lines keyed by source line number.
|
|
32
|
+
* - `matches` — keep the items whose value matches a constant regular expression.
|
|
33
|
+
* - `toolUses` — one item per tool call of a session snapshot, keyed by the call's own
|
|
34
|
+
* observation ordinal; `names` and `subagentType` narrow it and conjoin.
|
|
35
|
+
* - `userTexts` — one item per user turn of a session snapshot whose text matches a
|
|
36
|
+
* constant expression, keyed by the turn's ordinal, the value carrying the snapshot clock.
|
|
37
|
+
* - `agentType` — one item per parsed sidecar record whose `agentType` equals `is`, keyed by
|
|
38
|
+
* its position in the record list.
|
|
39
|
+
* - `first` — the first item alone, under its own key; it does not sort.
|
|
40
|
+
* - `ageMs` — add `ageMs = observedAtMs − timestampMs` to each value, dropping an item whose
|
|
41
|
+
* timestamp is not a number or lies after the clock, because an undated or future-dated
|
|
42
|
+
* observation is not a recent one.
|
|
43
|
+
*
|
|
44
|
+
* A step that would compare two world values against each other is not a unary step: that
|
|
45
|
+
* comparison belongs to a combinator, and the combinators are three and closed.
|
|
46
|
+
*/
|
|
47
|
+
/** One element of an extraction. `key` combines and compares, `value` is what a relation sees. */
|
|
48
|
+
export type Item = {
|
|
49
|
+
readonly key: string;
|
|
50
|
+
readonly value: unknown;
|
|
51
|
+
};
|
|
52
|
+
/** The input and the output of every extract step; the order is the supply's. */
|
|
53
|
+
export type Items = readonly Item[];
|
|
54
|
+
/** An extraction split by a paired source into its two states. */
|
|
55
|
+
export type PairedItems = {
|
|
56
|
+
readonly pre: Items;
|
|
57
|
+
readonly post: Items;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* One session's observations as plain data — the value a host flattens its own history into
|
|
61
|
+
* once, so this module judges data rather than a query interface. `index` is an observation
|
|
62
|
+
* ordinal within its own list, and `observedAtMs` the clock the ages are measured against.
|
|
63
|
+
*/
|
|
64
|
+
export type SessionSnapshot = {
|
|
65
|
+
readonly observedAtMs: number;
|
|
66
|
+
readonly userMessages: readonly {
|
|
67
|
+
index: number;
|
|
68
|
+
text: string;
|
|
69
|
+
timestampMs?: number;
|
|
70
|
+
}[];
|
|
71
|
+
readonly toolCalls: readonly {
|
|
72
|
+
index: number;
|
|
73
|
+
name: string;
|
|
74
|
+
args: Record<string, unknown>;
|
|
75
|
+
succeeded?: boolean;
|
|
76
|
+
}[];
|
|
77
|
+
};
|
|
78
|
+
/** Source name to value. `state` carries `{ pre, post }` and is read as a pair. */
|
|
79
|
+
export type World = Readonly<Record<string, unknown>>;
|
|
80
|
+
/** What compilation answers with instead of a compiled declaration; never a throw. */
|
|
81
|
+
export type ConfigFault = {
|
|
82
|
+
readonly kind: 'config-fault';
|
|
83
|
+
readonly location: string;
|
|
84
|
+
readonly reason: string;
|
|
85
|
+
};
|
|
86
|
+
/** One registry entry: the closed argument keys, their validator, and the runner. */
|
|
87
|
+
export type ExtractStepEntry = {
|
|
88
|
+
readonly args: ReadonlySet<string>;
|
|
89
|
+
validate(args: Readonly<Record<string, unknown>>, location: string): ConfigFault | undefined;
|
|
90
|
+
run(items: Items, args: Readonly<Record<string, unknown>>): Items;
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* A value `json` could not parse, thrown out of `run` and caught by the engine, which
|
|
94
|
+
* names the source and answers a supply failure. It never leaves the engine.
|
|
95
|
+
*/
|
|
96
|
+
export declare class SupplyFailure extends Error {
|
|
97
|
+
}
|
|
98
|
+
/** Build the value compilation answers with when a declaration is wrong at `location`. */
|
|
99
|
+
export declare function fault(location: string, reason: string): ConfigFault;
|
|
100
|
+
/**
|
|
101
|
+
* The canonical form of a value for structural comparison — object keys sorted so that two
|
|
102
|
+
* objects written in different orders compare equal. `undefined` is distinct from `null`:
|
|
103
|
+
* an absent field and a field set to null are different answers, and a discipline that
|
|
104
|
+
* excludes `null` must not thereby exclude the absent one.
|
|
105
|
+
*/
|
|
106
|
+
export declare function canonical(value: unknown): string;
|
|
107
|
+
/** Structural equality — the equality every relation and the `filter` predicates use. */
|
|
108
|
+
export declare function sameValue(left: unknown, right: unknown): boolean;
|
|
109
|
+
/**
|
|
110
|
+
* The order `sort` and `Ordered` share, chosen once for a whole list rather than pair by
|
|
111
|
+
* pair: all-numbers compare numerically, anything else by its stringified form. A per-pair
|
|
112
|
+
* choice would order a mixed list inconsistently — `2 < 10` numerically and `'10' < '2'` as
|
|
113
|
+
* text, so the answer would depend on which pairs the comparison happened to visit.
|
|
114
|
+
*/
|
|
115
|
+
export declare function comparatorFor(items: Items): (left: Item, right: Item) => number;
|
|
116
|
+
/**
|
|
117
|
+
* The unary vocabulary, by step name. A declaration may name only these; the engine looks a
|
|
118
|
+
* step up here when it compiles and refuses any name the table does not carry.
|
|
119
|
+
*/
|
|
120
|
+
export declare const EXTRACT_STEPS: Readonly<Record<string, ExtractStepEntry>>;
|
|
121
|
+
/** The names the registry carries — the whole of the unary vocabulary. */
|
|
122
|
+
export declare const UNARY_STEP_NAMES: readonly string[];
|
|
Binary file
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `mentionsPath` — the single path-mention semantic shared by the dispatcher and the
|
|
3
|
+
* pure judges.
|
|
4
|
+
*
|
|
5
|
+
* Both the path-routing dispatcher (`matchRegistrations`) and any covenant judge that keys
|
|
6
|
+
* on a protected path import this one function, so the two layers can never drift apart.
|
|
7
|
+
* Argument names are never inspected — only string *values* are scanned, at any depth,
|
|
8
|
+
* keeping the traversal agent-neutral.
|
|
9
|
+
*
|
|
10
|
+
* Interior `.`/`..` resolution is a SECOND comparison, never a replacement: raw segments
|
|
11
|
+
* match first, dot-resolved segments only if that fails. Because the result is a union, a
|
|
12
|
+
* newly closed notation can only ever add matches — a replacement pass would silently
|
|
13
|
+
* withdraw defences that the raw comparison already had.
|
|
14
|
+
*
|
|
15
|
+
* A glob, a variable expansion, and a tilde are deliberately NOT read. None can be resolved
|
|
16
|
+
* without running the shell or touching the filesystem, and a judge that guesses at them
|
|
17
|
+
* either misses the real target or blocks an innocent one. They stay undecidable here and
|
|
18
|
+
* are answered where undecidability belongs: the Bash axis's opaque-token rule and the skip
|
|
19
|
+
* registrations. A spelling some layer genuinely *can* resolve — the home directory in front
|
|
20
|
+
* of the session transcript — is closed by the layer that knows the value, via a dedicated
|
|
21
|
+
* `matches` predicate rather than a protected path, so no home spelling is inferred here.
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* Normalize a path into segments: strip leading `./`, trailing `/`, split on `/`, drop
|
|
25
|
+
* empties. Exported so the self-mod judge can tell a judgeable evidence path from a
|
|
26
|
+
* degenerate one (`''`, `'.'`, `'/'` — zero segments) that proves nothing.
|
|
27
|
+
*
|
|
28
|
+
* A lone `.` survives as a segment, and resolving interior dots is a separate pass inside
|
|
29
|
+
* {@link pathMatchesProtected}. Folding that pass in here would change what the
|
|
30
|
+
* degenerate-evidence check built on top of this function counts as degenerate.
|
|
31
|
+
*/
|
|
32
|
+
export declare function pathSegments(path: string): string[];
|
|
33
|
+
/**
|
|
34
|
+
* Return the mutation target proven by a call's `fileChange` evidence, or `null` when the
|
|
35
|
+
* evidence proves none.
|
|
36
|
+
*
|
|
37
|
+
* @param call - The tool call whose evidence is inspected
|
|
38
|
+
* @returns The change path when the evidence carries a recognized kind and a path with at
|
|
39
|
+
* least one non-`.` segment; otherwise `null`
|
|
40
|
+
*/
|
|
41
|
+
export declare function provenChangePath(call: {
|
|
42
|
+
fileChange?: unknown;
|
|
43
|
+
}): string | null;
|
|
44
|
+
/**
|
|
45
|
+
* Resolve `.` and `..` against the preceding segment — pure string work, no filesystem and
|
|
46
|
+
* no working directory, so the answer is the same wherever the judge runs.
|
|
47
|
+
*
|
|
48
|
+
* A `..` with nothing left to cancel is KEPT rather than dropped. Dropping it would collapse
|
|
49
|
+
* `../packages` into `packages` and hand a sibling checkout the protection meant for this
|
|
50
|
+
* one; keeping it leaves a segment that matches nothing, which is the honest answer for a
|
|
51
|
+
* path that points outside the tree.
|
|
52
|
+
*
|
|
53
|
+
* Exported so a judge whose own equality needs the same second pass (the transcript
|
|
54
|
+
* predicate) shares this one implementation instead of forking it per site.
|
|
55
|
+
*/
|
|
56
|
+
export declare function resolveDotSegments(segments: string[]): string[];
|
|
57
|
+
/**
|
|
58
|
+
* True iff `candidate` names the protected path, a descendant of it, or a (relative) ancestor
|
|
59
|
+
* of it — compared on path segments, not raw substrings, by {@link segmentsMatch}.
|
|
60
|
+
*
|
|
61
|
+
* Two passes, unioned. The raw pass is the shipped semantic and runs first: a command that
|
|
62
|
+
* spells the protected path out loud is caught by it no matter what the path resolves to
|
|
63
|
+
* afterwards, which is why `rm -rf .claude/hooks/../..` breaks here rather than needing a
|
|
64
|
+
* rule of its own. The dot-resolved pass runs only when the raw one finds nothing, and is
|
|
65
|
+
* what `packages/core/./dist/index.js` and `packages/core/src/../dist/index.js` need. Because
|
|
66
|
+
* it is a union it can only ever add matches, never withdraw one.
|
|
67
|
+
*/
|
|
68
|
+
export declare function pathMatchesProtected(candidate: string, protectedPath: string): boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Extract path candidates from one string token. The token is split on shell separators
|
|
71
|
+
* that join a path to other lexemes — whitespace, `=`, `,`, parentheses, backtick — so a path
|
|
72
|
+
* embedded in a compound token (a `--flag=path`, an opaque command substitution, an eval's
|
|
73
|
+
* quoted argument) surfaces as its own candidate while a standalone token stays intact (so the
|
|
74
|
+
* segment-boundary trap still rejects a sibling like `core/src-generated`). `/` is never a
|
|
75
|
+
* separator (it is the path's own segment boundary); `:` is deliberately NOT a separator
|
|
76
|
+
* either — splitting on it shatters URLs (`https://…`) into fragments that the offset-free
|
|
77
|
+
* descendant match then over-blocks, and a colon-joined path list is already reached by the
|
|
78
|
+
* contiguous-run match without the split.
|
|
79
|
+
*/
|
|
80
|
+
export declare function pathCandidates(token: string): string[];
|
|
81
|
+
/**
|
|
82
|
+
* Extract path candidates from a whole command line the tokenizer REFUSED — the fallback-only
|
|
83
|
+
* counterpart of {@link pathCandidates}.
|
|
84
|
+
*
|
|
85
|
+
* The precondition is the opposite one. On the tokenized path an operator between two words
|
|
86
|
+
* has already become a word boundary, so `pathCandidates`' separator set never needed the
|
|
87
|
+
* operators themselves. A fallback branch has no tokenizer left and gets the raw line, where
|
|
88
|
+
* nothing consumed them and a path glued to one (`packages/core/dist;echo x`) stayed a single
|
|
89
|
+
* unmatchable segment — so the set here is wider by exactly what the tokenizer would have
|
|
90
|
+
* eaten: `;` `&` `|` `<` `>`.
|
|
91
|
+
*
|
|
92
|
+
* The line itself stays a candidate alongside the fragments, so a protected path whose own
|
|
93
|
+
* segment carries an operator (`pkg/a&b/dist`) is still matchable; an added form can only add
|
|
94
|
+
* a match, never withdraw one. `:` stays out for the reason {@link pathCandidates} records.
|
|
95
|
+
*
|
|
96
|
+
* Widening a fragment boundary widens the ancestor direction with it: `…?x=1&packages=1`
|
|
97
|
+
* splits to a bare `packages`, which `segmentsMatch` accepts as a root-anchored ancestor of a
|
|
98
|
+
* protected `packages/core/dist`. That over-block is accepted rather than narrowed — the
|
|
99
|
+
* narrowing that would spare it also drops a glued ancestor destroy
|
|
100
|
+
* (`rm -rf packages/core;echo x`), which is the defence this fallback exists to provide.
|
|
101
|
+
*/
|
|
102
|
+
export declare function untokenizableLineCandidates(line: string): string[];
|
|
103
|
+
/**
|
|
104
|
+
* True when `predicate` holds for any string value inside `value`, at any depth.
|
|
105
|
+
*
|
|
106
|
+
* Arrays and plain objects are walked by value; keys are never scanned, and non-string
|
|
107
|
+
* primitives never match. Short-circuits on the first hit — the walk answers an existence
|
|
108
|
+
* question, so a caller that needs every match wants its own traversal.
|
|
109
|
+
*/
|
|
110
|
+
export declare function someStringValue(value: unknown, predicate: (text: string) => boolean): boolean;
|
|
111
|
+
/**
|
|
112
|
+
* Recursively test whether any string value inside `value` matches `path` by path-segment
|
|
113
|
+
* containment (ancestor / descendant / equal). Each string is split into path candidates,
|
|
114
|
+
* each tested via {@link pathMatchesProtected}. Only string values are scanned; keys,
|
|
115
|
+
* numbers, and other primitives never match.
|
|
116
|
+
*/
|
|
117
|
+
export declare function mentionsPath(value: unknown, path: string): boolean;
|