@danypops/pi-packed 0.28.3 → 0.28.5

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.
@@ -7,6 +7,15 @@ export interface SmokeRegistrations {
7
7
  providers: string[];
8
8
  events: string[];
9
9
  renderers: string[];
10
+ /**
11
+ * Names, within tools/commands/shortcuts/flags respectively, whose registration call carried an
12
+ * explicit `shared: true` marker -- see @danypops/vehicle-client-pi's own markSharedRegistration.
13
+ * doctor.ts only suppresses a name's conflict once EVERY one of its claimants marked it shared.
14
+ */
15
+ sharedTools: string[];
16
+ sharedCommands: string[];
17
+ sharedShortcuts: string[];
18
+ sharedFlags: string[];
10
19
  }
11
20
  export interface ExtensionSmokeResult {
12
21
  path: string;
@@ -1 +1 @@
1
- {"version":3,"file":"smoke.d.ts","sourceRoot":"","sources":["../../service/src/adoption/smoke.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,WAAW,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,GAAG,mBAAmB,GAAG,cAAc,GAAG,qBAAqB,CAAC;AAEpH,MAAM,WAAW,kBAAkB;IAClC,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,oBAAoB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,WAAW,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,kBAAkB,CAAC;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB;AAuDD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAO7F;AAsLD,wBAAsB,iBAAiB,CACtC,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,MAAM,EACrB,OAAO,GAAE,YAAiB,GACxB,OAAO,CAAC,oBAAoB,CAAC,CAoF/B"}
1
+ {"version":3,"file":"smoke.d.ts","sourceRoot":"","sources":["../../service/src/adoption/smoke.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,WAAW,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,GAAG,mBAAmB,GAAG,cAAc,GAAG,qBAAqB,CAAC;AAEpH,MAAM,WAAW,kBAAkB;IAClC,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB;;;;OAIG;IACH,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,WAAW,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,oBAAoB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,WAAW,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,kBAAkB,CAAC;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB;AA2DD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAO7F;AA0LD,wBAAsB,iBAAiB,CACtC,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,MAAM,EACrB,OAAO,GAAE,YAAiB,GACxB,OAAO,CAAC,oBAAoB,CAAC,CAoF/B"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danypops/pi-packed",
3
- "version": "0.28.3",
3
+ "version": "0.28.5",
4
4
  "description": "Pi package lifecycle, validation, daemon, tools, profiles, and TUI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -40,13 +40,13 @@
40
40
  },
41
41
  "devDependencies": {
42
42
  "@danypops/pi-tui-harness": "^0.0.1",
43
- "@danypops/vehicle-client-pi": "^0.43.1",
43
+ "@danypops/vehicle-client-pi": "^0.45.0",
44
44
  "@danypops/vehicle-conformance": "^0.4.0",
45
45
  "@types/semver": "^7.7.1",
46
46
  "follow-redirects": "^1.16.0"
47
47
  },
48
48
  "peerDependencies": {
49
- "@danypops/vehicle-client-pi": "^0.43.1",
49
+ "@danypops/vehicle-client-pi": "^0.45.0",
50
50
  "@earendil-works/pi-coding-agent": "*",
51
51
  "@earendil-works/pi-tui": "*",
52
52
  "typebox": "*"
@@ -31,6 +31,18 @@ const CONFLICT_KIND: Record<RegistrationKind, "tool" | "command" | "shortcut" |
31
31
  shortcuts: "shortcut",
32
32
  flags: "flag",
33
33
  };
34
+ /**
35
+ * Maps each RegistrationKind to the smoke result's own parallel "which of these names were marked
36
+ * shared" field (see smoke.ts's own SmokeRegistrations doc comment). A name is only exempted from
37
+ * conflict reporting once EVERY one of its claimants marked it shared -- see runDoctor's own
38
+ * allSharedByName tracking below.
39
+ */
40
+ const SHARED_REGISTRATION_KIND: Record<RegistrationKind, "sharedTools" | "sharedCommands" | "sharedShortcuts" | "sharedFlags"> = {
41
+ tools: "sharedTools",
42
+ commands: "sharedCommands",
43
+ shortcuts: "sharedShortcuts",
44
+ flags: "sharedFlags",
45
+ };
34
46
 
35
47
  const MAX_EXTENSIONS_SCANNED = 50;
36
48
 
@@ -63,6 +75,11 @@ export async function runDoctor(piHome: string, projectRoot?: string, options: S
63
75
  const bounded = jobs.slice(0, MAX_EXTENSIONS_SCANNED);
64
76
 
65
77
  const claims = new Map<RegistrationKind, Map<string, DoctorClaim[]>>(REGISTRATION_KINDS.map((kind) => [kind, new Map()]));
78
+ // True for a (kind, name) pair only while EVERY claimant seen so far marked that exact
79
+ // registration shared: true -- a single unmarked claimant permanently flips it false, since an
80
+ // unmarked registration is a genuinely ambiguous case, not the coordinated-by-design pattern
81
+ // shared:true exists to exempt.
82
+ const allSharedByName = new Map<RegistrationKind, Map<string, boolean>>(REGISTRATION_KINDS.map((kind) => [kind, new Map()]));
66
83
  const extensions: DoctorExtensionResult[] = [];
67
84
  let anyNotOk = false;
68
85
 
@@ -82,14 +99,20 @@ export async function runDoctor(piHome: string, projectRoot?: string, options: S
82
99
  const claim: DoctorClaim = { name: job.group.name, source: job.group.source, scope: job.group.scope, extension: job.extension };
83
100
  for (const kind of REGISTRATION_KINDS) {
84
101
  const byName = claims.get(kind)!;
85
- for (const name of result.registrations[kind]) byName.set(name, [...(byName.get(name) ?? []), claim]);
102
+ const sharedFlags = allSharedByName.get(kind)!;
103
+ const sharedNamesHere = new Set(result.registrations[SHARED_REGISTRATION_KIND[kind]]);
104
+ for (const name of result.registrations[kind]) {
105
+ byName.set(name, [...(byName.get(name) ?? []), claim]);
106
+ sharedFlags.set(name, (sharedFlags.get(name) ?? true) && sharedNamesHere.has(name));
107
+ }
86
108
  }
87
109
  }
88
110
 
89
111
  const conflicts: DoctorConflict[] = [];
90
112
  for (const kind of REGISTRATION_KINDS) {
113
+ const sharedFlags = allSharedByName.get(kind)!;
91
114
  for (const [name, claimants] of claims.get(kind)!) {
92
- if (claimants.length > 1) conflicts.push({ kind: CONFLICT_KIND[kind], name, claimants });
115
+ if (claimants.length > 1 && !sharedFlags.get(name)) conflicts.push({ kind: CONFLICT_KIND[kind], name, claimants });
93
116
  }
94
117
  }
95
118
  conflicts.sort((a, b) => a.kind.localeCompare(b.kind) || a.name.localeCompare(b.name));
@@ -8,6 +8,15 @@ interface Registrations {
8
8
  providers: string[];
9
9
  events: string[];
10
10
  renderers: string[];
11
+ /**
12
+ * Names, within tools/commands/shortcuts/flags respectively, whose registration call carried an
13
+ * explicit `shared: true` marker -- see @danypops/vehicle-client-pi's own markSharedRegistration.
14
+ * doctor.ts only suppresses a name's conflict once EVERY one of its claimants marked it shared.
15
+ */
16
+ sharedTools: string[];
17
+ sharedCommands: string[];
18
+ sharedShortcuts: string[];
19
+ sharedFlags: string[];
11
20
  }
12
21
 
13
22
  const MAX_REGISTRATIONS_TOTAL = 100;
@@ -43,6 +52,10 @@ const registrations: Registrations = {
43
52
  providers: [],
44
53
  events: [],
45
54
  renderers: [],
55
+ sharedTools: [],
56
+ sharedCommands: [],
57
+ sharedShortcuts: [],
58
+ sharedFlags: [],
46
59
  };
47
60
 
48
61
  function capture(kind: keyof Registrations, value: unknown): void {
@@ -51,19 +64,33 @@ function capture(kind: keyof Registrations, value: unknown): void {
51
64
  registrations[kind].push(name.slice(0, MAX_NAME_LENGTH));
52
65
  }
53
66
 
67
+ const SHARED_KIND: Partial<Record<keyof Registrations, keyof Registrations>> = {
68
+ tools: "sharedTools",
69
+ commands: "sharedCommands",
70
+ shortcuts: "sharedShortcuts",
71
+ flags: "sharedFlags",
72
+ };
73
+
74
+ /** Captures the plain registration as usual, plus a parallel sharedX entry when the caller's own definition/options object carried an explicit `shared: true` marker. */
75
+ function captureWithSharedMarker(kind: keyof Registrations, value: unknown, sharedMarker: unknown): void {
76
+ capture(kind, value);
77
+ const sharedKind = SHARED_KIND[kind];
78
+ if (sharedKind && sharedMarker === true) capture(sharedKind, value);
79
+ }
80
+
54
81
  const api = new Proxy<Record<string, unknown>>(
55
82
  {},
56
83
  {
57
84
  get(_target, property) {
58
85
  switch (property) {
59
86
  case "registerTool":
60
- return (definition: { name?: unknown }) => capture("tools", definition?.name);
87
+ return (definition: { name?: unknown; shared?: unknown }) => captureWithSharedMarker("tools", definition?.name, definition?.shared);
61
88
  case "registerCommand":
62
- return (name: unknown) => capture("commands", name);
89
+ return (name: unknown, options?: { shared?: unknown }) => captureWithSharedMarker("commands", name, options?.shared);
63
90
  case "registerShortcut":
64
- return (name: unknown) => capture("shortcuts", name);
91
+ return (name: unknown, options?: { shared?: unknown }) => captureWithSharedMarker("shortcuts", name, options?.shared);
65
92
  case "registerFlag":
66
- return (name: unknown) => capture("flags", name);
93
+ return (name: unknown, options?: { shared?: unknown }) => captureWithSharedMarker("flags", name, options?.shared);
67
94
  case "registerProvider":
68
95
  return (name: unknown) => capture("providers", name);
69
96
  case "registerMessageRenderer":
@@ -13,6 +13,15 @@ export interface SmokeRegistrations {
13
13
  providers: string[];
14
14
  events: string[];
15
15
  renderers: string[];
16
+ /**
17
+ * Names, within tools/commands/shortcuts/flags respectively, whose registration call carried an
18
+ * explicit `shared: true` marker -- see @danypops/vehicle-client-pi's own markSharedRegistration.
19
+ * doctor.ts only suppresses a name's conflict once EVERY one of its claimants marked it shared.
20
+ */
21
+ sharedTools: string[];
22
+ sharedCommands: string[];
23
+ sharedShortcuts: string[];
24
+ sharedFlags: string[];
16
25
  }
17
26
 
18
27
  export interface ExtensionSmokeResult {
@@ -43,6 +52,10 @@ const EMPTY_REGISTRATIONS: SmokeRegistrations = {
43
52
  providers: [],
44
53
  events: [],
45
54
  renderers: [],
55
+ sharedTools: [],
56
+ sharedCommands: [],
57
+ sharedShortcuts: [],
58
+ sharedFlags: [],
46
59
  };
47
60
 
48
61
  function bounded(value: number | undefined, fallback: number, maximum: number): number {
@@ -282,6 +295,10 @@ function normalizeRegistrations(value: unknown): SmokeRegistrations {
282
295
  providers: names("providers"),
283
296
  events: names("events"),
284
297
  renderers: names("renderers"),
298
+ sharedTools: names("sharedTools"),
299
+ sharedCommands: names("sharedCommands"),
300
+ sharedShortcuts: names("sharedShortcuts"),
301
+ sharedFlags: names("sharedFlags"),
285
302
  };
286
303
  }
287
304
 
@@ -132,6 +132,57 @@ describeIfSandboxed("runDoctor", () => {
132
132
  expect(report.conflicts).toEqual([]);
133
133
  });
134
134
 
135
+ it("never flags a conflict when every claimant self-declared the shared name via shared: true -- e.g. registerSharedSecretsCommand's own /secrets command, or Vehicle Shell's tools_list/tools_man/tools_type, both of which every claimant's own smoke scan sees as 'the first (and only) one' since each runs in its own isolated sandbox", async () => {
136
+ const home = piHome(["npm:pi-a", "npm:pi-b"]);
137
+ installNpmPackage(
138
+ home,
139
+ "pi-a",
140
+ { extensions: ["extension/index.ts"] },
141
+ {
142
+ "extension/index.ts": 'export default function (pi: any) { pi.registerCommand("secrets", { handler() {}, shared: true }); }',
143
+ },
144
+ );
145
+ installNpmPackage(
146
+ home,
147
+ "pi-b",
148
+ { extensions: ["extension/index.ts"] },
149
+ {
150
+ "extension/index.ts": 'export default function (pi: any) { pi.registerCommand("secrets", { handler() {}, shared: true }); }',
151
+ },
152
+ );
153
+
154
+ const report = await runDoctor(home);
155
+
156
+ expect(report.ok).toBe(true);
157
+ expect(report.conflicts).toEqual([]);
158
+ });
159
+
160
+ it("still flags a conflict when only SOME claimants self-declared the name shared -- an unmarked claimant is a genuinely ambiguous case, not the coordinated-by-design pattern shared: true exists to exempt", async () => {
161
+ const home = piHome(["npm:pi-a", "npm:pi-b"]);
162
+ installNpmPackage(
163
+ home,
164
+ "pi-a",
165
+ { extensions: ["extension/index.ts"] },
166
+ {
167
+ "extension/index.ts": 'export default function (pi: any) { pi.registerCommand("secrets", { handler() {}, shared: true }); }',
168
+ },
169
+ );
170
+ installNpmPackage(
171
+ home,
172
+ "pi-b",
173
+ { extensions: ["extension/index.ts"] },
174
+ {
175
+ "extension/index.ts": 'export default function (pi: any) { pi.registerCommand("secrets", { handler() {} }); }',
176
+ },
177
+ );
178
+
179
+ const report = await runDoctor(home);
180
+
181
+ expect(report.ok).toBe(false);
182
+ expect(report.conflicts).toHaveLength(1);
183
+ expect(report.conflicts[0]).toMatchObject({ kind: "command", name: "secrets" });
184
+ });
185
+
135
186
  it("surfaces a genuinely crashing extension as a non-ok result without treating it as a conflict", async () => {
136
187
  const home = piHome(["npm:pi-broken"]);
137
188
  installNpmPackage(
@@ -112,6 +112,10 @@ describeIfSandboxed("isolated extension smoke runner", () => {
112
112
  commands: ["hello"],
113
113
  shortcuts: ["ctrl+h"],
114
114
  flags: ["hello"],
115
+ sharedTools: [],
116
+ sharedCommands: [],
117
+ sharedShortcuts: [],
118
+ sharedFlags: [],
115
119
  providers: ["local"],
116
120
  events: ["session_start"],
117
121
  renderers: [],