@almadar/core 8.6.2 → 8.6.3
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/factory/index.d.ts +263 -47
- package/dist/factory/index.js +308 -5
- package/dist/factory/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +308 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/factory/index.d.ts
CHANGED
|
@@ -181,7 +181,27 @@ interface FactoryConfigParam {
|
|
|
181
181
|
* Stays a single free-form string; consumers decide their own
|
|
182
182
|
* splitting/formatting policy. */
|
|
183
183
|
synonyms?: string;
|
|
184
|
+
/** Visibility tier authored in `.lolo` as `@tier "..."` next to the
|
|
185
|
+
* knob declaration. Drives the studio Questionnaire's filter +
|
|
186
|
+
* disclosure: `internal` knobs are hidden entirely, `advanced` knobs
|
|
187
|
+
* collapse under a "Show advanced" toggle, `essential` /
|
|
188
|
+
* `customization` knobs render inline. Untagged knobs are treated as
|
|
189
|
+
* `customization` by the studio (current visible behavior). */
|
|
190
|
+
tier?: FactoryConfigTier;
|
|
184
191
|
}
|
|
192
|
+
/**
|
|
193
|
+
* Audience tier for a `FactoryConfigParam`. Source-tagged via the
|
|
194
|
+
* `@tier "..."` annotation in `.lolo` — no heuristic inference on the
|
|
195
|
+
* consumer side. Missing tag = author did not tag = treat as
|
|
196
|
+
* `customization` at render time.
|
|
197
|
+
*
|
|
198
|
+
* - `essential` — every project of this kind needs to answer (app
|
|
199
|
+
* name, entity fields, primary nav items).
|
|
200
|
+
* - `customization` — meaningful refinement most projects will tweak.
|
|
201
|
+
* - `advanced` — sensible defaults; only specialised projects override.
|
|
202
|
+
* - `internal` — platform-internal; the studio never surfaces these.
|
|
203
|
+
*/
|
|
204
|
+
type FactoryConfigTier = 'essential' | 'customization' | 'advanced' | 'internal';
|
|
185
205
|
/**
|
|
186
206
|
* One trait the factory composes into the orbital. The projector reads
|
|
187
207
|
* these to determine which factory's trait stack covers a given
|
|
@@ -321,66 +341,187 @@ type FactoryParamValue = string | number | boolean | ReadonlyArray<FactoryParamV
|
|
|
321
341
|
};
|
|
322
342
|
|
|
323
343
|
/**
|
|
324
|
-
*
|
|
325
|
-
*
|
|
326
|
-
*
|
|
344
|
+
* Typed questionnaire surface — shapes the studio renders + answers.
|
|
345
|
+
*
|
|
346
|
+
* Each `DomainQuestion` is one open slot derived deterministically from
|
|
347
|
+
* a `FactoryCallSite` + its matching `FactorySignature`. The studio
|
|
348
|
+
* renders the widget keyed off `inputType` (with optional structural
|
|
349
|
+
* carriers `itemSchema` / `objectSchema` for complex types), and the
|
|
350
|
+
* user's answer flows through `answersToMutations` into a typed
|
|
351
|
+
* `FactoryCallPlanMutation`.
|
|
352
|
+
*
|
|
353
|
+
* Relocated from `@almadar-io/agent/analysis/questions/` — the
|
|
354
|
+
* function is a pure transform over `@almadar/core` factory types
|
|
355
|
+
* and belongs alongside them.
|
|
356
|
+
*
|
|
357
|
+
* @packageDocumentation
|
|
327
358
|
*/
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
/**
|
|
337
|
-
|
|
338
|
-
/**
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
interface TranslationWarning {
|
|
342
|
-
/** Dotted path of the binding field that couldn't be lowered. */
|
|
343
|
-
field: string;
|
|
344
|
-
/** Human-readable reason. */
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* One open slot in the questionnaire. The studio renders the question
|
|
362
|
+
* + input widget keyed off `inputType` + `suggestedAnswers` (and, for
|
|
363
|
+
* structured types, `itemSchema` / `objectSchema`); the answer fills
|
|
364
|
+
* the matching `mutationTemplate`.
|
|
365
|
+
*/
|
|
366
|
+
interface DomainQuestion {
|
|
367
|
+
/** Unique within a session. Keys the answer record. */
|
|
368
|
+
id: string;
|
|
369
|
+
/** Natural-language prompt shown to the user. */
|
|
370
|
+
question: string;
|
|
371
|
+
/** One short sentence — why this question matters. */
|
|
345
372
|
reason: string;
|
|
373
|
+
/** Orbital this question scopes to (matches
|
|
374
|
+
* `FactoryCallSite.orbital`). */
|
|
375
|
+
orbitalName: string;
|
|
376
|
+
/** Free-form capability tag this question explores. Set when the
|
|
377
|
+
* question was emitted from a factory trait's `capabilities[]`
|
|
378
|
+
* advertisement (e.g. `'privacy'`, `'audit'`). Undefined for
|
|
379
|
+
* config-key-driven questions. */
|
|
380
|
+
capability?: string;
|
|
381
|
+
/** Drives the form widget. Matches a narrow type set the questionnaire
|
|
382
|
+
* UI knows how to render. */
|
|
383
|
+
inputType: DomainQuestionInputType;
|
|
384
|
+
/** Typed slot for the answer. `answersToMutations` switches on the
|
|
385
|
+
* template's `kind` to produce the matching
|
|
386
|
+
* `FactoryCallPlanMutation`. */
|
|
387
|
+
mutationTemplate: FactoryCallPlanMutationTemplate;
|
|
388
|
+
/** Canonical default value from the factory signature — the studio
|
|
389
|
+
* pre-fills the input, the user can confirm or override. */
|
|
390
|
+
defaultValue?: DomainQuestionAnswer;
|
|
391
|
+
/** Closed-enum or curated suggestions. Empty when the input is
|
|
392
|
+
* free-form. */
|
|
393
|
+
suggestedAnswers?: ReadonlyArray<string>;
|
|
394
|
+
/** Free-text help shown under the input. */
|
|
395
|
+
helpText?: string;
|
|
396
|
+
/** Per-element schema for array-of-objects (`listOfObjects`) and
|
|
397
|
+
* free-form chip lists (`tagList`). Propagated from
|
|
398
|
+
* `FactoryConfigParam.items`. */
|
|
399
|
+
itemSchema?: EntityField;
|
|
400
|
+
/** Per-property schema for structured objects (`objectForm`).
|
|
401
|
+
* Propagated from `FactoryConfigParam.properties`. */
|
|
402
|
+
objectSchema?: Readonly<Record<string, EntityField>>;
|
|
403
|
+
/** Visibility tier propagated from `FactoryConfigParam.tier`.
|
|
404
|
+
* Undefined when the source `.lolo` did not author an explicit
|
|
405
|
+
* `@tier` annotation — the studio treats undefined as
|
|
406
|
+
* `'customization'`. */
|
|
407
|
+
tier?: FactoryConfigTier;
|
|
346
408
|
}
|
|
347
|
-
interface TranslationResult {
|
|
348
|
-
callSite: FactoryCallSite;
|
|
349
|
-
warnings: ReadonlyArray<TranslationWarning>;
|
|
350
|
-
}
|
|
351
|
-
declare function translateOverlaysToParams(binding: TranslationBinding, signature: FactorySignature, presentation?: PresentationOverlay, ruleOverlay?: RuleOverlay, traitOverlay?: TraitOverlay, catalog?: ReadonlyArray<FactorySignature>): TranslationResult;
|
|
352
|
-
|
|
353
409
|
/**
|
|
354
|
-
*
|
|
355
|
-
* no side effects. Used by downstream consumers (agent planner, UI
|
|
356
|
-
* cascade view) to turn a "previous calls" + "next calls" pair into a
|
|
357
|
-
* minimal change set.
|
|
410
|
+
* Narrow set of widget kinds the studio renders.
|
|
358
411
|
*
|
|
359
|
-
*
|
|
360
|
-
*
|
|
361
|
-
*
|
|
362
|
-
* the
|
|
412
|
+
* The derivation in `generate.ts:deriveInputType` reads structural
|
|
413
|
+
* metadata (`param.items.properties`, `param.properties`,
|
|
414
|
+
* `param.enumValues`) on top of `param.type` so authors can ship a
|
|
415
|
+
* `[NavItem]` field and have the studio render a per-element form,
|
|
416
|
+
* not a JSON textarea.
|
|
363
417
|
*/
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
418
|
+
type DomainQuestionInputType = 'text' | 'number' | 'boolean' | 'enum' | 'persistence' | 'capability' | 'multiselect' | 'tagList' | 'fieldList' | 'urlPath' | 'listOfObjects' | 'objectForm';
|
|
419
|
+
/**
|
|
420
|
+
* One variant per `FactoryCallPlanMutation.kind` the questionnaire
|
|
421
|
+
* surface supports. Each variant carries the discriminant pieces of
|
|
422
|
+
* the target mutation that are known at question-generation time
|
|
423
|
+
* (e.g. the orbital we're editing). The answer fills the remaining
|
|
424
|
+
* value in `answersToMutations`.
|
|
425
|
+
*/
|
|
426
|
+
type FactoryCallPlanMutationTemplate = {
|
|
427
|
+
kind: 'set-orbital-entity-name';
|
|
367
428
|
orbitalName: string;
|
|
368
|
-
next: FactoryCallSite;
|
|
369
429
|
} | {
|
|
370
|
-
kind: '
|
|
430
|
+
kind: 'set-orbital-entity-fields';
|
|
371
431
|
orbitalName: string;
|
|
372
|
-
prior: FactoryCallSite;
|
|
373
432
|
} | {
|
|
374
|
-
kind: '
|
|
433
|
+
kind: 'set-orbital-persistence';
|
|
375
434
|
orbitalName: string;
|
|
376
|
-
prior: FactoryCallSite;
|
|
377
|
-
next: FactoryCallSite;
|
|
378
435
|
} | {
|
|
379
|
-
kind: '
|
|
436
|
+
kind: 'set-orbital-collection';
|
|
380
437
|
orbitalName: string;
|
|
381
|
-
|
|
438
|
+
} | {
|
|
439
|
+
kind: 'set-orbital-page-path';
|
|
440
|
+
orbitalName: string;
|
|
441
|
+
pageName: string;
|
|
442
|
+
} | {
|
|
443
|
+
kind: 'set-trait-override-config';
|
|
444
|
+
orbitalName: string;
|
|
445
|
+
traitName: string;
|
|
446
|
+
configKey: string;
|
|
447
|
+
} | {
|
|
448
|
+
kind: 'add-extra-trait';
|
|
449
|
+
orbitalName: string;
|
|
450
|
+
} | {
|
|
451
|
+
kind: 'remove-extra-trait';
|
|
452
|
+
orbitalName: string;
|
|
453
|
+
} | {
|
|
454
|
+
kind: 'set-rule';
|
|
455
|
+
capability: string;
|
|
456
|
+
appliesTo: ReadonlyArray<string>;
|
|
457
|
+
} | {
|
|
458
|
+
kind: 'remove-rule';
|
|
459
|
+
ruleId: string;
|
|
382
460
|
};
|
|
383
|
-
|
|
461
|
+
/**
|
|
462
|
+
* Typed answer shape. The studio coerces the input into one of these
|
|
463
|
+
* before submitting. `answersToMutations` narrows on the matching
|
|
464
|
+
* `mutationTemplate.kind`.
|
|
465
|
+
*
|
|
466
|
+
* `null` means "answer skipped" (UI honours, reducer emits no
|
|
467
|
+
* mutation).
|
|
468
|
+
*/
|
|
469
|
+
type DomainQuestionAnswer = string | EntityPersistence | ReadonlyArray<EntityField> | TraitReference | FactoryParamValue | null;
|
|
470
|
+
/**
|
|
471
|
+
* The shape passed to `answersToMutations` — keyed by
|
|
472
|
+
* `DomainQuestion.id`. Questions the user skipped MAY be omitted
|
|
473
|
+
* entirely OR carry `null`; both behave the same downstream.
|
|
474
|
+
*/
|
|
475
|
+
type DomainQuestionAnswers = Readonly<Record<string, DomainQuestionAnswer>>;
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* `generateQuestions(plan, catalog, ruleOverlay?)`.
|
|
479
|
+
*
|
|
480
|
+
* Deterministic, no LLM call. Walks each `FactoryCallSite` in the
|
|
481
|
+
* durable plan against its matching `FactorySignature` and emits one
|
|
482
|
+
* open question per advertised knob the user hasn't already filled.
|
|
483
|
+
*
|
|
484
|
+
* Two families of questions, both source-derived:
|
|
485
|
+
*
|
|
486
|
+
* 1. CONFIG-KEY — one question per
|
|
487
|
+
* `signature.traits[*].overridableConfigKeys[*]`. Each entry is a
|
|
488
|
+
* `FactoryConfigParam` carrying `{ key, type, default?, label?,
|
|
489
|
+
* description?, enumValues?, items?, properties?, tier? }` lifted
|
|
490
|
+
* directly from the source `.lolo` `config { }` block. The widget
|
|
491
|
+
* choice falls out of `deriveInputType` (reads `items.properties`
|
|
492
|
+
* and `properties` so structured types render proper editors, not
|
|
493
|
+
* JSON textareas); the prompt comes from `label` (or a humanized
|
|
494
|
+
* fallback of `key`); the default value pre-fills the widget.
|
|
495
|
+
* Knobs tagged `tier: 'internal'` are filtered out entirely.
|
|
496
|
+
*
|
|
497
|
+
* 2. CAPABILITY-DRIVEN — one question per unique
|
|
498
|
+
* `signature.traits[*].capabilities[*]` tag NOT already covered
|
|
499
|
+
* by `ruleOverlay.rules[].capability`. Surfaces a `set-rule`
|
|
500
|
+
* mutation template scoped to the bound entity.
|
|
501
|
+
*
|
|
502
|
+
* @packageDocumentation
|
|
503
|
+
*/
|
|
504
|
+
|
|
505
|
+
declare function generateQuestions(plan: ReadonlyArray<FactoryCallSite>, catalog: ReadonlyArray<FactorySignature>, ruleOverlay?: RuleOverlay): DomainQuestion[];
|
|
506
|
+
/**
|
|
507
|
+
* Pick a widget for a config-key question from the param's typed
|
|
508
|
+
* declaration + its structural carriers. The type tag is lifted
|
|
509
|
+
* verbatim from `.lolo`; `items.properties` and `properties` carry the
|
|
510
|
+
* per-element / per-property schema for structured types so the studio
|
|
511
|
+
* can render a real form instead of a JSON textarea.
|
|
512
|
+
*
|
|
513
|
+
* - `[T]` with `items.properties` → `listOfObjects` (repeatable form)
|
|
514
|
+
* - `[T]` with `enumValues` → `multiselect` (closed checkboxes)
|
|
515
|
+
* - `[T]` otherwise → `tagList` (free-form chips)
|
|
516
|
+
* - `object` with `properties` → `objectForm` (per-property form)
|
|
517
|
+
* - `object` otherwise → `text` (rare; flagged by tests)
|
|
518
|
+
* - `number` / `integer` → `number`
|
|
519
|
+
* - `boolean` → `boolean`
|
|
520
|
+
* - `enum` → `enum`
|
|
521
|
+
* - `string` with `enumValues` → `enum`
|
|
522
|
+
* - `string` → `text`
|
|
523
|
+
*/
|
|
524
|
+
declare function deriveInputType(param: FactoryConfigParam): DomainQuestionInputType;
|
|
384
525
|
|
|
385
526
|
/**
|
|
386
527
|
* One slot the agent emits per orbital. Slim shape — agent-facing,
|
|
@@ -462,4 +603,79 @@ interface FactoryCallPlanState {
|
|
|
462
603
|
*/
|
|
463
604
|
declare function applyFactoryCallPlanMutation(state: FactoryCallPlanState, m: FactoryCallPlanMutation): FactoryCallPlanState;
|
|
464
605
|
|
|
465
|
-
|
|
606
|
+
/**
|
|
607
|
+
* `answersToMutations(answers, questions)` — turns a
|
|
608
|
+
* `DomainQuestionAnswers` record into a typed
|
|
609
|
+
* `FactoryCallPlanMutation[]`. Each question's `mutationTemplate`
|
|
610
|
+
* matches one variant of the union; the answer fills in the value.
|
|
611
|
+
* `null` (or omitted) answers produce no mutation.
|
|
612
|
+
*
|
|
613
|
+
* @packageDocumentation
|
|
614
|
+
*/
|
|
615
|
+
|
|
616
|
+
declare function answersToMutations(answers: DomainQuestionAnswers, questions: ReadonlyArray<DomainQuestion>): FactoryCallPlanMutation[];
|
|
617
|
+
declare function answerToMutations(template: FactoryCallPlanMutationTemplate, answer: DomainQuestionAnswer, _question: DomainQuestion): FactoryCallPlanMutation[];
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* The per-orbital binding the agent assembles when it picks a
|
|
621
|
+
* signature. Carries the user-authored overrides directly; no shared
|
|
622
|
+
* domain ontology, no DomainDocument.
|
|
623
|
+
*/
|
|
624
|
+
interface TranslationBinding {
|
|
625
|
+
/** Entity name override. When equal to `signature.entities[0].name`,
|
|
626
|
+
* no rename is emitted. */
|
|
627
|
+
entityName: string;
|
|
628
|
+
/** Extra fields beyond the signature's canonical entity surface. */
|
|
629
|
+
entityFields?: ReadonlyArray<EntityField>;
|
|
630
|
+
/** Persistence override. */
|
|
631
|
+
persistence?: EntityPersistence;
|
|
632
|
+
/** Collection name override. */
|
|
633
|
+
collection?: string;
|
|
634
|
+
/** Per-page path overrides keyed by `signature.pages[i].name`. */
|
|
635
|
+
pagePaths?: Readonly<Record<string, string>>;
|
|
636
|
+
}
|
|
637
|
+
interface TranslationWarning {
|
|
638
|
+
/** Dotted path of the binding field that couldn't be lowered. */
|
|
639
|
+
field: string;
|
|
640
|
+
/** Human-readable reason. */
|
|
641
|
+
reason: string;
|
|
642
|
+
}
|
|
643
|
+
interface TranslationResult {
|
|
644
|
+
callSite: FactoryCallSite;
|
|
645
|
+
warnings: ReadonlyArray<TranslationWarning>;
|
|
646
|
+
}
|
|
647
|
+
declare function translateOverlaysToParams(binding: TranslationBinding, signature: FactorySignature, presentation?: PresentationOverlay, ruleOverlay?: RuleOverlay, traitOverlay?: TraitOverlay, catalog?: ReadonlyArray<FactorySignature>): TranslationResult;
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* Structural diff between two `FactoryCallSite[]` lists. Pure typed,
|
|
651
|
+
* no side effects. Used by downstream consumers (agent planner, UI
|
|
652
|
+
* cascade view) to turn a "previous calls" + "next calls" pair into a
|
|
653
|
+
* minimal change set.
|
|
654
|
+
*
|
|
655
|
+
* Identity for diffing is `(organism, orbital)`. Renames are not
|
|
656
|
+
* handled here — they show up as a delete + add. The agent layer is
|
|
657
|
+
* expected to merge those back into a `'rename'` op if it can prove
|
|
658
|
+
* the underlying domain entity was renamed.
|
|
659
|
+
*/
|
|
660
|
+
|
|
661
|
+
type CallSiteDiff = {
|
|
662
|
+
kind: 'add';
|
|
663
|
+
orbitalName: string;
|
|
664
|
+
next: FactoryCallSite;
|
|
665
|
+
} | {
|
|
666
|
+
kind: 'delete';
|
|
667
|
+
orbitalName: string;
|
|
668
|
+
prior: FactoryCallSite;
|
|
669
|
+
} | {
|
|
670
|
+
kind: 'edit';
|
|
671
|
+
orbitalName: string;
|
|
672
|
+
prior: FactoryCallSite;
|
|
673
|
+
next: FactoryCallSite;
|
|
674
|
+
} | {
|
|
675
|
+
kind: 'keep';
|
|
676
|
+
orbitalName: string;
|
|
677
|
+
call: FactoryCallSite;
|
|
678
|
+
};
|
|
679
|
+
declare function diffFactoryCalls(prior: ReadonlyArray<FactoryCallSite>, next: ReadonlyArray<FactoryCallSite>): ReadonlyArray<CallSiteDiff>;
|
|
680
|
+
|
|
681
|
+
export { type CallSiteDiff, type DomainQuestion, type DomainQuestionAnswer, type DomainQuestionAnswers, type DomainQuestionInputType, type FactoryCallPlanMutation, type FactoryCallPlanMutationTemplate, type FactoryCallPlanState, type FactoryCallSite, type FactoryCallSiteParams, type FactoryConfigParam, type FactoryConfigTier, type FactoryEntitySignature, type FactoryPageSignature, type FactoryParamValue, type FactorySignature, type FactorySignatureCatalog, type FactorySignatureEntityField, type FactoryTraitSignature, type JsonSchema, type JsonSchemaType, type JsonValue, type OrbitalCallInput, type OwnershipOverlayEntry, type PresentationNavItem, type PresentationOverlay, type RuleOverlay, type RuleOverlayEntry, type SchemaFieldType, type TraitOverlay, type TraitOverlayEntry, type TraitOverlayListener, type TranslationBinding, type TranslationResult, type TranslationWarning, answerToMutations, answersToMutations, applyFactoryCallPlanMutation, deriveInputType, diffFactoryCalls, generateQuestions, translateOverlaysToParams };
|
package/dist/factory/index.js
CHANGED
|
@@ -1,3 +1,294 @@
|
|
|
1
|
+
// src/factory/questions/generate.ts
|
|
2
|
+
function generateQuestions(plan, catalog, ruleOverlay) {
|
|
3
|
+
const out = [];
|
|
4
|
+
const ruleCapabilities = collectRuleCapabilities(ruleOverlay);
|
|
5
|
+
for (const call of plan) {
|
|
6
|
+
const signature = findSignature(catalog, call.organism, call.orbital);
|
|
7
|
+
if (!signature) continue;
|
|
8
|
+
out.push(...configKeyQuestions(call, signature));
|
|
9
|
+
out.push(...capabilityQuestions(call, signature, ruleCapabilities));
|
|
10
|
+
}
|
|
11
|
+
return out;
|
|
12
|
+
}
|
|
13
|
+
function configKeyQuestions(call, signature) {
|
|
14
|
+
const out = [];
|
|
15
|
+
for (const trait of signature.traits) {
|
|
16
|
+
for (const param of trait.overridableConfigKeys) {
|
|
17
|
+
if (param.tier === "internal") continue;
|
|
18
|
+
if (isAlreadyCustomized(call, trait.name, param.key)) continue;
|
|
19
|
+
out.push(buildConfigKeyQuestion(call, trait, param));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return out;
|
|
23
|
+
}
|
|
24
|
+
function buildConfigKeyQuestion(call, trait, param) {
|
|
25
|
+
const label = param.label ?? humanizeKey(param.key);
|
|
26
|
+
const question = `${label}?`;
|
|
27
|
+
const reason = param.description ?? `Customizes "${param.key}" on the ${trait.name} trait. ` + (param.default !== void 0 ? `Default: ${stringifyDefault(param.default)}.` : `No default \u2014 leave blank to inherit the factory's behavior.`);
|
|
28
|
+
const out = {
|
|
29
|
+
id: `${call.orbital}.${trait.name}.${param.key}`,
|
|
30
|
+
orbitalName: call.orbital,
|
|
31
|
+
question,
|
|
32
|
+
reason,
|
|
33
|
+
inputType: deriveInputType(param),
|
|
34
|
+
mutationTemplate: {
|
|
35
|
+
kind: "set-trait-override-config",
|
|
36
|
+
orbitalName: call.orbital,
|
|
37
|
+
traitName: trait.name,
|
|
38
|
+
configKey: param.key
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
if (param.default !== void 0) {
|
|
42
|
+
out.defaultValue = param.default;
|
|
43
|
+
}
|
|
44
|
+
if (param.enumValues && param.enumValues.length > 0) {
|
|
45
|
+
out.suggestedAnswers = param.enumValues;
|
|
46
|
+
}
|
|
47
|
+
if (param.items) {
|
|
48
|
+
out.itemSchema = param.items;
|
|
49
|
+
}
|
|
50
|
+
if (param.properties) {
|
|
51
|
+
out.objectSchema = param.properties;
|
|
52
|
+
}
|
|
53
|
+
if (param.tier) {
|
|
54
|
+
out.tier = param.tier;
|
|
55
|
+
}
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
function isAlreadyCustomized(call, traitName, configKey) {
|
|
59
|
+
const override = call.params.traitOverrides?.[traitName];
|
|
60
|
+
if (!override?.config) return false;
|
|
61
|
+
return Object.prototype.hasOwnProperty.call(override.config, configKey);
|
|
62
|
+
}
|
|
63
|
+
function deriveInputType(param) {
|
|
64
|
+
if (param.type.startsWith("[")) {
|
|
65
|
+
if (param.items?.properties && Object.keys(param.items.properties).length > 0) {
|
|
66
|
+
return "listOfObjects";
|
|
67
|
+
}
|
|
68
|
+
if (param.enumValues && param.enumValues.length > 0) {
|
|
69
|
+
return "multiselect";
|
|
70
|
+
}
|
|
71
|
+
return "tagList";
|
|
72
|
+
}
|
|
73
|
+
if (param.type === "object") {
|
|
74
|
+
if (param.properties && Object.keys(param.properties).length > 0) {
|
|
75
|
+
return "objectForm";
|
|
76
|
+
}
|
|
77
|
+
return "text";
|
|
78
|
+
}
|
|
79
|
+
switch (param.type) {
|
|
80
|
+
case "number":
|
|
81
|
+
case "integer":
|
|
82
|
+
return "number";
|
|
83
|
+
case "boolean":
|
|
84
|
+
return "boolean";
|
|
85
|
+
case "enum":
|
|
86
|
+
return "enum";
|
|
87
|
+
case "string":
|
|
88
|
+
return param.enumValues && param.enumValues.length > 0 ? "enum" : "text";
|
|
89
|
+
default:
|
|
90
|
+
return "text";
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function humanizeKey(key) {
|
|
94
|
+
const parts = key.replace(/[-_]+/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").split(" ").filter((s) => s.length > 0);
|
|
95
|
+
return parts.map((p) => /^[A-Z]+$/.test(p) ? p : p.charAt(0).toUpperCase() + p.slice(1)).join(" ");
|
|
96
|
+
}
|
|
97
|
+
function stringifyDefault(v) {
|
|
98
|
+
if (typeof v === "string") return JSON.stringify(v);
|
|
99
|
+
if (typeof v === "number" || typeof v === "boolean") return String(v);
|
|
100
|
+
if (Array.isArray(v)) return `${v.length} item${v.length === 1 ? "" : "s"}`;
|
|
101
|
+
return "object";
|
|
102
|
+
}
|
|
103
|
+
function capabilityQuestions(call, signature, ruleCapabilities) {
|
|
104
|
+
const entityName = call.params.entityName ?? signature.entities[0]?.name ?? call.orbital;
|
|
105
|
+
const seen = /* @__PURE__ */ new Set();
|
|
106
|
+
const out = [];
|
|
107
|
+
for (const trait of signature.traits) {
|
|
108
|
+
for (const cap of trait.capabilities) {
|
|
109
|
+
if (seen.has(cap)) continue;
|
|
110
|
+
seen.add(cap);
|
|
111
|
+
if (ruleCapabilities.has(cap)) continue;
|
|
112
|
+
out.push({
|
|
113
|
+
id: `${call.orbital}.capability.${cap}`,
|
|
114
|
+
orbitalName: call.orbital,
|
|
115
|
+
question: capabilityPrompt(cap, entityName),
|
|
116
|
+
reason: capabilityReason(cap, trait),
|
|
117
|
+
capability: cap,
|
|
118
|
+
inputType: "capability",
|
|
119
|
+
mutationTemplate: {
|
|
120
|
+
kind: "set-rule",
|
|
121
|
+
capability: cap,
|
|
122
|
+
appliesTo: [entityName]
|
|
123
|
+
},
|
|
124
|
+
suggestedAnswers: ["yes", "skip"]
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return out;
|
|
129
|
+
}
|
|
130
|
+
function capabilityPrompt(capability, entity) {
|
|
131
|
+
return `Should "${capability}" apply to ${entity}?`;
|
|
132
|
+
}
|
|
133
|
+
function capabilityReason(capability, trait) {
|
|
134
|
+
return `The "${trait.name}" trait advertises capability "${capability}". Answer "yes" to wire a matching rule into the schema.`;
|
|
135
|
+
}
|
|
136
|
+
function findSignature(catalog, organism, orbital) {
|
|
137
|
+
return catalog.find((s) => s.organism === organism && s.orbital === orbital);
|
|
138
|
+
}
|
|
139
|
+
function collectRuleCapabilities(overlay) {
|
|
140
|
+
const out = /* @__PURE__ */ new Set();
|
|
141
|
+
for (const r of overlay?.rules ?? []) {
|
|
142
|
+
if (typeof r.capability === "string") out.add(r.capability);
|
|
143
|
+
}
|
|
144
|
+
return out;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// src/factory/questions/reducer.ts
|
|
148
|
+
var PERSISTENCE_VALUES = [
|
|
149
|
+
"persistent",
|
|
150
|
+
"runtime",
|
|
151
|
+
"singleton",
|
|
152
|
+
"instance",
|
|
153
|
+
"local"
|
|
154
|
+
];
|
|
155
|
+
function answersToMutations(answers, questions) {
|
|
156
|
+
const out = [];
|
|
157
|
+
for (const q of questions) {
|
|
158
|
+
const answer = answers[q.id];
|
|
159
|
+
if (answer === void 0 || answer === null) continue;
|
|
160
|
+
out.push(...answerToMutations(q.mutationTemplate, answer));
|
|
161
|
+
}
|
|
162
|
+
return out;
|
|
163
|
+
}
|
|
164
|
+
function answerToMutations(template, answer, _question) {
|
|
165
|
+
switch (template.kind) {
|
|
166
|
+
case "set-orbital-entity-name": {
|
|
167
|
+
if (typeof answer !== "string" || answer.length === 0) return [];
|
|
168
|
+
return [
|
|
169
|
+
{
|
|
170
|
+
kind: "set-orbital-entity-name",
|
|
171
|
+
orbitalName: template.orbitalName,
|
|
172
|
+
name: answer
|
|
173
|
+
}
|
|
174
|
+
];
|
|
175
|
+
}
|
|
176
|
+
case "set-orbital-entity-fields": {
|
|
177
|
+
if (!isEntityFieldArray(answer)) return [];
|
|
178
|
+
if (answer.length === 0) return [];
|
|
179
|
+
return [
|
|
180
|
+
{
|
|
181
|
+
kind: "set-orbital-entity-fields",
|
|
182
|
+
orbitalName: template.orbitalName,
|
|
183
|
+
fields: answer
|
|
184
|
+
}
|
|
185
|
+
];
|
|
186
|
+
}
|
|
187
|
+
case "set-orbital-persistence": {
|
|
188
|
+
if (!isPersistence(answer)) return [];
|
|
189
|
+
return [
|
|
190
|
+
{
|
|
191
|
+
kind: "set-orbital-persistence",
|
|
192
|
+
orbitalName: template.orbitalName,
|
|
193
|
+
persistence: answer
|
|
194
|
+
}
|
|
195
|
+
];
|
|
196
|
+
}
|
|
197
|
+
case "set-orbital-collection": {
|
|
198
|
+
if (typeof answer !== "string" || answer.length === 0) return [];
|
|
199
|
+
return [
|
|
200
|
+
{
|
|
201
|
+
kind: "set-orbital-collection",
|
|
202
|
+
orbitalName: template.orbitalName,
|
|
203
|
+
collection: answer
|
|
204
|
+
}
|
|
205
|
+
];
|
|
206
|
+
}
|
|
207
|
+
case "set-orbital-page-path": {
|
|
208
|
+
if (typeof answer !== "string" || answer.length === 0) return [];
|
|
209
|
+
return [
|
|
210
|
+
{
|
|
211
|
+
kind: "set-orbital-page-path",
|
|
212
|
+
orbitalName: template.orbitalName,
|
|
213
|
+
pageName: template.pageName,
|
|
214
|
+
path: answer
|
|
215
|
+
}
|
|
216
|
+
];
|
|
217
|
+
}
|
|
218
|
+
case "set-trait-override-config": {
|
|
219
|
+
if (!isFactoryParamValue(answer)) return [];
|
|
220
|
+
return [
|
|
221
|
+
{
|
|
222
|
+
kind: "set-trait-override-config",
|
|
223
|
+
orbitalName: template.orbitalName,
|
|
224
|
+
traitName: template.traitName,
|
|
225
|
+
key: template.configKey,
|
|
226
|
+
value: answer
|
|
227
|
+
}
|
|
228
|
+
];
|
|
229
|
+
}
|
|
230
|
+
case "add-extra-trait": {
|
|
231
|
+
if (!isTraitReference(answer)) return [];
|
|
232
|
+
return [
|
|
233
|
+
{
|
|
234
|
+
kind: "add-extra-trait",
|
|
235
|
+
orbitalName: template.orbitalName,
|
|
236
|
+
trait: answer
|
|
237
|
+
}
|
|
238
|
+
];
|
|
239
|
+
}
|
|
240
|
+
case "remove-extra-trait": {
|
|
241
|
+
if (typeof answer !== "string" || answer.length === 0) return [];
|
|
242
|
+
return [
|
|
243
|
+
{
|
|
244
|
+
kind: "remove-extra-trait",
|
|
245
|
+
orbitalName: template.orbitalName,
|
|
246
|
+
ref: answer
|
|
247
|
+
}
|
|
248
|
+
];
|
|
249
|
+
}
|
|
250
|
+
case "set-rule": {
|
|
251
|
+
if (typeof answer !== "string") return [];
|
|
252
|
+
if (answer !== "yes") return [];
|
|
253
|
+
const rule = {
|
|
254
|
+
id: `${template.capability}::${template.appliesTo.join(",")}`,
|
|
255
|
+
capability: template.capability,
|
|
256
|
+
description: `User accepted ${template.capability} for ${template.appliesTo.join(", ")}.`,
|
|
257
|
+
appliesTo: template.appliesTo
|
|
258
|
+
};
|
|
259
|
+
return [{ kind: "set-rule", rule }];
|
|
260
|
+
}
|
|
261
|
+
case "remove-rule": {
|
|
262
|
+
return [{ kind: "remove-rule", ruleId: template.ruleId }];
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
function isEntityFieldArray(a) {
|
|
267
|
+
if (!Array.isArray(a)) return false;
|
|
268
|
+
for (const x of a) {
|
|
269
|
+
if (typeof x !== "object" || x === null) return false;
|
|
270
|
+
if (!("name" in x) || !("type" in x)) return false;
|
|
271
|
+
if (typeof x.name !== "string" || typeof x.type !== "string") return false;
|
|
272
|
+
}
|
|
273
|
+
return true;
|
|
274
|
+
}
|
|
275
|
+
function isPersistence(a) {
|
|
276
|
+
return typeof a === "string" && PERSISTENCE_VALUES.includes(a);
|
|
277
|
+
}
|
|
278
|
+
function isFactoryParamValue(a) {
|
|
279
|
+
if (a === null) return false;
|
|
280
|
+
const t = typeof a;
|
|
281
|
+
if (t === "string" || t === "number" || t === "boolean") return true;
|
|
282
|
+
if (Array.isArray(a)) return true;
|
|
283
|
+
if (t === "object") return true;
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
286
|
+
function isTraitReference(a) {
|
|
287
|
+
if (typeof a !== "object" || a === null || Array.isArray(a)) return false;
|
|
288
|
+
if (!("ref" in a)) return false;
|
|
289
|
+
return typeof a.ref === "string";
|
|
290
|
+
}
|
|
291
|
+
|
|
1
292
|
// src/factory/translate.ts
|
|
2
293
|
function translateOverlaysToParams(binding, signature, presentation, ruleOverlay, traitOverlay, catalog) {
|
|
3
294
|
const warnings = [];
|
|
@@ -145,8 +436,9 @@ function mergeTraitOverride(traitName, entry, trait, params, warnings) {
|
|
|
145
436
|
}
|
|
146
437
|
function applyRuleOverlay(overlay, signature, binding, catalog, params, warnings) {
|
|
147
438
|
if (!overlay) return;
|
|
439
|
+
const matchableNames = collectMatchableEntityNames(signature, binding);
|
|
148
440
|
for (const rule of overlay.rules) {
|
|
149
|
-
if (!
|
|
441
|
+
if (!ruleAppliesToAnyName(rule, matchableNames)) continue;
|
|
150
442
|
if (!catalog) {
|
|
151
443
|
warnings.push({
|
|
152
444
|
field: `ruleOverlay.rules.${rule.id}`,
|
|
@@ -169,14 +461,25 @@ function applyRuleOverlay(overlay, signature, binding, catalog, params, warnings
|
|
|
169
461
|
}
|
|
170
462
|
if (overlay.ownership) {
|
|
171
463
|
for (const entry of overlay.ownership) {
|
|
172
|
-
if (entry.entity
|
|
464
|
+
if (!matchableNames.includes(entry.entity)) continue;
|
|
173
465
|
applyOwnership(entry, params, signature, catalog, warnings);
|
|
174
466
|
}
|
|
175
467
|
}
|
|
176
468
|
}
|
|
177
|
-
function
|
|
469
|
+
function collectMatchableEntityNames(signature, binding) {
|
|
470
|
+
const out = /* @__PURE__ */ new Set();
|
|
471
|
+
if (binding.entityName.length > 0) out.add(binding.entityName);
|
|
472
|
+
for (const ent of signature.entities) {
|
|
473
|
+
if (typeof ent.name === "string" && ent.name.length > 0) out.add(ent.name);
|
|
474
|
+
}
|
|
475
|
+
return [...out];
|
|
476
|
+
}
|
|
477
|
+
function ruleAppliesToAnyName(rule, matchableNames) {
|
|
178
478
|
if (rule.appliesTo.length === 0) return true;
|
|
179
|
-
|
|
479
|
+
for (const n of rule.appliesTo) {
|
|
480
|
+
if (matchableNames.includes(n)) return true;
|
|
481
|
+
}
|
|
482
|
+
return false;
|
|
180
483
|
}
|
|
181
484
|
function findTraitByCapability(catalog, capability) {
|
|
182
485
|
for (const sig of catalog) {
|
|
@@ -433,6 +736,6 @@ function patchOrbital(state, orbitalName, patch) {
|
|
|
433
736
|
};
|
|
434
737
|
}
|
|
435
738
|
|
|
436
|
-
export { applyFactoryCallPlanMutation, diffFactoryCalls, translateOverlaysToParams };
|
|
739
|
+
export { answerToMutations, answersToMutations, applyFactoryCallPlanMutation, deriveInputType, diffFactoryCalls, generateQuestions, translateOverlaysToParams };
|
|
437
740
|
//# sourceMappingURL=index.js.map
|
|
438
741
|
//# sourceMappingURL=index.js.map
|