@unbrained/pm-cli 2026.5.27 → 2026.5.29
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 +107 -80
- package/dist/cli/commander-usage.js +8 -8
- package/dist/cli/commander-usage.js.map +1 -1
- package/dist/cli/commands/aggregate.js +4 -3
- package/dist/cli/commands/aggregate.js.map +1 -1
- package/dist/cli/commands/calendar.d.ts +8 -0
- package/dist/cli/commands/calendar.js +13 -2
- package/dist/cli/commands/calendar.js.map +1 -1
- package/dist/cli/commands/close.d.ts +3 -0
- package/dist/cli/commands/close.js +24 -2
- package/dist/cli/commands/close.js.map +1 -1
- package/dist/cli/commands/completion.js +34 -2
- package/dist/cli/commands/completion.js.map +1 -1
- package/dist/cli/commands/config.d.ts +11 -1
- package/dist/cli/commands/config.js +68 -6
- package/dist/cli/commands/config.js.map +1 -1
- package/dist/cli/commands/create.d.ts +1 -0
- package/dist/cli/commands/create.js +40 -4
- package/dist/cli/commands/create.js.map +1 -1
- package/dist/cli/commands/extension/bundled-catalog.js +4 -3
- package/dist/cli/commands/extension/bundled-catalog.js.map +1 -1
- package/dist/cli/commands/extension/scaffold.js +54 -21
- package/dist/cli/commands/extension/scaffold.js.map +1 -1
- package/dist/cli/commands/health.js +3 -11
- package/dist/cli/commands/health.js.map +1 -1
- package/dist/cli/commands/linked-test-parsers.js +5 -4
- package/dist/cli/commands/linked-test-parsers.js.map +1 -1
- package/dist/cli/commands/list.js +2 -3
- package/dist/cli/commands/list.js.map +1 -1
- package/dist/cli/commands/plan.d.ts +5 -0
- package/dist/cli/commands/plan.js +59 -10
- package/dist/cli/commands/plan.js.map +1 -1
- package/dist/cli/commands/search.js +45 -6
- package/dist/cli/commands/search.js.map +1 -1
- package/dist/cli/commands/test.js +3 -3
- package/dist/cli/commands/test.js.map +1 -1
- package/dist/cli/commands/update-many.js +35 -6
- package/dist/cli/commands/update-many.js.map +1 -1
- package/dist/cli/commands/update.d.ts +2 -0
- package/dist/cli/commands/update.js +59 -8
- package/dist/cli/commands/update.js.map +1 -1
- package/dist/cli/commands/validate.js +32 -12
- package/dist/cli/commands/validate.js.map +1 -1
- package/dist/cli/help-json-payload.d.ts +1 -11
- package/dist/cli/help-json-payload.js +12 -12
- package/dist/cli/help-json-payload.js.map +1 -1
- package/dist/cli/register-mutation.js +64 -5
- package/dist/cli/register-mutation.js.map +1 -1
- package/dist/cli/register-setup.js +4 -2
- package/dist/cli/register-setup.js.map +1 -1
- package/dist/cli/registration-helpers.d.ts +2 -6
- package/dist/cli/registration-helpers.js +9 -6
- package/dist/cli/registration-helpers.js.map +1 -1
- package/dist/core/config/nested-settings.d.ts +86 -0
- package/dist/core/config/nested-settings.js +258 -0
- package/dist/core/config/nested-settings.js.map +1 -0
- package/dist/core/item/parse.d.ts +19 -0
- package/dist/core/item/parse.js +76 -2
- package/dist/core/item/parse.js.map +1 -1
- package/dist/core/item/priority.d.ts +2 -1
- package/dist/core/item/priority.js +12 -2
- package/dist/core/item/priority.js.map +1 -1
- package/dist/core/search/providers.js +25 -5
- package/dist/core/search/providers.js.map +1 -1
- package/dist/core/search/staleness.d.ts +23 -0
- package/dist/core/search/staleness.js +34 -0
- package/dist/core/search/staleness.js.map +1 -0
- package/dist/core/search/vector-stores.js +12 -3
- package/dist/core/search/vector-stores.js.map +1 -1
- package/dist/core/shared/html-entity-decode.d.ts +21 -0
- package/dist/core/shared/html-entity-decode.js +122 -0
- package/dist/core/shared/html-entity-decode.js.map +1 -0
- package/dist/core/shared/levenshtein.js +23 -7
- package/dist/core/shared/levenshtein.js.map +1 -1
- package/dist/core/shared/split-comma-list.d.ts +20 -0
- package/dist/core/shared/split-comma-list.js +29 -0
- package/dist/core/shared/split-comma-list.js.map +1 -0
- package/dist/mcp/server.js +10 -3
- package/dist/mcp/server.js.map +1 -1
- package/dist/sdk/cli-contracts/commander-mutation-options.js +47 -11
- package/dist/sdk/cli-contracts/commander-mutation-options.js.map +1 -1
- package/dist/sdk/cli-contracts/tool-option-contracts.js +5 -2
- package/dist/sdk/cli-contracts/tool-option-contracts.js.map +1 -1
- package/dist/sdk/cli-contracts/tool-parameter-tables.js +12 -2
- package/dist/sdk/cli-contracts/tool-parameter-tables.js.map +1 -1
- package/dist/sdk/cli-contracts.js +30 -2
- package/dist/sdk/cli-contracts.js.map +1 -1
- package/dist/sdk/runtime.d.ts +1 -1
- package/dist/sdk/runtime.js +3 -3
- package/dist/sdk/runtime.js.map +1 -1
- package/docs/AGENT_GUIDE.md +7 -0
- package/docs/COMMANDS.md +17 -0
- package/docs/CONFIGURATION.md +55 -0
- package/docs/QUICKSTART.md +3 -0
- package/package.json +1 -1
- package/packages/pm-calendar/extensions/calendar/runtime.js +5 -0
- package/packages/pm-calendar/extensions/calendar/runtime.ts +6 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// Defensive HTML-entity decode for free-text fields arriving over the MCP boundary.
|
|
2
|
+
// Background (pm-ydkl 2026-05-28): when Claude / the Anthropic MCP SDK forwards
|
|
3
|
+
// tool arguments containing `<` or `>`, the upstream platform HTML-encodes those
|
|
4
|
+
// characters before they reach pm-cli. The result is stored pm text containing
|
|
5
|
+
// literal `<type>` instead of `<type>`. Direct CLI calls do NOT have this
|
|
6
|
+
// issue — only the MCP path — so the decode is applied exclusively at the MCP
|
|
7
|
+
// server boundary on incoming tool-call arguments.
|
|
8
|
+
//
|
|
9
|
+
// We decode only the five core HTML entities and ONLY when `<` or `>` is
|
|
10
|
+
// present in the string (the signal that something upstream HTML-encoded it).
|
|
11
|
+
// That makes the function a true no-op for normal text. All replacements run in
|
|
12
|
+
// a single non-greedy pass via a lookup map so we never double-decode — most
|
|
13
|
+
// importantly `&lt;` stays as the literal `<` (because `&` is the
|
|
14
|
+
// last entity resolved in the pass), preserving any text that was already
|
|
15
|
+
// double-encoded upstream.
|
|
16
|
+
|
|
17
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="59b9e6dc-4fde-5bd0-b4bc-972055df82fe")}catch(e){}}();
|
|
18
|
+
const ENTITY_MAP = Object.freeze({
|
|
19
|
+
"<": "<",
|
|
20
|
+
">": ">",
|
|
21
|
+
""": '"',
|
|
22
|
+
"'": "'",
|
|
23
|
+
"&": "&",
|
|
24
|
+
});
|
|
25
|
+
// Pattern order is important for documentation only: the alternation matches
|
|
26
|
+
// the leftmost occurrence at each position, and the map lookup resolves each
|
|
27
|
+
// match independently. Crucially, `&` is matched as a whole token, so a
|
|
28
|
+
// substring like `&lt;` matches `&` once → `<` (literal) and the
|
|
29
|
+
// regex engine then advances past the inserted text without re-scanning it.
|
|
30
|
+
const ENTITY_PATTERN = /&(?:lt|gt|quot|#39|amp);/g;
|
|
31
|
+
/**
|
|
32
|
+
* Decode the five core HTML entities (`<`, `>`, `"`, `'`,
|
|
33
|
+
* `&`) in a single pass — but only when the input contains `<` or
|
|
34
|
+
* `>`. Returns the input unchanged otherwise so the function is a no-op for
|
|
35
|
+
* normal text and idempotent on already-decoded strings.
|
|
36
|
+
*
|
|
37
|
+
* Single-pass semantics guarantee `&lt;` decodes to `<` (literal) rather
|
|
38
|
+
* than collapsing to `<`, preserving any intentional double-encoding.
|
|
39
|
+
*/
|
|
40
|
+
export function decodeHtmlEntitiesIfEscaped(input) {
|
|
41
|
+
if (typeof input !== "string") {
|
|
42
|
+
return input;
|
|
43
|
+
}
|
|
44
|
+
// Activation signal is INTENTIONALLY narrow: only `<` / `>` trigger
|
|
45
|
+
// decoding. Rationale: the observed MCP-platform behavior only encodes
|
|
46
|
+
// angle brackets (the characters that risk display-time HTML
|
|
47
|
+
// misinterpretation upstream). Widening to `&` / `"` / `'`
|
|
48
|
+
// would risk altering legitimate text that contains those literal token
|
|
49
|
+
// sequences for unrelated reasons (a URL containing `&`, a snippet
|
|
50
|
+
// of HTML being intentionally stored as escaped, etc.). If upstream
|
|
51
|
+
// changes its encoding policy to cover `&` / `"` / `'` standalone, the
|
|
52
|
+
// signal-guard here will need to be widened in lockstep — covered by
|
|
53
|
+
// tests `&-only is no-op` and `"-only is no-op`.
|
|
54
|
+
if (!input.includes("<") && !input.includes(">")) {
|
|
55
|
+
return input;
|
|
56
|
+
}
|
|
57
|
+
// The regex only matches keys present in ENTITY_MAP, so the lookup is total.
|
|
58
|
+
return input.replace(ENTITY_PATTERN, (match) => ENTITY_MAP[match]);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Walk an arbitrary value (string / array / plain object) and apply
|
|
62
|
+
* {@link decodeHtmlEntitiesIfEscaped} to every string leaf. Non-string scalars
|
|
63
|
+
* (numbers, booleans, null, undefined) and non-plain values pass through
|
|
64
|
+
* untouched.
|
|
65
|
+
*
|
|
66
|
+
* The walker mutates a fresh shallow copy at each level so the caller's input
|
|
67
|
+
* is not modified. Cycles are not expected (MCP arguments arrive as JSON), but
|
|
68
|
+
* a visited-set is used as defensive protection against accidental cycles.
|
|
69
|
+
*/
|
|
70
|
+
export function decodeHtmlEntitiesInOptions(options) {
|
|
71
|
+
return decodeValue(options, new WeakSet());
|
|
72
|
+
}
|
|
73
|
+
function decodeValue(value, seen) {
|
|
74
|
+
if (typeof value === "string") {
|
|
75
|
+
return decodeHtmlEntitiesIfEscaped(value);
|
|
76
|
+
}
|
|
77
|
+
if (Array.isArray(value)) {
|
|
78
|
+
if (seen.has(value)) {
|
|
79
|
+
return value;
|
|
80
|
+
}
|
|
81
|
+
seen.add(value);
|
|
82
|
+
return value.map((entry) => decodeValue(entry, seen));
|
|
83
|
+
}
|
|
84
|
+
if (value !== null && typeof value === "object") {
|
|
85
|
+
// Only traverse plain objects (`{}` and `Object.create(null)` literals).
|
|
86
|
+
// Class instances (Date, RegExp, Map, Set, Buffer, etc.) and `null`-proto
|
|
87
|
+
// objects with no proto would lose their prototype and methods if we
|
|
88
|
+
// rebuilt them as a `Record<string, unknown>` here, so we pass them through.
|
|
89
|
+
if (!isPlainObject(value)) {
|
|
90
|
+
return value;
|
|
91
|
+
}
|
|
92
|
+
if (seen.has(value)) {
|
|
93
|
+
return value;
|
|
94
|
+
}
|
|
95
|
+
seen.add(value);
|
|
96
|
+
const source = value;
|
|
97
|
+
// Preserve the original prototype so downstream callers can still rely on
|
|
98
|
+
// standard methods like `.hasOwnProperty` on plain objects.
|
|
99
|
+
const result = Object.create(Object.getPrototypeOf(value));
|
|
100
|
+
// Use Object.defineProperty (not bracket assignment) for ALL keys so a
|
|
101
|
+
// smuggled `__proto__` / `constructor` / `prototype` key from an MCP
|
|
102
|
+
// caller becomes a regular own property — never triggers JS's special
|
|
103
|
+
// prototype-chain assignment semantics that would otherwise pollute
|
|
104
|
+
// Object.prototype. This preserves legitimate data while staying safe.
|
|
105
|
+
for (const [key, entry] of Object.entries(source)) {
|
|
106
|
+
Object.defineProperty(result, key, {
|
|
107
|
+
value: decodeValue(entry, seen),
|
|
108
|
+
enumerable: true,
|
|
109
|
+
writable: true,
|
|
110
|
+
configurable: true,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
return result;
|
|
114
|
+
}
|
|
115
|
+
return value;
|
|
116
|
+
}
|
|
117
|
+
function isPlainObject(value) {
|
|
118
|
+
const proto = Object.getPrototypeOf(value);
|
|
119
|
+
return proto === Object.prototype || proto === null;
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=html-entity-decode.js.map
|
|
122
|
+
//# debugId=59b9e6dc-4fde-5bd0-b4bc-972055df82fe
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"html-entity-decode.js","sources":["core/shared/html-entity-decode.ts"],"sourceRoot":"/","sourcesContent":["// Defensive HTML-entity decode for free-text fields arriving over the MCP boundary.\n// Background (pm-ydkl 2026-05-28): when Claude / the Anthropic MCP SDK forwards\n// tool arguments containing `<` or `>`, the upstream platform HTML-encodes those\n// characters before they reach pm-cli. The result is stored pm text containing\n// literal `<type>` instead of `<type>`. Direct CLI calls do NOT have this\n// issue — only the MCP path — so the decode is applied exclusively at the MCP\n// server boundary on incoming tool-call arguments.\n//\n// We decode only the five core HTML entities and ONLY when `<` or `>` is\n// present in the string (the signal that something upstream HTML-encoded it).\n// That makes the function a true no-op for normal text. All replacements run in\n// a single non-greedy pass via a lookup map so we never double-decode — most\n// importantly `&lt;` stays as the literal `<` (because `&` is the\n// last entity resolved in the pass), preserving any text that was already\n// double-encoded upstream.\n\nconst ENTITY_MAP: Readonly<Record<string, string>> = Object.freeze({\n \"<\": \"<\",\n \">\": \">\",\n \""\": '\"',\n \"'\": \"'\",\n \"&\": \"&\",\n});\n\n// Pattern order is important for documentation only: the alternation matches\n// the leftmost occurrence at each position, and the map lookup resolves each\n// match independently. Crucially, `&` is matched as a whole token, so a\n// substring like `&lt;` matches `&` once → `<` (literal) and the\n// regex engine then advances past the inserted text without re-scanning it.\nconst ENTITY_PATTERN = /&(?:lt|gt|quot|#39|amp);/g;\n\n/**\n * Decode the five core HTML entities (`<`, `>`, `"`, `'`,\n * `&`) in a single pass — but only when the input contains `<` or\n * `>`. Returns the input unchanged otherwise so the function is a no-op for\n * normal text and idempotent on already-decoded strings.\n *\n * Single-pass semantics guarantee `&lt;` decodes to `<` (literal) rather\n * than collapsing to `<`, preserving any intentional double-encoding.\n */\nexport function decodeHtmlEntitiesIfEscaped(input: string): string {\n if (typeof input !== \"string\") {\n return input;\n }\n // Activation signal is INTENTIONALLY narrow: only `<` / `>` trigger\n // decoding. Rationale: the observed MCP-platform behavior only encodes\n // angle brackets (the characters that risk display-time HTML\n // misinterpretation upstream). Widening to `&` / `"` / `'`\n // would risk altering legitimate text that contains those literal token\n // sequences for unrelated reasons (a URL containing `&`, a snippet\n // of HTML being intentionally stored as escaped, etc.). If upstream\n // changes its encoding policy to cover `&` / `\"` / `'` standalone, the\n // signal-guard here will need to be widened in lockstep — covered by\n // tests `&-only is no-op` and `"-only is no-op`.\n if (!input.includes(\"<\") && !input.includes(\">\")) {\n return input;\n }\n // The regex only matches keys present in ENTITY_MAP, so the lookup is total.\n return input.replace(ENTITY_PATTERN, (match) => ENTITY_MAP[match] as string);\n}\n\n/**\n * Walk an arbitrary value (string / array / plain object) and apply\n * {@link decodeHtmlEntitiesIfEscaped} to every string leaf. Non-string scalars\n * (numbers, booleans, null, undefined) and non-plain values pass through\n * untouched.\n *\n * The walker mutates a fresh shallow copy at each level so the caller's input\n * is not modified. Cycles are not expected (MCP arguments arrive as JSON), but\n * a visited-set is used as defensive protection against accidental cycles.\n */\nexport function decodeHtmlEntitiesInOptions<T>(options: T): T {\n return decodeValue(options, new WeakSet<object>()) as T;\n}\n\nfunction decodeValue(value: unknown, seen: WeakSet<object>): unknown {\n if (typeof value === \"string\") {\n return decodeHtmlEntitiesIfEscaped(value);\n }\n if (Array.isArray(value)) {\n if (seen.has(value)) {\n return value;\n }\n seen.add(value);\n return value.map((entry) => decodeValue(entry, seen));\n }\n if (value !== null && typeof value === \"object\") {\n // Only traverse plain objects (`{}` and `Object.create(null)` literals).\n // Class instances (Date, RegExp, Map, Set, Buffer, etc.) and `null`-proto\n // objects with no proto would lose their prototype and methods if we\n // rebuilt them as a `Record<string, unknown>` here, so we pass them through.\n if (!isPlainObject(value)) {\n return value;\n }\n if (seen.has(value as object)) {\n return value;\n }\n seen.add(value as object);\n const source = value as Record<string, unknown>;\n // Preserve the original prototype so downstream callers can still rely on\n // standard methods like `.hasOwnProperty` on plain objects.\n const result: Record<string, unknown> = Object.create(Object.getPrototypeOf(value as object));\n // Use Object.defineProperty (not bracket assignment) for ALL keys so a\n // smuggled `__proto__` / `constructor` / `prototype` key from an MCP\n // caller becomes a regular own property — never triggers JS's special\n // prototype-chain assignment semantics that would otherwise pollute\n // Object.prototype. This preserves legitimate data while staying safe.\n for (const [key, entry] of Object.entries(source)) {\n Object.defineProperty(result, key, {\n value: decodeValue(entry, seen),\n enumerable: true,\n writable: true,\n configurable: true,\n });\n }\n return result;\n }\n return value;\n}\n\nfunction isPlainObject(value: object): boolean {\n const proto = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null;\n}\n"],"names":[],"mappings":"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","debugId":"59b9e6dc-4fde-5bd0-b4bc-972055df82fe"}
|
|
@@ -1,5 +1,11 @@
|
|
|
1
|
+
// Optimal String Alignment (OSA) Damerau–Levenshtein: counts a single adjacent
|
|
2
|
+
// transposition as one edit (e.g. "titel" vs "title", "lst" vs "lts" -> "list")
|
|
3
|
+
// so flag/command typo suggestions catch transpositions at the same maxDistance
|
|
4
|
+
// budget plain Levenshtein uses for substitutions. pm-fl0c #6 (2026-05-28):
|
|
5
|
+
// fixed because plain Levenshtein scored "titel"->"title" at 2, defeating the
|
|
6
|
+
// length-5 maxDistance=1 ceiling in suggestNearestLongFlags.
|
|
1
7
|
|
|
2
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
8
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="6f3e0977-8505-552c-b2ff-862f2a5d5a91")}catch(e){}}();
|
|
3
9
|
export function levenshteinDistanceWithinLimit(left, right, limit) {
|
|
4
10
|
if (left === right) {
|
|
5
11
|
return 0;
|
|
@@ -7,9 +13,12 @@ export function levenshteinDistanceWithinLimit(left, right, limit) {
|
|
|
7
13
|
if (Math.abs(left.length - right.length) > limit) {
|
|
8
14
|
return null;
|
|
9
15
|
}
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
|
|
16
|
+
const width = right.length + 1;
|
|
17
|
+
const beforePrevious = new Array(width);
|
|
18
|
+
const previous = new Array(width);
|
|
19
|
+
const current = new Array(width);
|
|
20
|
+
for (let column = 0; column < width; column += 1) {
|
|
21
|
+
beforePrevious[column] = 0;
|
|
13
22
|
previous[column] = column;
|
|
14
23
|
}
|
|
15
24
|
for (let row = 1; row <= left.length; row += 1) {
|
|
@@ -20,7 +29,13 @@ export function levenshteinDistanceWithinLimit(left, right, limit) {
|
|
|
20
29
|
const substitution = previous[column - 1] + cost;
|
|
21
30
|
const insertion = current[column - 1] + 1;
|
|
22
31
|
const deletion = previous[column] + 1;
|
|
23
|
-
|
|
32
|
+
let candidate = Math.min(substitution, insertion, deletion);
|
|
33
|
+
if (row > 1 &&
|
|
34
|
+
column > 1 &&
|
|
35
|
+
left[row - 1] === right[column - 2] &&
|
|
36
|
+
left[row - 2] === right[column - 1]) {
|
|
37
|
+
candidate = Math.min(candidate, beforePrevious[column - 2] + 1);
|
|
38
|
+
}
|
|
24
39
|
current[column] = candidate;
|
|
25
40
|
if (candidate < rowMin) {
|
|
26
41
|
rowMin = candidate;
|
|
@@ -29,7 +44,8 @@ export function levenshteinDistanceWithinLimit(left, right, limit) {
|
|
|
29
44
|
if (rowMin > limit) {
|
|
30
45
|
return null;
|
|
31
46
|
}
|
|
32
|
-
for (let column = 0; column
|
|
47
|
+
for (let column = 0; column < width; column += 1) {
|
|
48
|
+
beforePrevious[column] = previous[column];
|
|
33
49
|
previous[column] = current[column];
|
|
34
50
|
}
|
|
35
51
|
}
|
|
@@ -37,4 +53,4 @@ export function levenshteinDistanceWithinLimit(left, right, limit) {
|
|
|
37
53
|
return result <= limit ? result : null;
|
|
38
54
|
}
|
|
39
55
|
//# sourceMappingURL=levenshtein.js.map
|
|
40
|
-
//# debugId=
|
|
56
|
+
//# debugId=6f3e0977-8505-552c-b2ff-862f2a5d5a91
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"levenshtein.js","sources":["core/shared/levenshtein.ts"],"sourceRoot":"/","sourcesContent":["
|
|
1
|
+
{"version":3,"file":"levenshtein.js","sources":["core/shared/levenshtein.ts"],"sourceRoot":"/","sourcesContent":["// Optimal String Alignment (OSA) Damerau–Levenshtein: counts a single adjacent\n// transposition as one edit (e.g. \"titel\" vs \"title\", \"lst\" vs \"lts\" -> \"list\")\n// so flag/command typo suggestions catch transpositions at the same maxDistance\n// budget plain Levenshtein uses for substitutions. pm-fl0c #6 (2026-05-28):\n// fixed because plain Levenshtein scored \"titel\"->\"title\" at 2, defeating the\n// length-5 maxDistance=1 ceiling in suggestNearestLongFlags.\nexport function levenshteinDistanceWithinLimit(left: string, right: string, limit: number): number | null {\n if (left === right) {\n return 0;\n }\n if (Math.abs(left.length - right.length) > limit) {\n return null;\n }\n const width = right.length + 1;\n const beforePrevious = new Array<number>(width);\n const previous = new Array<number>(width);\n const current = new Array<number>(width);\n for (let column = 0; column < width; column += 1) {\n beforePrevious[column] = 0;\n previous[column] = column;\n }\n for (let row = 1; row <= left.length; row += 1) {\n current[0] = row;\n let rowMin = current[0];\n for (let column = 1; column <= right.length; column += 1) {\n const cost = left[row - 1] === right[column - 1] ? 0 : 1;\n const substitution = previous[column - 1] + cost;\n const insertion = current[column - 1] + 1;\n const deletion = previous[column] + 1;\n let candidate = Math.min(substitution, insertion, deletion);\n if (\n row > 1 &&\n column > 1 &&\n left[row - 1] === right[column - 2] &&\n left[row - 2] === right[column - 1]\n ) {\n candidate = Math.min(candidate, beforePrevious[column - 2] + 1);\n }\n current[column] = candidate;\n if (candidate < rowMin) {\n rowMin = candidate;\n }\n }\n if (rowMin > limit) {\n return null;\n }\n for (let column = 0; column < width; column += 1) {\n beforePrevious[column] = previous[column];\n previous[column] = current[column];\n }\n }\n const result = previous[right.length];\n return result <= limit ? result : null;\n}\n"],"names":[],"mappings":"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","debugId":"6f3e0977-8505-552c-b2ff-862f2a5d5a91"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface SplitCommaListOptions {
|
|
2
|
+
/** Separator pattern. Defaults to `/,/`. */
|
|
3
|
+
separators?: RegExp | string;
|
|
4
|
+
/** De-duplicate entries while preserving first-seen order. Defaults to `true`. */
|
|
5
|
+
unique?: boolean;
|
|
6
|
+
/** Sort entries lexicographically (default JS string sort). Defaults to `false`. */
|
|
7
|
+
sort?: boolean;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Split a comma-separated (or custom-separator) string into trimmed, non-empty entries.
|
|
11
|
+
*
|
|
12
|
+
* Default behaviour:
|
|
13
|
+
* - Splits on `,`.
|
|
14
|
+
* - Trims each entry and discards empty results (collapsing leading/trailing/duplicate separators).
|
|
15
|
+
* - De-duplicates while preserving first-seen order.
|
|
16
|
+
* - Does not sort.
|
|
17
|
+
*
|
|
18
|
+
* Returns `[]` for `undefined`/`null` input. Pure, dependency-free.
|
|
19
|
+
*/
|
|
20
|
+
export declare function splitCommaList(raw: string | undefined | null, options?: SplitCommaListOptions): string[];
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Split a comma-separated (or custom-separator) string into trimmed, non-empty entries.
|
|
3
|
+
*
|
|
4
|
+
* Default behaviour:
|
|
5
|
+
* - Splits on `,`.
|
|
6
|
+
* - Trims each entry and discards empty results (collapsing leading/trailing/duplicate separators).
|
|
7
|
+
* - De-duplicates while preserving first-seen order.
|
|
8
|
+
* - Does not sort.
|
|
9
|
+
*
|
|
10
|
+
* Returns `[]` for `undefined`/`null` input. Pure, dependency-free.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="e5a31927-2a55-5d1d-97e4-b6190988caa1")}catch(e){}}();
|
|
14
|
+
export function splitCommaList(raw, options) {
|
|
15
|
+
if (raw === undefined || raw === null) {
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
const separators = options?.separators ?? /,/;
|
|
19
|
+
const parts = raw.split(separators);
|
|
20
|
+
const trimmed = parts.map((entry) => entry.trim()).filter((entry) => entry.length > 0);
|
|
21
|
+
const unique = options?.unique !== false;
|
|
22
|
+
const deduped = unique ? Array.from(new Set(trimmed)) : trimmed;
|
|
23
|
+
if (options?.sort === true) {
|
|
24
|
+
return [...deduped].sort();
|
|
25
|
+
}
|
|
26
|
+
return deduped;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=split-comma-list.js.map
|
|
29
|
+
//# debugId=e5a31927-2a55-5d1d-97e4-b6190988caa1
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"split-comma-list.js","sources":["core/shared/split-comma-list.ts"],"sourceRoot":"/","sourcesContent":["export interface SplitCommaListOptions {\n /** Separator pattern. Defaults to `/,/`. */\n separators?: RegExp | string;\n /** De-duplicate entries while preserving first-seen order. Defaults to `true`. */\n unique?: boolean;\n /** Sort entries lexicographically (default JS string sort). Defaults to `false`. */\n sort?: boolean;\n}\n\n/**\n * Split a comma-separated (or custom-separator) string into trimmed, non-empty entries.\n *\n * Default behaviour:\n * - Splits on `,`.\n * - Trims each entry and discards empty results (collapsing leading/trailing/duplicate separators).\n * - De-duplicates while preserving first-seen order.\n * - Does not sort.\n *\n * Returns `[]` for `undefined`/`null` input. Pure, dependency-free.\n */\nexport function splitCommaList(raw: string | undefined | null, options?: SplitCommaListOptions): string[] {\n if (raw === undefined || raw === null) {\n return [];\n }\n const separators = options?.separators ?? /,/;\n const parts = raw.split(separators as never);\n const trimmed = parts.map((entry) => entry.trim()).filter((entry) => entry.length > 0);\n const unique = options?.unique !== false;\n const deduped = unique ? Array.from(new Set(trimmed)) : trimmed;\n if (options?.sort === true) {\n return [...deduped].sort();\n }\n return deduped;\n}\n"],"names":[],"mappings":"AASA;;;;;;;;;;GAUG;;;AACH,MAAM,UAAU,cAAc,CAAC,GAA8B,EAAE,OAA+B;IAC5F,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;QACtC,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,MAAM,UAAU,GAAG,OAAO,EAAE,UAAU,IAAI,GAAG,CAAC;IAC9C,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,UAAmB,CAAC,CAAC;IAC7C,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACvF,MAAM,MAAM,GAAG,OAAO,EAAE,MAAM,KAAK,KAAK,CAAC;IACzC,MAAM,OAAO,GAAG,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;IAChE,IAAI,OAAO,EAAE,IAAI,KAAK,IAAI,EAAE,CAAC;QAC3B,OAAO,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;IAC7B,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC","debugId":"e5a31927-2a55-5d1d-97e4-b6190988caa1"}
|
package/dist/mcp/server.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
3
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="0b886283-0ee3-5dcd-bd65-6199134c8901")}catch(e){}}();
|
|
4
4
|
import readline from "node:readline";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { activateExtensions, loadExtensions, runActiveCommandHandler, setActiveExtensionCommands, setActiveExtensionHooks, setActiveExtensionParsers, setActiveExtensionPreflight, setActiveExtensionRegistrations, setActiveExtensionRenderers, setActiveExtensionServices, } from "../core/extensions/index.js";
|
|
7
7
|
import { pathExists } from "../core/fs/fs-utils.js";
|
|
8
8
|
import { projectMutationResult } from "../core/output/mutation-projection.js";
|
|
9
9
|
import { PmCliError } from "../core/shared/errors.js";
|
|
10
|
+
import { decodeHtmlEntitiesInOptions } from "../core/shared/html-entity-decode.js";
|
|
10
11
|
import { asRecordClone } from "../core/shared/primitives.js";
|
|
11
12
|
import { getSettingsPath, resolvePmRoot } from "../core/store/paths.js";
|
|
12
13
|
import { readSettings } from "../core/store/settings.js";
|
|
@@ -619,7 +620,13 @@ export async function handleRequest(request) {
|
|
|
619
620
|
if (!handler) {
|
|
620
621
|
throw new PmCliError(`Unknown pm MCP tool: ${name}`, 64);
|
|
621
622
|
}
|
|
622
|
-
|
|
623
|
+
// pm-ydkl: defensive HTML-entity decode for free-text fields. Claude / the
|
|
624
|
+
// Anthropic MCP SDK HTML-encodes `<` / `>` (and friends) in tool arguments
|
|
625
|
+
// before they reach pm-cli, which would otherwise leak `<type>` into
|
|
626
|
+
// stored pm comments / notes / item bodies. Direct CLI calls are not
|
|
627
|
+
// affected; decoding at the MCP boundary normalizes the agent path while
|
|
628
|
+
// leaving normal text untouched.
|
|
629
|
+
const args = decodeHtmlEntitiesInOptions(asRecordClone(params.arguments));
|
|
623
630
|
const result = await withCwd(args.cwd, () => handler(args));
|
|
624
631
|
return resultContent(result);
|
|
625
632
|
}
|
|
@@ -669,4 +676,4 @@ if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
|
|
|
669
676
|
startMcpServer();
|
|
670
677
|
}
|
|
671
678
|
//# sourceMappingURL=server.js.map
|
|
672
|
-
//# debugId=
|
|
679
|
+
//# debugId=0b886283-0ee3-5dcd-bd65-6199134c8901
|