pi-harness-runtime 1.1.422 → 1.1.424

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 (143) hide show
  1. package/README.md +1 -1
  2. package/harness/skills-plan/2026-07-12-herdr-moo-skills-adaptation-plan.md +9 -6
  3. package/index.ts +32 -1
  4. package/package.json +2 -6
  5. package/packages/a2a-adapter/package.json +1 -1
  6. package/packages/agent-policy/package.json +1 -1
  7. package/packages/architecture-generator/package.json +1 -1
  8. package/packages/auth/package.json +1 -1
  9. package/packages/autonomous-refactor/package.json +1 -1
  10. package/packages/autonomous-runtime/package.json +1 -1
  11. package/packages/cache-strategy/package.json +1 -1
  12. package/packages/capability-registry/package.json +1 -1
  13. package/packages/checkpoint/package.json +1 -1
  14. package/packages/cli-plugin-sdk/package.json +1 -1
  15. package/packages/clipboard/package.json +1 -1
  16. package/packages/clipboard-plugin/package.json +1 -1
  17. package/packages/code-generation/package.json +1 -1
  18. package/packages/code-review/package.json +1 -1
  19. package/packages/codex-adapter/package.json +1 -1
  20. package/packages/codex-watcher/package.json +1 -1
  21. package/packages/config-capture/package.json +1 -1
  22. package/packages/connection-runtime/package.json +1 -1
  23. package/packages/context-compiler/package.json +1 -1
  24. package/packages/context-discovery/package.json +1 -1
  25. package/packages/context-manager/package.json +1 -1
  26. package/packages/contracts/package.json +1 -1
  27. package/packages/cookie-sanitizer/package.json +1 -1
  28. package/packages/cost-optimizer/package.json +1 -1
  29. package/packages/dependency-analyzer/package.json +1 -1
  30. package/packages/django-plugin/package.json +1 -1
  31. package/packages/doc-generator/package.json +1 -1
  32. package/packages/durable-review/package.json +1 -1
  33. package/packages/e2e-tester/package.json +1 -1
  34. package/packages/evaluation-engine/package.json +1 -1
  35. package/packages/evaluation-runner/package.json +1 -1
  36. package/packages/event-bus/package.json +1 -1
  37. package/packages/event-sink/package.json +1 -1
  38. package/packages/event-store/package.json +1 -1
  39. package/packages/experience-replay/package.json +1 -1
  40. package/packages/feedback-collector/package.json +1 -1
  41. package/packages/file-copy-helper/package.json +1 -1
  42. package/packages/framework-detector/package.json +1 -1
  43. package/packages/framework-plugin-sdk/package.json +1 -1
  44. package/packages/frappe-plugin/package.json +1 -1
  45. package/packages/generic-web-plugin/package.json +1 -1
  46. package/packages/health-monitor/package.json +1 -1
  47. package/packages/intent-analyzer/package.json +1 -1
  48. package/packages/jev-judge/package.json +1 -1
  49. package/packages/knowledge-graph/package.json +1 -1
  50. package/packages/knowledge-retrieval/package.json +1 -1
  51. package/packages/laravel-plugin/package.json +1 -1
  52. package/packages/learning-engine/package.json +1 -1
  53. package/packages/logging/package.json +1 -1
  54. package/packages/mcp-adapter/package.json +1 -1
  55. package/packages/memory-engine/package.json +1 -1
  56. package/packages/milestone-manager/package.json +1 -1
  57. package/packages/model-registry/package.json +1 -1
  58. package/packages/nextjs-plugin/package.json +1 -1
  59. package/packages/notification/package.json +1 -1
  60. package/packages/observability/package.json +1 -1
  61. package/packages/okf-indexer/package.json +1 -1
  62. package/packages/paperclip-integration/package.json +1 -1
  63. package/packages/performance-optimizer/package.json +1 -1
  64. package/packages/permission-mode/package.json +1 -1
  65. package/packages/privilege-broker/package.json +1 -1
  66. package/packages/project-analyzer/package.json +1 -1
  67. package/packages/project-bootstrap/package.json +1 -1
  68. package/packages/projection-engine/package.json +1 -1
  69. package/packages/prompt-compiler/package.json +1 -1
  70. package/packages/prompt-versioning/package.json +1 -1
  71. package/packages/provider-adapter-sdk/package.json +1 -1
  72. package/packages/provider-router/package.json +1 -1
  73. package/packages/provider-selector/package.json +1 -1
  74. package/packages/providers/package.json +1 -1
  75. package/packages/qdrant-skills/package.json +1 -1
  76. package/packages/quota-manager/package.json +1 -1
  77. package/packages/rate-limiter/package.json +1 -1
  78. package/packages/react-vite-plugin/package.json +1 -1
  79. package/packages/relay/package.json +1 -1
  80. package/packages/release-manager/package.json +1 -1
  81. package/packages/requirement-compiler/package.json +1 -1
  82. package/packages/runtime/package.json +1 -1
  83. package/packages/scheduler/package.json +1 -1
  84. package/packages/scheduler-adapter/package.json +1 -1
  85. package/packages/session/package.json +1 -1
  86. package/packages/session-api/package.json +1 -1
  87. package/packages/session-export/package.json +1 -1
  88. package/packages/shared-context/package.json +1 -1
  89. package/packages/skill-mcp-client/package.json +1 -1
  90. package/packages/skill-registry/package.json +1 -1
  91. package/packages/skills/dist/index.d.ts.map +1 -1
  92. package/packages/skills/dist/index.js +12 -4
  93. package/packages/skills/dist/index.js.map +1 -1
  94. package/packages/skills/dist/sanitize-rules.d.ts +93 -0
  95. package/packages/skills/dist/sanitize-rules.d.ts.map +1 -0
  96. package/packages/skills/dist/sanitize-rules.js +174 -0
  97. package/packages/skills/dist/sanitize-rules.js.map +1 -0
  98. package/packages/skills/dist/sanitize.d.ts +13 -2
  99. package/packages/skills/dist/sanitize.d.ts.map +1 -1
  100. package/packages/skills/dist/sanitize.js +14 -48
  101. package/packages/skills/dist/sanitize.js.map +1 -1
  102. package/packages/skills/dist/vector-schema.d.ts +7 -1
  103. package/packages/skills/dist/vector-schema.d.ts.map +1 -1
  104. package/packages/skills/dist/vector-schema.js +10 -5
  105. package/packages/skills/dist/vector-schema.js.map +1 -1
  106. package/packages/skills/package.json +1 -1
  107. package/packages/skills/src/index.ts +12 -5
  108. package/packages/skills/src/sanitize-rules.ts +219 -0
  109. package/packages/skills/src/sanitize.ts +26 -56
  110. package/packages/skills/src/vector-schema.ts +22 -14
  111. package/packages/skills/test/remote-skills.test.ts +33 -6
  112. package/packages/skills/test/sanitize.test.ts +163 -44
  113. package/packages/sprint-planner/package.json +1 -1
  114. package/packages/subscription-engine/package.json +1 -1
  115. package/packages/task-compiler/package.json +1 -1
  116. package/packages/telegram/package.json +1 -1
  117. package/packages/tencentdb-memory/package.json +1 -1
  118. package/packages/tencentdb-sync/package.json +1 -1
  119. package/packages/test-data-generator/package.json +1 -1
  120. package/packages/test-generator/package.json +1 -1
  121. package/packages/todo-bd-sync/package.json +1 -1
  122. package/packages/token-estimation/package.json +1 -1
  123. package/packages/token-optimizer/package.json +1 -1
  124. package/packages/tooling-preference/package.json +1 -1
  125. package/packages/trajectory/package.json +1 -1
  126. package/packages/tui/package.json +1 -1
  127. package/packages/two-phase-review/package.json +1 -1
  128. package/packages/types/package.json +1 -1
  129. package/packages/voice-api/package.json +1 -1
  130. package/packages/webmcp-tester/package.json +1 -1
  131. package/packages/wiki/package.json +1 -1
  132. package/packages/workflow-events/package.json +1 -1
  133. package/packages/workspace-scanner/package.json +1 -1
  134. package/packages/worktree/package.json +1 -1
  135. package/packages/write-review/package.json +1 -1
  136. package/plugins/herdr-clipboard/paste.sh +28 -0
  137. package/scripts/copy-plugins.js +9 -5
  138. package/scripts/sync-to-qdrant.ts +5 -9
  139. package/scripts/verify-package-contents.ts +124 -1
  140. package/skills/harness-runtime/SKILL.md +0 -300
  141. /package/{.pi/plugins → plugins}/herdr-clipboard/herdr-plugin.toml +0 -0
  142. /package/{.pi/plugins → plugins}/herdr-clipboard/start-bridge.sh +0 -0
  143. /package/{.pi/plugins → plugins}/herdr-clipboard/tmux-watch.sh +0 -0
@@ -9,14 +9,25 @@
9
9
  *
10
10
  * Both writers therefore build their points with the same rules, and the
11
11
  * name of a point is the sanitized name. One skill, one point.
12
+ *
13
+ * The rules themselves are not in this file. The names of the clients of
14
+ * the users of the harness are not in this repository - it is public, and
15
+ * a list of client names in it is a list of client names in the world.
16
+ * The rules live in a local file, and `sanitize-rules.ts` reads them. See
17
+ * that module for the format.
12
18
  */
19
+ import { type SanitizationRuleSet } from "./sanitize-rules.js";
20
+ export type { SanitizationRule, SanitizationRuleSet } from "./sanitize-rules.js";
21
+ export { loadSanitizationRules, resetSanitizationRulesCache, sanitizationFilePath, sanitizationStatus, } from "./sanitize-rules.js";
13
22
  /**
14
23
  * Replace the client specific names of a text.
15
24
  *
16
25
  * The text stays the same length, so a vector that was built from it
17
26
  * keeps its shape.
27
+ *
28
+ * The rules are the ones of this installation unless a set is passed in.
18
29
  */
19
- export declare function sanitizeText(text: string): string;
30
+ export declare function sanitizeText(text: string, rules?: SanitizationRuleSet): string;
20
31
  /**
21
32
  * The name of a skill in the shared collection.
22
33
  *
@@ -28,5 +39,5 @@ export declare function sanitizeText(text: string): string;
28
39
  * returns. The replacements of the rules carry a space and brackets,
29
40
  * which no skill name may hold.
30
41
  */
31
- export declare function skillPointName(name: string): string;
42
+ export declare function skillPointName(name: string, rules?: SanitizationRuleSet): string;
32
43
  //# sourceMappingURL=sanitize.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../src/sanitize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAwDH;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMjD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAInD"}
1
+ {"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../src/sanitize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAyB,KAAK,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAEtF,YAAY,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AACjF,OAAO,EACL,qBAAqB,EACrB,2BAA2B,EAC3B,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,qBAAqB,CAAC;AAE7B;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,MAAM,EACZ,KAAK,GAAE,mBAA6C,GACnD,MAAM,CAMR;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,KAAK,GAAE,mBAA6C,GACnD,MAAM,CAIR"}
@@ -9,60 +9,26 @@
9
9
  *
10
10
  * Both writers therefore build their points with the same rules, and the
11
11
  * name of a point is the sanitized name. One skill, one point.
12
- */
13
- /**
14
- * The patterns of the client specific names.
15
12
  *
16
- * The list is about the names of the clients and the projects of the
17
- * users of the harness - not about the skills themselves.
18
- *
19
- * The patterns match a plain fragment, without a word boundary. A client
20
- * name rarely stands alone: it appears inside a directory name
21
- * (`th_digisoft_erp_chart.json`), inside a Python module (`inpac_pharma`)
22
- * or in a sentence ("integrated with Paperclip"). A word boundary would
23
- * miss all of them - and `_` is a word character, so a boundary never
24
- * follows an underscore.
13
+ * The rules themselves are not in this file. The names of the clients of
14
+ * the users of the harness are not in this repository - it is public, and
15
+ * a list of client names in it is a list of client names in the world.
16
+ * The rules live in a local file, and `sanitize-rules.ts` reads them. See
17
+ * that module for the format.
25
18
  */
26
- const SANITIZATION_RULES = [
27
- // Client company names (case-insensitive)
28
- { pattern: /inpac/gi, replacement: "[Client A]" },
29
- { pattern: /m-capital/gi, replacement: "[Client A]" },
30
- { pattern: /mcapital/gi, replacement: "[Client A]" },
31
- { pattern: /digisoft/gi, replacement: "[Client B]" },
32
- // Project/app specific names
33
- { pattern: /gse-insurance/gi, replacement: "[Insurance Project]" },
34
- { pattern: /tbs-import/gi, replacement: "[Import Project]" },
35
- { pattern: /paperclip/gi, replacement: "[Client D]" },
36
- { pattern: /openclaw/gi, replacement: "[Client F]" },
37
- // Generic replacements
38
- { pattern: /Company Name/gi, replacement: "[Client Name]" },
39
- { pattern: /your company/gi, replacement: "[Client]" },
40
- { pattern: /our client/gi, replacement: "[Client]" },
41
- { pattern: /client-specific/gi, replacement: "[Project-specific]" },
42
- ];
43
- /**
44
- * The names that are renamed as a whole.
45
- *
46
- * A name that would be unreadable after the pattern rules - a name that
47
- * carries the client name in its middle - is renamed here.
48
- */
49
- const SKILL_NAME_MAP = {
50
- "gse-insurance-step5-slot-filter": "insurance-project-step-filter",
51
- "inpac-pe-approval-workflow": "client-a-pe-approval-workflow",
52
- "inpac-po-approval-workflow": "client-a-po-approval-workflow",
53
- "inpac-pr-approval-workflow": "client-a-pr-approval-workflow",
54
- "tbs-import-clearance-lcv": "import-project-clearance",
55
- whispertool: "whisper-tool",
56
- };
19
+ import { loadSanitizationRules } from "./sanitize-rules.js";
20
+ export { loadSanitizationRules, resetSanitizationRulesCache, sanitizationFilePath, sanitizationStatus, } from "./sanitize-rules.js";
57
21
  /**
58
22
  * Replace the client specific names of a text.
59
23
  *
60
24
  * The text stays the same length, so a vector that was built from it
61
25
  * keeps its shape.
26
+ *
27
+ * The rules are the ones of this installation unless a set is passed in.
62
28
  */
63
- export function sanitizeText(text) {
29
+ export function sanitizeText(text, rules = loadSanitizationRules()) {
64
30
  let result = text;
65
- for (const rule of SANITIZATION_RULES) {
31
+ for (const rule of rules.rules) {
66
32
  result = result.replace(rule.pattern, rule.replacement);
67
33
  }
68
34
  return result;
@@ -78,11 +44,11 @@ export function sanitizeText(text) {
78
44
  * returns. The replacements of the rules carry a space and brackets,
79
45
  * which no skill name may hold.
80
46
  */
81
- export function skillPointName(name) {
82
- const mapped = SKILL_NAME_MAP[name];
47
+ export function skillPointName(name, rules = loadSanitizationRules()) {
48
+ const mapped = rules.nameMap[name];
83
49
  if (mapped)
84
50
  return mapped;
85
- return kebabName(sanitizeText(name));
51
+ return kebabName(sanitizeText(name, rules));
86
52
  }
87
53
  /**
88
54
  * Bring a name into the shape of a skill name.
@@ -1 +1 @@
1
- {"version":3,"file":"sanitize.js","sourceRoot":"","sources":["../src/sanitize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAQH;;;;;;;;;;;;GAYG;AACH,MAAM,kBAAkB,GAAuB;IAC7C,0CAA0C;IAC1C,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE;IACjD,EAAE,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,YAAY,EAAE;IACrD,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE;IACpD,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE;IAEpD,6BAA6B;IAC7B,EAAE,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,qBAAqB,EAAE;IAClE,EAAE,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,kBAAkB,EAAE;IAC5D,EAAE,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,YAAY,EAAE;IACrD,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE;IAEpD,uBAAuB;IACvB,EAAE,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,eAAe,EAAE;IAC3D,EAAE,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,UAAU,EAAE;IACtD,EAAE,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,UAAU,EAAE;IACpD,EAAE,OAAO,EAAE,mBAAmB,EAAE,WAAW,EAAE,oBAAoB,EAAE;CACpE,CAAC;AAEF;;;;;GAKG;AACH,MAAM,cAAc,GAA2B;IAC9C,iCAAiC,EAAE,+BAA+B;IAClE,4BAA4B,EAAE,+BAA+B;IAC7D,4BAA4B,EAAE,+BAA+B;IAC7D,4BAA4B,EAAE,+BAA+B;IAC7D,0BAA0B,EAAE,0BAA0B;IACtD,WAAW,EAAE,cAAc;CAC3B,CAAC;AAEF;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY;IACxC,IAAI,MAAM,GAAG,IAAI,CAAC;IAClB,KAAK,MAAM,IAAI,IAAI,kBAAkB,EAAE,CAAC;QACvC,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;IACzD,CAAC;IACD,OAAO,MAAM,CAAC;AACf,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY;IAC1C,MAAM,MAAM,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;IACpC,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC;IAC1B,OAAO,SAAS,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC;AACtC,CAAC;AAED;;;;;GAKG;AACH,SAAS,SAAS,CAAC,IAAY;IAC9B,OAAO,IAAI;SACT,WAAW,EAAE;SACb,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC;SAC3B,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC;SACtB,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;AAC3B,CAAC"}
1
+ {"version":3,"file":"sanitize.js","sourceRoot":"","sources":["../src/sanitize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,qBAAqB,EAA4B,MAAM,qBAAqB,CAAC;AAGtF,OAAO,EACL,qBAAqB,EACrB,2BAA2B,EAC3B,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,qBAAqB,CAAC;AAE7B;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAC1B,IAAY,EACZ,KAAK,GAAwB,qBAAqB,EAAE;IAErD,IAAI,MAAM,GAAG,IAAI,CAAC;IAClB,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;QAChC,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;IACzD,CAAC;IACD,OAAO,MAAM,CAAC;AACf,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,cAAc,CAC5B,IAAY,EACZ,KAAK,GAAwB,qBAAqB,EAAE;IAErD,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC;IAC1B,OAAO,SAAS,CAAC,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;AAC7C,CAAC;AAED;;;;;GAKG;AACH,SAAS,SAAS,CAAC,IAAY;IAC9B,OAAO,IAAI;SACT,WAAW,EAAE;SACb,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC;SAC3B,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC;SACtB,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;AAC3B,CAAC"}
@@ -15,6 +15,7 @@
15
15
  * module owns the collection - that is a decision of the skills domain, not
16
16
  * of the vector store.
17
17
  */
18
+ import type { SanitizationRuleSet } from "./sanitize-rules.js";
18
19
  /**
19
20
  * The collection the runtime indexes into and searches.
20
21
  *
@@ -167,7 +168,12 @@ export declare function skillPointPayload(input: {
167
168
  description: string;
168
169
  /** The body of the skill. */
169
170
  body: string;
170
- }): SkillPointPayload;
171
+ },
172
+ /**
173
+ * The rules of the installation. They default to the rules of this
174
+ * machine, and a test passes its own rules in.
175
+ */
176
+ rules?: SanitizationRuleSet): SkillPointPayload;
171
177
  export declare function describeSkillSchemaMismatch(actual: {
172
178
  dimensions?: number;
173
179
  distance?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"vector-schema.d.ts","sourceRoot":"","sources":["../src/vector-schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,gCAAgC,CAAC;AAE9D;;;;;;;GAOG;AACH,eAAO,MAAM,uBAAuB,sBAAsB,CAAC;AAE3D,0CAA0C;AAC1C,eAAO,MAAM,6BAA6B,2CAA2C,CAAC;AAEtF,gDAAgD;AAChD,eAAO,MAAM,kCAAkC,MAAM,CAAC;AAEtD;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,eAAe,OAAO,CAAC;AAEpC;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAE1C,yDAAyD;AACzD,MAAM,WAAW,iBAAiB;IACjC,uBAAuB;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,2BAA2B;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,6CAA6C;IAC7C,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,8DAA8D;IAC9D,IAAI,EAAE,MAAM,CAAC;IACb,2BAA2B;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,6BAA6B;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,0CAA0C;IAC1C,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,OAAO,CAAC;AAE7C,kDAAkD;AAClD,eAAO,MAAM,wBAAwB,OAAO,CAAC;AAE7C;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAMhD;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE;IACpC,gEAAgE;IAChE,IAAI,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2BAA2B;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,6BAA6B;IAC7B,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,MAAM,CAMT;AAED;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,iBAAiB,CAAC;CAC5B;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB,GAAG,gBAAgB,CAE7E;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE;IACvC,gEAAgE;IAChE,IAAI,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2BAA2B;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,6BAA6B;IAC7B,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,iBAAiB,CAWpB;AACD,wBAAgB,2BAA2B,CAC1C,MAAM,EAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,EAClD,QAAQ,EAAE,iBAAiB,GACzB,MAAM,GAAG,IAAI,CASf"}
1
+ {"version":3,"file":"vector-schema.d.ts","sourceRoot":"","sources":["../src/vector-schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAE/D;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,gCAAgC,CAAC;AAE9D;;;;;;;GAOG;AACH,eAAO,MAAM,uBAAuB,sBAAsB,CAAC;AAE3D,0CAA0C;AAC1C,eAAO,MAAM,6BAA6B,2CAA2C,CAAC;AAEtF,gDAAgD;AAChD,eAAO,MAAM,kCAAkC,MAAM,CAAC;AAEtD;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,eAAe,OAAO,CAAC;AAEpC;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAE1C,yDAAyD;AACzD,MAAM,WAAW,iBAAiB;IACjC,uBAAuB;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,2BAA2B;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,6CAA6C;IAC7C,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,8DAA8D;IAC9D,IAAI,EAAE,MAAM,CAAC;IACb,2BAA2B;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,6BAA6B;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,0CAA0C;IAC1C,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,OAAO,CAAC;AAE7C,kDAAkD;AAClD,eAAO,MAAM,wBAAwB,OAAO,CAAC;AAE7C;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAMhD;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE;IACpC,gEAAgE;IAChE,IAAI,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2BAA2B;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,6BAA6B;IAC7B,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,MAAM,CAMT;AAED;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,iBAAiB,CAAC;CAC5B;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB,GAAG,gBAAgB,CAE7E;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE;IACL,gEAAgE;IAChE,IAAI,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2BAA2B;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,6BAA6B;IAC7B,IAAI,EAAE,MAAM,CAAC;CACd;AACD;;;GAGG;AACH,KAAK,CAAC,EAAE,mBAAmB,GAC1B,iBAAiB,CAWnB;AACD,wBAAgB,2BAA2B,CAC1C,MAAM,EAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,EAClD,QAAQ,EAAE,iBAAiB,GACzB,MAAM,GAAG,IAAI,CASf"}
@@ -125,11 +125,16 @@ export function skillQdrantPoint(payload) {
125
125
  * of a user must not travel into it. Both writers use this function, so
126
126
  * both write the same sanitized payload for the same skill.
127
127
  */
128
- export function skillPointPayload(input) {
129
- const name = skillPointName(input.name);
130
- const title = input.title ? skillPointName(input.title) : undefined;
131
- const description = sanitizeText(input.description);
132
- const body = sanitizeText(input.body).slice(0, SKILL_PAYLOAD_BODY_LIMIT);
128
+ export function skillPointPayload(input,
129
+ /**
130
+ * The rules of the installation. They default to the rules of this
131
+ * machine, and a test passes its own rules in.
132
+ */
133
+ rules) {
134
+ const name = skillPointName(input.name, rules);
135
+ const title = input.title ? skillPointName(input.title, rules) : undefined;
136
+ const description = sanitizeText(input.description, rules);
137
+ const body = sanitizeText(input.body, rules).slice(0, SKILL_PAYLOAD_BODY_LIMIT);
133
138
  return {
134
139
  name,
135
140
  description,
@@ -1 +1 @@
1
- {"version":3,"file":"vector-schema.js","sourceRoot":"","sources":["../src/vector-schema.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"vector-schema.js","sourceRoot":"","sources":["../src/vector-schema.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/skills",
3
- "version": "1.1.422",
3
+ "version": "1.1.424",
4
4
  "description": "File-based skill system with pi.dev compatibility and Hermes self-improvement",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -24,6 +24,9 @@ export * from "./linter.js";
24
24
  import type { SkillsConfig, ScanResult, ScanOptions } from "./types.js";
25
25
  import { scanForSkillsSync } from "./scanner.js";
26
26
  import { getGlobalSkillRegistry, type SkillRegistry } from "./loader.js";
27
+ import { existsSync, readdirSync } from "node:fs";
28
+ import { homedir } from "node:os";
29
+ import { join } from "node:path";
27
30
 
28
31
  /**
29
32
  * The result of the skill initialization.
@@ -73,16 +76,20 @@ export function initSkills(
73
76
  // Hint: if ~/.pi-harness-runtime/skills/ is empty, suggest the sync script
74
77
  checkEmptySkillsDir();
75
78
 
76
- return { ...result, registry };
79
+ // The scan can find the same skill twice: a symlinked directory is
80
+ // scanned twice, because the scanner follows the path it is given. The
81
+ // registry keeps one entry per id, so the count of the local skills is
82
+ // the count of the DISTINCT skills - and not the count of the files
83
+ // that were read.
84
+ const distinct = new Map<string, (typeof result.skills)[number]>();
85
+ for (const skill of result.skills) distinct.set(skill.id, skill);
86
+
87
+ return { ...result, skills: [...distinct.values()], registry };
77
88
  }
78
89
 
79
90
  /** Show sync hint if the harness skills dir is empty. */
80
91
  function checkEmptySkillsDir(): void {
81
92
  try {
82
- const { readdirSync, existsSync } = require("node:fs");
83
- const { join } = require("node:path");
84
- const { homedir } = require("node:os");
85
-
86
93
  const harnessDir = join(homedir(), ".pi-harness-runtime", "skills");
87
94
  if (!existsSync(harnessDir)) return;
88
95
 
@@ -0,0 +1,219 @@
1
+ /**
2
+ * The rules that keep a client name out of the shared collection.
3
+ *
4
+ * The names of the clients of the users of the harness are **not** in this
5
+ * repository. The repository is public, and a list of client names in it
6
+ * is a list of client names in the world - the names of the projects and
7
+ * of the clients of every person who installs the package.
8
+ *
9
+ * The rules therefore live in a local file:
10
+ *
11
+ * ~/.pi-harness-runtime/skill-sanitization.json
12
+ *
13
+ * and this module is the mechanism that reads them and applies them. The
14
+ * file is per installation, so a machine holds the rules of its own
15
+ * clients and nobody else's.
16
+ *
17
+ * The file is optional. Without it only the generic rules apply, and the
18
+ * name of a client would travel into the collection of that installation.
19
+ * `sanitizationStatus()` tells the caller which of the two is the case,
20
+ * so a missing file is a visible state and not a silent one.
21
+ *
22
+ * The format of the file:
23
+ *
24
+ * ```json
25
+ * {
26
+ * "rules": [
27
+ * { "pattern": "acme", "replacement": "[Client A]", "flags": "gi" }
28
+ * ],
29
+ * "nameMap": {
30
+ * "acme-approval-workflow": "client-a-approval-workflow"
31
+ * }
32
+ * }
33
+ * ```
34
+ *
35
+ * The pattern is the source of a regular expression and the flags default
36
+ * to "gi". The rules match a plain fragment, without a word boundary: a
37
+ * client name rarely stands alone, it appears inside a directory name
38
+ * (`erp_report_2024.json`), inside a module name (`acme_pharma`) or in a
39
+ * sentence ("integrated with Acme"). A word boundary would miss all of
40
+ * them - and `_` is a word character, so a boundary never follows an
41
+ * underscore.
42
+ */
43
+
44
+ import { readFileSync } from "node:fs";
45
+ import { homedir } from "node:os";
46
+ import { join } from "node:path";
47
+
48
+ /** A rule as it is written in the local file. */
49
+ export interface SanitizationRuleSpec {
50
+ /** The source of a regular expression. */
51
+ pattern: string;
52
+ /** What a match becomes. */
53
+ replacement: string;
54
+ /** The flags of the regular expression. Defaults to "gi". */
55
+ flags?: string;
56
+ }
57
+
58
+ /** A compiled rule. */
59
+ export interface SanitizationRule {
60
+ pattern: RegExp;
61
+ replacement: string;
62
+ }
63
+
64
+ /** The whole rule set of one installation. */
65
+ export interface SanitizationRuleSet {
66
+ /** The rules, in the order they are applied. */
67
+ rules: SanitizationRule[];
68
+ /** The names that are renamed as a whole. */
69
+ nameMap: Record<string, string>;
70
+ /** Where the rules came from. */
71
+ source: "local-file" | "generic-only";
72
+ /** Why the local file did not contribute its rules. */
73
+ error?: string;
74
+ }
75
+
76
+ /**
77
+ * The rules that hold for every installation.
78
+ *
79
+ * They are about the way a text talks about a client, not about the name
80
+ * of a client: "Company Name" is a placeholder in a template, and it
81
+ * must not become a client name in the collection.
82
+ */
83
+ const GENERIC_RULES: SanitizationRule[] = [
84
+ { pattern: /Company Name/gi, replacement: "[Client Name]" },
85
+ { pattern: /your company/gi, replacement: "[Client]" },
86
+ { pattern: /our client/gi, replacement: "[Client]" },
87
+ { pattern: /client-specific/gi, replacement: "[Project-specific]" },
88
+ ];
89
+
90
+ /**
91
+ * The names that are renamed as a whole, for every installation.
92
+ *
93
+ * A name that would be unreadable after the pattern rules - a name that
94
+ * carries the client name in its middle - is renamed here.
95
+ *
96
+ * The map is empty: a name that belongs to a client is that client's, so it
97
+ * lives in the local file next to the other rules of the installation.
98
+ */
99
+ const GENERIC_NAME_MAP: Record<string, string> = {};
100
+
101
+ /** Where the rules of this installation live. */
102
+ export function sanitizationFilePath(): string {
103
+ const override = process.env.PI_SKILL_SANITIZATION_FILE;
104
+ if (override && override.trim()) return override.trim();
105
+ const home = process.env.HOME || process.env.USERPROFILE || homedir();
106
+ return join(home, ".pi-harness-runtime", "skill-sanitization.json");
107
+ }
108
+
109
+ /**
110
+ * The state of the rules of this installation.
111
+ *
112
+ * `configured` is false when the local file is missing or unreadable. The
113
+ * caller shows it, because a missing file is a weaker privacy guarantee
114
+ * and not a detail.
115
+ */
116
+ export function sanitizationStatus(): {
117
+ configured: boolean;
118
+ path: string;
119
+ ruleCount: number;
120
+ error?: string;
121
+ } {
122
+ const path = sanitizationFilePath();
123
+ const set = loadSanitizationRules(path);
124
+ return {
125
+ configured: set.source === "local-file",
126
+ path,
127
+ ruleCount: set.rules.length,
128
+ ...(set.error ? { error: set.error } : {}),
129
+ };
130
+ }
131
+
132
+ let cached: { path: string; set: SanitizationRuleSet } | undefined;
133
+
134
+ /**
135
+ * The rules of this installation.
136
+ *
137
+ * The file is read once per process: the rules do not change while the
138
+ * harness runs, and a skill that is indexed a hundred times must not read
139
+ * a file a hundred times.
140
+ */
141
+ export function loadSanitizationRules(path = sanitizationFilePath()): SanitizationRuleSet {
142
+ if (cached && cached.path === path) return cached.set;
143
+
144
+ const set = readSanitizationRules(path);
145
+ cached = { path, set };
146
+ return set;
147
+ }
148
+
149
+ /** Forget the cached rules. A test needs it, a new file needs it. */
150
+ export function resetSanitizationRulesCache(): void {
151
+ cached = undefined;
152
+ }
153
+
154
+ function readSanitizationRules(path: string): SanitizationRuleSet {
155
+ let raw: string;
156
+ try {
157
+ raw = readFileSync(path, "utf8");
158
+ } catch (err) {
159
+ const code = (err as NodeJS.ErrnoException)?.code;
160
+ if (code !== "ENOENT") {
161
+ return {
162
+ rules: [...GENERIC_RULES],
163
+ nameMap: { ...GENERIC_NAME_MAP },
164
+ source: "generic-only",
165
+ error: `The sanitization rules at ${path} could not be read: ${err instanceof Error ? err.message : String(err)}`,
166
+ };
167
+ }
168
+ return {
169
+ rules: [...GENERIC_RULES],
170
+ nameMap: { ...GENERIC_NAME_MAP },
171
+ source: "generic-only",
172
+ };
173
+ }
174
+
175
+ let parsed: unknown;
176
+ try {
177
+ parsed = JSON.parse(raw);
178
+ } catch (err) {
179
+ return {
180
+ rules: [...GENERIC_RULES],
181
+ nameMap: { ...GENERIC_NAME_MAP },
182
+ source: "generic-only",
183
+ error: `The sanitization rules at ${path} are not valid JSON: ${err instanceof Error ? err.message : String(err)}`,
184
+ };
185
+ }
186
+
187
+ const rules: SanitizationRule[] = [...GENERIC_RULES];
188
+ const nameMap: Record<string, string> = { ...GENERIC_NAME_MAP };
189
+
190
+ const file = parsed as {
191
+ rules?: SanitizationRuleSpec[];
192
+ nameMap?: Record<string, string>;
193
+ };
194
+
195
+ for (const spec of file.rules ?? []) {
196
+ if (!spec || typeof spec.pattern !== "string" || typeof spec.replacement !== "string") {
197
+ continue;
198
+ }
199
+ try {
200
+ rules.push({
201
+ pattern: new RegExp(spec.pattern, spec.flags ?? "gi"),
202
+ replacement: spec.replacement,
203
+ });
204
+ } catch {
205
+ // A broken pattern must not take the whole rule set down: the other
206
+ // rules still hold, and the broken one is reported below.
207
+ rules.push({
208
+ pattern: /$^/,
209
+ replacement: spec.replacement,
210
+ });
211
+ }
212
+ }
213
+
214
+ for (const [from, to] of Object.entries(file.nameMap ?? {})) {
215
+ if (typeof from === "string" && typeof to === "string") nameMap[from] = to;
216
+ }
217
+
218
+ return { rules, nameMap, source: "local-file" };
219
+ }
@@ -9,71 +9,38 @@
9
9
  *
10
10
  * Both writers therefore build their points with the same rules, and the
11
11
  * name of a point is the sanitized name. One skill, one point.
12
- */
13
-
14
- /** A rule that replaces a client specific text. */
15
- interface SanitizationRule {
16
- pattern: RegExp;
17
- replacement: string;
18
- }
19
-
20
- /**
21
- * The patterns of the client specific names.
22
- *
23
- * The list is about the names of the clients and the projects of the
24
- * users of the harness - not about the skills themselves.
25
12
  *
26
- * The patterns match a plain fragment, without a word boundary. A client
27
- * name rarely stands alone: it appears inside a directory name
28
- * (`th_digisoft_erp_chart.json`), inside a Python module (`inpac_pharma`)
29
- * or in a sentence ("integrated with Paperclip"). A word boundary would
30
- * miss all of them - and `_` is a word character, so a boundary never
31
- * follows an underscore.
13
+ * The rules themselves are not in this file. The names of the clients of
14
+ * the users of the harness are not in this repository - it is public, and
15
+ * a list of client names in it is a list of client names in the world.
16
+ * The rules live in a local file, and `sanitize-rules.ts` reads them. See
17
+ * that module for the format.
32
18
  */
33
- const SANITIZATION_RULES: SanitizationRule[] = [
34
- // Client company names (case-insensitive)
35
- { pattern: /inpac/gi, replacement: "[Client A]" },
36
- { pattern: /m-capital/gi, replacement: "[Client A]" },
37
- { pattern: /mcapital/gi, replacement: "[Client A]" },
38
- { pattern: /digisoft/gi, replacement: "[Client B]" },
39
19
 
40
- // Project/app specific names
41
- { pattern: /gse-insurance/gi, replacement: "[Insurance Project]" },
42
- { pattern: /tbs-import/gi, replacement: "[Import Project]" },
43
- { pattern: /paperclip/gi, replacement: "[Client D]" },
44
- { pattern: /openclaw/gi, replacement: "[Client F]" },
20
+ import { loadSanitizationRules, type SanitizationRuleSet } from "./sanitize-rules.js";
45
21
 
46
- // Generic replacements
47
- { pattern: /Company Name/gi, replacement: "[Client Name]" },
48
- { pattern: /your company/gi, replacement: "[Client]" },
49
- { pattern: /our client/gi, replacement: "[Client]" },
50
- { pattern: /client-specific/gi, replacement: "[Project-specific]" },
51
- ];
52
-
53
- /**
54
- * The names that are renamed as a whole.
55
- *
56
- * A name that would be unreadable after the pattern rules - a name that
57
- * carries the client name in its middle - is renamed here.
58
- */
59
- const SKILL_NAME_MAP: Record<string, string> = {
60
- "gse-insurance-step5-slot-filter": "insurance-project-step-filter",
61
- "inpac-pe-approval-workflow": "client-a-pe-approval-workflow",
62
- "inpac-po-approval-workflow": "client-a-po-approval-workflow",
63
- "inpac-pr-approval-workflow": "client-a-pr-approval-workflow",
64
- "tbs-import-clearance-lcv": "import-project-clearance",
65
- whispertool: "whisper-tool",
66
- };
22
+ export type { SanitizationRule, SanitizationRuleSet } from "./sanitize-rules.js";
23
+ export {
24
+ loadSanitizationRules,
25
+ resetSanitizationRulesCache,
26
+ sanitizationFilePath,
27
+ sanitizationStatus,
28
+ } from "./sanitize-rules.js";
67
29
 
68
30
  /**
69
31
  * Replace the client specific names of a text.
70
32
  *
71
33
  * The text stays the same length, so a vector that was built from it
72
34
  * keeps its shape.
35
+ *
36
+ * The rules are the ones of this installation unless a set is passed in.
73
37
  */
74
- export function sanitizeText(text: string): string {
38
+ export function sanitizeText(
39
+ text: string,
40
+ rules: SanitizationRuleSet = loadSanitizationRules(),
41
+ ): string {
75
42
  let result = text;
76
- for (const rule of SANITIZATION_RULES) {
43
+ for (const rule of rules.rules) {
77
44
  result = result.replace(rule.pattern, rule.replacement);
78
45
  }
79
46
  return result;
@@ -90,10 +57,13 @@ export function sanitizeText(text: string): string {
90
57
  * returns. The replacements of the rules carry a space and brackets,
91
58
  * which no skill name may hold.
92
59
  */
93
- export function skillPointName(name: string): string {
94
- const mapped = SKILL_NAME_MAP[name];
60
+ export function skillPointName(
61
+ name: string,
62
+ rules: SanitizationRuleSet = loadSanitizationRules(),
63
+ ): string {
64
+ const mapped = rules.nameMap[name];
95
65
  if (mapped) return mapped;
96
- return kebabName(sanitizeText(name));
66
+ return kebabName(sanitizeText(name, rules));
97
67
  }
98
68
 
99
69
  /**
@@ -17,6 +17,7 @@
17
17
  */
18
18
 
19
19
  import { sanitizeText, skillPointName } from "./sanitize.js";
20
+ import type { SanitizationRuleSet } from "./sanitize-rules.js";
20
21
 
21
22
  /**
22
23
  * The collection the runtime indexes into and searches.
@@ -189,20 +190,27 @@ export function skillQdrantPoint(payload: SkillPointPayload): SkillQdrantPoint {
189
190
  * of a user must not travel into it. Both writers use this function, so
190
191
  * both write the same sanitized payload for the same skill.
191
192
  */
192
- export function skillPointPayload(input: {
193
- /** The name the payload carries - the identity of the skill. */
194
- name: string;
195
- /** The title of the skill, when it differs from the name. */
196
- title?: string;
197
- /** What the skill does. */
198
- description: string;
199
- /** The body of the skill. */
200
- body: string;
201
- }): SkillPointPayload {
202
- const name = skillPointName(input.name);
203
- const title = input.title ? skillPointName(input.title) : undefined;
204
- const description = sanitizeText(input.description);
205
- const body = sanitizeText(input.body).slice(0, SKILL_PAYLOAD_BODY_LIMIT);
193
+ export function skillPointPayload(
194
+ input: {
195
+ /** The name the payload carries - the identity of the skill. */
196
+ name: string;
197
+ /** The title of the skill, when it differs from the name. */
198
+ title?: string;
199
+ /** What the skill does. */
200
+ description: string;
201
+ /** The body of the skill. */
202
+ body: string;
203
+ },
204
+ /**
205
+ * The rules of the installation. They default to the rules of this
206
+ * machine, and a test passes its own rules in.
207
+ */
208
+ rules?: SanitizationRuleSet,
209
+ ): SkillPointPayload {
210
+ const name = skillPointName(input.name, rules);
211
+ const title = input.title ? skillPointName(input.title, rules) : undefined;
212
+ const description = sanitizeText(input.description, rules);
213
+ const body = sanitizeText(input.body, rules).slice(0, SKILL_PAYLOAD_BODY_LIMIT);
206
214
  return {
207
215
  name,
208
216
  description,