@hamak/smart-data-dico 1.8.2 → 1.9.0
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
CHANGED
|
@@ -321,6 +321,89 @@ Set via `PROFILE` environment variable:
|
|
|
321
321
|
| **team** | Basic/JWT | Remote | Small team sharing a repo |
|
|
322
322
|
| **server** | Auth0/SSO | Remote | Organization-wide deployment |
|
|
323
323
|
|
|
324
|
+
## AI Assistance
|
|
325
|
+
|
|
326
|
+
The dictionary ships with three AI-aware surfaces, all optional. They share one set of credentials stored at `~/.dico-app/dico-app.json` (mode 0600 — never checked into git):
|
|
327
|
+
|
|
328
|
+
| Surface | What it is | Where to find it |
|
|
329
|
+
|---|---|---|
|
|
330
|
+
| **In-app AI chat panel** | Sidebar chat that grounds against the live dictionary — list packages, describe entities, generate docs, propose edits | Toolbar button **AI Assistant** in the running web app |
|
|
331
|
+
| **Slash commands** | Built-in chat shortcuts (`/list`, `/quality`, `/describe`, `/create`, `/relate`, `/export`, `/diagram`, `/help`) + your own saved prompts | Type `/` in the chat composer |
|
|
332
|
+
| **MCP server** (`dico-mcp`) | A [Model Context Protocol](https://modelcontextprotocol.io) stdio server that exposes the same operations to external clients — Claude Desktop, Cursor, Roo Code, Claude Code | `npx @hamak/smart-data-dico-mcp` or `node bin/dico-mcp.js` from source |
|
|
333
|
+
|
|
334
|
+
### Configure the in-app chat (one-time)
|
|
335
|
+
|
|
336
|
+
1. Start the app (Option A, B, or C above).
|
|
337
|
+
2. Open **Settings → AI** (or click the AI Assistant button → ⚙ icon).
|
|
338
|
+
3. Pick a **provider** (`anthropic`, `openai`, or `openai-compatible`), paste an API key, and choose a **model** (e.g. `claude-opus-4-7`, `gpt-4o`, or any model your `openai-compatible` endpoint exposes).
|
|
339
|
+
4. Save. The config writes to `~/.dico-app/dico-app.json` with mode `0600`; no env vars are required.
|
|
340
|
+
|
|
341
|
+
Alternatively, if you set `ANTHROPIC_API_KEY` or `OPENAI_API_KEY` before launching the server, the provider is auto-detected for the first session — but the **Settings dialog is the source of truth**; saving overrides env-var defaults.
|
|
342
|
+
|
|
343
|
+
Conversations and saved prompts live under `~/.dico-app/storage/` as JSON files — portable, diffable, deletable.
|
|
344
|
+
|
|
345
|
+
### Slash commands
|
|
346
|
+
|
|
347
|
+
| Command | What it does |
|
|
348
|
+
|---|---|
|
|
349
|
+
| `/help` | List all available slash commands |
|
|
350
|
+
| `/list` | List every package and its entity count |
|
|
351
|
+
| `/describe` | Describe the current entity in detail (uses page context) |
|
|
352
|
+
| `/quality` | Quality review of the current package (severity-grouped findings) |
|
|
353
|
+
| `/create` | Skeleton: create a new entity with suggested attributes |
|
|
354
|
+
| `/relate` | Skeleton: create a relationship between two entities |
|
|
355
|
+
| `/export` | Generate Markdown documentation for the current package |
|
|
356
|
+
| `/diagram` | Navigate to the organization diagram |
|
|
357
|
+
|
|
358
|
+
The `pageContext` placeholder is filled automatically from the current route (entity / package / etc.). User-saved prompts (via **Prompts** tab in the chat panel) appear in the same slash-command picker.
|
|
359
|
+
|
|
360
|
+
### MCP server (`dico-mcp`)
|
|
361
|
+
|
|
362
|
+
The MCP server reuses the same backend services as the web app — it's a different **transport** for the same operations, not a duplicate code path. You can run the web UI and the MCP server side-by-side against the same project folder.
|
|
363
|
+
|
|
364
|
+
**Tools exposed:** `listPackages`, `listEntities`, `getEntityDetails`, `createEntity`, `createRelationship`, `listStereotypes`, `listRoutes`.
|
|
365
|
+
|
|
366
|
+
**Launch (from npm):**
|
|
367
|
+
|
|
368
|
+
```bash
|
|
369
|
+
# stdio server — talks JSON-RPC over stdin/stdout
|
|
370
|
+
npx @hamak/smart-data-dico --data-dir /path/to/your/project # web UI
|
|
371
|
+
# (the MCP entrypoint is bin/dico-mcp.js inside the package)
|
|
372
|
+
node "$(npm root -g)/@hamak/smart-data-dico/bin/dico-mcp.js" --data-dir /path/to/your/project
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
**Register with Claude Desktop** — edit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
|
|
376
|
+
|
|
377
|
+
```json
|
|
378
|
+
{
|
|
379
|
+
"mcpServers": {
|
|
380
|
+
"smart-data-dico": {
|
|
381
|
+
"command": "npx",
|
|
382
|
+
"args": ["-y", "@hamak/smart-data-dico", "dico-mcp", "--data-dir", "/absolute/path/to/your/project"]
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
Or, if installed from source, point at the bin script directly:
|
|
389
|
+
|
|
390
|
+
```json
|
|
391
|
+
{
|
|
392
|
+
"command": "node",
|
|
393
|
+
"args": ["/absolute/path/to/smart-data-dico/bin/dico-mcp.js", "--data-dir", "/absolute/path/to/your/project"]
|
|
394
|
+
}
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
**Register with Cursor** — `.cursor/mcp.json` (project-level) or `~/.cursor/mcp.json` (global): identical shape.
|
|
398
|
+
|
|
399
|
+
**Register with Roo Code** — `.roo/mcp.json`: identical shape.
|
|
400
|
+
|
|
401
|
+
The MCP process speaks JSON-RPC on stdio and stays attached to its parent; all logging goes to stderr so it doesn't corrupt the stream. Git auto-commit honours the same `GIT_AUTO_COMMIT` env var as the web backend.
|
|
402
|
+
|
|
403
|
+
### Connect external MCP servers *to* the in-app chat
|
|
404
|
+
|
|
405
|
+
The flip side of the above: the in-app chat can also consume external MCP servers (Filesystem, GitHub, etc.). Go to **Settings → MCP** for a curated registry — pick a server, paste any required tokens, save. The chat will then surface those server's tools alongside the built-in ones.
|
|
406
|
+
|
|
324
407
|
## Technologies
|
|
325
408
|
|
|
326
409
|
| Layer | Stack |
|
|
@@ -331,3 +414,65 @@ Set via `PROFILE` environment variable:
|
|
|
331
414
|
| Visualization | Cytoscape.js (dagre + fcose layouts) |
|
|
332
415
|
| Auth | JWT + Auth0 (mock mode for dev) |
|
|
333
416
|
| Deployment | Docker (multi-stage build) |
|
|
417
|
+
|
|
418
|
+
## Documentation
|
|
419
|
+
|
|
420
|
+
| Doc | What it covers |
|
|
421
|
+
|-----|----------------|
|
|
422
|
+
| [Format reference](docs/format-reference.md) | Authoritative spec for the on-disk format — project layout, `dico.config.json`, entities, validation, constraints, relationships, stereotypes, rules, cases, actions, state machines |
|
|
423
|
+
| [User guide](docs/user-guide.md) | Task-oriented walkthrough of the app |
|
|
424
|
+
| [API reference](docs/api-reference.md) | REST endpoints (live Swagger UI at `/api-docs` when the backend is running) |
|
|
425
|
+
| [Deployment](docs/deployment.md) | Desktop vs. server modes, file layout, configuration |
|
|
426
|
+
| [Migration plan](docs/migration-plan.md) | @hamak/app-framework migration notes |
|
|
427
|
+
| [ADRs](docs/adr/) | Architecture decision records |
|
|
428
|
+
|
|
429
|
+
### Claude Code skill
|
|
430
|
+
|
|
431
|
+
The `docs/` folder doubles as a [Claude Code](https://claude.com/claude-code) skill
|
|
432
|
+
(`docs/SKILL.md` + the format reference) for authoring and validating dictionary files in
|
|
433
|
+
any project. Installing it means copying `docs/` into a skills directory as `smart-data-dico`.
|
|
434
|
+
Claude then loads it automatically whenever you work in a folder containing `dico.config.json`.
|
|
435
|
+
|
|
436
|
+
**From a local checkout of this repo:**
|
|
437
|
+
|
|
438
|
+
```bash
|
|
439
|
+
npm run install:skill # → ~/.claude/skills/smart-data-dico
|
|
440
|
+
# or into a specific project:
|
|
441
|
+
scripts/install-skill.sh /path/to/project/.claude/skills
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
**From another repo / machine (no checkout):** pull just `docs/` into your skills directory
|
|
445
|
+
with [`degit`](https://github.com/Rich-Harris/degit) — no auth needed:
|
|
446
|
+
|
|
447
|
+
```bash
|
|
448
|
+
# global → ~/.claude/skills/smart-data-dico
|
|
449
|
+
npx degit amah/smart-data-dico/docs ~/.claude/skills/smart-data-dico
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
Or, without Node, via the release tarball:
|
|
453
|
+
|
|
454
|
+
```bash
|
|
455
|
+
tmp=$(mktemp -d) && curl -fsSL https://codeload.github.com/amah/smart-data-dico/tar.gz/refs/heads/main | tar -xz -C "$tmp" \
|
|
456
|
+
&& rm -rf ~/.claude/skills/smart-data-dico \
|
|
457
|
+
&& cp -R "$tmp"/*/docs ~/.claude/skills/smart-data-dico && rm -rf "$tmp"
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
Swap the destination for `<project>/.claude/skills/smart-data-dico` to scope it to one project.
|
|
461
|
+
Re-run either command to update.
|
|
462
|
+
|
|
463
|
+
**Behind a proxy:** `degit` reads the lowercase `https_proxy` env var (not the CLI), while
|
|
464
|
+
`curl` takes an explicit `--proxy` flag — the curl form is usually the more reliable in
|
|
465
|
+
locked-down networks:
|
|
466
|
+
|
|
467
|
+
```bash
|
|
468
|
+
# degit
|
|
469
|
+
https_proxy=http://proxy.example.com:8080 npx degit amah/smart-data-dico/docs ~/.claude/skills/smart-data-dico
|
|
470
|
+
|
|
471
|
+
# curl
|
|
472
|
+
tmp=$(mktemp -d) && curl -fsSL --proxy http://proxy.example.com:8080 https://codeload.github.com/amah/smart-data-dico/tar.gz/refs/heads/main | tar -xz -C "$tmp" \
|
|
473
|
+
&& rm -rf ~/.claude/skills/smart-data-dico \
|
|
474
|
+
&& cp -R "$tmp"/*/docs ~/.claude/skills/smart-data-dico && rm -rf "$tmp"
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
If the proxy does TLS interception, point Node/curl at your CA bundle (`NODE_EXTRA_CA_CERTS=…`
|
|
478
|
+
for degit, `--cacert …` for curl).
|
package/backend/dist/server.mjs
CHANGED
|
@@ -37667,6 +37667,18 @@ function normalizeRelationshipEnds(rel) {
|
|
|
37667
37667
|
}
|
|
37668
37668
|
];
|
|
37669
37669
|
}
|
|
37670
|
+
function normalizeRelationship(rel) {
|
|
37671
|
+
const hasEnds = !!(rel.ends && rel.ends.length >= 2);
|
|
37672
|
+
const hasLegacy = !!(rel.source && rel.target);
|
|
37673
|
+
if (!hasEnds && !hasLegacy) return null;
|
|
37674
|
+
const [a, b] = normalizeRelationshipEnds(rel);
|
|
37675
|
+
return {
|
|
37676
|
+
...rel,
|
|
37677
|
+
ends: hasEnds ? rel.ends : [a, b],
|
|
37678
|
+
source: rel.source ?? { entity: a.entity, cardinality: a.cardinality, name: a.role, referenceAttributes: a.referenceAttributes },
|
|
37679
|
+
target: rel.target ?? { entity: b.entity, cardinality: b.cardinality, name: b.role, referenceAttributes: b.referenceAttributes }
|
|
37680
|
+
};
|
|
37681
|
+
}
|
|
37670
37682
|
function validateEntity(entity) {
|
|
37671
37683
|
const validator = new import_jsonschema.Validator();
|
|
37672
37684
|
const result = validator.validate(entity, entitySchema);
|
|
@@ -37721,12 +37733,13 @@ var init_EntitySchema = __esm({
|
|
|
37721
37733
|
AttributeType3["UUID"] = "uuid";
|
|
37722
37734
|
return AttributeType3;
|
|
37723
37735
|
})(AttributeType || {});
|
|
37724
|
-
Cardinality = /* @__PURE__ */ ((
|
|
37725
|
-
|
|
37726
|
-
|
|
37727
|
-
return
|
|
37736
|
+
Cardinality = /* @__PURE__ */ ((Cardinality3) => {
|
|
37737
|
+
Cardinality3["ONE"] = "one";
|
|
37738
|
+
Cardinality3["MANY"] = "many";
|
|
37739
|
+
return Cardinality3;
|
|
37728
37740
|
})(Cardinality || {});
|
|
37729
37741
|
__name(normalizeRelationshipEnds, "normalizeRelationshipEnds");
|
|
37742
|
+
__name(normalizeRelationship, "normalizeRelationship");
|
|
37730
37743
|
entitySchema = {
|
|
37731
37744
|
type: "object",
|
|
37732
37745
|
required: ["uuid", "name", "attributes"],
|
|
@@ -37891,11 +37904,27 @@ var init_StorageBackendToken = __esm({
|
|
|
37891
37904
|
STORAGE_BACKEND_TOKEN = /* @__PURE__ */ Symbol("STORAGE_BACKEND");
|
|
37892
37905
|
DICTIONARY_QUERY_TOKEN = /* @__PURE__ */ Symbol("DICTIONARY_QUERY");
|
|
37893
37906
|
StorageRegistry = class {
|
|
37907
|
+
constructor() {
|
|
37908
|
+
this.changeListeners = [];
|
|
37909
|
+
}
|
|
37894
37910
|
static {
|
|
37895
37911
|
__name(this, "StorageRegistry");
|
|
37896
37912
|
}
|
|
37913
|
+
/**
|
|
37914
|
+
* Subscribe to backend swaps/resets. Caches keyed on backend contents
|
|
37915
|
+
* (e.g. the loadPackage cache) register here so they clear when the
|
|
37916
|
+
* backend changes — chiefly so tests that install a fresh in-memory
|
|
37917
|
+
* backend per case don't read stale cached data.
|
|
37918
|
+
*/
|
|
37919
|
+
onBackendChange(fn) {
|
|
37920
|
+
this.changeListeners.push(fn);
|
|
37921
|
+
}
|
|
37922
|
+
notifyChange() {
|
|
37923
|
+
for (const fn of this.changeListeners) fn();
|
|
37924
|
+
}
|
|
37897
37925
|
setBackend(b) {
|
|
37898
37926
|
this.backend = b;
|
|
37927
|
+
this.notifyChange();
|
|
37899
37928
|
}
|
|
37900
37929
|
getBackend() {
|
|
37901
37930
|
if (!this.backend) throw new Error("STORAGE_BACKEND not registered. server.ts must call storageRegistry.setBackend() at startup.");
|
|
@@ -37911,6 +37940,7 @@ var init_StorageBackendToken = __esm({
|
|
|
37911
37940
|
reset() {
|
|
37912
37941
|
this.backend = void 0;
|
|
37913
37942
|
this.query = void 0;
|
|
37943
|
+
this.notifyChange();
|
|
37914
37944
|
}
|
|
37915
37945
|
};
|
|
37916
37946
|
storageRegistry = new StorageRegistry();
|
|
@@ -44170,6 +44200,7 @@ __export(fileOperations_exports, {
|
|
|
44170
44200
|
getPackagePath: () => getPackagePath,
|
|
44171
44201
|
getReservedPackageFiles: () => getReservedPackageFiles,
|
|
44172
44202
|
getSchemaPackagePath: () => getSchemaPackagePath,
|
|
44203
|
+
invalidatePackageCache: () => invalidatePackageCache,
|
|
44173
44204
|
listAllDictionaries: () => listAllDictionaries,
|
|
44174
44205
|
listAllEntities: () => listAllEntities,
|
|
44175
44206
|
listAllEntityRuleFiles: () => listAllEntityRuleFiles,
|
|
@@ -44507,6 +44538,8 @@ async function writeSectionsToStorage(p, sections) {
|
|
|
44507
44538
|
if (sections.cases.length > 0) payload.cases = sections.cases;
|
|
44508
44539
|
if (sections.actions.length > 0) payload.actions = sections.actions;
|
|
44509
44540
|
if (sections.stateMachines.length > 0) payload.stateMachines = sections.stateMachines;
|
|
44541
|
+
const pkgSeg = String(p).replace(/^\/+/, "").split("/")[0];
|
|
44542
|
+
if (pkgSeg) invalidatePackageCache(pkgSeg);
|
|
44510
44543
|
if (Object.keys(payload).length === 0) {
|
|
44511
44544
|
await deleteIfExists(p);
|
|
44512
44545
|
return;
|
|
@@ -44519,7 +44552,13 @@ async function listPackageYamlFilePaths(packageName) {
|
|
|
44519
44552
|
const entries = await getStorage().list(WS, dir);
|
|
44520
44553
|
return entries.filter((e) => !e.isDirectory).map((e) => e.name).filter((f) => f.endsWith(".yaml") && !RESERVED_PACKAGE_FILES.has(f)).sort().map((f) => pathOf(`${packageName}/${f}`));
|
|
44521
44554
|
}
|
|
44555
|
+
function invalidatePackageCache(packageName) {
|
|
44556
|
+
if (packageName === void 0) _packageCache.clear();
|
|
44557
|
+
else _packageCache.delete(packageName);
|
|
44558
|
+
}
|
|
44522
44559
|
async function loadPackage(packageName) {
|
|
44560
|
+
const cached2 = _packageCache.get(packageName);
|
|
44561
|
+
if (cached2 && cached2.expires > Date.now()) return cached2.model;
|
|
44523
44562
|
const files = await listPackageYamlFilePaths(packageName);
|
|
44524
44563
|
const parsed = await Promise.all(
|
|
44525
44564
|
files.map(async (p) => ({
|
|
@@ -44527,7 +44566,9 @@ async function loadPackage(packageName) {
|
|
|
44527
44566
|
sections: await parseSectionsFromStorage(p, String(p))
|
|
44528
44567
|
}))
|
|
44529
44568
|
);
|
|
44530
|
-
|
|
44569
|
+
const model = mergePackageSections(packageName, parsed);
|
|
44570
|
+
_packageCache.set(packageName, { model, expires: Date.now() + PACKAGE_CACHE_TTL_MS });
|
|
44571
|
+
return model;
|
|
44531
44572
|
}
|
|
44532
44573
|
function getSchemaPackagePath() {
|
|
44533
44574
|
return path4.join(getDataDir(), ".dico", "schemas");
|
|
@@ -44684,7 +44725,13 @@ async function readRelationshipsFile(packagePath) {
|
|
|
44684
44725
|
try {
|
|
44685
44726
|
const packageName = path4.basename(packagePath);
|
|
44686
44727
|
const pkg = await loadPackage(packageName);
|
|
44687
|
-
|
|
44728
|
+
const normalized = [];
|
|
44729
|
+
for (const rel of pkg.relationships) {
|
|
44730
|
+
const n = normalizeRelationship(rel);
|
|
44731
|
+
if (n) normalized.push(n);
|
|
44732
|
+
else logger.warn(`Skipping malformed relationship ${rel.uuid} in package '${packageName}': no ends and no source/target`);
|
|
44733
|
+
}
|
|
44734
|
+
return normalized;
|
|
44688
44735
|
} catch (error48) {
|
|
44689
44736
|
logger.error(`Error reading relationships file: ${error48}`);
|
|
44690
44737
|
return [];
|
|
@@ -45244,7 +45291,7 @@ async function deleteStateMachine(uuid3) {
|
|
|
45244
45291
|
return { ok: false };
|
|
45245
45292
|
}
|
|
45246
45293
|
}
|
|
45247
|
-
var import_yaml, WS, getDataDir, RESERVED_DIRS, RESERVED_PACKAGE_FILES, VALIDATION_FIELD_NAMES, gitServiceInstance;
|
|
45294
|
+
var import_yaml, WS, getDataDir, RESERVED_DIRS, RESERVED_PACKAGE_FILES, VALIDATION_FIELD_NAMES, gitServiceInstance, PACKAGE_CACHE_TTL_MS, _packageCache;
|
|
45248
45295
|
var init_fileOperations = __esm({
|
|
45249
45296
|
"backend/src/utils/fileOperations.ts"() {
|
|
45250
45297
|
"use strict";
|
|
@@ -45289,6 +45336,10 @@ var init_fileOperations = __esm({
|
|
|
45289
45336
|
__name(getReservedPackageFiles, "getReservedPackageFiles");
|
|
45290
45337
|
__name(writeSectionsToStorage, "writeSectionsToStorage");
|
|
45291
45338
|
__name(listPackageYamlFilePaths, "listPackageYamlFilePaths");
|
|
45339
|
+
PACKAGE_CACHE_TTL_MS = 2e3;
|
|
45340
|
+
_packageCache = /* @__PURE__ */ new Map();
|
|
45341
|
+
__name(invalidatePackageCache, "invalidatePackageCache");
|
|
45342
|
+
storageRegistry.onBackendChange(() => invalidatePackageCache());
|
|
45292
45343
|
__name(loadPackage, "loadPackage");
|
|
45293
45344
|
__name(getSchemaPackagePath, "getSchemaPackagePath");
|
|
45294
45345
|
__name(listSchemaPackageYamlFilePaths, "listSchemaPackageYamlFilePaths");
|
|
@@ -45629,21 +45680,18 @@ var init_dictionaryService = __esm({
|
|
|
45629
45680
|
try {
|
|
45630
45681
|
if (id.startsWith("microservices/")) {
|
|
45631
45682
|
const microservice = id.replace("microservices/", "");
|
|
45632
|
-
const
|
|
45683
|
+
const pkg = await loadPackage(microservice);
|
|
45633
45684
|
const entries = [];
|
|
45634
|
-
for (const
|
|
45635
|
-
const
|
|
45636
|
-
|
|
45637
|
-
|
|
45638
|
-
|
|
45639
|
-
|
|
45640
|
-
|
|
45641
|
-
|
|
45642
|
-
|
|
45643
|
-
|
|
45644
|
-
required: attr.required || false
|
|
45645
|
-
});
|
|
45646
|
-
}
|
|
45685
|
+
for (const entity of pkg.entities) {
|
|
45686
|
+
for (const attr of entity.attributes || []) {
|
|
45687
|
+
entries.push({
|
|
45688
|
+
id: `${entity.uuid || ""}_${attr.name}`,
|
|
45689
|
+
name: attr.name,
|
|
45690
|
+
description: attr.description || "",
|
|
45691
|
+
type: attr.type || "string",
|
|
45692
|
+
format: attr.validation?.format,
|
|
45693
|
+
required: attr.required || false
|
|
45694
|
+
});
|
|
45647
45695
|
}
|
|
45648
45696
|
}
|
|
45649
45697
|
return entries;
|
|
@@ -45791,9 +45839,8 @@ var init_dictionaryService = __esm({
|
|
|
45791
45839
|
const microservices = await listMicroservices();
|
|
45792
45840
|
const result = [];
|
|
45793
45841
|
for (const microservice of microservices) {
|
|
45794
|
-
const
|
|
45795
|
-
for (const
|
|
45796
|
-
const entity = await readEntityFile(microservice, entityName);
|
|
45842
|
+
const pkg = await loadPackage(microservice);
|
|
45843
|
+
for (const entity of pkg.entities) {
|
|
45797
45844
|
if (!entity) continue;
|
|
45798
45845
|
if (filters.name && !entity.name.toLowerCase().includes(filters.name.toLowerCase())) {
|
|
45799
45846
|
continue;
|
|
@@ -45882,13 +45929,8 @@ var init_dictionaryService = __esm({
|
|
|
45882
45929
|
}
|
|
45883
45930
|
}
|
|
45884
45931
|
async getServiceEntities(service) {
|
|
45885
|
-
const
|
|
45886
|
-
|
|
45887
|
-
for (const name21 of entityNames) {
|
|
45888
|
-
const entity = await readEntityFile(service, name21);
|
|
45889
|
-
if (entity) entities.push(entity);
|
|
45890
|
-
}
|
|
45891
|
-
return entities;
|
|
45932
|
+
const pkg = await loadPackage(service);
|
|
45933
|
+
return pkg.entities;
|
|
45892
45934
|
}
|
|
45893
45935
|
};
|
|
45894
45936
|
dictionaryService = new DictionaryService();
|
|
@@ -47321,29 +47363,13 @@ var init_serviceService = __esm({
|
|
|
47321
47363
|
}
|
|
47322
47364
|
}
|
|
47323
47365
|
async getServiceEntities(service) {
|
|
47324
|
-
logger.info(`Getting entities for service: ${service}`);
|
|
47325
47366
|
const startTime = process.hrtime();
|
|
47326
47367
|
try {
|
|
47327
|
-
const
|
|
47328
|
-
const entityNames = await listMicroserviceEntities(service);
|
|
47329
|
-
const listEndTime = process.hrtime(listStartTime);
|
|
47330
|
-
const listTimeMs = Number((listEndTime[0] * 1e3 + listEndTime[1] / 1e6).toFixed(2));
|
|
47331
|
-
logger.info(`Listed ${entityNames.length} entity names for service ${service} in ${listTimeMs}ms`);
|
|
47332
|
-
const entities = [];
|
|
47333
|
-
const readStartTime = process.hrtime();
|
|
47334
|
-
for (const entityName of entityNames) {
|
|
47335
|
-
const entity = await readEntityFile(service, entityName);
|
|
47336
|
-
if (entity) {
|
|
47337
|
-
entities.push(entity);
|
|
47338
|
-
}
|
|
47339
|
-
}
|
|
47340
|
-
const readEndTime = process.hrtime(readStartTime);
|
|
47341
|
-
const readTimeMs = Number((readEndTime[0] * 1e3 + readEndTime[1] / 1e6).toFixed(2));
|
|
47342
|
-
logger.info(`Read ${entities.length} entity files for service ${service} in ${readTimeMs}ms`);
|
|
47368
|
+
const pkg = await loadPackage(service);
|
|
47343
47369
|
const endTime = process.hrtime(startTime);
|
|
47344
47370
|
const totalTimeMs = Number((endTime[0] * 1e3 + endTime[1] / 1e6).toFixed(2));
|
|
47345
|
-
logger.info(`
|
|
47346
|
-
return entities;
|
|
47371
|
+
logger.info(`Got ${pkg.entities.length} entities for service ${service} in ${totalTimeMs}ms`);
|
|
47372
|
+
return pkg.entities;
|
|
47347
47373
|
} catch (error48) {
|
|
47348
47374
|
logger.error(`Error getting service entities: ${error48}`);
|
|
47349
47375
|
return [];
|
|
@@ -47970,6 +47996,17 @@ async function getGitService2() {
|
|
|
47970
47996
|
return null;
|
|
47971
47997
|
}
|
|
47972
47998
|
}
|
|
47999
|
+
function describeError(e) {
|
|
48000
|
+
if (e instanceof Error) return e.message;
|
|
48001
|
+
try {
|
|
48002
|
+
return JSON.stringify(e);
|
|
48003
|
+
} catch {
|
|
48004
|
+
return String(e);
|
|
48005
|
+
}
|
|
48006
|
+
}
|
|
48007
|
+
function isNotAGitRepo(detail) {
|
|
48008
|
+
return /not a git repo|could not find .*git repo|NotGitRepo|NOT_A_REPO|fatal: not a git/i.test(detail);
|
|
48009
|
+
}
|
|
47973
48010
|
var gitServiceInstance2, VersionService, versionService;
|
|
47974
48011
|
var init_versionService = __esm({
|
|
47975
48012
|
"backend/src/services/versionService.ts"() {
|
|
@@ -47978,6 +48015,8 @@ var init_versionService = __esm({
|
|
|
47978
48015
|
init_config();
|
|
47979
48016
|
gitServiceInstance2 = null;
|
|
47980
48017
|
__name(getGitService2, "getGitService");
|
|
48018
|
+
__name(describeError, "describeError");
|
|
48019
|
+
__name(isNotAGitRepo, "isNotAGitRepo");
|
|
47981
48020
|
VersionService = class {
|
|
47982
48021
|
static {
|
|
47983
48022
|
__name(this, "VersionService");
|
|
@@ -47996,7 +48035,12 @@ var init_versionService = __esm({
|
|
|
47996
48035
|
const ddFiles = (status.files || []).map((f) => f.path).filter((p) => p.startsWith(ddPrefix));
|
|
47997
48036
|
return { clean: ddFiles.length === 0, files: ddFiles };
|
|
47998
48037
|
} catch (error48) {
|
|
47999
|
-
|
|
48038
|
+
const detail = describeError(error48);
|
|
48039
|
+
if (isNotAGitRepo(detail)) {
|
|
48040
|
+
logger.debug(`getWorkingTreeStatus: project is not a git repository \u2014 treating as clean (${detail})`);
|
|
48041
|
+
} else {
|
|
48042
|
+
logger.warn(`getWorkingTreeStatus failed: ${detail}`);
|
|
48043
|
+
}
|
|
48000
48044
|
return { clean: true, files: [] };
|
|
48001
48045
|
}
|
|
48002
48046
|
}
|
|
@@ -148774,7 +148818,8 @@ var init_UuidIndex = __esm({
|
|
|
148774
148818
|
}
|
|
148775
148819
|
this.rebuildInFlight = true;
|
|
148776
148820
|
try {
|
|
148777
|
-
const { listPackages: listPackages2 } = await Promise.resolve().then(() => (init_fileOperations(), fileOperations_exports));
|
|
148821
|
+
const { listPackages: listPackages2, invalidatePackageCache: invalidatePackageCache2 } = await Promise.resolve().then(() => (init_fileOperations(), fileOperations_exports));
|
|
148822
|
+
invalidatePackageCache2();
|
|
148778
148823
|
const top = await listPackages2();
|
|
148779
148824
|
const allPackages = [];
|
|
148780
148825
|
for (const pkg of top) {
|
|
@@ -148899,6 +148944,8 @@ var init_UuidIndex = __esm({
|
|
|
148899
148944
|
while (this.rebuildInFlight) {
|
|
148900
148945
|
await new Promise((r) => setTimeout(r, 0));
|
|
148901
148946
|
}
|
|
148947
|
+
const { invalidatePackageCache: invalidatePackageCache2 } = await Promise.resolve().then(() => (init_fileOperations(), fileOperations_exports));
|
|
148948
|
+
invalidatePackageCache2(packageName);
|
|
148902
148949
|
const packagePrefix = `packages/${packageName}/entities/`;
|
|
148903
148950
|
const previouslyMappedUuids = /* @__PURE__ */ new Set();
|
|
148904
148951
|
for (const [uuid3, lp] of this.uuidToPath) {
|
|
@@ -160474,9 +160521,9 @@ var QualityService = class {
|
|
|
160474
160521
|
const services = service ? [service] : await listMicroservices();
|
|
160475
160522
|
const packages = [];
|
|
160476
160523
|
for (const svc of services) {
|
|
160477
|
-
const
|
|
160524
|
+
const pkg = await loadPackage(svc);
|
|
160478
160525
|
const entities = [];
|
|
160479
|
-
|
|
160526
|
+
const relEntityUuids = /* @__PURE__ */ new Set();
|
|
160480
160527
|
try {
|
|
160481
160528
|
const rels = await readRelationshipsFile(getPackagePath(svc));
|
|
160482
160529
|
for (const rel of rels) {
|
|
@@ -160485,9 +160532,7 @@ var QualityService = class {
|
|
|
160485
160532
|
}
|
|
160486
160533
|
} catch {
|
|
160487
160534
|
}
|
|
160488
|
-
for (const
|
|
160489
|
-
const name21 = rawName.includes("_") ? rawName.split("_").slice(1).join("_") : rawName;
|
|
160490
|
-
const entity = await readEntityFile(svc, name21);
|
|
160535
|
+
for (const entity of pkg.entities) {
|
|
160491
160536
|
if (!entity) continue;
|
|
160492
160537
|
const descFilled = !!entity.description;
|
|
160493
160538
|
const totalAttrs = entity.attributes.length;
|
|
@@ -162295,6 +162340,13 @@ async function mountFrameworkRoutes() {
|
|
|
162295
162340
|
workspaceRoots
|
|
162296
162341
|
});
|
|
162297
162342
|
enricherRegistry2.register(gitEnricher);
|
|
162343
|
+
app.use("/api/git/dictionaries/status", (_req, res, next) => {
|
|
162344
|
+
if (!fs5.existsSync(path13.join(config.dataDir, ".git"))) {
|
|
162345
|
+
res.json({ files: [], hasUncommittedChanges: false });
|
|
162346
|
+
return;
|
|
162347
|
+
}
|
|
162348
|
+
next();
|
|
162349
|
+
});
|
|
162298
162350
|
const gitRoutes = gitModule.createGitRoutes({ gitService, debug: !config.isProduction });
|
|
162299
162351
|
app.use("/api/git", gitRoutes);
|
|
162300
162352
|
app.use("/api/git", gitModule.gitErrorHandler);
|