pi-harness-runtime 1.1.414 → 1.1.415

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 (134) hide show
  1. package/package.json +1 -1
  2. package/packages/a2a-adapter/package.json +1 -1
  3. package/packages/agent-policy/package.json +1 -1
  4. package/packages/architecture-generator/package.json +1 -1
  5. package/packages/auth/package.json +1 -1
  6. package/packages/autonomous-refactor/package.json +1 -1
  7. package/packages/autonomous-runtime/package.json +1 -1
  8. package/packages/cache-strategy/package.json +1 -1
  9. package/packages/capability-registry/package.json +1 -1
  10. package/packages/checkpoint/package.json +1 -1
  11. package/packages/cli-plugin-sdk/package.json +1 -1
  12. package/packages/clipboard/package.json +1 -1
  13. package/packages/clipboard-plugin/package.json +1 -1
  14. package/packages/code-generation/package.json +1 -1
  15. package/packages/code-review/package.json +1 -1
  16. package/packages/codex-adapter/package.json +1 -1
  17. package/packages/codex-watcher/package.json +1 -1
  18. package/packages/config-capture/package.json +1 -1
  19. package/packages/connection-runtime/package.json +1 -1
  20. package/packages/context-compiler/package.json +1 -1
  21. package/packages/context-discovery/package.json +1 -1
  22. package/packages/context-manager/package.json +1 -1
  23. package/packages/contracts/package.json +1 -1
  24. package/packages/cookie-sanitizer/package.json +1 -1
  25. package/packages/cost-optimizer/package.json +1 -1
  26. package/packages/dependency-analyzer/package.json +1 -1
  27. package/packages/django-plugin/package.json +1 -1
  28. package/packages/doc-generator/package.json +1 -1
  29. package/packages/durable-review/package.json +1 -1
  30. package/packages/e2e-tester/package.json +1 -1
  31. package/packages/evaluation-engine/package.json +1 -1
  32. package/packages/evaluation-runner/package.json +1 -1
  33. package/packages/event-bus/package.json +1 -1
  34. package/packages/event-sink/package.json +1 -1
  35. package/packages/event-store/package.json +1 -1
  36. package/packages/experience-replay/package.json +1 -1
  37. package/packages/feedback-collector/package.json +1 -1
  38. package/packages/file-copy-helper/package.json +1 -1
  39. package/packages/framework-detector/package.json +1 -1
  40. package/packages/framework-plugin-sdk/package.json +1 -1
  41. package/packages/frappe-plugin/package.json +1 -1
  42. package/packages/generic-web-plugin/package.json +1 -1
  43. package/packages/health-monitor/package.json +1 -1
  44. package/packages/intent-analyzer/package.json +1 -1
  45. package/packages/jev-judge/package.json +1 -1
  46. package/packages/knowledge-graph/package.json +1 -1
  47. package/packages/knowledge-retrieval/package.json +1 -1
  48. package/packages/laravel-plugin/package.json +1 -1
  49. package/packages/learning-engine/package.json +1 -1
  50. package/packages/logging/package.json +1 -1
  51. package/packages/mcp-adapter/package.json +1 -1
  52. package/packages/memory-engine/package.json +1 -1
  53. package/packages/milestone-manager/package.json +1 -1
  54. package/packages/model-registry/package.json +1 -1
  55. package/packages/nextjs-plugin/package.json +1 -1
  56. package/packages/notification/package.json +1 -1
  57. package/packages/observability/package.json +1 -1
  58. package/packages/okf-indexer/package.json +1 -1
  59. package/packages/paperclip-integration/package.json +1 -1
  60. package/packages/performance-optimizer/package.json +1 -1
  61. package/packages/permission-mode/package.json +1 -1
  62. package/packages/privilege-broker/package.json +1 -1
  63. package/packages/project-analyzer/package.json +1 -1
  64. package/packages/project-bootstrap/package.json +1 -1
  65. package/packages/projection-engine/package.json +1 -1
  66. package/packages/prompt-compiler/package.json +1 -1
  67. package/packages/prompt-versioning/package.json +1 -1
  68. package/packages/provider-adapter-sdk/package.json +1 -1
  69. package/packages/provider-router/package.json +1 -1
  70. package/packages/provider-selector/package.json +1 -1
  71. package/packages/providers/package.json +1 -1
  72. package/packages/qdrant-skills/package.json +1 -1
  73. package/packages/quota-manager/package.json +1 -1
  74. package/packages/rate-limiter/package.json +1 -1
  75. package/packages/react-vite-plugin/package.json +1 -1
  76. package/packages/relay/package.json +1 -1
  77. package/packages/release-manager/package.json +1 -1
  78. package/packages/requirement-compiler/package.json +1 -1
  79. package/packages/runtime/package.json +1 -1
  80. package/packages/scheduler/package.json +1 -1
  81. package/packages/scheduler-adapter/package.json +1 -1
  82. package/packages/session/package.json +1 -1
  83. package/packages/session-api/package.json +1 -1
  84. package/packages/session-export/package.json +1 -1
  85. package/packages/shared-context/package.json +1 -1
  86. package/packages/skill-mcp-client/package.json +1 -1
  87. package/packages/skill-registry/package.json +1 -1
  88. package/packages/skills/dist/index.d.ts +1 -0
  89. package/packages/skills/dist/index.d.ts.map +1 -1
  90. package/packages/skills/dist/index.js +1 -0
  91. package/packages/skills/dist/index.js.map +1 -1
  92. package/packages/skills/dist/loader.d.ts +9 -0
  93. package/packages/skills/dist/loader.d.ts.map +1 -1
  94. package/packages/skills/dist/loader.js +24 -3
  95. package/packages/skills/dist/loader.js.map +1 -1
  96. package/packages/skills/dist/sanitize.d.ts +27 -0
  97. package/packages/skills/dist/sanitize.d.ts.map +1 -0
  98. package/packages/skills/dist/sanitize.js +75 -0
  99. package/packages/skills/dist/sanitize.js.map +1 -0
  100. package/packages/skills/dist/vector-schema.d.ts +4 -0
  101. package/packages/skills/dist/vector-schema.d.ts.map +1 -1
  102. package/packages/skills/dist/vector-schema.js +13 -4
  103. package/packages/skills/dist/vector-schema.js.map +1 -1
  104. package/packages/skills/package.json +1 -1
  105. package/packages/skills/src/index.ts +1 -0
  106. package/packages/skills/src/loader.ts +26 -3
  107. package/packages/skills/src/sanitize.ts +86 -0
  108. package/packages/skills/src/vector-schema.ts +14 -4
  109. package/packages/skills/test/remote-skills.test.ts +41 -0
  110. package/packages/skills/test/sanitize.test.ts +88 -0
  111. package/packages/sprint-planner/package.json +1 -1
  112. package/packages/subscription-engine/package.json +1 -1
  113. package/packages/task-compiler/package.json +1 -1
  114. package/packages/telegram/package.json +1 -1
  115. package/packages/tencentdb-memory/package.json +1 -1
  116. package/packages/tencentdb-sync/package.json +1 -1
  117. package/packages/test-data-generator/package.json +1 -1
  118. package/packages/test-generator/package.json +1 -1
  119. package/packages/todo-bd-sync/package.json +1 -1
  120. package/packages/token-estimation/package.json +1 -1
  121. package/packages/token-optimizer/package.json +1 -1
  122. package/packages/tooling-preference/package.json +1 -1
  123. package/packages/trajectory/package.json +1 -1
  124. package/packages/tui/package.json +1 -1
  125. package/packages/two-phase-review/package.json +1 -1
  126. package/packages/types/package.json +1 -1
  127. package/packages/voice-api/package.json +1 -1
  128. package/packages/webmcp-tester/package.json +1 -1
  129. package/packages/wiki/package.json +1 -1
  130. package/packages/workflow-events/package.json +1 -1
  131. package/packages/workspace-scanner/package.json +1 -1
  132. package/packages/worktree/package.json +1 -1
  133. package/packages/write-review/package.json +1 -1
  134. package/scripts/sync-to-qdrant.ts +10 -56
@@ -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 { sanitizeText, skillPointName } from "./sanitize.js";
18
19
  /**
19
20
  * The collection the runtime indexes into and searches.
20
21
  *
@@ -91,13 +92,21 @@ export function skillQdrantPoint(payload) {
91
92
  }
92
93
  /**
93
94
  * The payload of a skill point.
95
+ *
96
+ * The payload is sanitized: the collection is shared, so the client names
97
+ * of a user must not travel into it. Both writers use this function, so
98
+ * both write the same sanitized payload for the same skill.
94
99
  */
95
100
  export function skillPointPayload(input) {
101
+ const name = skillPointName(input.name);
102
+ const title = input.title ? skillPointName(input.title) : undefined;
103
+ const description = sanitizeText(input.description);
104
+ const body = sanitizeText(input.body).slice(0, SKILL_PAYLOAD_BODY_LIMIT);
96
105
  return {
97
- name: input.name,
98
- description: input.description,
99
- body: input.body.slice(0, SKILL_PAYLOAD_BODY_LIMIT),
100
- text: skillPointText(input),
106
+ name,
107
+ description,
108
+ body,
109
+ text: skillPointText({ name, title, description, body }),
101
110
  };
102
111
  }
103
112
  export function describeSkillSchemaMismatch(actual, expected) {
@@ -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.414",
3
+ "version": "1.1.415",
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",
@@ -14,6 +14,7 @@ export * from "./parser.js";
14
14
  export * from "./scanner.js";
15
15
  export * from "./loader.js";
16
16
  export * from "./vector-schema.js";
17
+ export * from "./sanitize.js";
17
18
  export * from "./remote-source.js";
18
19
  export * from "./skill-commands.js";
19
20
  export * from "./skill-tool.js";
@@ -28,6 +28,7 @@ import {
28
28
  type SkillPointPayload,
29
29
  type SkillVectorSchema,
30
30
  } from "./vector-schema.js";
31
+ import { skillPointName } from "./sanitize.js";
31
32
  import {
32
33
  describeRemoteFailure,
33
34
  isStale,
@@ -339,6 +340,15 @@ export class SkillRegistry {
339
340
  * disk and how many it can reach through the vector store.
340
341
  */
341
342
  private remoteSkills: Map<string, Skill> = new Map();
343
+ /**
344
+ * The local skills by their name in the shared collection.
345
+ *
346
+ * The name of a point is the SANITIZED name of the skill, because the
347
+ * collection is shared. A hit of the collection therefore carries a name
348
+ * that this machine does not use - the registry is keyed by the name on
349
+ * this machine. This map bridges the two.
350
+ */
351
+ private _skillsByPointName: Map<string, Skill> = new Map();
342
352
  /**
343
353
  * The remote skills whose content is COMPLETE.
344
354
  *
@@ -766,6 +776,8 @@ export class SkillRegistry {
766
776
  */
767
777
  register(skill: Skill): void {
768
778
  this.skills.set(skill.id, skill);
779
+ // The shared collection knows the skill by its sanitized name.
780
+ this._skillsByPointName.set(skillPointName(skill.id), skill);
769
781
  this.updateIndex(skill);
770
782
  // Queue the skill for the vector index without blocking registration.
771
783
  this._scheduleIndex(skill);
@@ -778,6 +790,12 @@ export class SkillRegistry {
778
790
  const skill = this.skills.get(skillId);
779
791
  if (skill) {
780
792
  this.skills.delete(skillId);
793
+ // Only drop the shared name when it still points at this skill: two
794
+ // skills may share a sanitized name.
795
+ const pointName = skillPointName(skill.id);
796
+ if (this._skillsByPointName.get(pointName) === skill) {
797
+ this._skillsByPointName.delete(pointName);
798
+ }
781
799
  this.index.entries = this.index.entries.filter(
782
800
  (e) => e.id !== skillId
783
801
  );
@@ -791,8 +809,13 @@ export class SkillRegistry {
791
809
  */
792
810
  get(skillId: string): Skill | undefined {
793
811
  // A server skill is only known after a vector search found it, so it is
794
- // looked up second - the local skill always wins.
795
- return this.skills.get(skillId) ?? this.remoteSkills.get(skillId);
812
+ // looked up second - the local skill always wins. The shared name of a
813
+ // skill resolves to the local skill as well.
814
+ return (
815
+ this.skills.get(skillId) ??
816
+ this._skillsByPointName.get(skillId) ??
817
+ this.remoteSkills.get(skillId)
818
+ );
796
819
  }
797
820
 
798
821
  /**
@@ -1054,7 +1077,7 @@ export class SkillRegistry {
1054
1077
  const payload = pt.payload ?? {};
1055
1078
  const name = typeof payload.name === "string" ? payload.name : undefined;
1056
1079
  if (!name) continue;
1057
- const local = this.skills.get(name);
1080
+ const local = this._skillsByPointName.get(name) ?? this.skills.get(name);
1058
1081
  if (local) {
1059
1082
  results.push({ skill: local, score: pt.score, source: "local" });
1060
1083
  continue;
@@ -0,0 +1,86 @@
1
+ /**
2
+ * The sanitization of a skill for the shared collection.
3
+ *
4
+ * The collection is shared, so a client name must not travel into it. The
5
+ * rules lived in `scripts/sync-to-qdrant.ts` - the only writer that could
6
+ * reach them. The runtime registry writes into the same collection, so it
7
+ * needs them too: a writer that skipped the rules would leave the raw
8
+ * name and the raw body of a client skill in a shared cluster.
9
+ *
10
+ * Both writers therefore build their points with the same rules, and the
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
+ */
26
+ const SANITIZATION_RULES: SanitizationRule[] = [
27
+ // Client company names (case-insensitive)
28
+ { pattern: /\binpac\b/gi, replacement: "[Client A]" },
29
+ { pattern: /\bm-capital\b/gi, replacement: "[Client A]" },
30
+ { pattern: /\bmcapital\b/gi, replacement: "[Client A]" },
31
+ { pattern: /\bdigisoft\b/gi, replacement: "[Client B]" },
32
+
33
+ // Project/app specific names
34
+ { pattern: /\bgse-insurance\b/gi, replacement: "[Insurance Project]" },
35
+ { pattern: /\btbs-import\b/gi, replacement: "[Import Project]" },
36
+ { pattern: /\bdigisoft_erp\b/gi, replacement: "[Client B Erp]" },
37
+ { pattern: /\bm_capital\b/gi, replacement: "[Client A]" },
38
+ { pattern: /\binpac_\b/gi, replacement: "[Client A]_" },
39
+ { pattern: /\bpaperclip_\b/gi, replacement: "[Client D]_" },
40
+ { pattern: /\bopenclaw_\b/gi, replacement: "[Client F]_" },
41
+
42
+ // Generic replacements
43
+ { pattern: /Company Name/gi, replacement: "[Client Name]" },
44
+ { pattern: /your company/gi, replacement: "[Client]" },
45
+ { pattern: /our client/gi, replacement: "[Client]" },
46
+ { pattern: /client-specific/gi, replacement: "[Project-specific]" },
47
+ ];
48
+
49
+ /**
50
+ * The names that are renamed as a whole.
51
+ *
52
+ * A name that would be unreadable after the pattern rules - a name that
53
+ * carries the client name in its middle - is renamed here.
54
+ */
55
+ const SKILL_NAME_MAP: Record<string, string> = {
56
+ "gse-insurance-step5-slot-filter": "insurance-project-step-filter",
57
+ "inpac-pe-approval-workflow": "client-a-pe-approval-workflow",
58
+ "inpac-po-approval-workflow": "client-a-po-approval-workflow",
59
+ "inpac-pr-approval-workflow": "client-a-pr-approval-workflow",
60
+ "tbs-import-clearance-lcv": "import-project-clearance",
61
+ whispertool: "whisper-tool",
62
+ };
63
+
64
+ /**
65
+ * Replace the client specific names of a text.
66
+ *
67
+ * The text stays the same length, so a vector that was built from it
68
+ * keeps its shape.
69
+ */
70
+ export function sanitizeText(text: string): string {
71
+ let result = text;
72
+ for (const rule of SANITIZATION_RULES) {
73
+ result = result.replace(rule.pattern, rule.replacement);
74
+ }
75
+ return result;
76
+ }
77
+
78
+ /**
79
+ * The name of a skill in the shared collection.
80
+ *
81
+ * The name is the identity of the point, so it has to be the same for
82
+ * every writer: the map first, then the pattern rules.
83
+ */
84
+ export function skillPointName(name: string): string {
85
+ return SKILL_NAME_MAP[name] || sanitizeText(name);
86
+ }
@@ -16,6 +16,8 @@
16
16
  * of the vector store.
17
17
  */
18
18
 
19
+ import { sanitizeText, skillPointName } from "./sanitize.js";
20
+
19
21
  /**
20
22
  * The collection the runtime indexes into and searches.
21
23
  *
@@ -152,6 +154,10 @@ export function skillQdrantPoint(payload: SkillPointPayload): SkillQdrantPoint {
152
154
 
153
155
  /**
154
156
  * The payload of a skill point.
157
+ *
158
+ * The payload is sanitized: the collection is shared, so the client names
159
+ * of a user must not travel into it. Both writers use this function, so
160
+ * both write the same sanitized payload for the same skill.
155
161
  */
156
162
  export function skillPointPayload(input: {
157
163
  /** The name the payload carries - the identity of the skill. */
@@ -163,11 +169,15 @@ export function skillPointPayload(input: {
163
169
  /** The body of the skill. */
164
170
  body: string;
165
171
  }): SkillPointPayload {
172
+ const name = skillPointName(input.name);
173
+ const title = input.title ? skillPointName(input.title) : undefined;
174
+ const description = sanitizeText(input.description);
175
+ const body = sanitizeText(input.body).slice(0, SKILL_PAYLOAD_BODY_LIMIT);
166
176
  return {
167
- name: input.name,
168
- description: input.description,
169
- body: input.body.slice(0, SKILL_PAYLOAD_BODY_LIMIT),
170
- text: skillPointText(input),
177
+ name,
178
+ description,
179
+ body,
180
+ text: skillPointText({ name, title, description, body }),
171
181
  };
172
182
  }
173
183
  export function describeSkillSchemaMismatch(
@@ -169,6 +169,47 @@ describe("a skill that only exists in the vector store", () => {
169
169
  });
170
170
  });
171
171
 
172
+ describe("a hit that carries the shared name of a local skill", () => {
173
+ it("resolves to the local skill", async () => {
174
+ // The collection is shared, so the point carries the SANITIZED name.
175
+ const client = fakeClient([
176
+ {
177
+ id: 1,
178
+ score: 0.9,
179
+ payload: {
180
+ name: "client-a-pe-approval-workflow",
181
+ description: "the workflow",
182
+ body: "the body",
183
+ },
184
+ },
185
+ ]);
186
+ const registry = createSkillRegistry({
187
+ qdrantClient: client,
188
+ embedder: fakeEmbedder,
189
+ collection: "pi-harness-skills-test",
190
+ });
191
+ registry.register(localSkill("inpac-pe-approval-workflow"));
192
+
193
+ const results = await registry.findVector("workflow");
194
+
195
+ expect(results).toHaveLength(1);
196
+ // The skill of this machine stays a local skill - with its own
197
+ // name and its own body, not the sanitized copy.
198
+ expect(results[0].source).toBe("local");
199
+ expect(results[0].skill.id).toBe("inpac-pe-approval-workflow");
200
+ expect(results[0].skill.body).toBe("the local body of inpac-pe-approval-workflow");
201
+ });
202
+
203
+ it("resolves the shared name of a local skill in a lookup", () => {
204
+ const registry = createSkillRegistry({ indexer: null });
205
+ registry.register(localSkill("inpac-pe-approval-workflow"));
206
+
207
+ expect(registry.get("client-a-pe-approval-workflow")?.id).toBe(
208
+ "inpac-pe-approval-workflow",
209
+ );
210
+ });
211
+ });
212
+
172
213
  describe("the state of the vector store", () => {
173
214
  it("reports the number of skills", async () => {
174
215
  const client = fakeClient([], {
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Tests for the sanitization of a skill for the shared collection.
3
+ *
4
+ * The collection is shared, so the client names of a user must not travel
5
+ * into it. The rules used to live in `scripts/sync-to-qdrant.ts` - the
6
+ * only writer that could reach them. The runtime registry writes into the
7
+ * same collection, so it needs them too.
8
+ */
9
+
10
+ import { describe, expect, test } from "bun:test";
11
+
12
+ import { sanitizeText, skillPointName } from "../src/sanitize.js";
13
+ import { skillPointPayload } from "../src/vector-schema.js";
14
+
15
+ describe("the sanitization of a text", () => {
16
+ test("the name of a client is replaced", () => {
17
+ expect(sanitizeText("the inpac workflow")).toBe("the [Client A] workflow");
18
+ expect(sanitizeText("for digisoft")).toBe("for [Client B]");
19
+ expect(sanitizeText("gse-insurance step")).toBe("[Insurance Project] step");
20
+ });
21
+
22
+ test("the name of a client is replaced in any case", () => {
23
+ expect(sanitizeText("INPAC and Inpac")).toBe("[Client A] and [Client A]");
24
+ });
25
+
26
+ test("a text without a client name stays the same", () => {
27
+ expect(sanitizeText("a plain skill")).toBe("a plain skill");
28
+ });
29
+
30
+ test("the generic phrases are replaced", () => {
31
+ expect(sanitizeText("Company Name")).toBe("[Client Name]");
32
+ expect(sanitizeText("your company")).toBe("[Client]");
33
+ expect(sanitizeText("our client")).toBe("[Client]");
34
+ });
35
+ });
36
+
37
+ describe("the name of a skill in the shared collection", () => {
38
+ test("a name of the map is renamed", () => {
39
+ expect(skillPointName("inpac-pe-approval-workflow")).toBe(
40
+ "client-a-pe-approval-workflow",
41
+ );
42
+ expect(skillPointName("tbs-import-clearance-lcv")).toBe(
43
+ "import-project-clearance",
44
+ );
45
+ expect(skillPointName("whispertool")).toBe("whisper-tool");
46
+ });
47
+
48
+ test("a name without a client name stays the same", () => {
49
+ expect(skillPointName("code-review")).toBe("code-review");
50
+ });
51
+
52
+ test("the name never carries a client name", () => {
53
+ for (const name of [
54
+ "inpac-pe-approval-workflow",
55
+ "gse-insurance-step5-slot-filter",
56
+ "tbs-import-clearance-lcv",
57
+ "whispertool",
58
+ ]) {
59
+ expect(skillPointName(name)).not.toMatch(/inpac|gse-insurance|tbs-import/i);
60
+ }
61
+ });
62
+ });
63
+
64
+ describe("the payload of a point is sanitized", () => {
65
+ test("the client names of the body do not travel into the collection", () => {
66
+ const payload = skillPointPayload({
67
+ name: "inpac-pe-approval-workflow",
68
+ description: "the workflow of inpac",
69
+ body: "the approval of inpac for the client",
70
+ });
71
+
72
+ expect(payload.name).toBe("client-a-pe-approval-workflow");
73
+ expect(payload.description).not.toMatch(/inpac/i);
74
+ expect(payload.body).not.toMatch(/inpac/i);
75
+ expect(payload.text).not.toMatch(/inpac/i);
76
+ });
77
+
78
+ test("the text is built from the sanitized parts", () => {
79
+ const payload = skillPointPayload({
80
+ name: "inpac-pe-approval-workflow",
81
+ description: "the workflow",
82
+ body: "the body",
83
+ });
84
+
85
+ expect(payload.text).toContain("client-a-pe-approval-workflow");
86
+ expect(payload.text).not.toContain("inpac-pe-approval-workflow");
87
+ });
88
+ });
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/sprint-planner",
3
- "version": "1.1.414",
3
+ "version": "1.1.415",
4
4
  "type": "module",
5
5
  "description": "Sprint planning engine for pi-harness",
6
6
  "main": "dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/subscription-engine",
3
- "version": "1.1.414",
3
+ "version": "1.1.415",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/task-compiler",
3
- "version": "1.1.414",
3
+ "version": "1.1.415",
4
4
  "description": "Task Compiler for pi-harness-runtime — produces deterministic task DAG from CompiledRequirement",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/telegram",
3
- "version": "1.1.414",
3
+ "version": "1.1.415",
4
4
  "description": "pi-harness-runtime — Telegram inbound/outbound",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moocoding/tencentdb-memory",
3
- "version": "1.1.414",
3
+ "version": "1.1.415",
4
4
  "description": "TencentDB Agent Memory client - sync skills to centralized knowledge base",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/tencentdb-sync",
3
- "version": "1.1.414",
3
+ "version": "1.1.415",
4
4
  "description": "Sync SKILL.md skills to TencentDB-Agent-Memory server (RFC-0105/0106)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/test-data-generator",
3
- "version": "1.1.414",
3
+ "version": "1.1.415",
4
4
  "description": "Test data generation for pi-harness-runtime",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/test-generator",
3
- "version": "1.1.414",
3
+ "version": "1.1.415",
4
4
  "description": "Auto-Test Generator — generate unit and E2E tests from source code (RFC-0013)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moocoding/todo-bd-sync",
3
- "version": "1.1.414",
3
+ "version": "1.1.415",
4
4
  "description": "Two-way sync between rpiv-todo overlay and bd issue tracker",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/token-estimation",
3
- "version": "1.1.414",
3
+ "version": "1.1.415",
4
4
  "description": "pi-harness-runtime — token-estimation",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/token-optimizer",
3
- "version": "1.1.414",
3
+ "version": "1.1.415",
4
4
  "description": "Token Budget Optimizer — maximize prompt value within token limits (RFC-0011)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/tooling-preference",
3
- "version": "1.1.414",
3
+ "version": "1.1.415",
4
4
  "description": "pi-harness-runtime — JS/TS tooling preference (Biome vs ESLint/Prettier) proposal",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/trajectory",
3
- "version": "1.1.414",
3
+ "version": "1.1.415",
4
4
  "description": "Trajectory capture for the write-review loop — persists cycle records for learning",
5
5
  "type": "module",
6
6
  "main": "./dist/src/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/tui",
3
- "version": "1.1.414",
3
+ "version": "1.1.415",
4
4
  "description": "pi-harness-runtime — tui",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/two-phase-review",
3
- "version": "1.1.414",
3
+ "version": "1.1.415",
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",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/types",
3
- "version": "1.1.414",
3
+ "version": "1.1.415",
4
4
  "description": "Shared TypeScript types for pi-harness-runtime packages",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/voice-api",
3
- "version": "1.1.414",
3
+ "version": "1.1.415",
4
4
  "type": "module",
5
5
  "description": "Voice-to-text API using Whisper for Telegram and LINE integration",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/webmcp-tester",
3
- "version": "1.1.414",
3
+ "version": "1.1.415",
4
4
  "description": "WebMCP-powered E2E testing for pi-harness - natural language test scenarios",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/wiki",
3
- "version": "1.1.414",
3
+ "version": "1.1.415",
4
4
  "description": "LLM Wiki integration for pi-harness-runtime",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/workflow-events",
3
- "version": "1.1.414",
3
+ "version": "1.1.415",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/workspace-scanner",
3
- "version": "1.1.414",
3
+ "version": "1.1.415",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/worktree",
3
- "version": "1.1.414",
3
+ "version": "1.1.415",
4
4
  "description": "pi-harness-runtime — worktree",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moocoding/write-review",
3
- "version": "1.1.414",
3
+ "version": "1.1.415",
4
4
  "description": "Two-agent write-review loop with blackboard coordination",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",