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.
- package/README.md +1 -1
- package/harness/skills-plan/2026-07-12-herdr-moo-skills-adaptation-plan.md +9 -6
- package/index.ts +32 -1
- package/package.json +2 -6
- 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 +174 -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 +219 -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/plugins/herdr-clipboard/paste.sh +28 -0
- package/scripts/copy-plugins.js +9 -5
- package/scripts/sync-to-qdrant.ts +5 -9
- package/scripts/verify-package-contents.ts +124 -1
- package/skills/harness-runtime/SKILL.md +0 -300
- /package/{.pi/plugins → plugins}/herdr-clipboard/herdr-plugin.toml +0 -0
- /package/{.pi/plugins → plugins}/herdr-clipboard/start-bridge.sh +0 -0
- /package/{.pi/plugins → plugins}/herdr-clipboard/tmux-watch.sh +0 -0
|
@@ -13,15 +13,40 @@
|
|
|
13
13
|
* network nor replaces a process-global module.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import { describe, expect, it } from "bun:test";
|
|
16
|
+
import { afterEach, beforeEach, describe, expect, it } from "bun:test";
|
|
17
|
+
import { fileURLToPath } from "node:url";
|
|
17
18
|
|
|
18
19
|
import {
|
|
19
20
|
createSkillRegistry,
|
|
20
21
|
type QdrantClientLike,
|
|
21
22
|
type QdrantEmbedderLike,
|
|
22
23
|
} from "../src/loader.js";
|
|
24
|
+
import { resetSanitizationRulesCache } from "../src/sanitize-rules.js";
|
|
23
25
|
import type { Skill } from "../src/types.js";
|
|
24
26
|
|
|
27
|
+
/**
|
|
28
|
+
* The rules of the test.
|
|
29
|
+
*
|
|
30
|
+
* The rules of a client live in a local file, outside the repository. The
|
|
31
|
+
* test uses a fixture with made up names, so that the result of the test
|
|
32
|
+
* does not depend on the machine it runs on.
|
|
33
|
+
*/
|
|
34
|
+
const FIXTURE = fileURLToPath(
|
|
35
|
+
new URL("./fixtures/sanitization.json", import.meta.url),
|
|
36
|
+
);
|
|
37
|
+
const originalEnv = process.env.PI_SKILL_SANITIZATION_FILE;
|
|
38
|
+
|
|
39
|
+
beforeEach(() => {
|
|
40
|
+
process.env.PI_SKILL_SANITIZATION_FILE = FIXTURE;
|
|
41
|
+
resetSanitizationRulesCache();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
afterEach(() => {
|
|
45
|
+
resetSanitizationRulesCache();
|
|
46
|
+
if (originalEnv === undefined) delete process.env.PI_SKILL_SANITIZATION_FILE;
|
|
47
|
+
else process.env.PI_SKILL_SANITIZATION_FILE = originalEnv;
|
|
48
|
+
});
|
|
49
|
+
|
|
25
50
|
/** A point of the vector store. */
|
|
26
51
|
interface VectorPoint {
|
|
27
52
|
id: number;
|
|
@@ -188,7 +213,7 @@ describe("a hit that carries the shared name of a local skill", () => {
|
|
|
188
213
|
embedder: fakeEmbedder,
|
|
189
214
|
collection: "pi-harness-skills-test",
|
|
190
215
|
});
|
|
191
|
-
registry.register(localSkill("
|
|
216
|
+
registry.register(localSkill("acmecorp-pe-approval-workflow"));
|
|
192
217
|
|
|
193
218
|
const results = await registry.findVector("workflow");
|
|
194
219
|
|
|
@@ -196,16 +221,18 @@ describe("a hit that carries the shared name of a local skill", () => {
|
|
|
196
221
|
// The skill of this machine stays a local skill - with its own
|
|
197
222
|
// name and its own body, not the sanitized copy.
|
|
198
223
|
expect(results[0].source).toBe("local");
|
|
199
|
-
expect(results[0].skill.id).toBe("
|
|
200
|
-
expect(results[0].skill.body).toBe(
|
|
224
|
+
expect(results[0].skill.id).toBe("acmecorp-pe-approval-workflow");
|
|
225
|
+
expect(results[0].skill.body).toBe(
|
|
226
|
+
"the local body of acmecorp-pe-approval-workflow",
|
|
227
|
+
);
|
|
201
228
|
});
|
|
202
229
|
|
|
203
230
|
it("resolves the shared name of a local skill in a lookup", () => {
|
|
204
231
|
const registry = createSkillRegistry({ indexer: null });
|
|
205
|
-
registry.register(localSkill("
|
|
232
|
+
registry.register(localSkill("acmecorp-pe-approval-workflow"));
|
|
206
233
|
|
|
207
234
|
expect(registry.get("client-a-pe-approval-workflow")?.id).toBe(
|
|
208
|
-
"
|
|
235
|
+
"acmecorp-pe-approval-workflow",
|
|
209
236
|
);
|
|
210
237
|
});
|
|
211
238
|
});
|
|
@@ -5,29 +5,81 @@
|
|
|
5
5
|
* into it. The rules used to live in `scripts/sync-to-qdrant.ts` - the
|
|
6
6
|
* only writer that could reach them. The runtime registry writes into the
|
|
7
7
|
* same collection, so it needs them too.
|
|
8
|
+
*
|
|
9
|
+
* The rules of the clients are **not** in this repository: it is public,
|
|
10
|
+
* and a list of client names in it is a list of client names in the world.
|
|
11
|
+
* They live in a local file, so the tests use a fixture with made up
|
|
12
|
+
* names and pass the rules in. A test must never read the rules of the
|
|
13
|
+
* machine it runs on - that would make the result of the test depend on
|
|
14
|
+
* the machine.
|
|
8
15
|
*/
|
|
9
16
|
|
|
10
|
-
import { describe, expect, test } from "bun:test";
|
|
17
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
18
|
+
import { mkdtempSync, writeFileSync } from "node:fs";
|
|
19
|
+
import { tmpdir } from "node:os";
|
|
20
|
+
import { join } from "node:path";
|
|
21
|
+
import { fileURLToPath } from "node:url";
|
|
11
22
|
|
|
12
23
|
import { sanitizeText, skillPointName } from "../src/sanitize.js";
|
|
24
|
+
import {
|
|
25
|
+
loadSanitizationRules,
|
|
26
|
+
resetSanitizationRulesCache,
|
|
27
|
+
sanitizationStatus,
|
|
28
|
+
} from "../src/sanitize-rules.js";
|
|
13
29
|
import { skillPointPayload } from "../src/vector-schema.js";
|
|
14
30
|
|
|
31
|
+
const FIXTURE = fileURLToPath(new URL("./fixtures/sanitization.json", import.meta.url));
|
|
32
|
+
const MISSING = join(tmpdir(), "pi-harness-no-such-sanitization-file.json");
|
|
33
|
+
|
|
34
|
+
const originalEnv = process.env.PI_SKILL_SANITIZATION_FILE;
|
|
35
|
+
|
|
36
|
+
/** The rules of the fixture, as the mechanism reads them. */
|
|
37
|
+
function fixtureRules() {
|
|
38
|
+
resetSanitizationRulesCache();
|
|
39
|
+
process.env.PI_SKILL_SANITIZATION_FILE = FIXTURE;
|
|
40
|
+
const set = loadSanitizationRules();
|
|
41
|
+
resetSanitizationRulesCache();
|
|
42
|
+
return set;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
beforeEach(() => {
|
|
46
|
+
resetSanitizationRulesCache();
|
|
47
|
+
process.env.PI_SKILL_SANITIZATION_FILE = FIXTURE;
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
afterEach(() => {
|
|
51
|
+
resetSanitizationRulesCache();
|
|
52
|
+
if (originalEnv === undefined) delete process.env.PI_SKILL_SANITIZATION_FILE;
|
|
53
|
+
else process.env.PI_SKILL_SANITIZATION_FILE = originalEnv;
|
|
54
|
+
});
|
|
55
|
+
|
|
15
56
|
describe("the sanitization of a text", () => {
|
|
16
57
|
test("the name of a client is replaced", () => {
|
|
17
|
-
|
|
18
|
-
expect(sanitizeText("
|
|
19
|
-
|
|
58
|
+
const rules = fixtureRules();
|
|
59
|
+
expect(sanitizeText("the acmecorp workflow", rules)).toBe(
|
|
60
|
+
"the [Client A] workflow",
|
|
61
|
+
);
|
|
62
|
+
expect(sanitizeText("for globex", rules)).toBe("for [Client B]");
|
|
63
|
+
expect(sanitizeText("initech step", rules)).toBe("[Insurance Project] step");
|
|
20
64
|
});
|
|
21
65
|
|
|
22
66
|
test("the name of a client is replaced in any case", () => {
|
|
23
|
-
|
|
67
|
+
const rules = fixtureRules();
|
|
68
|
+
expect(sanitizeText("ACMEcorp and Acmecorp", rules)).toBe(
|
|
69
|
+
"[Client A] and [Client A]",
|
|
70
|
+
);
|
|
24
71
|
});
|
|
25
72
|
|
|
26
73
|
test("a text without a client name stays the same", () => {
|
|
27
|
-
|
|
74
|
+
const rules = fixtureRules();
|
|
75
|
+
expect(sanitizeText("a plain skill", rules)).toBe("a plain skill");
|
|
28
76
|
});
|
|
29
77
|
|
|
30
78
|
test("the generic phrases are replaced", () => {
|
|
79
|
+
// The generic rules hold for every installation, so they work
|
|
80
|
+
// without a local file.
|
|
81
|
+
process.env.PI_SKILL_SANITIZATION_FILE = MISSING;
|
|
82
|
+
resetSanitizationRulesCache();
|
|
31
83
|
expect(sanitizeText("Company Name")).toBe("[Client Name]");
|
|
32
84
|
expect(sanitizeText("your company")).toBe("[Client]");
|
|
33
85
|
expect(sanitizeText("our client")).toBe("[Client]");
|
|
@@ -37,46 +89,53 @@ describe("the sanitization of a text", () => {
|
|
|
37
89
|
// The name of a client rarely stands alone. A word boundary would
|
|
38
90
|
// miss every one of these - and an underscore is a word character,
|
|
39
91
|
// so a boundary never follows one.
|
|
40
|
-
|
|
41
|
-
expect(sanitizeText("
|
|
92
|
+
const rules = fixtureRules();
|
|
93
|
+
expect(sanitizeText("acmecorp_pharma", rules)).toBe("[Client A]_pharma");
|
|
94
|
+
expect(sanitizeText("th_globex_erp_chart.json", rules)).toBe(
|
|
42
95
|
"th_[Client B]_erp_chart.json",
|
|
43
96
|
);
|
|
44
|
-
expect(sanitizeText("apps/
|
|
97
|
+
expect(sanitizeText("apps/acmecorp_pharma/setup", rules)).toBe(
|
|
45
98
|
"apps/[Client A]_pharma/setup",
|
|
46
99
|
);
|
|
47
100
|
});
|
|
48
101
|
|
|
49
102
|
test("a client name in a sentence is replaced", () => {
|
|
50
|
-
|
|
51
|
-
|
|
103
|
+
const rules = fixtureRules();
|
|
104
|
+
expect(sanitizeText("integrated with Globex", rules)).toBe(
|
|
105
|
+
"integrated with [Client B]",
|
|
106
|
+
);
|
|
107
|
+
expect(sanitizeText("Initech secrets", rules)).toBe(
|
|
108
|
+
"[Insurance Project] secrets",
|
|
52
109
|
);
|
|
53
|
-
expect(sanitizeText("OpenClaw secrets")).toBe("[Client F] secrets");
|
|
54
110
|
});
|
|
55
111
|
});
|
|
56
112
|
|
|
57
113
|
describe("the name of a skill in the shared collection", () => {
|
|
58
114
|
test("a name of the map is renamed", () => {
|
|
59
|
-
|
|
115
|
+
const rules = fixtureRules();
|
|
116
|
+
expect(skillPointName("acmecorp-pe-approval-workflow", rules)).toBe(
|
|
60
117
|
"client-a-pe-approval-workflow",
|
|
61
118
|
);
|
|
62
|
-
expect(skillPointName("
|
|
63
|
-
"
|
|
119
|
+
expect(skillPointName("initech-step5-slot-filter", rules)).toBe(
|
|
120
|
+
"insurance-project-step-filter",
|
|
64
121
|
);
|
|
65
|
-
expect(skillPointName("whispertool")).toBe("whisper-tool");
|
|
66
122
|
});
|
|
67
123
|
|
|
68
124
|
test("a name without a client name stays the same", () => {
|
|
69
|
-
|
|
125
|
+
const rules = fixtureRules();
|
|
126
|
+
expect(skillPointName("code-review", rules)).toBe("code-review");
|
|
70
127
|
});
|
|
71
128
|
|
|
72
129
|
test("the name never carries a client name", () => {
|
|
130
|
+
const rules = fixtureRules();
|
|
73
131
|
for (const name of [
|
|
74
|
-
"
|
|
75
|
-
"
|
|
76
|
-
"
|
|
77
|
-
"whispertool",
|
|
132
|
+
"acmecorp-pe-approval-workflow",
|
|
133
|
+
"initech-step5-slot-filter",
|
|
134
|
+
"globex-channel",
|
|
78
135
|
]) {
|
|
79
|
-
expect(skillPointName(name)).not.toMatch(
|
|
136
|
+
expect(skillPointName(name, rules)).not.toMatch(
|
|
137
|
+
/acmecorp|initech|globex/i,
|
|
138
|
+
);
|
|
80
139
|
}
|
|
81
140
|
});
|
|
82
141
|
|
|
@@ -84,48 +143,108 @@ describe("the name of a skill in the shared collection", () => {
|
|
|
84
143
|
// The name of a point is the id of the skill that a search returns,
|
|
85
144
|
// so it has to be a name that a skill may carry.
|
|
86
145
|
const NAME = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
146
|
+
const rules = fixtureRules();
|
|
87
147
|
for (const name of [
|
|
88
|
-
"
|
|
89
|
-
"
|
|
90
|
-
"
|
|
91
|
-
"tbs-import-clearance-lcv",
|
|
92
|
-
"whispertool",
|
|
148
|
+
"acmecorp-pe-approval-workflow",
|
|
149
|
+
"globex-channel",
|
|
150
|
+
"initech-step5-slot-filter",
|
|
93
151
|
"code-review",
|
|
94
152
|
]) {
|
|
95
|
-
expect(skillPointName(name)).toMatch(NAME);
|
|
153
|
+
expect(skillPointName(name, rules)).toMatch(NAME);
|
|
96
154
|
}
|
|
97
155
|
});
|
|
98
156
|
|
|
99
157
|
test("a name that needs no sanitization stays the same", () => {
|
|
100
|
-
|
|
101
|
-
expect(skillPointName("
|
|
158
|
+
const rules = fixtureRules();
|
|
159
|
+
expect(skillPointName("code-review", rules)).toBe("code-review");
|
|
160
|
+
expect(skillPointName("frappe-doctype-field-order", rules)).toBe(
|
|
102
161
|
"frappe-doctype-field-order",
|
|
103
162
|
);
|
|
104
163
|
});
|
|
105
164
|
});
|
|
106
165
|
|
|
166
|
+
describe("the rules of the installation", () => {
|
|
167
|
+
test("a missing file leaves the generic rules", () => {
|
|
168
|
+
process.env.PI_SKILL_SANITIZATION_FILE = MISSING;
|
|
169
|
+
resetSanitizationRulesCache();
|
|
170
|
+
const status = sanitizationStatus();
|
|
171
|
+
expect(status.configured).toBe(false);
|
|
172
|
+
expect(status.ruleCount).toBeGreaterThan(0);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test("a broken file is reported and does not take the rules down", () => {
|
|
176
|
+
const dir = mkdtempSync(join(tmpdir(), "pi-harness-sanitization-"));
|
|
177
|
+
const path = join(dir, "rules.json");
|
|
178
|
+
writeFileSync(path, "{ not json");
|
|
179
|
+
process.env.PI_SKILL_SANITIZATION_FILE = path;
|
|
180
|
+
resetSanitizationRulesCache();
|
|
181
|
+
const status = sanitizationStatus();
|
|
182
|
+
expect(status.configured).toBe(false);
|
|
183
|
+
expect(status.error).toContain("not valid JSON");
|
|
184
|
+
// The generic rules still hold.
|
|
185
|
+
expect(sanitizeText("Company Name")).toBe("[Client Name]");
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test("a broken pattern does not take the other rules down", () => {
|
|
189
|
+
const dir = mkdtempSync(join(tmpdir(), "pi-harness-sanitization-"));
|
|
190
|
+
const path = join(dir, "rules.json");
|
|
191
|
+
writeFileSync(
|
|
192
|
+
path,
|
|
193
|
+
JSON.stringify({
|
|
194
|
+
rules: [
|
|
195
|
+
{ pattern: "acmecorp", replacement: "[Client A]" },
|
|
196
|
+
{ pattern: "([unclosed", replacement: "[Broken]" },
|
|
197
|
+
{ pattern: "globex", replacement: "[Client B]" },
|
|
198
|
+
],
|
|
199
|
+
}),
|
|
200
|
+
);
|
|
201
|
+
process.env.PI_SKILL_SANITIZATION_FILE = path;
|
|
202
|
+
resetSanitizationRulesCache();
|
|
203
|
+
const rules = loadSanitizationRules();
|
|
204
|
+
expect(sanitizeText("acmecorp and globex", rules)).toBe(
|
|
205
|
+
"[Client A] and [Client B]",
|
|
206
|
+
);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test("the local file is read once", () => {
|
|
210
|
+
process.env.PI_SKILL_SANITIZATION_FILE = FIXTURE;
|
|
211
|
+
resetSanitizationRulesCache();
|
|
212
|
+
const first = loadSanitizationRules();
|
|
213
|
+
const second = loadSanitizationRules();
|
|
214
|
+
expect(second).toBe(first);
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
|
|
107
218
|
describe("the payload of a point is sanitized", () => {
|
|
108
219
|
test("the client names of the body do not travel into the collection", () => {
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
220
|
+
const rules = fixtureRules();
|
|
221
|
+
const payload = skillPointPayload(
|
|
222
|
+
{
|
|
223
|
+
name: "acmecorp-pe-approval-workflow",
|
|
224
|
+
description: "the workflow of acmecorp",
|
|
225
|
+
body: "the approval of acmecorp for the client",
|
|
226
|
+
},
|
|
227
|
+
rules,
|
|
228
|
+
);
|
|
114
229
|
|
|
115
230
|
expect(payload.name).toBe("client-a-pe-approval-workflow");
|
|
116
|
-
expect(payload.description).not.toMatch(/
|
|
117
|
-
expect(payload.body).not.toMatch(/
|
|
118
|
-
expect(payload.text).not.toMatch(/
|
|
231
|
+
expect(payload.description).not.toMatch(/acmecorp/i);
|
|
232
|
+
expect(payload.body).not.toMatch(/acmecorp/i);
|
|
233
|
+
expect(payload.text).not.toMatch(/acmecorp/i);
|
|
119
234
|
});
|
|
120
235
|
|
|
121
236
|
test("the text is built from the sanitized parts", () => {
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
237
|
+
const rules = fixtureRules();
|
|
238
|
+
const payload = skillPointPayload(
|
|
239
|
+
{
|
|
240
|
+
name: "acmecorp-pe-approval-workflow",
|
|
241
|
+
description: "the workflow",
|
|
242
|
+
body: "the body",
|
|
243
|
+
},
|
|
244
|
+
rules,
|
|
245
|
+
);
|
|
127
246
|
|
|
128
247
|
expect(payload.text).toContain("client-a-pe-approval-workflow");
|
|
129
|
-
expect(payload.text).not.toContain("
|
|
248
|
+
expect(payload.text).not.toContain("acmecorp-pe-approval-workflow");
|
|
130
249
|
});
|
|
131
250
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-harness/two-phase-review",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.424",
|
|
4
4
|
"description": "pi-harness-runtime — two-phase implementation and review workflow (queue review signals, gate the phase, scope the diff)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Paste from local clipboard - reads from tmux paste buffer and outputs to stdout
|
|
3
|
+
# Used by herdr's prefix+p keybinding to paste from local machine into herdr pane
|
|
4
|
+
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
BRIDGE_DIR="${HOME}/.herdr-clipboard"
|
|
8
|
+
PASTE_BUFFER="${BRIDGE_DIR}/paste-buffer"
|
|
9
|
+
|
|
10
|
+
# First, try to get content from tmux paste buffer
|
|
11
|
+
if command -v tmux &>/dev/null; then
|
|
12
|
+
# Try to capture tmux buffer content
|
|
13
|
+
CONTENT=$(tmux show-buffer 2>/dev/null || true)
|
|
14
|
+
if [[ -n "${CONTENT}" ]]; then
|
|
15
|
+
echo -n "${CONTENT}"
|
|
16
|
+
exit 0
|
|
17
|
+
fi
|
|
18
|
+
fi
|
|
19
|
+
|
|
20
|
+
# Fallback: check if there's pending paste content from remote herdr
|
|
21
|
+
if [[ -f "${PASTE_BUFFER}" ]]; then
|
|
22
|
+
cat "${PASTE_BUFFER}"
|
|
23
|
+
rm -f "${PASTE_BUFFER}"
|
|
24
|
+
exit 0
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
# Nothing to paste
|
|
28
|
+
exit 1
|
package/scripts/copy-plugins.js
CHANGED
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Copy herdr plugins to runtime directory
|
|
3
3
|
*
|
|
4
|
-
* Copies herdr plugins from
|
|
5
|
-
* The source
|
|
4
|
+
* Copies herdr plugins from plugins/ to ~/.config/herdr/plugins/
|
|
5
|
+
* The source plugins/ directory is tracked in git and included in the npm
|
|
6
|
+
* package via the files array.
|
|
7
|
+
*
|
|
8
|
+
* Skill payloads are NOT shipped: they live in the user's own Qdrant
|
|
9
|
+
* collection and in the installation-local skill store.
|
|
6
10
|
*/
|
|
7
11
|
|
|
8
|
-
import {
|
|
12
|
+
import { mkdirSync, existsSync, readdirSync, cpSync } from "node:fs";
|
|
9
13
|
import { join, dirname } from "node:path";
|
|
10
14
|
import { fileURLToPath } from "node:url";
|
|
11
15
|
|
|
12
16
|
const __filename = fileURLToPath(import.meta.url);
|
|
13
17
|
const __dirname = dirname(__filename);
|
|
14
18
|
|
|
15
|
-
const SOURCE_PLUGINS_DIR = join(__dirname, "..", "
|
|
19
|
+
const SOURCE_PLUGINS_DIR = join(__dirname, "..", "plugins");
|
|
16
20
|
const HERDR_PLUGINS_DIR = join(process.env.HOME || "~", ".config", "herdr", "plugins");
|
|
17
21
|
|
|
18
22
|
function ensureDir(dir) {
|
|
@@ -38,7 +42,7 @@ function copyPlugin(pluginName) {
|
|
|
38
42
|
|
|
39
43
|
function main() {
|
|
40
44
|
if (!existsSync(SOURCE_PLUGINS_DIR)) {
|
|
41
|
-
console.log("[copy-plugins] No
|
|
45
|
+
console.log("[copy-plugins] No plugins/ directory found, skipping");
|
|
42
46
|
return;
|
|
43
47
|
}
|
|
44
48
|
|