@danypops/pi-packed 0.28.3 → 0.28.4
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/dist/adoption/smoke.d.ts +9 -0
- package/dist/adoption/smoke.d.ts.map +1 -1
- package/package.json +3 -3
- package/service/src/adoption/doctor.ts +25 -2
- package/service/src/adoption/smoke-child.ts +31 -4
- package/service/src/adoption/smoke.ts +17 -0
- package/service/test/doctor.test.ts +51 -0
- package/service/test/smoke.test.ts +4 -0
package/dist/adoption/smoke.d.ts
CHANGED
|
@@ -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;
|
|
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
|
+
"version": "0.28.4",
|
|
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
|
+
"@danypops/vehicle-client-pi": "^0.44.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.
|
|
49
|
+
"@danypops/vehicle-client-pi": "^0.44.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
|
-
|
|
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 }) =>
|
|
87
|
+
return (definition: { name?: unknown; shared?: unknown }) => captureWithSharedMarker("tools", definition?.name, definition?.shared);
|
|
61
88
|
case "registerCommand":
|
|
62
|
-
return (name: unknown) =>
|
|
89
|
+
return (name: unknown, options?: { shared?: unknown }) => captureWithSharedMarker("commands", name, options?.shared);
|
|
63
90
|
case "registerShortcut":
|
|
64
|
-
return (name: unknown) =>
|
|
91
|
+
return (name: unknown, options?: { shared?: unknown }) => captureWithSharedMarker("shortcuts", name, options?.shared);
|
|
65
92
|
case "registerFlag":
|
|
66
|
-
return (name: unknown) =>
|
|
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: [],
|