pi-harness-runtime 1.1.422 → 1.1.423
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/index.ts +32 -1
- package/package.json +1 -1
- package/packages/a2a-adapter/package.json +1 -1
- package/packages/agent-policy/package.json +1 -1
- package/packages/architecture-generator/package.json +1 -1
- package/packages/auth/package.json +1 -1
- package/packages/autonomous-refactor/package.json +1 -1
- package/packages/autonomous-runtime/package.json +1 -1
- package/packages/cache-strategy/package.json +1 -1
- package/packages/capability-registry/package.json +1 -1
- package/packages/checkpoint/package.json +1 -1
- package/packages/cli-plugin-sdk/package.json +1 -1
- package/packages/clipboard/package.json +1 -1
- package/packages/clipboard-plugin/package.json +1 -1
- package/packages/code-generation/package.json +1 -1
- package/packages/code-review/package.json +1 -1
- package/packages/codex-adapter/package.json +1 -1
- package/packages/codex-watcher/package.json +1 -1
- package/packages/config-capture/package.json +1 -1
- package/packages/connection-runtime/package.json +1 -1
- package/packages/context-compiler/package.json +1 -1
- package/packages/context-discovery/package.json +1 -1
- package/packages/context-manager/package.json +1 -1
- package/packages/contracts/package.json +1 -1
- package/packages/cookie-sanitizer/package.json +1 -1
- package/packages/cost-optimizer/package.json +1 -1
- package/packages/dependency-analyzer/package.json +1 -1
- package/packages/django-plugin/package.json +1 -1
- package/packages/doc-generator/package.json +1 -1
- package/packages/durable-review/package.json +1 -1
- package/packages/e2e-tester/package.json +1 -1
- package/packages/evaluation-engine/package.json +1 -1
- package/packages/evaluation-runner/package.json +1 -1
- package/packages/event-bus/package.json +1 -1
- package/packages/event-sink/package.json +1 -1
- package/packages/event-store/package.json +1 -1
- package/packages/experience-replay/package.json +1 -1
- package/packages/feedback-collector/package.json +1 -1
- package/packages/file-copy-helper/package.json +1 -1
- package/packages/framework-detector/package.json +1 -1
- package/packages/framework-plugin-sdk/package.json +1 -1
- package/packages/frappe-plugin/package.json +1 -1
- package/packages/generic-web-plugin/package.json +1 -1
- package/packages/health-monitor/package.json +1 -1
- package/packages/intent-analyzer/package.json +1 -1
- package/packages/jev-judge/package.json +1 -1
- package/packages/knowledge-graph/package.json +1 -1
- package/packages/knowledge-retrieval/package.json +1 -1
- package/packages/laravel-plugin/package.json +1 -1
- package/packages/learning-engine/package.json +1 -1
- package/packages/logging/package.json +1 -1
- package/packages/mcp-adapter/package.json +1 -1
- package/packages/memory-engine/package.json +1 -1
- package/packages/milestone-manager/package.json +1 -1
- package/packages/model-registry/package.json +1 -1
- package/packages/nextjs-plugin/package.json +1 -1
- package/packages/notification/package.json +1 -1
- package/packages/observability/package.json +1 -1
- package/packages/okf-indexer/package.json +1 -1
- package/packages/paperclip-integration/package.json +1 -1
- package/packages/performance-optimizer/package.json +1 -1
- package/packages/permission-mode/package.json +1 -1
- package/packages/privilege-broker/package.json +1 -1
- package/packages/project-analyzer/package.json +1 -1
- package/packages/project-bootstrap/package.json +1 -1
- package/packages/projection-engine/package.json +1 -1
- package/packages/prompt-compiler/package.json +1 -1
- package/packages/prompt-versioning/package.json +1 -1
- package/packages/provider-adapter-sdk/package.json +1 -1
- package/packages/provider-router/package.json +1 -1
- package/packages/provider-selector/package.json +1 -1
- package/packages/providers/package.json +1 -1
- package/packages/qdrant-skills/package.json +1 -1
- package/packages/quota-manager/package.json +1 -1
- package/packages/rate-limiter/package.json +1 -1
- package/packages/react-vite-plugin/package.json +1 -1
- package/packages/relay/package.json +1 -1
- package/packages/release-manager/package.json +1 -1
- package/packages/requirement-compiler/package.json +1 -1
- package/packages/runtime/package.json +1 -1
- package/packages/scheduler/package.json +1 -1
- package/packages/scheduler-adapter/package.json +1 -1
- package/packages/session/package.json +1 -1
- package/packages/session-api/package.json +1 -1
- package/packages/session-export/package.json +1 -1
- package/packages/shared-context/package.json +1 -1
- package/packages/skill-mcp-client/package.json +1 -1
- package/packages/skill-registry/package.json +1 -1
- package/packages/skills/dist/index.d.ts.map +1 -1
- package/packages/skills/dist/index.js +12 -4
- package/packages/skills/dist/index.js.map +1 -1
- package/packages/skills/dist/sanitize-rules.d.ts +93 -0
- package/packages/skills/dist/sanitize-rules.d.ts.map +1 -0
- package/packages/skills/dist/sanitize-rules.js +173 -0
- package/packages/skills/dist/sanitize-rules.js.map +1 -0
- package/packages/skills/dist/sanitize.d.ts +13 -2
- package/packages/skills/dist/sanitize.d.ts.map +1 -1
- package/packages/skills/dist/sanitize.js +14 -48
- package/packages/skills/dist/sanitize.js.map +1 -1
- package/packages/skills/dist/vector-schema.d.ts +7 -1
- package/packages/skills/dist/vector-schema.d.ts.map +1 -1
- package/packages/skills/dist/vector-schema.js +10 -5
- package/packages/skills/dist/vector-schema.js.map +1 -1
- package/packages/skills/package.json +1 -1
- package/packages/skills/src/index.ts +12 -5
- package/packages/skills/src/sanitize-rules.ts +218 -0
- package/packages/skills/src/sanitize.ts +26 -56
- package/packages/skills/src/vector-schema.ts +22 -14
- package/packages/skills/test/remote-skills.test.ts +33 -6
- package/packages/skills/test/sanitize.test.ts +163 -44
- package/packages/sprint-planner/package.json +1 -1
- package/packages/subscription-engine/package.json +1 -1
- package/packages/task-compiler/package.json +1 -1
- package/packages/telegram/package.json +1 -1
- package/packages/tencentdb-memory/package.json +1 -1
- package/packages/tencentdb-sync/package.json +1 -1
- package/packages/test-data-generator/package.json +1 -1
- package/packages/test-generator/package.json +1 -1
- package/packages/todo-bd-sync/package.json +1 -1
- package/packages/token-estimation/package.json +1 -1
- package/packages/token-optimizer/package.json +1 -1
- package/packages/tooling-preference/package.json +1 -1
- package/packages/trajectory/package.json +1 -1
- package/packages/tui/package.json +1 -1
- package/packages/two-phase-review/package.json +1 -1
- package/packages/types/package.json +1 -1
- package/packages/voice-api/package.json +1 -1
- package/packages/webmcp-tester/package.json +1 -1
- package/packages/wiki/package.json +1 -1
- package/packages/workflow-events/package.json +1 -1
- package/packages/workspace-scanner/package.json +1 -1
- package/packages/worktree/package.json +1 -1
- package/packages/write-review/package.json +1 -1
- package/scripts/sync-to-qdrant.ts +5 -9
|
@@ -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
|
|
17
|
-
* users of the harness
|
|
18
|
-
*
|
|
19
|
-
* The
|
|
20
|
-
*
|
|
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
|
-
|
|
27
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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
|
-
}
|
|
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;
|
|
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
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"vector-schema.js","sourceRoot":"","sources":["../src/vector-schema.ts"],"names":[],"mappings":"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"}
|
|
@@ -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
|
-
|
|
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,218 @@
|
|
|
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
|
+
const GENERIC_NAME_MAP: Record<string, string> = {
|
|
97
|
+
whispertool: "whisper-tool",
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/** Where the rules of this installation live. */
|
|
101
|
+
export function sanitizationFilePath(): string {
|
|
102
|
+
const override = process.env.PI_SKILL_SANITIZATION_FILE;
|
|
103
|
+
if (override && override.trim()) return override.trim();
|
|
104
|
+
const home = process.env.HOME || process.env.USERPROFILE || homedir();
|
|
105
|
+
return join(home, ".pi-harness-runtime", "skill-sanitization.json");
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* The state of the rules of this installation.
|
|
110
|
+
*
|
|
111
|
+
* `configured` is false when the local file is missing or unreadable. The
|
|
112
|
+
* caller shows it, because a missing file is a weaker privacy guarantee
|
|
113
|
+
* and not a detail.
|
|
114
|
+
*/
|
|
115
|
+
export function sanitizationStatus(): {
|
|
116
|
+
configured: boolean;
|
|
117
|
+
path: string;
|
|
118
|
+
ruleCount: number;
|
|
119
|
+
error?: string;
|
|
120
|
+
} {
|
|
121
|
+
const path = sanitizationFilePath();
|
|
122
|
+
const set = loadSanitizationRules(path);
|
|
123
|
+
return {
|
|
124
|
+
configured: set.source === "local-file",
|
|
125
|
+
path,
|
|
126
|
+
ruleCount: set.rules.length,
|
|
127
|
+
...(set.error ? { error: set.error } : {}),
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
let cached: { path: string; set: SanitizationRuleSet } | undefined;
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* The rules of this installation.
|
|
135
|
+
*
|
|
136
|
+
* The file is read once per process: the rules do not change while the
|
|
137
|
+
* harness runs, and a skill that is indexed a hundred times must not read
|
|
138
|
+
* a file a hundred times.
|
|
139
|
+
*/
|
|
140
|
+
export function loadSanitizationRules(path = sanitizationFilePath()): SanitizationRuleSet {
|
|
141
|
+
if (cached && cached.path === path) return cached.set;
|
|
142
|
+
|
|
143
|
+
const set = readSanitizationRules(path);
|
|
144
|
+
cached = { path, set };
|
|
145
|
+
return set;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Forget the cached rules. A test needs it, a new file needs it. */
|
|
149
|
+
export function resetSanitizationRulesCache(): void {
|
|
150
|
+
cached = undefined;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function readSanitizationRules(path: string): SanitizationRuleSet {
|
|
154
|
+
let raw: string;
|
|
155
|
+
try {
|
|
156
|
+
raw = readFileSync(path, "utf8");
|
|
157
|
+
} catch (err) {
|
|
158
|
+
const code = (err as NodeJS.ErrnoException)?.code;
|
|
159
|
+
if (code !== "ENOENT") {
|
|
160
|
+
return {
|
|
161
|
+
rules: [...GENERIC_RULES],
|
|
162
|
+
nameMap: { ...GENERIC_NAME_MAP },
|
|
163
|
+
source: "generic-only",
|
|
164
|
+
error: `The sanitization rules at ${path} could not be read: ${err instanceof Error ? err.message : String(err)}`,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
return {
|
|
168
|
+
rules: [...GENERIC_RULES],
|
|
169
|
+
nameMap: { ...GENERIC_NAME_MAP },
|
|
170
|
+
source: "generic-only",
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
let parsed: unknown;
|
|
175
|
+
try {
|
|
176
|
+
parsed = JSON.parse(raw);
|
|
177
|
+
} catch (err) {
|
|
178
|
+
return {
|
|
179
|
+
rules: [...GENERIC_RULES],
|
|
180
|
+
nameMap: { ...GENERIC_NAME_MAP },
|
|
181
|
+
source: "generic-only",
|
|
182
|
+
error: `The sanitization rules at ${path} are not valid JSON: ${err instanceof Error ? err.message : String(err)}`,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const rules: SanitizationRule[] = [...GENERIC_RULES];
|
|
187
|
+
const nameMap: Record<string, string> = { ...GENERIC_NAME_MAP };
|
|
188
|
+
|
|
189
|
+
const file = parsed as {
|
|
190
|
+
rules?: SanitizationRuleSpec[];
|
|
191
|
+
nameMap?: Record<string, string>;
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
for (const spec of file.rules ?? []) {
|
|
195
|
+
if (!spec || typeof spec.pattern !== "string" || typeof spec.replacement !== "string") {
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
try {
|
|
199
|
+
rules.push({
|
|
200
|
+
pattern: new RegExp(spec.pattern, spec.flags ?? "gi"),
|
|
201
|
+
replacement: spec.replacement,
|
|
202
|
+
});
|
|
203
|
+
} catch {
|
|
204
|
+
// A broken pattern must not take the whole rule set down: the other
|
|
205
|
+
// rules still hold, and the broken one is reported below.
|
|
206
|
+
rules.push({
|
|
207
|
+
pattern: /$^/,
|
|
208
|
+
replacement: spec.replacement,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
for (const [from, to] of Object.entries(file.nameMap ?? {})) {
|
|
214
|
+
if (typeof from === "string" && typeof to === "string") nameMap[from] = to;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return { rules, nameMap, source: "local-file" };
|
|
218
|
+
}
|
|
@@ -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
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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(
|
|
38
|
+
export function sanitizeText(
|
|
39
|
+
text: string,
|
|
40
|
+
rules: SanitizationRuleSet = loadSanitizationRules(),
|
|
41
|
+
): string {
|
|
75
42
|
let result = text;
|
|
76
|
-
for (const rule of
|
|
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(
|
|
94
|
-
|
|
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(
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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,
|