@almadar/workspace 0.1.3 → 0.1.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/types.d.ts CHANGED
@@ -7,6 +7,7 @@
7
7
  * @packageDocumentation
8
8
  */
9
9
  import type { JsonObject, JsonValue } from '@almadar/core';
10
+ import type { EmbedderPort, WorkspaceIndex } from './workspace-index/types.js';
10
11
  /**
11
12
  * The only extension point. Consumers register one observer via
12
13
  * `service.subscribe(observer)`; every workspace write fans out
@@ -152,6 +153,13 @@ export interface OpenWorkspaceOptions {
152
153
  backend?: 'local' | 'memory';
153
154
  /** Optional project name baked into the mint-time schema template. */
154
155
  projectName?: string;
156
+ /**
157
+ * Embedder used by the workspace index (R-10 + R-8 coercion).
158
+ * Defaults to `createDefaultEmbedder()` (lazy `@almadar/llm`
159
+ * EmbeddingClient on openrouter) when omitted; tests inject a
160
+ * deterministic mock.
161
+ */
162
+ embedder?: EmbedderPort;
155
163
  }
156
164
  /**
157
165
  * The single workspace I/O surface. Every consumer (rabit, apps/builder,
@@ -218,5 +226,6 @@ export interface WorkspaceService {
218
226
  pullIfLinked(): Promise<boolean>;
219
227
  gitStatus(): Promise<GitStatusInfo>;
220
228
  subscribe(observer: WorkspaceObserver): () => void;
229
+ readonly index: WorkspaceIndex;
221
230
  dispose(): Promise<void>;
222
231
  }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Cosine similarity. Defensive: zero-vector and length-mismatch
3
+ * return 0 instead of throwing or returning NaN — a malformed manifest
4
+ * entry shouldn't crash the index.
5
+ *
6
+ * @packageDocumentation
7
+ */
8
+ export declare function cosineSimilarity(a: readonly number[], b: readonly number[]): number;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Embedder port + production binding to `@almadar/llm`'s
3
+ * `EmbeddingClient`.
4
+ *
5
+ * The port lets tests inject a deterministic mock without paying for
6
+ * real network calls. Production wiring constructs an `EmbeddingClient`
7
+ * once per workspace open and reuses it for every bake.
8
+ *
9
+ * @packageDocumentation
10
+ */
11
+ import type { EmbedderPort } from './types.js';
12
+ /**
13
+ * Production embedder backed by `@almadar/llm`'s `EmbeddingClient` on
14
+ * the openrouter provider (bge-base-en-v1.5, 768-d) — same model the
15
+ * catalog narrower uses, so vectors share an embedding space across
16
+ * the workspace + catalog surfaces.
17
+ *
18
+ * Construction is lazy: the `EmbeddingClient` (and its env-key check)
19
+ * only fires when `embedBatch` is first called. A workspace that has
20
+ * no orbitals to bake never pays the cost or trips the missing-key
21
+ * error — useful for the memory-backed CLI runner that opens fresh
22
+ * workspaces without an API key.
23
+ */
24
+ export declare function createDefaultEmbedder(): EmbedderPort;
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Fingerprint composition — pure functions that turn typed spec state
3
+ * into the short text strings the embedder hashes.
4
+ *
5
+ * Per the doc, orbital identity vectors stay small (~50-100 tokens)
6
+ * so they're stable under spec edits, while content/extraTrait
7
+ * fingerprints can grow more.
8
+ *
9
+ * @packageDocumentation
10
+ */
11
+ import type { JsonObject } from '@almadar/core';
12
+ /**
13
+ * Compose the orbital identity fingerprint from a spec.
14
+ *
15
+ * Pulls these typed fields off the spec (all optional — when missing
16
+ * the segment is omitted, never `undefined` interpolated):
17
+ * - `orbitalName` (top-level on spec)
18
+ * - `params.entityName`
19
+ * - `organism`
20
+ * - `params.extraTraits[].name` (or trait-name tail of ref if no
21
+ * name) — included so renames + extras enter the identity surface
22
+ *
23
+ * The output is a single string with ` · ` separators; the embedder
24
+ * normalizes whitespace on its end.
25
+ */
26
+ export declare function composeOrbitalIdentityFingerprint(spec: JsonObject): string;
27
+ /**
28
+ * Compose the per-extraTraits[] entry fingerprint.
29
+ *
30
+ * Used both at bake time (for the sidecar's extraTraitIdentities[])
31
+ * and at query time (for the LLM-emitted entry being coerced). The
32
+ * shape MUST be identical on both sides so cosine geometry is
33
+ * meaningful — same fields in the same order.
34
+ */
35
+ export declare function composeExtraTraitIdentityFingerprint(emit: {
36
+ ref: string;
37
+ name?: string;
38
+ linkedEntity?: string;
39
+ }): string;
40
+ /**
41
+ * Derive the alias an entry would canonically be referenced by. Used by
42
+ * the sidecar baker to store the `alias` field next to each extraTrait
43
+ * identity vector — also the value `resolveTraitRef` returns as
44
+ * `coercedTo` when a match fires.
45
+ */
46
+ export declare function deriveExtraTraitAlias(emit: {
47
+ ref: string;
48
+ name?: string;
49
+ }): string;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * `WorkspaceIndexImpl` — the runtime carrying the identity vectors per
3
+ * orbital + per extraTraits[] entry. Wires into the workspace's
4
+ * `SinkManager` to re-bake on each `kind: 'spec'` write; exposes the
5
+ * coercion methods `resolveOrbitalName` + `resolveTraitRef`.
6
+ *
7
+ * @packageDocumentation
8
+ */
9
+ import type { JsonObject } from '@almadar/core';
10
+ import type { WorkspaceBackend } from '../internal/types.js';
11
+ import type { SinkManager } from '../internal/sink-manager.js';
12
+ import type { EmbedderPort, ResolveOptions, ResolveResult, TraitRefEmit, WorkspaceIndex, WorkspaceIndexStats } from './types.js';
13
+ export interface WorkspaceIndexDeps {
14
+ workDir: string;
15
+ backend: WorkspaceBackend;
16
+ sinks: SinkManager;
17
+ embedder: EmbedderPort;
18
+ listOrbitals: () => string[];
19
+ readSpec: (orbital: string) => JsonObject | null;
20
+ }
21
+ export declare class WorkspaceIndexImpl implements WorkspaceIndex {
22
+ private readonly deps;
23
+ private readonly entries;
24
+ /** Serialized re-bake per orbital so concurrent writeSpec events don't race. */
25
+ private readonly bakeQueue;
26
+ /** Names whose checksum doesn't match the current spec — surfaced via stats. */
27
+ private readonly stale;
28
+ constructor(deps: WorkspaceIndexDeps);
29
+ warm(): Promise<void>;
30
+ resolveOrbitalName(name: string, opts?: ResolveOptions): Promise<ResolveResult>;
31
+ resolveTraitRef(emit: TraitRefEmit, orbitalContext: {
32
+ orbitalName: string;
33
+ }, opts?: ResolveOptions): Promise<ResolveResult>;
34
+ stats(): WorkspaceIndexStats;
35
+ private rebakeOrbital;
36
+ private enqueueBake;
37
+ private bakeAndPersist;
38
+ private embedOne;
39
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Public surface of the workspace index module. Re-exported from the
3
+ * top-level `@almadar/workspace` barrel.
4
+ *
5
+ * @packageDocumentation
6
+ */
7
+ export type { EmbedderPort, ExtraTraitIdentity, OrbitalIndexEntry, ResolveOptions, ResolveResult, TraitRefEmit, WorkspaceIndex, WorkspaceIndexStats, } from './types.js';
8
+ export { DEFAULT_COERCION_THRESHOLD, WORKSPACE_INDEX_SCHEMA_VERSION, } from './types.js';
9
+ export { WorkspaceIndexImpl } from './index-impl.js';
10
+ export type { WorkspaceIndexDeps } from './index-impl.js';
11
+ export { createDefaultEmbedder } from './embedder.js';
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Sidecar I/O for per-orbital index entries.
3
+ *
4
+ * Reuses the workspace package's existing typed file helpers
5
+ * (`readJsonFile` + `writeJsonFile`) and path layout
6
+ * (`orbitalSessionFile`). No parallel file plumbing; no parallel path
7
+ * math.
8
+ *
9
+ * @packageDocumentation
10
+ */
11
+ import type { JsonObject } from '@almadar/core';
12
+ import type { WorkspaceBackend } from '../internal/types.js';
13
+ import type { OrbitalIndexEntry } from './types.js';
14
+ export declare function sidecarPath(workDir: string, orbital: string): string;
15
+ export declare function readSidecar(backend: WorkspaceBackend, workDir: string, orbital: string): Promise<OrbitalIndexEntry | null>;
16
+ export declare function writeSidecar(backend: WorkspaceBackend, workDir: string, orbital: string, entry: OrbitalIndexEntry): Promise<void>;
17
+ /**
18
+ * SHA-256 over the JSON-stringified spec — the same content the
19
+ * sidecar's embeddings were computed from. Used to detect stale
20
+ * sidecars (spec changed since last bake → re-bake).
21
+ */
22
+ export declare function checksumSpec(spec: JsonObject): string;
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Public types for the workspace index — Phase A of
3
+ * `docs/Almadar_Workspace_Index.md`. Identity vectors per orbital and
4
+ * per extraTraits[] entry, used for semantic coercion of LLM-emitted
5
+ * names back to canonical entities (R-10 orbital name drift + R-8
6
+ * duplicate-ref trait drift).
7
+ *
8
+ * @packageDocumentation
9
+ */
10
+ import type { JsonObject } from '@almadar/core';
11
+ /** Schema version baked into every sidecar; mismatch triggers re-bake. */
12
+ export declare const WORKSPACE_INDEX_SCHEMA_VERSION: 1;
13
+ /** Default coercion threshold per the doc's locked decision. */
14
+ export declare const DEFAULT_COERCION_THRESHOLD: 0.85;
15
+ /**
16
+ * Per-extraTraits[] entry identity vector. Baked alongside the orbital
17
+ * sidecar whenever the orbital's spec.json is written.
18
+ */
19
+ export interface ExtraTraitIdentity {
20
+ /** Canonical ref of this entry (the key for coercion match). */
21
+ ref: string;
22
+ /** Entry's `name` rename, or trait-name tail of ref if no rename. */
23
+ alias: string;
24
+ /** Fingerprint text actually embedded — kept for debugging. */
25
+ fingerprint: string;
26
+ /** 768-d vector from bge-base-en-v1.5 (or whatever model the embedder uses). */
27
+ vector: readonly number[];
28
+ }
29
+ /**
30
+ * Per-orbital sidecar contents, persisted at
31
+ * `.almadar/sessions/<Orbital>/index.json` after every spec write.
32
+ */
33
+ export interface OrbitalIndexEntry {
34
+ schemaVersion: typeof WORKSPACE_INDEX_SCHEMA_VERSION;
35
+ /** sha256 of the spec.json that produced this sidecar. */
36
+ specChecksum: string;
37
+ /** Orbital-level identity vector. */
38
+ identityVector: readonly number[];
39
+ /** Fingerprint text for the orbital identity vector — kept for debugging. */
40
+ identityFingerprint: string;
41
+ /** One per current `extraTraits[]` entry on this orbital. */
42
+ extraTraitIdentities: readonly ExtraTraitIdentity[];
43
+ /** Epoch ms at bake time — debugging only. */
44
+ bakedAt: number;
45
+ }
46
+ /**
47
+ * Input to `resolveTraitRef` — the LLM's emitted extraTraits entry,
48
+ * narrowed to the fields the coercion needs to see. Mirrors the
49
+ * `RawExtraTraitRef` shape rabit's analyzer constructs.
50
+ */
51
+ export interface TraitRefEmit {
52
+ ref: string;
53
+ name?: string;
54
+ linkedEntity?: string;
55
+ }
56
+ /**
57
+ * Common result shape for both `resolveOrbitalName` and
58
+ * `resolveTraitRef`. `coercedTo` is null when no existing entity
59
+ * exceeded the threshold — the emit is genuinely new.
60
+ */
61
+ export interface ResolveResult {
62
+ coercedTo: string | null;
63
+ similarity: number;
64
+ method: 'identity-vector';
65
+ }
66
+ /**
67
+ * Options for both `resolveOrbitalName` and `resolveTraitRef`. The
68
+ * default threshold (0.85) lives in `DEFAULT_COERCION_THRESHOLD`.
69
+ */
70
+ export interface ResolveOptions {
71
+ threshold?: number;
72
+ }
73
+ /**
74
+ * Stats surface for diagnostics. Cheap to compute (read in-memory
75
+ * state).
76
+ */
77
+ export interface WorkspaceIndexStats {
78
+ orbitalCount: number;
79
+ extraTraitIdentityCount: number;
80
+ staleOrbitals: readonly string[];
81
+ }
82
+ /**
83
+ * Public surface exposed via `WorkspaceService.index`.
84
+ */
85
+ export interface WorkspaceIndex {
86
+ /**
87
+ * Walk every existing orbital, bake any missing or stale sidecars.
88
+ * Called synchronously by `openWorkspace` (strict cold start per the
89
+ * doc decision) so this is the authoritative point past which all
90
+ * subsequent queries return real data. Idempotent — re-baking an
91
+ * already-warm sidecar is a no-op (checksum matches).
92
+ */
93
+ warm(): Promise<void>;
94
+ /**
95
+ * R-10 coercion. Embed `name`, cosine-match against every orbital's
96
+ * identity vector, return the best match if it exceeds the
97
+ * threshold. Returns `{ coercedTo: null, ... }` when below threshold
98
+ * or when no orbitals exist in the workspace.
99
+ */
100
+ resolveOrbitalName(name: string, opts?: ResolveOptions): Promise<ResolveResult>;
101
+ /**
102
+ * R-8 coercion. Embed an LLM-emitted trait emit, cosine-match against
103
+ * the existing `extraTraits[]` identity vectors on the named orbital.
104
+ * Returns the existing entry's `ref` (or its `name` rename) as
105
+ * `coercedTo` when match exceeds threshold; null when the emit is a
106
+ * genuinely new addition.
107
+ *
108
+ * Scoped per orbital — the same trait import on two different orbitals
109
+ * is structurally legitimate, so we don't cross-coerce.
110
+ */
111
+ resolveTraitRef(emit: TraitRefEmit, orbitalContext: {
112
+ orbitalName: string;
113
+ }, opts?: ResolveOptions): Promise<ResolveResult>;
114
+ /** Diagnostics surface. */
115
+ stats(): WorkspaceIndexStats;
116
+ }
117
+ /**
118
+ * Pluggable embedder — production wires `@almadar/llm`'s
119
+ * `EmbeddingClient`; tests inject a deterministic mock.
120
+ */
121
+ export interface EmbedderPort {
122
+ /** Embed a batch of texts; returned vectors are in input order. */
123
+ embedBatch(texts: readonly string[]): Promise<{
124
+ embeddings: readonly (readonly number[])[];
125
+ }>;
126
+ }
127
+ /**
128
+ * JSON shape persisted at `.almadar/sessions/<Orbital>/index.json`.
129
+ * Mirrors `OrbitalIndexEntry` but with the JsonObject upper-bound the
130
+ * workspace's writeJsonFile helper requires.
131
+ */
132
+ export type OrbitalIndexSidecar = OrbitalIndexEntry & JsonObject;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@almadar/workspace",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Storage-agnostic workspace primitives shared by Almadar consumers. One service, six exports, hidden paths, single observer. See docs/Almadar_Workspace.md.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -26,6 +26,7 @@
26
26
  "license": "BUSL-1.1",
27
27
  "dependencies": {
28
28
  "@almadar/core": "^8.8.0",
29
+ "@almadar/llm": "^2.16.0",
29
30
  "typescript": "^5.4.0"
30
31
  },
31
32
  "devDependencies": {