@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.
Files changed (97) hide show
  1. package/CHANGELOG.md +107 -80
  2. package/dist/cli/commander-usage.js +8 -8
  3. package/dist/cli/commander-usage.js.map +1 -1
  4. package/dist/cli/commands/aggregate.js +4 -3
  5. package/dist/cli/commands/aggregate.js.map +1 -1
  6. package/dist/cli/commands/calendar.d.ts +8 -0
  7. package/dist/cli/commands/calendar.js +13 -2
  8. package/dist/cli/commands/calendar.js.map +1 -1
  9. package/dist/cli/commands/close.d.ts +3 -0
  10. package/dist/cli/commands/close.js +24 -2
  11. package/dist/cli/commands/close.js.map +1 -1
  12. package/dist/cli/commands/completion.js +34 -2
  13. package/dist/cli/commands/completion.js.map +1 -1
  14. package/dist/cli/commands/config.d.ts +11 -1
  15. package/dist/cli/commands/config.js +68 -6
  16. package/dist/cli/commands/config.js.map +1 -1
  17. package/dist/cli/commands/create.d.ts +1 -0
  18. package/dist/cli/commands/create.js +40 -4
  19. package/dist/cli/commands/create.js.map +1 -1
  20. package/dist/cli/commands/extension/bundled-catalog.js +4 -3
  21. package/dist/cli/commands/extension/bundled-catalog.js.map +1 -1
  22. package/dist/cli/commands/extension/scaffold.js +54 -21
  23. package/dist/cli/commands/extension/scaffold.js.map +1 -1
  24. package/dist/cli/commands/health.js +3 -11
  25. package/dist/cli/commands/health.js.map +1 -1
  26. package/dist/cli/commands/linked-test-parsers.js +5 -4
  27. package/dist/cli/commands/linked-test-parsers.js.map +1 -1
  28. package/dist/cli/commands/list.js +2 -3
  29. package/dist/cli/commands/list.js.map +1 -1
  30. package/dist/cli/commands/plan.d.ts +5 -0
  31. package/dist/cli/commands/plan.js +59 -10
  32. package/dist/cli/commands/plan.js.map +1 -1
  33. package/dist/cli/commands/search.js +45 -6
  34. package/dist/cli/commands/search.js.map +1 -1
  35. package/dist/cli/commands/test.js +3 -3
  36. package/dist/cli/commands/test.js.map +1 -1
  37. package/dist/cli/commands/update-many.js +35 -6
  38. package/dist/cli/commands/update-many.js.map +1 -1
  39. package/dist/cli/commands/update.d.ts +2 -0
  40. package/dist/cli/commands/update.js +59 -8
  41. package/dist/cli/commands/update.js.map +1 -1
  42. package/dist/cli/commands/validate.js +32 -12
  43. package/dist/cli/commands/validate.js.map +1 -1
  44. package/dist/cli/help-json-payload.d.ts +1 -11
  45. package/dist/cli/help-json-payload.js +12 -12
  46. package/dist/cli/help-json-payload.js.map +1 -1
  47. package/dist/cli/register-mutation.js +64 -5
  48. package/dist/cli/register-mutation.js.map +1 -1
  49. package/dist/cli/register-setup.js +4 -2
  50. package/dist/cli/register-setup.js.map +1 -1
  51. package/dist/cli/registration-helpers.d.ts +2 -6
  52. package/dist/cli/registration-helpers.js +9 -6
  53. package/dist/cli/registration-helpers.js.map +1 -1
  54. package/dist/core/config/nested-settings.d.ts +86 -0
  55. package/dist/core/config/nested-settings.js +258 -0
  56. package/dist/core/config/nested-settings.js.map +1 -0
  57. package/dist/core/item/parse.d.ts +19 -0
  58. package/dist/core/item/parse.js +76 -2
  59. package/dist/core/item/parse.js.map +1 -1
  60. package/dist/core/item/priority.d.ts +2 -1
  61. package/dist/core/item/priority.js +12 -2
  62. package/dist/core/item/priority.js.map +1 -1
  63. package/dist/core/search/providers.js +25 -5
  64. package/dist/core/search/providers.js.map +1 -1
  65. package/dist/core/search/staleness.d.ts +23 -0
  66. package/dist/core/search/staleness.js +34 -0
  67. package/dist/core/search/staleness.js.map +1 -0
  68. package/dist/core/search/vector-stores.js +12 -3
  69. package/dist/core/search/vector-stores.js.map +1 -1
  70. package/dist/core/shared/html-entity-decode.d.ts +21 -0
  71. package/dist/core/shared/html-entity-decode.js +122 -0
  72. package/dist/core/shared/html-entity-decode.js.map +1 -0
  73. package/dist/core/shared/levenshtein.js +23 -7
  74. package/dist/core/shared/levenshtein.js.map +1 -1
  75. package/dist/core/shared/split-comma-list.d.ts +20 -0
  76. package/dist/core/shared/split-comma-list.js +29 -0
  77. package/dist/core/shared/split-comma-list.js.map +1 -0
  78. package/dist/mcp/server.js +10 -3
  79. package/dist/mcp/server.js.map +1 -1
  80. package/dist/sdk/cli-contracts/commander-mutation-options.js +47 -11
  81. package/dist/sdk/cli-contracts/commander-mutation-options.js.map +1 -1
  82. package/dist/sdk/cli-contracts/tool-option-contracts.js +5 -2
  83. package/dist/sdk/cli-contracts/tool-option-contracts.js.map +1 -1
  84. package/dist/sdk/cli-contracts/tool-parameter-tables.js +12 -2
  85. package/dist/sdk/cli-contracts/tool-parameter-tables.js.map +1 -1
  86. package/dist/sdk/cli-contracts.js +30 -2
  87. package/dist/sdk/cli-contracts.js.map +1 -1
  88. package/dist/sdk/runtime.d.ts +1 -1
  89. package/dist/sdk/runtime.js +3 -3
  90. package/dist/sdk/runtime.js.map +1 -1
  91. package/docs/AGENT_GUIDE.md +7 -0
  92. package/docs/COMMANDS.md +17 -0
  93. package/docs/CONFIGURATION.md +55 -0
  94. package/docs/QUICKSTART.md +3 -0
  95. package/package.json +1 -1
  96. package/packages/pm-calendar/extensions/calendar/runtime.js +5 -0
  97. 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 `&lt;type&gt;` 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 `&lt;` or `&gt;` 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 `&amp;lt;` stays as the literal `&lt;` (because `&amp;` 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
+ "&lt;": "<",
20
+ "&gt;": ">",
21
+ "&quot;": '"',
22
+ "&#39;": "'",
23
+ "&amp;": "&",
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, `&amp;` is matched as a whole token, so a
28
+ // substring like `&amp;lt;` matches `&amp;` once → `&lt;` (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 (`&lt;`, `&gt;`, `&quot;`, `&#39;`,
33
+ * `&amp;`) in a single pass — but only when the input contains `&lt;` or
34
+ * `&gt;`. 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 `&amp;lt;` decodes to `&lt;` (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 `&lt;` / `&gt;` 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 `&amp;` / `&quot;` / `&#39;`
48
+ // would risk altering legitimate text that contains those literal token
49
+ // sequences for unrelated reasons (a URL containing `&amp;`, 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 `&amp;-only is no-op` and `&quot;-only is no-op`.
54
+ if (!input.includes("&lt;") && !input.includes("&gt;")) {
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 `&lt;type&gt;` 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 `&lt;` or `&gt;` 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 `&amp;lt;` stays as the literal `&lt;` (because `&amp;` 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 \"&lt;\": \"<\",\n \"&gt;\": \">\",\n \"&quot;\": '\"',\n \"&#39;\": \"'\",\n \"&amp;\": \"&\",\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, `&amp;` is matched as a whole token, so a\n// substring like `&amp;lt;` matches `&amp;` once → `&lt;` (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 (`&lt;`, `&gt;`, `&quot;`, `&#39;`,\n * `&amp;`) in a single pass — but only when the input contains `&lt;` or\n * `&gt;`. 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 `&amp;lt;` decodes to `&lt;` (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 `&lt;` / `&gt;` 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 `&amp;` / `&quot;` / `&#39;`\n // would risk altering legitimate text that contains those literal token\n // sequences for unrelated reasons (a URL containing `&amp;`, 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 `&amp;-only is no-op` and `&quot;-only is no-op`.\n if (!input.includes(\"&lt;\") && !input.includes(\"&gt;\")) {\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":"AAAA,oFAAoF;AACpF,gFAAgF;AAChF,iFAAiF;AACjF,+EAA+E;AAC/E,gFAAgF;AAChF,8EAA8E;AAC9E,mDAAmD;AACnD,EAAE;AACF,+EAA+E;AAC/E,8EAA8E;AAC9E,gFAAgF;AAChF,6EAA6E;AAC7E,6EAA6E;AAC7E,0EAA0E;AAC1E,2BAA2B;;;AAE3B,MAAM,UAAU,GAAqC,MAAM,CAAC,MAAM,CAAC;IACjE,MAAM,EAAE,GAAG;IACX,MAAM,EAAE,GAAG;IACX,QAAQ,EAAE,GAAG;IACb,OAAO,EAAE,GAAG;IACZ,OAAO,EAAE,GAAG;CACb,CAAC,CAAC;AAEH,6EAA6E;AAC7E,6EAA6E;AAC7E,4EAA4E;AAC5E,4EAA4E;AAC5E,4EAA4E;AAC5E,MAAM,cAAc,GAAG,2BAA2B,CAAC;AAEnD;;;;;;;;GAQG;AACH,MAAM,UAAU,2BAA2B,CAAC,KAAa;IACvD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,KAAK,CAAC;IACf,CAAC;IACD,0EAA0E;IAC1E,uEAAuE;IACvE,6DAA6D;IAC7D,wEAAwE;IACxE,wEAAwE;IACxE,uEAAuE;IACvE,oEAAoE;IACpE,uEAAuE;IACvE,qEAAqE;IACrE,0DAA0D;IAC1D,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QACvD,OAAO,KAAK,CAAC;IACf,CAAC;IACD,6EAA6E;IAC7E,OAAO,KAAK,CAAC,OAAO,CAAC,cAAc,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,UAAU,CAAC,KAAK,CAAW,CAAC,CAAC;AAC/E,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,2BAA2B,CAAI,OAAU;IACvD,OAAO,WAAW,CAAC,OAAO,EAAE,IAAI,OAAO,EAAU,CAAM,CAAC;AAC1D,CAAC;AAED,SAAS,WAAW,CAAC,KAAc,EAAE,IAAqB;IACxD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,2BAA2B,CAAC,KAAK,CAAC,CAAC;IAC5C,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;YACpB,OAAO,KAAK,CAAC;QACf,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAChB,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,WAAW,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC;IACxD,CAAC;IACD,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAChD,yEAAyE;QACzE,0EAA0E;QAC1E,qEAAqE;QACrE,6EAA6E;QAC7E,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;YAC1B,OAAO,KAAK,CAAC;QACf,CAAC;QACD,IAAI,IAAI,CAAC,GAAG,CAAC,KAAe,CAAC,EAAE,CAAC;YAC9B,OAAO,KAAK,CAAC;QACf,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,KAAe,CAAC,CAAC;QAC1B,MAAM,MAAM,GAAG,KAAgC,CAAC;QAChD,0EAA0E;QAC1E,4DAA4D;QAC5D,MAAM,MAAM,GAA4B,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,KAAe,CAAC,CAAC,CAAC;QAC9F,uEAAuE;QACvE,qEAAqE;QACrE,sEAAsE;QACtE,oEAAoE;QACpE,uEAAuE;QACvE,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YAClD,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,GAAG,EAAE;gBACjC,KAAK,EAAE,WAAW,CAAC,KAAK,EAAE,IAAI,CAAC;gBAC/B,UAAU,EAAE,IAAI;gBAChB,QAAQ,EAAE,IAAI;gBACd,YAAY,EAAE,IAAI;aACnB,CAAC,CAAC;QACL,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,aAAa,CAAC,KAAa;IAClC,MAAM,KAAK,GAAG,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;IAC3C,OAAO,KAAK,KAAK,MAAM,CAAC,SAAS,IAAI,KAAK,KAAK,IAAI,CAAC;AACtD,CAAC","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]="ed0e9098-b7bf-5a5c-a816-6fcdf522a50b")}catch(e){}}();
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 previous = new Array(right.length + 1);
11
- const current = new Array(right.length + 1);
12
- for (let column = 0; column <= right.length; column += 1) {
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
- const candidate = Math.min(substitution, insertion, deletion);
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 <= right.length; column += 1) {
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=ed0e9098-b7bf-5a5c-a816-6fcdf522a50b
56
+ //# debugId=6f3e0977-8505-552c-b2ff-862f2a5d5a91
@@ -1 +1 @@
1
- {"version":3,"file":"levenshtein.js","sources":["core/shared/levenshtein.ts"],"sourceRoot":"/","sourcesContent":["export 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 previous = new Array<number>(right.length + 1);\n const current = new Array<number>(right.length + 1);\n for (let column = 0; column <= right.length; column += 1) {\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 const candidate = Math.min(substitution, insertion, deletion);\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 <= right.length; column += 1) {\n previous[column] = current[column];\n }\n }\n const result = previous[right.length];\n return result <= limit ? result : null;\n}\n"],"names":[],"mappings":";;AAAA,MAAM,UAAU,8BAA8B,CAAC,IAAY,EAAE,KAAa,EAAE,KAAa;IACvF,IAAI,IAAI,KAAK,KAAK,EAAE,CAAC;QACnB,OAAO,CAAC,CAAC;IACX,CAAC;IACD,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,KAAK,EAAE,CAAC;QACjD,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,QAAQ,GAAG,IAAI,KAAK,CAAS,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACrD,MAAM,OAAO,GAAG,IAAI,KAAK,CAAS,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACpD,KAAK,IAAI,MAAM,GAAG,CAAC,EAAE,MAAM,IAAI,KAAK,CAAC,MAAM,EAAE,MAAM,IAAI,CAAC,EAAE,CAAC;QACzD,QAAQ,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC;IAC5B,CAAC;IACD,KAAK,IAAI,GAAG,GAAG,CAAC,EAAE,GAAG,IAAI,IAAI,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC;QAC/C,OAAO,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC;QACjB,IAAI,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;QACxB,KAAK,IAAI,MAAM,GAAG,CAAC,EAAE,MAAM,IAAI,KAAK,CAAC,MAAM,EAAE,MAAM,IAAI,CAAC,EAAE,CAAC;YACzD,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,GAAG,CAAC,CAAC,KAAK,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YACzD,MAAM,YAAY,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC;YACjD,MAAM,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC;YAC1C,MAAM,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACtC,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;YAC9D,OAAO,CAAC,MAAM,CAAC,GAAG,SAAS,CAAC;YAC5B,IAAI,SAAS,GAAG,MAAM,EAAE,CAAC;gBACvB,MAAM,GAAG,SAAS,CAAC;YACrB,CAAC;QACH,CAAC;QACD,IAAI,MAAM,GAAG,KAAK,EAAE,CAAC;YACnB,OAAO,IAAI,CAAC;QACd,CAAC;QACD,KAAK,IAAI,MAAM,GAAG,CAAC,EAAE,MAAM,IAAI,KAAK,CAAC,MAAM,EAAE,MAAM,IAAI,CAAC,EAAE,CAAC;YACzD,QAAQ,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;QACrC,CAAC;IACH,CAAC;IACD,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IACtC,OAAO,MAAM,IAAI,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;AACzC,CAAC","debugId":"ed0e9098-b7bf-5a5c-a816-6fcdf522a50b"}
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"}
@@ -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]="16ab349e-99ce-51eb-85d5-62e1c2c56c10")}catch(e){}}();
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
- const args = asRecordClone(params.arguments);
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 `&lt;type&gt;` 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=16ab349e-99ce-51eb-85d5-62e1c2c56c10
679
+ //# debugId=0b886283-0ee3-5dcd-bd65-6199134c8901