@pikku/inspector 0.12.70 → 0.12.72
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/CHANGELOG.md +49 -0
- package/dist/add/add-file-with-factory.js +96 -14
- package/dist/add/add-rpc-invocations.js +48 -0
- package/dist/error-codes.d.ts +1 -0
- package/dist/error-codes.js +1 -0
- package/dist/inspector.js +3 -0
- package/dist/types.d.ts +12 -0
- package/dist/utils/filter-inspector-state.js +17 -0
- package/dist/utils/post-process.d.ts +10 -0
- package/dist/utils/post-process.js +87 -0
- package/dist/utils/serialize-inspector-state.d.ts +3 -0
- package/dist/utils/serialize-inspector-state.js +6 -0
- package/package.json +3 -3
- package/src/add/add-file-with-factory.test.ts +134 -0
- package/src/add/add-file-with-factory.ts +109 -17
- package/src/add/add-rpc-invocations.test.ts +124 -0
- package/src/add/add-rpc-invocations.ts +52 -0
- package/src/add/add-scenario-step.test.ts +61 -0
- package/src/error-codes.ts +1 -0
- package/src/inspector.ts +3 -0
- package/src/types.ts +12 -0
- package/src/utils/filter-inspector-state.test.ts +79 -0
- package/src/utils/filter-inspector-state.ts +19 -0
- package/src/utils/post-process.test.ts +42 -0
- package/src/utils/post-process.ts +110 -0
- package/src/utils/serialize-inspector-state.ts +17 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -22,6 +22,7 @@ import { relative } from 'node:path'
|
|
|
22
22
|
import { AUTH_HANDLER_FUNC_ID } from '../add/add-auth.js'
|
|
23
23
|
import { flattenScopeDefinitions } from '@pikku/core/scope'
|
|
24
24
|
import type { WorkflowStepMeta } from '@pikku/core/workflow'
|
|
25
|
+
import type { ScenarioStepMeta } from '@pikku/core/scenario'
|
|
25
26
|
import { DYNAMIC_SCENARIO_STEP_TARGET } from './workflow/dsl/patterns.js'
|
|
26
27
|
|
|
27
28
|
/**
|
|
@@ -381,6 +382,35 @@ export function aggregateRequiredServices(
|
|
|
381
382
|
requiredServices.add(service)
|
|
382
383
|
}
|
|
383
384
|
}
|
|
385
|
+
|
|
386
|
+
// 8. When this project IS an addon, what it needs from its parent is whatever
|
|
387
|
+
// its functions use minus whatever its own factory builds. Reading only the
|
|
388
|
+
// factory's second parameter missed everything the functions destructure and
|
|
389
|
+
// the factory never names — `kysely` above all — which left the contract empty
|
|
390
|
+
// and let a consumer omit it and fail at the first query instead of at build.
|
|
391
|
+
if (state.addonServicesFactorySeen) {
|
|
392
|
+
// Every function an addon declares is part of its published surface — a
|
|
393
|
+
// consumer calls it — so none of them are reachable from a local wiring and
|
|
394
|
+
// `usedFunctions` is empty. Aggregating only from wirings left the services
|
|
395
|
+
// map claiming an addon needs nothing, which is what drove addons to list
|
|
396
|
+
// their own services under `forceRequiredServices` by hand.
|
|
397
|
+
for (const funcMeta of Object.values(state.functions.meta)) {
|
|
398
|
+
addServices(funcMeta?.services)
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const createdSet = new Set(state.addonCreatedServices ?? [])
|
|
402
|
+
const parentServices = new Set(parentDeclared)
|
|
403
|
+
for (const service of requiredServices) {
|
|
404
|
+
if (
|
|
405
|
+
!createdSet.has(service) &&
|
|
406
|
+
!internalServices.has(service) &&
|
|
407
|
+
!defaultServices.has(service)
|
|
408
|
+
) {
|
|
409
|
+
parentServices.add(service)
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
state.addonRequiredParentServices = [...parentServices]
|
|
413
|
+
}
|
|
384
414
|
}
|
|
385
415
|
|
|
386
416
|
export function validateSecretOverrides(
|
|
@@ -911,6 +941,85 @@ export function validateScenarioServices(
|
|
|
911
941
|
}
|
|
912
942
|
}
|
|
913
943
|
|
|
944
|
+
/**
|
|
945
|
+
* Does this step's prose open by naming the actor who is already its subject?
|
|
946
|
+
*
|
|
947
|
+
* `composeStepProse` renders the actor as the sentence's subject, so a step
|
|
948
|
+
* authored as `'sam' creates the client` reads "Given sam 'sam' creates the
|
|
949
|
+
* client". Only the subject position counts: a persona keyed after a role noun
|
|
950
|
+
* ("admin") must still be able to say "creates the admin client", and naming
|
|
951
|
+
* someone mid-sentence — "sends sam an invite" — is ordinary prose.
|
|
952
|
+
*/
|
|
953
|
+
export const proseOpensWithActor = (prose: string, actor: string): boolean => {
|
|
954
|
+
if (!actor) {
|
|
955
|
+
return false
|
|
956
|
+
}
|
|
957
|
+
const escaped = actor.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
958
|
+
return new RegExp(`^["'“‘\\s]*${escaped}(?:['’]s)?\\b`, 'i').test(prose)
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
/**
|
|
962
|
+
* The prose that will actually render for one step, in the runtime's own
|
|
963
|
+
* precedence: a call-site description overrides the step's template, which
|
|
964
|
+
* overrides its declared description, which falls back to the durable name.
|
|
965
|
+
*
|
|
966
|
+
* Every candidate is returned rather than only the winner, because a template
|
|
967
|
+
* is authored where the actor is not yet known — so it is wrong at every call
|
|
968
|
+
* site that runs it as the persona it names, not just the winning one.
|
|
969
|
+
*/
|
|
970
|
+
const renderableProse = (
|
|
971
|
+
step: ScenarioStepMeta,
|
|
972
|
+
stepMeta: { description?: string; scenarioStepTemplate?: string } | undefined
|
|
973
|
+
): string[] => {
|
|
974
|
+
const description = step.options?.description
|
|
975
|
+
if (description) {
|
|
976
|
+
return [step.stepName, description]
|
|
977
|
+
}
|
|
978
|
+
return [
|
|
979
|
+
step.stepName,
|
|
980
|
+
stepMeta?.scenarioStepTemplate,
|
|
981
|
+
stepMeta?.description,
|
|
982
|
+
].filter((prose): prose is string => typeof prose === 'string')
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
/**
|
|
986
|
+
* A step's prose must not open by naming the actor it already runs as (PKU681).
|
|
987
|
+
*
|
|
988
|
+
* The reporter renders the actor as the sentence's subject, so `'sam' creates
|
|
989
|
+
* the client` run as `actors.sam` reads "Given sam 'sam' creates the client" —
|
|
990
|
+
* and the hardcoded copy silently desyncs the moment the call site changes
|
|
991
|
+
* actor. Only the subject position is refused; naming someone mid-sentence is
|
|
992
|
+
* ordinary prose, and a persona keyed after a role noun can still say "creates
|
|
993
|
+
* the admin client".
|
|
994
|
+
*/
|
|
995
|
+
const validateStepProse = (
|
|
996
|
+
logger: InspectorLogger,
|
|
997
|
+
workflowName: string,
|
|
998
|
+
step: ScenarioStepMeta,
|
|
999
|
+
stepMeta: { description?: string; scenarioStepTemplate?: string } | undefined
|
|
1000
|
+
): void => {
|
|
1001
|
+
const actor = step.actor
|
|
1002
|
+
if (!actor) {
|
|
1003
|
+
return
|
|
1004
|
+
}
|
|
1005
|
+
const offender = renderableProse(step, stepMeta).find((prose) =>
|
|
1006
|
+
proseOpensWithActor(prose, actor)
|
|
1007
|
+
)
|
|
1008
|
+
if (offender === undefined) {
|
|
1009
|
+
return
|
|
1010
|
+
}
|
|
1011
|
+
logger.critical(
|
|
1012
|
+
ErrorCode.SCENARIO_STEP_PROSE_NAMES_ACTOR,
|
|
1013
|
+
`Scenario '${workflowName}' names its actor in the step prose: '${offender}' runs as '${actor}'. ` +
|
|
1014
|
+
`The reporter already renders the actor as the subject, so this reads ` +
|
|
1015
|
+
`"${capitaliseFirst(step.phase)} ${actor} ${offender}". Drop the name and let ` +
|
|
1016
|
+
`{ actor: actors.${actor} } supply it — the step reads as a bare third-person predicate.`
|
|
1017
|
+
)
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
const capitaliseFirst = (value: string): string =>
|
|
1021
|
+
value.charAt(0).toUpperCase() + value.slice(1)
|
|
1022
|
+
|
|
914
1023
|
/**
|
|
915
1024
|
* Walk every scenario step, wherever it is nested, and validate the two things
|
|
916
1025
|
* that can only be known once both the workflow meta and the function meta
|
|
@@ -949,6 +1058,7 @@ export function validateScenarioSteps(
|
|
|
949
1058
|
}
|
|
950
1059
|
state.serviceAggregation.usedFunctions.add(step.stepFunc)
|
|
951
1060
|
const stepMeta = state.functions.meta[step.stepFunc]
|
|
1061
|
+
validateStepProse(logger, workflowName, step, stepMeta)
|
|
952
1062
|
if (stepMeta?.scenarioStepRequiresActor && !step.actor) {
|
|
953
1063
|
const why = stepMeta.scenarioStepSurfaces?.includes('browser')
|
|
954
1064
|
? 'declares a browser binding'
|
|
@@ -51,6 +51,8 @@ export interface SerializableInspectorState {
|
|
|
51
51
|
>
|
|
52
52
|
wireServicesMeta: Array<[string, string[]]>
|
|
53
53
|
addonRequiredParentServices: string[]
|
|
54
|
+
addonCreatedServices: string[]
|
|
55
|
+
addonServicesFactorySeen: boolean
|
|
54
56
|
addonServerlessIncompatible: Array<[string, string[]]>
|
|
55
57
|
configFactories: Array<
|
|
56
58
|
[
|
|
@@ -186,6 +188,7 @@ export interface SerializableInspectorState {
|
|
|
186
188
|
files: string[]
|
|
187
189
|
}
|
|
188
190
|
agents: {
|
|
191
|
+
invokedAgentsByFile?: Array<[string, string[]]>
|
|
189
192
|
agentsMeta: InspectorState['agents']['agentsMeta']
|
|
190
193
|
files: [string, { path: string; exportedName: string }][]
|
|
191
194
|
}
|
|
@@ -335,6 +338,8 @@ export function serializeInspectorState(
|
|
|
335
338
|
wireServicesFactories: Array.from(state.wireServicesFactories.entries()),
|
|
336
339
|
wireServicesMeta: Array.from(state.wireServicesMeta.entries()),
|
|
337
340
|
addonRequiredParentServices: state.addonRequiredParentServices,
|
|
341
|
+
addonCreatedServices: state.addonCreatedServices,
|
|
342
|
+
addonServicesFactorySeen: state.addonServicesFactorySeen,
|
|
338
343
|
addonServerlessIncompatible: Array.from(
|
|
339
344
|
state.addonServerlessIncompatible.entries()
|
|
340
345
|
),
|
|
@@ -417,6 +422,11 @@ export function serializeInspectorState(
|
|
|
417
422
|
agents: {
|
|
418
423
|
agentsMeta: state.agents?.agentsMeta ?? {},
|
|
419
424
|
files: Array.from(state.agents?.files?.entries() ?? []),
|
|
425
|
+
invokedAgentsByFile: Array.from(
|
|
426
|
+
(
|
|
427
|
+
state.agents?.invokedAgentsByFile ?? new Map<string, Set<string>>()
|
|
428
|
+
).entries()
|
|
429
|
+
).map(([file, agents]): [string, string[]] => [file, Array.from(agents)]),
|
|
420
430
|
},
|
|
421
431
|
scorers: {
|
|
422
432
|
scorersMeta: state.scorers?.scorersMeta ?? {},
|
|
@@ -544,6 +554,8 @@ export function deserializeInspectorState(
|
|
|
544
554
|
wireServicesFactories: new Map(data.wireServicesFactories),
|
|
545
555
|
wireServicesMeta: new Map(data.wireServicesMeta),
|
|
546
556
|
addonRequiredParentServices: data.addonRequiredParentServices || [],
|
|
557
|
+
addonCreatedServices: data.addonCreatedServices || [],
|
|
558
|
+
addonServicesFactorySeen: data.addonServicesFactorySeen ?? false,
|
|
547
559
|
addonServerlessIncompatible: new Map(
|
|
548
560
|
data.addonServerlessIncompatible || []
|
|
549
561
|
),
|
|
@@ -626,6 +638,11 @@ export function deserializeInspectorState(
|
|
|
626
638
|
agents: {
|
|
627
639
|
agentsMeta: data.agents?.agentsMeta || {},
|
|
628
640
|
files: new Map(data.agents?.files || []),
|
|
641
|
+
invokedAgentsByFile: new Map(
|
|
642
|
+
(data.agents?.invokedAgentsByFile || []).map(
|
|
643
|
+
([file, agents]): [string, Set<string>] => [file, new Set(agents)]
|
|
644
|
+
)
|
|
645
|
+
),
|
|
629
646
|
},
|
|
630
647
|
scorers: {
|
|
631
648
|
scorersMeta: data.scorers?.scorersMeta || {},
|