pi-harness-runtime 1.1.413 → 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 (135) 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 +19 -7
  93. package/packages/skills/dist/loader.d.ts.map +1 -1
  94. package/packages/skills/dist/loader.js +39 -22
  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 +93 -4
  101. package/packages/skills/dist/vector-schema.d.ts.map +1 -1
  102. package/packages/skills/dist/vector-schema.js +71 -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 +53 -27
  107. package/packages/skills/src/sanitize.ts +86 -0
  108. package/packages/skills/src/vector-schema.ts +127 -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/skills/test/vector-schema.test.ts +167 -0
  112. package/packages/sprint-planner/package.json +1 -1
  113. package/packages/subscription-engine/package.json +1 -1
  114. package/packages/task-compiler/package.json +1 -1
  115. package/packages/telegram/package.json +1 -1
  116. package/packages/tencentdb-memory/package.json +1 -1
  117. package/packages/tencentdb-sync/package.json +1 -1
  118. package/packages/test-data-generator/package.json +1 -1
  119. package/packages/test-generator/package.json +1 -1
  120. package/packages/todo-bd-sync/package.json +1 -1
  121. package/packages/token-estimation/package.json +1 -1
  122. package/packages/token-optimizer/package.json +1 -1
  123. package/packages/tooling-preference/package.json +1 -1
  124. package/packages/trajectory/package.json +1 -1
  125. package/packages/tui/package.json +1 -1
  126. package/packages/two-phase-review/package.json +1 -1
  127. package/packages/types/package.json +1 -1
  128. package/packages/voice-api/package.json +1 -1
  129. package/packages/webmcp-tester/package.json +1 -1
  130. package/packages/wiki/package.json +1 -1
  131. package/packages/workflow-events/package.json +1 -1
  132. package/packages/workspace-scanner/package.json +1 -1
  133. package/packages/worktree/package.json +1 -1
  134. package/packages/write-review/package.json +1 -1
  135. package/scripts/sync-to-qdrant.ts +29 -69
@@ -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
  *
@@ -51,12 +53,133 @@ export interface SkillVectorSchema {
51
53
  }
52
54
 
53
55
  /**
54
- * Why the collection does not match the expected schema - or null when it
55
- * does.
56
+ * The payload of a skill point.
57
+ *
58
+ * Both writers of the collection - the runtime registry and
59
+ * `scripts/sync-to-qdrant.ts` - store the SAME payload under the SAME
60
+ * point id. A point is addressed by its id, so a writer that used a
61
+ * different id for the same skill would leave two points behind, and a
62
+ * writer whose id happened to collide with a foreign one would overwrite
63
+ * an unrelated skill.
64
+ */
65
+ export type SkillPointPayload = {
66
+ /** The name of the skill. The point id is derived from it. */
67
+ name: string;
68
+ /** What the skill does. */
69
+ description: string;
70
+ /** The body of the skill. */
71
+ body: string;
72
+ /** The text the vector was built from. */
73
+ text: string;
74
+ };
75
+
76
+ /**
77
+ * The body of a payload is truncated.
78
+ *
79
+ * A payload is an index, not a file store - the complete skill comes from
80
+ * the skill service.
81
+ */
82
+ export const SKILL_PAYLOAD_BODY_LIMIT = 2000;
83
+
84
+ /** The text of a payload is truncated as well. */
85
+ export const SKILL_PAYLOAD_TEXT_LIMIT = 1500;
86
+
87
+ /**
88
+ * A stable hash of a string.
89
+ *
90
+ * The same string always produces the same number, in every process and
91
+ * on every machine - a point id has to survive a restart.
92
+ *
93
+ * Two uses:
94
+ * - the id of a point is the hash of the name its payload carries, so the
95
+ * point id and the payload name always belong together. Two writers
96
+ * that store the same name address the same point.
97
+ * - the hash of the text of a skill says whether its content changed.
98
+ */
99
+ export function stableHash(value: string): number {
100
+ let hash = 0;
101
+ for (let i = 0; i < value.length; i++) {
102
+ hash = (Math.imul(31, hash) + value.charCodeAt(i)) | 0;
103
+ }
104
+ return Math.abs(hash);
105
+ }
106
+
107
+ /**
108
+ * The text a skill vector is built from.
56
109
  *
57
- * The message names the numbers, so the reader can tell WHICH schema the
58
- * collection was built with.
110
+ * The title is added when it differs from the name: a skill whose
111
+ * directory is named differently from its title carries both.
59
112
  */
113
+ export function skillPointText(input: {
114
+ /** The name the payload carries - the identity of the skill. */
115
+ name: string;
116
+ /** The title of the skill, when it differs from the name. */
117
+ title?: string;
118
+ /** What the skill does. */
119
+ description: string;
120
+ /** The body of the skill. */
121
+ body: string;
122
+ }): string {
123
+ const parts = [input.name];
124
+ if (input.title && input.title !== input.name) parts.push(input.title);
125
+ parts.push(input.description);
126
+ parts.push(input.body.slice(0, SKILL_PAYLOAD_TEXT_LIMIT));
127
+ return parts.join(" ");
128
+ }
129
+
130
+ /**
131
+ * A point of the skill collection.
132
+ *
133
+ * The id is the hash of the name the payload carries, so the point id and
134
+ * the payload name always belong together: two writers that store the
135
+ * same name address the same point.
136
+ */
137
+ export interface SkillQdrantPoint {
138
+ id: number;
139
+ payload: SkillPointPayload;
140
+ }
141
+
142
+ /**
143
+ * The point of a skill.
144
+ *
145
+ * The runtime registry and `scripts/sync-to-qdrant.ts` both build their
146
+ * points here, so they cannot drift apart. A writer that used its own id
147
+ * would leave a second point for every skill the other one indexes, and
148
+ * an id that happens to collide with a foreign one would overwrite an
149
+ * unrelated skill.
150
+ */
151
+ export function skillQdrantPoint(payload: SkillPointPayload): SkillQdrantPoint {
152
+ return { id: stableHash(payload.name), payload };
153
+ }
154
+
155
+ /**
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.
161
+ */
162
+ export function skillPointPayload(input: {
163
+ /** The name the payload carries - the identity of the skill. */
164
+ name: string;
165
+ /** The title of the skill, when it differs from the name. */
166
+ title?: string;
167
+ /** What the skill does. */
168
+ description: string;
169
+ /** The body of the skill. */
170
+ body: string;
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);
176
+ return {
177
+ name,
178
+ description,
179
+ body,
180
+ text: skillPointText({ name, title, description, body }),
181
+ };
182
+ }
60
183
  export function describeSkillSchemaMismatch(
61
184
  actual: { dimensions?: number; distance?: string },
62
185
  expected: SkillVectorSchema,
@@ -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
+ });
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Regression tests for the point identity of the skill collection.
3
+ *
4
+ * Two writers fill the same collection: the runtime registry and
5
+ * `scripts/sync-to-qdrant.ts`. They used to disagree about the identity of
6
+ * a point - the runtime hashed the name of the skill, the script used a
7
+ * running number. The same skill then existed twice, and a running number
8
+ * could overwrite a point of an unrelated skill.
9
+ *
10
+ * The identity now comes from one shared place, and these tests hold it
11
+ * there.
12
+ */
13
+
14
+ import { describe, expect, test } from "bun:test";
15
+
16
+ import {
17
+ createSkillRegistry,
18
+ type SkillVectorIndexer,
19
+ type SkillVectorPoint,
20
+ } from "../src/loader.js";
21
+ import {
22
+ SKILL_PAYLOAD_BODY_LIMIT,
23
+ SKILL_PAYLOAD_TEXT_LIMIT,
24
+ skillPointPayload,
25
+ skillQdrantPoint,
26
+ stableHash,
27
+ } from "../src/vector-schema.js";
28
+ import type { Skill } from "../src/types.js";
29
+
30
+ /** Wait until the registry has no index work left. */
31
+ async function settle(registry: { isIndexing(): boolean }): Promise<void> {
32
+ for (let attempt = 0; attempt < 200 && registry.isIndexing(); attempt++) {
33
+ await new Promise((resolve) => setTimeout(resolve, 5));
34
+ }
35
+ }
36
+
37
+ /** A skill with the given id and body. */
38
+ function makeSkill(id: string, body = `body of ${id}`): Skill {
39
+ return {
40
+ id,
41
+ path: `/tmp/skills/${id}`,
42
+ frontmatter: {
43
+ name: id,
44
+ description: `the ${id} skill`,
45
+ },
46
+ body,
47
+ metadata: {} as Skill["metadata"],
48
+ } as Skill;
49
+ }
50
+
51
+ /** An indexer that keeps the points it receives. */
52
+ class RecordingIndexer implements SkillVectorIndexer {
53
+ readonly upsertCalls: SkillVectorPoint[][] = [];
54
+
55
+ async embed(texts: string[]): Promise<number[][]> {
56
+ return texts.map((text) => [text.length, 1, 2]);
57
+ }
58
+
59
+ async upsert(points: SkillVectorPoint[]): Promise<void> {
60
+ this.upsertCalls.push(points);
61
+ }
62
+ }
63
+
64
+ describe("the identity of a skill point", () => {
65
+ test("the id of the point is the hash of the name in the payload", () => {
66
+ const payload = skillPointPayload({
67
+ name: "a-skill",
68
+ description: "does something",
69
+ body: "the body",
70
+ });
71
+ const point = skillQdrantPoint(payload);
72
+
73
+ expect(point.id).toBe(stableHash(payload.name));
74
+ });
75
+
76
+ test("the same name always addresses the same point", () => {
77
+ const first = skillQdrantPoint(
78
+ skillPointPayload({ name: "a-skill", description: "one", body: "one" }),
79
+ );
80
+ const second = skillQdrantPoint(
81
+ skillPointPayload({ name: "a-skill", description: "two", body: "two" }),
82
+ );
83
+
84
+ // The content may change, the identity of the point may not.
85
+ expect(second.id).toBe(first.id);
86
+ });
87
+
88
+ test("different names address different points", () => {
89
+ const first = skillQdrantPoint(
90
+ skillPointPayload({ name: "a-skill", description: "d", body: "b" }),
91
+ );
92
+ const second = skillQdrantPoint(
93
+ skillPointPayload({ name: "b-skill", description: "d", body: "b" }),
94
+ );
95
+
96
+ expect(second.id).not.toBe(first.id);
97
+ });
98
+
99
+ test("the payload carries the shared fields and nothing else", () => {
100
+ const payload = skillPointPayload({
101
+ name: "a-skill",
102
+ title: "A Skill",
103
+ description: "does something",
104
+ body: "the body",
105
+ });
106
+
107
+ expect(Object.keys(payload).sort()).toEqual([
108
+ "body",
109
+ "description",
110
+ "name",
111
+ "text",
112
+ ]);
113
+ });
114
+
115
+ test("the payload and the text are truncated", () => {
116
+ const payload = skillPointPayload({
117
+ name: "a-skill",
118
+ description: "d",
119
+ body: "x".repeat(SKILL_PAYLOAD_BODY_LIMIT + 500),
120
+ });
121
+
122
+ expect(payload.body.length).toBe(SKILL_PAYLOAD_BODY_LIMIT);
123
+ expect(payload.text.length).toBeLessThanOrEqual(
124
+ SKILL_PAYLOAD_TEXT_LIMIT + payload.name.length + payload.description.length + 10,
125
+ );
126
+ });
127
+
128
+ test("the title is part of the text when it differs from the name", () => {
129
+ const withTitle = skillPointPayload({
130
+ name: "a-skill",
131
+ title: "A Human Title",
132
+ description: "d",
133
+ body: "b",
134
+ });
135
+ const withoutTitle = skillPointPayload({
136
+ name: "a-skill",
137
+ description: "d",
138
+ body: "b",
139
+ });
140
+
141
+ expect(withTitle.text).toContain("A Human Title");
142
+ expect(withoutTitle.text).not.toContain("A Human Title");
143
+ });
144
+ });
145
+
146
+ describe("the runtime writes the points of the shared schema", () => {
147
+ test("the indexed point carries the shared payload and id", async () => {
148
+ const indexer = new RecordingIndexer();
149
+ const registry = createSkillRegistry({ indexer });
150
+
151
+ registry.register(makeSkill("alpha"));
152
+ await settle(registry);
153
+
154
+ const points = indexer.upsertCalls.flat();
155
+ expect(points.length).toBe(1);
156
+
157
+ const point = points[0];
158
+ expect(point.id).toBe(stableHash(point.payload.name));
159
+ expect(point.payload.name).toBe("alpha");
160
+ expect(Object.keys(point.payload).sort()).toEqual([
161
+ "body",
162
+ "description",
163
+ "name",
164
+ "text",
165
+ ]);
166
+ });
167
+ });
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/sprint-planner",
3
- "version": "1.1.413",
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.413",
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.413",
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.413",
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.413",
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.413",
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.413",
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.413",
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.413",
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.413",
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.413",
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.413",
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.413",
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.413",
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.413",
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.413",
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.413",
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.413",
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.413",
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.413",
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.413",
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.413",
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.413",
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",