@intent-framework/core 0.1.0-alpha.0 → 0.1.0-alpha.2

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/README.md ADDED
@@ -0,0 +1,67 @@
1
+ # @intent-framework/core
2
+
3
+ Platformless semantic graph and runtime for Intent applications.
4
+
5
+ ## Install
6
+
7
+ ```sh
8
+ pnpm add @intent-framework/core@0.1.0-alpha.1
9
+ ```
10
+
11
+ ```sh
12
+ npm install @intent-framework/core@0.1.0-alpha.1
13
+ ```
14
+
15
+ ## What it provides
16
+
17
+ - `screen()` — define a semantic interaction space
18
+ - `$.state.text()` / `$.state.boolean()` / `$.state.choice()` — reactive state
19
+ - `$.ask()` — user-facing question with validation
20
+ - `$.act()` — executable action with conditions, lifecycle, and feedback
21
+ - `$.resource()` — async state with load/reload lifecycle
22
+ - `$.surface()` — named containment surface
23
+ - `createScreenRuntime()` — runtime that owns screen state and resources
24
+ - `inspectScreen()` — semantic graph snapshot with diagnostics
25
+ - Condition and signal primitives
26
+
27
+ ## Minimal example
28
+
29
+ ```ts
30
+ import { screen, inspectScreen } from "@intent-framework/core"
31
+
32
+ const InviteMember = screen("InviteMember", $ => {
33
+ const email = $.state.text("email")
34
+
35
+ const emailAsk = $.ask("Email", email)
36
+ .required("Email is required")
37
+ .validate(value => value.includes("@") ? true : "Enter a valid email")
38
+
39
+ const invite = $.act("Invite member")
40
+ .primary()
41
+ .when(emailAsk.valid, "Enter a valid email first")
42
+ .does(() => {
43
+ console.log("invite", email.value)
44
+ })
45
+
46
+ $.surface("main").contains(emailAsk, invite)
47
+ })
48
+
49
+ const graph = inspectScreen(InviteMember)
50
+ console.log(graph.diagnostics)
51
+ ```
52
+
53
+ ## Where this fits
54
+
55
+ Core defines the product graph. It has no DOM, React, Node, or framework dependencies. Renderers (`@intent-framework/dom`), the router (`@intent-framework/router`), and testing (`@intent-framework/testing`) all build on core.
56
+
57
+ ## Learn more
58
+
59
+ - [Root README](../../README.md) — project overview and philosophy
60
+ - [Quickstart](../../docs/Quickstart.md) — step-by-step guide
61
+ - [Inspect Screen and Diagnostics Guide](../../docs/Inspect-Screen.md) — graph inspection and diagnostics
62
+ - [Resources Guide](../../docs/Resources.md) — resource lifecycle and runtime scoping
63
+ - [MVP Checkpoint](../../docs/MVP-Checkpoint.md) — current implementation boundaries
64
+
65
+ ## Status
66
+
67
+ Experimental alpha. Version `0.1.0-alpha.1`. APIs may change. Not recommended for production use.
package/dist/graph.d.ts CHANGED
@@ -7,11 +7,14 @@ export type GraphDiagnostic = {
7
7
  code: string;
8
8
  message: string;
9
9
  nodeId?: string;
10
+ semanticNodeId?: string;
10
11
  };
11
12
  export type InspectedScreen = {
12
13
  name: string;
14
+ semanticId: string;
13
15
  asks: Array<{
14
16
  id: string;
17
+ semanticId: string;
15
18
  label: string;
16
19
  kind: string;
17
20
  required: boolean;
@@ -21,6 +24,7 @@ export type InspectedScreen = {
21
24
  }>;
22
25
  acts: Array<{
23
26
  id: string;
27
+ semanticId: string;
24
28
  label: string;
25
29
  primary: boolean;
26
30
  enabled: boolean;
@@ -31,16 +35,19 @@ export type InspectedScreen = {
31
35
  }>;
32
36
  flows: Array<{
33
37
  id: string;
38
+ semanticId: string;
34
39
  name: string;
35
40
  stepCount: number;
36
41
  }>;
37
42
  surfaces: Array<{
38
43
  id: string;
44
+ semanticId: string;
39
45
  name: string;
40
46
  itemCount: number;
41
47
  }>;
42
48
  resources: Array<{
43
49
  id: string;
50
+ semanticId: string;
44
51
  name: string;
45
52
  status: string;
46
53
  hasValue: boolean;
package/dist/index.js CHANGED
@@ -351,6 +351,11 @@ function getSurfaces() {
351
351
  function getResources() {
352
352
  return resourceMap;
353
353
  }
354
+ function nextSuffix(baseId, exists) {
355
+ let counter = 2;
356
+ while (exists(`${baseId}-${counter}`)) counter++;
357
+ return `${baseId}-${counter}`;
358
+ }
354
359
 
355
360
  //#endregion
356
361
  //#region src/ask.ts
@@ -408,7 +413,9 @@ function computeAskError(node) {
408
413
  var AskBuilder = class {
409
414
  node;
410
415
  constructor(label, stateRef) {
411
- const id = `ask_${label.toLowerCase().replace(/\s+/g, "_")}`;
416
+ const baseId = `ask_${label.toLowerCase().replace(/\s+/g, "_")}`;
417
+ const existing = getAsks();
418
+ const id = existing.has(baseId) ? nextSuffix(baseId, (id$1) => existing.has(id$1)) : baseId;
412
419
  const onChange = stateRef.onChange;
413
420
  const subscribeToState = onChange ? (fn) => onChange((_v) => fn()) : void 0;
414
421
  this.node = createAskNode(id, label, stateRef, subscribeToState);
@@ -520,7 +527,9 @@ async function executeAct(node, context, notify) {
520
527
  var ActBuilder = class {
521
528
  node;
522
529
  constructor(label) {
523
- const id = `act_${label.toLowerCase().replace(/\s+/g, "_")}`;
530
+ const baseId = `act_${label.toLowerCase().replace(/\s+/g, "_")}`;
531
+ const existing = getActs();
532
+ const id = existing.has(baseId) ? nextSuffix(baseId, (id$1) => existing.has(id$1)) : baseId;
524
533
  this.node = createActNode(id, label, [], null, void 0, false);
525
534
  registerActNode(this.node);
526
535
  }
@@ -578,7 +587,9 @@ var ActBuilder = class {
578
587
  var FlowBuilder = class {
579
588
  node;
580
589
  constructor(name) {
581
- const id = `flow_${name}`;
590
+ const baseId = `flow_${name}`;
591
+ const existing = getFlows();
592
+ const id = existing.has(baseId) ? nextSuffix(baseId, (id$1) => existing.has(id$1)) : baseId;
582
593
  this.node = {
583
594
  id,
584
595
  name,
@@ -623,7 +634,9 @@ var FlowBuilder = class {
623
634
  var SurfaceBuilder = class {
624
635
  node;
625
636
  constructor(name) {
626
- const id = `surface_${name}`;
637
+ const baseId = `surface_${name}`;
638
+ const existing = getSurfaces();
639
+ const id = existing.has(baseId) ? nextSuffix(baseId, (id$1) => existing.has(id$1)) : baseId;
627
640
  this.node = {
628
641
  id,
629
642
  name,
@@ -662,7 +675,8 @@ function screen(name, fn) {
662
675
  flow: (n) => new FlowBuilder(n),
663
676
  surface: (n) => new SurfaceBuilder(n),
664
677
  resource: (n, config) => {
665
- const id = `resource_${n}`;
678
+ const baseId = `resource_${n}`;
679
+ const id = configs.some((c) => c.id === baseId) ? nextSuffix(baseId, (id$1) => configs.some((c) => c.id === id$1)) : baseId;
666
680
  const ref = new ResourceRef(id, n, config.load, config.autoLoad ?? true);
667
681
  configs.push({
668
682
  id,
@@ -691,6 +705,28 @@ function screen(name, fn) {
691
705
 
692
706
  //#endregion
693
707
  //#region src/graph.ts
708
+ const NODE_KINDS = {
709
+ ask: "ask",
710
+ act: "action",
711
+ flow: "flow",
712
+ surface: "surface",
713
+ resource: "resource"
714
+ };
715
+ function slugify(text) {
716
+ return text.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
717
+ }
718
+ function createSemanticIdFactory(kind) {
719
+ const prefix = NODE_KINDS[kind] ?? kind;
720
+ const used = /* @__PURE__ */ new Map();
721
+ let unnamed = 0;
722
+ return (source) => {
723
+ const slug = slugify(source);
724
+ const base = slug.length > 0 ? slug : String(++unnamed);
725
+ const count = used.get(base) ?? 0;
726
+ used.set(base, count + 1);
727
+ return count === 0 ? `${prefix}:${base}` : `${prefix}:${base}-${count + 1}`;
728
+ };
729
+ }
694
730
  function computeDiagnostics(screenDef) {
695
731
  const diagnostics = [];
696
732
  const primaryActions = screenDef.acts.filter((a) => a.primary);
@@ -725,13 +761,44 @@ function computeDiagnostics(screenDef) {
725
761
  message: "Action is defined but not included in any surface.",
726
762
  nodeId: act.id
727
763
  });
764
+ if (screenDef.flows.length > 0) {
765
+ const flowNodeIds = /* @__PURE__ */ new Set();
766
+ for (const flow of screenDef.flows) for (const step of flow.steps) flowNodeIds.add(step.node.id);
767
+ for (const ask of screenDef.asks) if (surfacedNodeIds.has(ask.id) && !flowNodeIds.has(ask.id)) diagnostics.push({
768
+ severity: "info",
769
+ code: "surfaced-node-not-in-any-flow",
770
+ message: `"${ask.label}" is surfaced but not referenced in any flow.`,
771
+ nodeId: ask.id
772
+ });
773
+ for (const act of screenDef.acts) if (surfacedNodeIds.has(act.id) && !flowNodeIds.has(act.id)) diagnostics.push({
774
+ severity: "info",
775
+ code: "surfaced-node-not-in-any-flow",
776
+ message: `"${act.label}" is surfaced but not referenced in any flow.`,
777
+ nodeId: act.id
778
+ });
779
+ }
728
780
  return diagnostics;
729
781
  }
730
782
  function inspectScreen(screenDef, runtimeResources) {
783
+ const diagnostics = computeDiagnostics(screenDef);
784
+ const askIds = createSemanticIdFactory("ask");
785
+ const actIds = createSemanticIdFactory("act");
786
+ const flowIds = createSemanticIdFactory("flow");
787
+ const surfaceIds = createSemanticIdFactory("surface");
788
+ const resourceIds = createSemanticIdFactory("resource");
789
+ const idToSemantic = /* @__PURE__ */ new Map();
790
+ for (const a of screenDef.asks) idToSemantic.set(a.id, askIds(a.label));
791
+ for (const a of screenDef.acts) idToSemantic.set(a.id, actIds(a.label));
792
+ const augmentedDiagnostics = diagnostics.map((d) => ({
793
+ ...d,
794
+ semanticNodeId: d.nodeId ? idToSemantic.get(d.nodeId) : void 0
795
+ }));
731
796
  return {
732
797
  name: screenDef.name,
798
+ semanticId: `screen:${slugify(screenDef.name)}`,
733
799
  asks: screenDef.asks.map((a) => ({
734
800
  id: a.id,
801
+ semanticId: idToSemantic.get(a.id),
735
802
  label: a.label,
736
803
  kind: a.kind,
737
804
  required: a.required,
@@ -741,6 +808,7 @@ function inspectScreen(screenDef, runtimeResources) {
741
808
  })),
742
809
  acts: screenDef.acts.map((a) => ({
743
810
  id: a.id,
811
+ semanticId: idToSemantic.get(a.id),
744
812
  label: a.label,
745
813
  primary: a.primary,
746
814
  enabled: a.enabled.current,
@@ -751,23 +819,26 @@ function inspectScreen(screenDef, runtimeResources) {
751
819
  })),
752
820
  flows: screenDef.flows.map((f) => ({
753
821
  id: f.id,
822
+ semanticId: flowIds(f.name),
754
823
  name: f.name,
755
824
  stepCount: f.steps.length
756
825
  })),
757
826
  surfaces: screenDef.surfaces.map((s) => ({
758
827
  id: s.id,
828
+ semanticId: surfaceIds(s.name),
759
829
  name: s.name,
760
830
  itemCount: s.items.length
761
831
  })),
762
832
  resources: (runtimeResources ?? []).map((r) => ({
763
833
  id: r.id,
834
+ semanticId: resourceIds(r.name),
764
835
  name: r.name,
765
836
  status: r.status,
766
837
  hasValue: r.value !== void 0,
767
838
  stale: r.stale.current,
768
839
  error: r.status === "failed" ? r.error instanceof Error ? r.error.message : String(r.error) : void 0
769
840
  })),
770
- diagnostics: computeDiagnostics(screenDef)
841
+ diagnostics: augmentedDiagnostics
771
842
  };
772
843
  }
773
844
 
@@ -23,3 +23,4 @@ export declare function getActs(): Map<string, ActNode<any>>;
23
23
  export declare function getFlows(): Map<string, FlowNode>;
24
24
  export declare function getSurfaces(): Map<string, SurfaceNode>;
25
25
  export declare function getResources(): Map<string, AnyResourceNode>;
26
+ export declare function nextSuffix(baseId: string, exists: (id: string) => boolean): string;
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.1.0-alpha.0",
6
+ "version": "0.1.0-alpha.2",
7
7
  "description": "Platformless semantic graph and runtime for Intent applications",
8
8
  "license": "MIT",
9
9
  "repository": {