fix(analysis-view): share positions with digit-free generated identifiers
prefix-variance accepted an unobserved literal only when it carried a digit, so a generated identifier that happened to hold none stayed a literal tool: an n8n workflow id such as FkMwxmXNGaGVsXFG, whose reads then never joined the item view. A literal now also qualifies when every value observed at the position has one length of eight or more characters and the literal has that length and uses only their character classes. Across the 50 showcase captures this templates 10 more read tools in Memos and n8n and no words. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
930c96d0a1
commit
33b0643e8c
6 files changed
+102
-41
No files matched your search
@@ -26,9 +26,13 @@ the parameter another tool already uses at that position, so the write becomes
|
||||
preceding parts compare by the rule that names them, so a position templated
|
||||
by `prefix-variance` matches the parameter it shares.
|
||||
- The literal qualifies when it is one of the values any such tool observed at
|
||||
that position, or when it carries a digit and has the length and character
|
||||
that position, when it carries a digit and has the length and character
|
||||
classes (digits, lower case, upper case, each other character) of one of
|
||||
them. An empty literal, a stop word or a `v1`-style version never qualifies.
|
||||
them, or when every observed value has one length of eight or more
|
||||
characters, as generated identifiers often do, and the literal has that
|
||||
length and uses only their character classes, so an identifier that happens
|
||||
to hold no digit still qualifies. An empty literal, a stop word or a
|
||||
`v1`-style version never qualifies.
|
||||
- Positions templated by the four format rules are never shared, so
|
||||
`/api/teams/{numeric}` still absorbs no literal.
|
||||
- The parameter takes the name of the parameter it shares (`collection_id`,
|
||||
|
||||
@@ -449,13 +449,13 @@ template and `params` carry the four format rules with names `id`, `uuid`,
|
||||
only when listed, each at most once (a repeated rule is an `invalid_request`),
|
||||
in this order:
|
||||
|
||||
| Rule | Applies to | Parameter |
|
||||
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- |
|
||||
| `after-uid-literal` | The literal segment right after a literal `uid`, unless it is a stop word | `uid` |
|
||||
| `opaque-token` | A lowercase alphanumeric segment of twelve or more characters mixing letters and digits that is not a `v1`-style version | `uid` |
|
||||
| `sibling-pair-variance` | Groups equal in method, origin and segment count that differ in two adjacent literals right after a plural literal at index 2 or later and followed by a literal, with at least three distinct pairs and both positions varying (`/repos/{owner}/{repo}/issues`); stop words and `v1`-style versions never qualify | singular plural literal plus `_namespace`, then `_name` |
|
||||
| `sibling-variance` | Groups equal in method, origin and segment count that differ in one literal at index 2 or later; the siblings whose literal is identifier-like (a digit or `:`, or three or more hyphenated names under a plural literal) merge, never a stop word, and the other literals stay their own tools | singular previous literal plus `_id`, else `id` |
|
||||
| `prefix-variance` | A literal at a position that another tool, of any method or segment count, templates by one of the rules above after the same preceding segments, when the literal is one of that parameter's observed values, or carries a digit and has the length and character classes (digits, lower case, upper case, each other character) of one; stop words and `v1`-style versions never qualify, and tools that now match merge | the name of the parameter it shares |
|
||||
| Rule | Applies to | Parameter |
|
||||
| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- |
|
||||
| `after-uid-literal` | The literal segment right after a literal `uid`, unless it is a stop word | `uid` |
|
||||
| `opaque-token` | A lowercase alphanumeric segment of twelve or more characters mixing letters and digits that is not a `v1`-style version | `uid` |
|
||||
| `sibling-pair-variance` | Groups equal in method, origin and segment count that differ in two adjacent literals right after a plural literal at index 2 or later and followed by a literal, with at least three distinct pairs and both positions varying (`/repos/{owner}/{repo}/issues`); stop words and `v1`-style versions never qualify | singular plural literal plus `_namespace`, then `_name` |
|
||||
| `sibling-variance` | Groups equal in method, origin and segment count that differ in one literal at index 2 or later; the siblings whose literal is identifier-like (a digit or `:`, or three or more hyphenated names under a plural literal) merge, never a stop word, and the other literals stay their own tools | singular previous literal plus `_id`, else `id` |
|
||||
| `prefix-variance` | A literal at a position that another tool, of any method or segment count, templates by one of the rules above after the same preceding segments, when the literal is one of that parameter's observed values, carries a digit and has the length and character classes (digits, lower case, upper case, each other character) of one, or has the single length of eight or more characters every observed value has and only their character classes; stop words and `v1`-style versions never qualify, and tools that now match merge | the name of the parameter it shares |
|
||||
|
||||
With `sibling-variance` selected, literal siblings within its comparison scope
|
||||
fold into existing opt-in parameters as observed values unless they are stop words.
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import {
|
||||
compareUnicodeCodePoints as compare,
|
||||
compositeKey,
|
||||
} from "../unicodeCodePointOrder.js";
|
||||
import { compositeKey } from "../unicodeCodePointOrder.js";
|
||||
import { spelledLikeObserved } from "./environmentManifestSpelling.js";
|
||||
import type { RouteRule } from "./webNetworkRouteTemplates.js";
|
||||
|
||||
/** Opt-in template rules applied after the four exact format rules. */
|
||||
@@ -444,34 +442,15 @@ const sharedPositions = (
|
||||
return shared;
|
||||
};
|
||||
|
||||
/** The distinct classes of a text's characters: digits, lower case, upper case and each other character. */
|
||||
const characterClasses = (text: string): string =>
|
||||
[
|
||||
...new Set(
|
||||
text
|
||||
.replaceAll(/\d/gu, "0")
|
||||
.replaceAll(/[a-z]/gu, "a")
|
||||
.replaceAll(/[A-Z]/gu, "A"),
|
||||
),
|
||||
]
|
||||
.sort(compare)
|
||||
.join("");
|
||||
|
||||
/** One of the observed values, or a digit-bearing value with the length and character classes of one; never a stop word or version. */
|
||||
/** One of the observed values or spelled like them; never empty, a stop word or a version. */
|
||||
const sharesPosition = (
|
||||
value: string,
|
||||
observed: ReadonlySet<string>,
|
||||
): boolean => {
|
||||
if (value === "" || STOP_WORDS.has(value) || VERSION_TOKEN.test(value))
|
||||
return false;
|
||||
if (observed.has(value)) return true;
|
||||
if (!/\d/u.test(value)) return false;
|
||||
const classes = characterClasses(value);
|
||||
return [...observed].some(
|
||||
(other) =>
|
||||
other.length === value.length && characterClasses(other) === classes,
|
||||
);
|
||||
};
|
||||
): boolean =>
|
||||
value !== "" &&
|
||||
!STOP_WORDS.has(value) &&
|
||||
!VERSION_TOKEN.test(value) &&
|
||||
(observed.has(value) || spelledLikeObserved(value, observed));
|
||||
|
||||
/** The bucket's earliest group, with each position's parameter taken from a group that templated it by its own rule when one did. */
|
||||
const sharedShape = (bucket: readonly ToolGroup[]): ToolGroup => {
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { compareUnicodeCodePoints as compare } from "../unicodeCodePointOrder.js";
|
||||
|
||||
/** Fewest characters of a fixed-length identifier family whose members need not carry a digit. */
|
||||
const FIXED_LENGTH_MINIMUM = 8;
|
||||
|
||||
/** The distinct classes of a text's characters: digits, lower case, upper case and each other character. */
|
||||
const characterClasses = (text: string): string =>
|
||||
[
|
||||
...new Set(
|
||||
text
|
||||
.replaceAll(/\d/gu, "0")
|
||||
.replaceAll(/[a-z]/gu, "a")
|
||||
.replaceAll(/[A-Z]/gu, "A"),
|
||||
),
|
||||
]
|
||||
.sort(compare)
|
||||
.join("");
|
||||
|
||||
/** Every observed value has the value's length, at least eight, and together they use every class the value uses. */
|
||||
const inFixedLengthFamily = (
|
||||
value: string,
|
||||
observed: readonly string[],
|
||||
): boolean => {
|
||||
if (
|
||||
value.length < FIXED_LENGTH_MINIMUM ||
|
||||
observed.length < 2 ||
|
||||
observed.some((other) => other.length !== value.length)
|
||||
)
|
||||
return false;
|
||||
const used = new Set(
|
||||
observed.flatMap((other) => [...characterClasses(other)]),
|
||||
);
|
||||
return [...characterClasses(value)].every((kind) => used.has(kind));
|
||||
};
|
||||
|
||||
/** Spelled like an observed identifier: a digit-bearing value with the length and character classes of one, or a member of their fixed-length family. */
|
||||
export const spelledLikeObserved = (
|
||||
value: string,
|
||||
observed: ReadonlySet<string>,
|
||||
): boolean => {
|
||||
const values = [...observed];
|
||||
if (inFixedLengthFamily(value, values)) return true;
|
||||
if (!/\d/u.test(value)) return false;
|
||||
const classes = characterClasses(value);
|
||||
return values.some(
|
||||
(other) =>
|
||||
other.length === value.length && characterClasses(other) === classes,
|
||||
);
|
||||
};
|
||||
@@ -1230,7 +1230,7 @@ it("templates an unobserved literal only when it carries a digit and is spelled
|
||||
["GET", "https://k.test/api/readlists/0RZ56NR37AC1T"],
|
||||
["PATCH", "https://k.test/api/readlists/0RZ576R5KA57B", null, "{}"],
|
||||
["PUT", "https://k.test/api/readlists/0rz576r5ka57b", null, "{}"],
|
||||
["DELETE", "https://k.test/api/readlists/ABCDEFGHIJKLM", null],
|
||||
["DELETE", "https://k.test/api/readlists/ABCDEFGHIJ", null],
|
||||
["POST", "https://k.test/api/readlists/Metrics::12", null, "{}"],
|
||||
["GET", "https://k.test/api/items/a1"],
|
||||
["GET", "https://k.test/api/items/b2"],
|
||||
@@ -1241,7 +1241,7 @@ it("templates an unobserved literal only when it carries a digit and is spelled
|
||||
"PUT /api/items/v2",
|
||||
"GET /api/items/{item_id}",
|
||||
"PUT /api/readlists/0rz576r5ka57b",
|
||||
"DELETE /api/readlists/ABCDEFGHIJKLM",
|
||||
"DELETE /api/readlists/ABCDEFGHIJ",
|
||||
"POST /api/readlists/Metrics::12",
|
||||
"GET /api/readlists/{readlist_id}",
|
||||
"PATCH /api/readlists/{readlist_id}",
|
||||
@@ -1345,3 +1345,32 @@ it("shares both halves of a templated pair and keeps each half's name", () => {
|
||||
["repo_name", "prefix-variance"],
|
||||
]);
|
||||
});
|
||||
|
||||
it("templates a digit-free literal only as a member of a fixed-length identifier family", () => {
|
||||
const projected = manifest(
|
||||
["sibling-variance", "prefix-variance"],
|
||||
syntheticCapture([
|
||||
["GET", "https://n.test/api/workflows/yDr9n16753TZr7Zc"],
|
||||
["GET", "https://n.test/api/workflows/5LPA94ML1gYbgAQx"],
|
||||
["PATCH", "https://n.test/api/workflows/FkMwxmXNGaGVsXFG", null, "{}"],
|
||||
["DELETE", "https://n.test/api/workflows/listForAccounts", null],
|
||||
["PUT", "https://n.test/api/workflows/fkmw-xmxn-gagv-x", null, "{}"],
|
||||
["GET", "https://n.test/api/tags/a1"],
|
||||
["GET", "https://n.test/api/tags/b22"],
|
||||
["DELETE", "https://n.test/api/tags/cc", null],
|
||||
["GET", "https://n.test/api/labels/ab12cd34"],
|
||||
["GET", "https://n.test/api/labels/ef56gh78"],
|
||||
["DELETE", "https://n.test/api/labels/abcdefgh", null],
|
||||
]),
|
||||
);
|
||||
expect(projected.tools.map(line)).toEqual([
|
||||
"DELETE /api/labels/{label_id}",
|
||||
"GET /api/labels/{label_id}",
|
||||
"DELETE /api/tags/cc",
|
||||
"GET /api/tags/{tag_id}",
|
||||
"PUT /api/workflows/fkmw-xmxn-gagv-x",
|
||||
"DELETE /api/workflows/listForAccounts",
|
||||
"GET /api/workflows/{workflow_id}",
|
||||
"PATCH /api/workflows/{workflow_id}",
|
||||
]);
|
||||
});
|
||||
@@ -30,7 +30,7 @@ export const templateRulesSchema = z
|
||||
export const environmentManifestViewRequestSchema = z.strictObject({
|
||||
kind: z.literal("environment-manifest"),
|
||||
template_rules: templateRulesSchema.describe(
|
||||
"Opt-in template rules applied after the four format rules, each listed at most once: after-uid-literal templates the segment following a literal uid, opaque-token templates long mixed alphanumeric segments, sibling-variance merges groups that differ in one identifier-like segment, sibling-pair-variance merges groups that differ in two adjacent literals between a plural literal and another literal when at least three distinct pairs vary in both, prefix-variance templates a literal at a position another tool templates by one of the other opt-in rules after the same preceding segments, at any method or length, when the literal is one of that parameter's observed values or carries a digit and has the length and character classes of one. Literal sibling folding into existing opt-in parameters also requires sibling-variance. Merged parameters are inferences.",
|
||||
"Opt-in template rules applied after the four format rules, each listed at most once: after-uid-literal templates the segment following a literal uid, opaque-token templates long mixed alphanumeric segments, sibling-variance merges groups that differ in one identifier-like segment, sibling-pair-variance merges groups that differ in two adjacent literals between a plural literal and another literal when at least three distinct pairs vary in both, prefix-variance templates a literal at a position another tool templates by one of the other opt-in rules after the same preceding segments, at any method or length, when the literal is one of that parameter's observed values, carries a digit and has the length and character classes of one, or has the single length (eight or more characters) every observed value has and only their character classes. Literal sibling folding into existing opt-in parameters also requires sibling-variance. Merged parameters are inferences.",
|
||||
),
|
||||
include_query_parameter_names: z
|
||||
.boolean()
|
||||
|
||||
Reference in new issue
Block a user