@maadb/core 0.0.1 → 0.7.9
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/LICENSE +21 -0
- package/README.md +371 -3
- package/dist/architect.d.ts +2 -0
- package/dist/architect.d.ts.map +1 -0
- package/dist/architect.js +273 -0
- package/dist/architect.js.map +1 -0
- package/dist/auth/resolve.d.ts +36 -0
- package/dist/auth/resolve.d.ts.map +1 -0
- package/dist/auth/resolve.js +77 -0
- package/dist/auth/resolve.js.map +1 -0
- package/dist/auth/token-store.d.ts +82 -0
- package/dist/auth/token-store.d.ts.map +1 -0
- package/dist/auth/token-store.js +464 -0
- package/dist/auth/token-store.js.map +1 -0
- package/dist/auth/types.d.ts +75 -0
- package/dist/auth/types.d.ts.map +1 -0
- package/dist/auth/types.js +15 -0
- package/dist/auth/types.js.map +1 -0
- package/dist/backend/adapter.d.ts +57 -0
- package/dist/backend/adapter.d.ts.map +1 -0
- package/dist/backend/adapter.js +6 -0
- package/dist/backend/adapter.js.map +1 -0
- package/dist/backend/index.d.ts +3 -0
- package/dist/backend/index.d.ts.map +1 -0
- package/dist/backend/index.js +2 -0
- package/dist/backend/index.js.map +1 -0
- package/dist/backend/sqlite/index.d.ts +63 -0
- package/dist/backend/sqlite/index.d.ts.map +1 -0
- package/dist/backend/sqlite/index.js +521 -0
- package/dist/backend/sqlite/index.js.map +1 -0
- package/dist/backend/sqlite/schema.d.ts +2 -0
- package/dist/backend/sqlite/schema.d.ts.map +1 -0
- package/dist/backend/sqlite/schema.js +88 -0
- package/dist/backend/sqlite/schema.js.map +1 -0
- package/dist/claude-md.d.ts +2 -0
- package/dist/claude-md.d.ts.map +1 -0
- package/dist/claude-md.js +97 -0
- package/dist/claude-md.js.map +1 -0
- package/dist/cli/commands/audit.d.ts +4 -0
- package/dist/cli/commands/audit.d.ts.map +1 -0
- package/dist/cli/commands/audit.js +39 -0
- package/dist/cli/commands/audit.js.map +1 -0
- package/dist/cli/commands/auth.d.ts +8 -0
- package/dist/cli/commands/auth.d.ts.map +1 -0
- package/dist/cli/commands/auth.js +242 -0
- package/dist/cli/commands/auth.js.map +1 -0
- package/dist/cli/commands/discover.d.ts +5 -0
- package/dist/cli/commands/discover.d.ts.map +1 -0
- package/dist/cli/commands/discover.js +48 -0
- package/dist/cli/commands/discover.js.map +1 -0
- package/dist/cli/commands/maintain.d.ts +6 -0
- package/dist/cli/commands/maintain.d.ts.map +1 -0
- package/dist/cli/commands/maintain.js +168 -0
- package/dist/cli/commands/maintain.js.map +1 -0
- package/dist/cli/commands/read.d.ts +7 -0
- package/dist/cli/commands/read.d.ts.map +1 -0
- package/dist/cli/commands/read.js +139 -0
- package/dist/cli/commands/read.js.map +1 -0
- package/dist/cli/commands/write.d.ts +4 -0
- package/dist/cli/commands/write.d.ts.map +1 -0
- package/dist/cli/commands/write.js +99 -0
- package/dist/cli/commands/write.js.map +1 -0
- package/dist/cli/helpers.d.ts +8 -0
- package/dist/cli/helpers.d.ts.map +1 -0
- package/dist/cli/helpers.js +17 -0
- package/dist/cli/helpers.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +296 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +7 -0
- package/dist/cli.js.map +1 -0
- package/dist/engine/audit.d.ts +15 -0
- package/dist/engine/audit.d.ts.map +1 -0
- package/dist/engine/audit.js +42 -0
- package/dist/engine/audit.js.map +1 -0
- package/dist/engine/composites.d.ts +6 -0
- package/dist/engine/composites.d.ts.map +1 -0
- package/dist/engine/composites.js +102 -0
- package/dist/engine/composites.js.map +1 -0
- package/dist/engine/context.d.ts +47 -0
- package/dist/engine/context.d.ts.map +1 -0
- package/dist/engine/context.js +55 -0
- package/dist/engine/context.js.map +1 -0
- package/dist/engine/docid-safe.d.ts +8 -0
- package/dist/engine/docid-safe.d.ts.map +1 -0
- package/dist/engine/docid-safe.js +0 -0
- package/dist/engine/docid-safe.js.map +1 -0
- package/dist/engine/helpers.d.ts +8 -0
- package/dist/engine/helpers.d.ts.map +1 -0
- package/dist/engine/helpers.js +102 -0
- package/dist/engine/helpers.js.map +1 -0
- package/dist/engine/index.d.ts +209 -0
- package/dist/engine/index.d.ts.map +1 -0
- package/dist/engine/index.js +489 -0
- package/dist/engine/index.js.map +1 -0
- package/dist/engine/indexing.d.ts +14 -0
- package/dist/engine/indexing.d.ts.map +1 -0
- package/dist/engine/indexing.js +225 -0
- package/dist/engine/indexing.js.map +1 -0
- package/dist/engine/journal.d.ts +32 -0
- package/dist/engine/journal.d.ts.map +1 -0
- package/dist/engine/journal.js +111 -0
- package/dist/engine/journal.js.map +1 -0
- package/dist/engine/logger.d.ts +19 -0
- package/dist/engine/logger.d.ts.map +1 -0
- package/dist/engine/logger.js +40 -0
- package/dist/engine/logger.js.map +1 -0
- package/dist/engine/maintenance.d.ts +15 -0
- package/dist/engine/maintenance.d.ts.map +1 -0
- package/dist/engine/maintenance.js +87 -0
- package/dist/engine/maintenance.js.map +1 -0
- package/dist/engine/mutex.d.ts +24 -0
- package/dist/engine/mutex.d.ts.map +1 -0
- package/dist/engine/mutex.js +50 -0
- package/dist/engine/mutex.js.map +1 -0
- package/dist/engine/pathguard.d.ts +16 -0
- package/dist/engine/pathguard.d.ts.map +1 -0
- package/dist/engine/pathguard.js +41 -0
- package/dist/engine/pathguard.js.map +1 -0
- package/dist/engine/reads.d.ts +24 -0
- package/dist/engine/reads.d.ts.map +1 -0
- package/dist/engine/reads.js +701 -0
- package/dist/engine/reads.js.map +1 -0
- package/dist/engine/types.d.ts +350 -0
- package/dist/engine/types.d.ts.map +1 -0
- package/dist/engine/types.js +5 -0
- package/dist/engine/types.js.map +1 -0
- package/dist/engine/writes.d.ts +10 -0
- package/dist/engine/writes.d.ts.map +1 -0
- package/dist/engine/writes.js +606 -0
- package/dist/engine/writes.js.map +1 -0
- package/dist/engine.d.ts +3 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +6 -0
- package/dist/engine.js.map +1 -0
- package/dist/errors.d.ts +20 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +17 -0
- package/dist/errors.js.map +1 -0
- package/dist/extractor/fields.d.ts +3 -0
- package/dist/extractor/fields.d.ts.map +1 -0
- package/dist/extractor/fields.js +58 -0
- package/dist/extractor/fields.js.map +1 -0
- package/dist/extractor/index.d.ts +7 -0
- package/dist/extractor/index.d.ts.map +1 -0
- package/dist/extractor/index.js +22 -0
- package/dist/extractor/index.js.map +1 -0
- package/dist/extractor/normalizers.d.ts +22 -0
- package/dist/extractor/normalizers.d.ts.map +1 -0
- package/dist/extractor/normalizers.js +250 -0
- package/dist/extractor/normalizers.js.map +1 -0
- package/dist/extractor/objects.d.ts +3 -0
- package/dist/extractor/objects.d.ts.map +1 -0
- package/dist/extractor/objects.js +33 -0
- package/dist/extractor/objects.js.map +1 -0
- package/dist/extractor/relationships.d.ts +3 -0
- package/dist/extractor/relationships.d.ts.map +1 -0
- package/dist/extractor/relationships.js +58 -0
- package/dist/extractor/relationships.js.map +1 -0
- package/dist/git/commit.d.ts +63 -0
- package/dist/git/commit.d.ts.map +1 -0
- package/dist/git/commit.js +161 -0
- package/dist/git/commit.js.map +1 -0
- package/dist/git/diff.d.ts +4 -0
- package/dist/git/diff.d.ts.map +1 -0
- package/dist/git/diff.js +56 -0
- package/dist/git/diff.js.map +1 -0
- package/dist/git/index.d.ts +41 -0
- package/dist/git/index.d.ts.map +1 -0
- package/dist/git/index.js +86 -0
- package/dist/git/index.js.map +1 -0
- package/dist/git/log.d.ts +13 -0
- package/dist/git/log.d.ts.map +1 -0
- package/dist/git/log.js +116 -0
- package/dist/git/log.js.map +1 -0
- package/dist/git/snapshot.d.ts +4 -0
- package/dist/git/snapshot.d.ts.map +1 -0
- package/dist/git/snapshot.js +65 -0
- package/dist/git/snapshot.js.map +1 -0
- package/dist/instance/config.d.ts +19 -0
- package/dist/instance/config.d.ts.map +1 -0
- package/dist/instance/config.js +107 -0
- package/dist/instance/config.js.map +1 -0
- package/dist/instance/pool.d.ts +138 -0
- package/dist/instance/pool.d.ts.map +1 -0
- package/dist/instance/pool.js +335 -0
- package/dist/instance/pool.js.map +1 -0
- package/dist/instance/session.d.ts +140 -0
- package/dist/instance/session.d.ts.map +1 -0
- package/dist/instance/session.js +267 -0
- package/dist/instance/session.js.map +1 -0
- package/dist/logging.d.ts +108 -0
- package/dist/logging.d.ts.map +1 -0
- package/dist/logging.js +120 -0
- package/dist/logging.js.map +1 -0
- package/dist/maad-md.d.ts +11 -0
- package/dist/maad-md.d.ts.map +1 -0
- package/dist/maad-md.js +252 -0
- package/dist/maad-md.js.map +1 -0
- package/dist/mcp/bulk-cap.d.ts +10 -0
- package/dist/mcp/bulk-cap.d.ts.map +1 -0
- package/dist/mcp/bulk-cap.js +37 -0
- package/dist/mcp/bulk-cap.js.map +1 -0
- package/dist/mcp/config.d.ts +18 -0
- package/dist/mcp/config.d.ts.map +1 -0
- package/dist/mcp/config.js +20 -0
- package/dist/mcp/config.js.map +1 -0
- package/dist/mcp/ctx.d.ts +17 -0
- package/dist/mcp/ctx.d.ts.map +1 -0
- package/dist/mcp/ctx.js +7 -0
- package/dist/mcp/ctx.js.map +1 -0
- package/dist/mcp/guardrails.d.ts +21 -0
- package/dist/mcp/guardrails.d.ts.map +1 -0
- package/dist/mcp/guardrails.js +39 -0
- package/dist/mcp/guardrails.js.map +1 -0
- package/dist/mcp/idempotency.d.ts +69 -0
- package/dist/mcp/idempotency.d.ts.map +1 -0
- package/dist/mcp/idempotency.js +168 -0
- package/dist/mcp/idempotency.js.map +1 -0
- package/dist/mcp/instance-reload.d.ts +23 -0
- package/dist/mcp/instance-reload.d.ts.map +1 -0
- package/dist/mcp/instance-reload.js +126 -0
- package/dist/mcp/instance-reload.js.map +1 -0
- package/dist/mcp/kinds.d.ts +22 -0
- package/dist/mcp/kinds.d.ts.map +1 -0
- package/dist/mcp/kinds.js +110 -0
- package/dist/mcp/kinds.js.map +1 -0
- package/dist/mcp/lifecycle.d.ts +8 -0
- package/dist/mcp/lifecycle.d.ts.map +1 -0
- package/dist/mcp/lifecycle.js +65 -0
- package/dist/mcp/lifecycle.js.map +1 -0
- package/dist/mcp/notifications.d.ts +87 -0
- package/dist/mcp/notifications.d.ts.map +1 -0
- package/dist/mcp/notifications.js +154 -0
- package/dist/mcp/notifications.js.map +1 -0
- package/dist/mcp/query-depth.d.ts +28 -0
- package/dist/mcp/query-depth.d.ts.map +1 -0
- package/dist/mcp/query-depth.js +43 -0
- package/dist/mcp/query-depth.js.map +1 -0
- package/dist/mcp/rate-limit.d.ts +60 -0
- package/dist/mcp/rate-limit.d.ts.map +1 -0
- package/dist/mcp/rate-limit.js +203 -0
- package/dist/mcp/rate-limit.js.map +1 -0
- package/dist/mcp/reload-signal.d.ts +13 -0
- package/dist/mcp/reload-signal.d.ts.map +1 -0
- package/dist/mcp/reload-signal.js +68 -0
- package/dist/mcp/reload-signal.js.map +1 -0
- package/dist/mcp/response.d.ts +109 -0
- package/dist/mcp/response.d.ts.map +1 -0
- package/dist/mcp/response.js +132 -0
- package/dist/mcp/response.js.map +1 -0
- package/dist/mcp/roles.d.ts +7 -0
- package/dist/mcp/roles.d.ts.map +1 -0
- package/dist/mcp/roles.js +56 -0
- package/dist/mcp/roles.js.map +1 -0
- package/dist/mcp/server.d.ts +33 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +242 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/shutdown.d.ts +32 -0
- package/dist/mcp/shutdown.d.ts.map +1 -0
- package/dist/mcp/shutdown.js +130 -0
- package/dist/mcp/shutdown.js.map +1 -0
- package/dist/mcp/tools/audit.d.ts +4 -0
- package/dist/mcp/tools/audit.d.ts.map +1 -0
- package/dist/mcp/tools/audit.js +45 -0
- package/dist/mcp/tools/audit.js.map +1 -0
- package/dist/mcp/tools/auth.d.ts +4 -0
- package/dist/mcp/tools/auth.d.ts.map +1 -0
- package/dist/mcp/tools/auth.js +181 -0
- package/dist/mcp/tools/auth.js.map +1 -0
- package/dist/mcp/tools/discover.d.ts +4 -0
- package/dist/mcp/tools/discover.d.ts.map +1 -0
- package/dist/mcp/tools/discover.js +83 -0
- package/dist/mcp/tools/discover.js.map +1 -0
- package/dist/mcp/tools/instance.d.ts +15 -0
- package/dist/mcp/tools/instance.d.ts.map +1 -0
- package/dist/mcp/tools/instance.js +207 -0
- package/dist/mcp/tools/instance.js.map +1 -0
- package/dist/mcp/tools/maintain.d.ts +4 -0
- package/dist/mcp/tools/maintain.d.ts.map +1 -0
- package/dist/mcp/tools/maintain.js +118 -0
- package/dist/mcp/tools/maintain.js.map +1 -0
- package/dist/mcp/tools/read.d.ts +4 -0
- package/dist/mcp/tools/read.d.ts.map +1 -0
- package/dist/mcp/tools/read.js +260 -0
- package/dist/mcp/tools/read.js.map +1 -0
- package/dist/mcp/tools/write.d.ts +4 -0
- package/dist/mcp/tools/write.d.ts.map +1 -0
- package/dist/mcp/tools/write.js +342 -0
- package/dist/mcp/tools/write.js.map +1 -0
- package/dist/mcp/transport/auth.d.ts +41 -0
- package/dist/mcp/transport/auth.d.ts.map +1 -0
- package/dist/mcp/transport/auth.js +115 -0
- package/dist/mcp/transport/auth.js.map +1 -0
- package/dist/mcp/transport/http.d.ts +62 -0
- package/dist/mcp/transport/http.d.ts.map +1 -0
- package/dist/mcp/transport/http.js +357 -0
- package/dist/mcp/transport/http.js.map +1 -0
- package/dist/mcp/transport/pin.d.ts +15 -0
- package/dist/mcp/transport/pin.d.ts.map +1 -0
- package/dist/mcp/transport/pin.js +71 -0
- package/dist/mcp/transport/pin.js.map +1 -0
- package/dist/mcp/transport/telemetry.d.ts +79 -0
- package/dist/mcp/transport/telemetry.d.ts.map +1 -0
- package/dist/mcp/transport/telemetry.js +97 -0
- package/dist/mcp/transport/telemetry.js.map +1 -0
- package/dist/mcp/with-session.d.ts +26 -0
- package/dist/mcp/with-session.d.ts.map +1 -0
- package/dist/mcp/with-session.js +275 -0
- package/dist/mcp/with-session.js.map +1 -0
- package/dist/parser/annotations.d.ts +3 -0
- package/dist/parser/annotations.d.ts.map +1 -0
- package/dist/parser/annotations.js +41 -0
- package/dist/parser/annotations.js.map +1 -0
- package/dist/parser/blocks.d.ts +3 -0
- package/dist/parser/blocks.d.ts.map +1 -0
- package/dist/parser/blocks.js +99 -0
- package/dist/parser/blocks.js.map +1 -0
- package/dist/parser/frontmatter.d.ts +9 -0
- package/dist/parser/frontmatter.d.ts.map +1 -0
- package/dist/parser/frontmatter.js +33 -0
- package/dist/parser/frontmatter.js.map +1 -0
- package/dist/parser/index.d.ts +11 -0
- package/dist/parser/index.d.ts.map +1 -0
- package/dist/parser/index.js +54 -0
- package/dist/parser/index.js.map +1 -0
- package/dist/parser/matter.d.ts +3 -0
- package/dist/parser/matter.d.ts.map +1 -0
- package/dist/parser/matter.js +32 -0
- package/dist/parser/matter.js.map +1 -0
- package/dist/parser/tags.d.ts +3 -0
- package/dist/parser/tags.d.ts.map +1 -0
- package/dist/parser/tags.js +32 -0
- package/dist/parser/tags.js.map +1 -0
- package/dist/parser/verbatim.d.ts +4 -0
- package/dist/parser/verbatim.d.ts.map +1 -0
- package/dist/parser/verbatim.js +121 -0
- package/dist/parser/verbatim.js.map +1 -0
- package/dist/parser/yaml-profile.d.ts +4 -0
- package/dist/parser/yaml-profile.d.ts.map +1 -0
- package/dist/parser/yaml-profile.js +73 -0
- package/dist/parser/yaml-profile.js.map +1 -0
- package/dist/registry/index.d.ts +2 -0
- package/dist/registry/index.d.ts.map +1 -0
- package/dist/registry/index.js +2 -0
- package/dist/registry/index.js.map +1 -0
- package/dist/registry/loader.d.ts +4 -0
- package/dist/registry/loader.d.ts.map +1 -0
- package/dist/registry/loader.js +147 -0
- package/dist/registry/loader.js.map +1 -0
- package/dist/registry/types.d.ts +2 -0
- package/dist/registry/types.d.ts.map +1 -0
- package/dist/registry/types.js +5 -0
- package/dist/registry/types.js.map +1 -0
- package/dist/scanner.d.ts +57 -0
- package/dist/scanner.d.ts.map +1 -0
- package/dist/scanner.js +223 -0
- package/dist/scanner.js.map +1 -0
- package/dist/schema/index.d.ts +3 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/index.js +3 -0
- package/dist/schema/index.js.map +1 -0
- package/dist/schema/loader.d.ts +4 -0
- package/dist/schema/loader.d.ts.map +1 -0
- package/dist/schema/loader.js +303 -0
- package/dist/schema/loader.js.map +1 -0
- package/dist/schema/precision.d.ts +6 -0
- package/dist/schema/precision.d.ts.map +1 -0
- package/dist/schema/precision.js +59 -0
- package/dist/schema/precision.js.map +1 -0
- package/dist/schema/validator.d.ts +21 -0
- package/dist/schema/validator.d.ts.map +1 -0
- package/dist/schema/validator.js +200 -0
- package/dist/schema/validator.js.map +1 -0
- package/dist/schema-md.d.ts +8 -0
- package/dist/schema-md.d.ts.map +1 -0
- package/dist/schema-md.js +98 -0
- package/dist/schema-md.js.map +1 -0
- package/dist/skill-files.d.ts +3 -0
- package/dist/skill-files.d.ts.map +1 -0
- package/dist/skill-files.js +362 -0
- package/dist/skill-files.js.map +1 -0
- package/dist/skills-scaffold.d.ts +10 -0
- package/dist/skills-scaffold.d.ts.map +1 -0
- package/dist/skills-scaffold.js +52 -0
- package/dist/skills-scaffold.js.map +1 -0
- package/dist/types.d.ts +309 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +111 -0
- package/dist/types.js.map +1 -0
- package/dist/writer/index.d.ts +7 -0
- package/dist/writer/index.d.ts.map +1 -0
- package/dist/writer/index.js +32 -0
- package/dist/writer/index.js.map +1 -0
- package/dist/writer/serializer.d.ts +4 -0
- package/dist/writer/serializer.d.ts.map +1 -0
- package/dist/writer/serializer.js +103 -0
- package/dist/writer/serializer.js.map +1 -0
- package/dist/writer/template.d.ts +3 -0
- package/dist/writer/template.d.ts.map +1 -0
- package/dist/writer/template.js +27 -0
- package/dist/writer/template.js.map +1 -0
- package/package.json +59 -7
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Idempotency Cache — dedupes retried writes per (project, tool, key).
|
|
3
|
+
//
|
|
4
|
+
// Scope intentionally per-project, not per-session: a client whose connection
|
|
5
|
+
// drops mid-retry reconnects with a fresh session ID and re-issues the write.
|
|
6
|
+
// Session-scoped cache would miss and create a duplicate. Collision across
|
|
7
|
+
// unrelated clients is the client's responsibility (use UUIDs).
|
|
8
|
+
//
|
|
9
|
+
// Replay returns the original response body byte-for-byte, with a fresh `_meta`
|
|
10
|
+
// envelope: { replayed: true, original_request_id, original_completed_at }.
|
|
11
|
+
// Clients that ignore `_meta` see a normal successful response.
|
|
12
|
+
//
|
|
13
|
+
// Bounded: 10-minute TTL, 10k-entry LRU cap. Tunable via env. In-memory only —
|
|
14
|
+
// process restart drops the cache; documented behavior.
|
|
15
|
+
// ============================================================================
|
|
16
|
+
const DEFAULT_TTL_MS = 10 * 60 * 1000;
|
|
17
|
+
const DEFAULT_MAX_ENTRIES = 10_000;
|
|
18
|
+
export class IdempotencyCache {
|
|
19
|
+
ttlMs;
|
|
20
|
+
maxEntries;
|
|
21
|
+
now;
|
|
22
|
+
// JS Map preserves insertion order; we exploit that for O(1) LRU:
|
|
23
|
+
// every successful get() reinserts the entry to move it to the tail.
|
|
24
|
+
entries = new Map();
|
|
25
|
+
constructor(opts = {}) {
|
|
26
|
+
this.ttlMs = opts.ttlMs ?? DEFAULT_TTL_MS;
|
|
27
|
+
this.maxEntries = opts.maxEntries ?? DEFAULT_MAX_ENTRIES;
|
|
28
|
+
this.now = opts.now ?? (() => Date.now());
|
|
29
|
+
}
|
|
30
|
+
/** Compose the cache key. Always use this — never pass raw strings. */
|
|
31
|
+
static composeKey(projectName, toolName, clientKey) {
|
|
32
|
+
return `${projectName}\u0000${toolName}\u0000${clientKey}`;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Return a cached entry if present and not expired, else null. Reinserts on
|
|
36
|
+
* hit so recent hits survive eviction.
|
|
37
|
+
*/
|
|
38
|
+
get(key) {
|
|
39
|
+
const entry = this.entries.get(key);
|
|
40
|
+
if (!entry)
|
|
41
|
+
return null;
|
|
42
|
+
if (this.now() - entry.insertedAtMs > this.ttlMs) {
|
|
43
|
+
this.entries.delete(key);
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
// Bump recency: delete + reinsert moves to the Map's tail.
|
|
47
|
+
this.entries.delete(key);
|
|
48
|
+
this.entries.set(key, entry);
|
|
49
|
+
return entry;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Store a response. Evicts the least-recently-used entry if the cap is full.
|
|
53
|
+
* Only call this for successful responses — failures must not short-circuit
|
|
54
|
+
* retries.
|
|
55
|
+
*/
|
|
56
|
+
put(key, entry) {
|
|
57
|
+
if (this.entries.has(key)) {
|
|
58
|
+
// Refresh existing entry
|
|
59
|
+
this.entries.delete(key);
|
|
60
|
+
}
|
|
61
|
+
this.entries.set(key, { ...entry, insertedAtMs: this.now() });
|
|
62
|
+
while (this.entries.size > this.maxEntries) {
|
|
63
|
+
const oldestKey = this.entries.keys().next().value;
|
|
64
|
+
if (oldestKey === undefined)
|
|
65
|
+
break;
|
|
66
|
+
this.entries.delete(oldestKey);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
size() {
|
|
70
|
+
return this.entries.size;
|
|
71
|
+
}
|
|
72
|
+
clear() {
|
|
73
|
+
this.entries.clear();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// ---- Module-level singleton (same pattern as response.ts provenance) --------
|
|
77
|
+
let cache = new IdempotencyCache();
|
|
78
|
+
/** Replace the singleton cache — called once from server startup with env-derived config. */
|
|
79
|
+
export function initIdempotencyCache(opts) {
|
|
80
|
+
cache = new IdempotencyCache(opts);
|
|
81
|
+
}
|
|
82
|
+
/** Access the current singleton. Never returns null. */
|
|
83
|
+
export function getIdempotencyCache() {
|
|
84
|
+
return cache;
|
|
85
|
+
}
|
|
86
|
+
// ---- Config from env --------------------------------------------------------
|
|
87
|
+
export function readIdempotencyEnv() {
|
|
88
|
+
const ttl = process.env.MAAD_IDEMPOTENCY_TTL_MS;
|
|
89
|
+
const max = process.env.MAAD_IDEMPOTENCY_MAX;
|
|
90
|
+
const opts = {};
|
|
91
|
+
if (ttl && !Number.isNaN(Number(ttl)))
|
|
92
|
+
opts.ttlMs = Number(ttl);
|
|
93
|
+
if (max && !Number.isNaN(Number(max)))
|
|
94
|
+
opts.maxEntries = Number(max);
|
|
95
|
+
return opts;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Returns true if the response body (parsed from content[0].text) has
|
|
99
|
+
* `ok: true`. Silent false on parse failure — never cache anything we can't
|
|
100
|
+
* confirm succeeded.
|
|
101
|
+
*/
|
|
102
|
+
export function isSuccessfulResponse(response) {
|
|
103
|
+
const first = response.content[0];
|
|
104
|
+
if (!first || first.type !== 'text')
|
|
105
|
+
return false;
|
|
106
|
+
try {
|
|
107
|
+
const parsed = JSON.parse(first.text);
|
|
108
|
+
return parsed.ok === true;
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Build a replay response: identical body to the cached original, plus a fresh
|
|
116
|
+
* `_meta` envelope identifying this as a replay. Clients that ignore `_meta`
|
|
117
|
+
* see a normal successful response.
|
|
118
|
+
*/
|
|
119
|
+
export function buildReplayResponse(cached, currentRequestId) {
|
|
120
|
+
const original = cached.response;
|
|
121
|
+
const first = original.content[0];
|
|
122
|
+
if (!first || first.type !== 'text')
|
|
123
|
+
return original;
|
|
124
|
+
let parsed;
|
|
125
|
+
try {
|
|
126
|
+
parsed = JSON.parse(first.text);
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
return original;
|
|
130
|
+
}
|
|
131
|
+
parsed._meta = {
|
|
132
|
+
request_id: currentRequestId,
|
|
133
|
+
replayed: true,
|
|
134
|
+
original_request_id: cached.originalRequestId,
|
|
135
|
+
original_completed_at: cached.originalCompletedAt,
|
|
136
|
+
};
|
|
137
|
+
return { content: [{ type: 'text', text: JSON.stringify(parsed) }] };
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Wrapper for mutating tool handlers. If `clientKey` is provided, checks the
|
|
141
|
+
* cache for a prior completed call matching `(projectName, toolName, clientKey)`.
|
|
142
|
+
* On hit: returns the cached response with fresh replay metadata.
|
|
143
|
+
* On miss: runs `fn()`, caches if successful, returns the result.
|
|
144
|
+
*
|
|
145
|
+
* `currentRequestId` is the request ID for this specific call (used both for
|
|
146
|
+
* _meta on replays and as `originalRequestId` when caching a fresh response).
|
|
147
|
+
* H6 will supply a real request ID from the MCP wrapper; for now callers can
|
|
148
|
+
* pass `crypto.randomUUID()` to satisfy the signature.
|
|
149
|
+
*/
|
|
150
|
+
export async function withIdempotency(projectName, toolName, clientKey, currentRequestId, fn) {
|
|
151
|
+
if (!clientKey)
|
|
152
|
+
return fn();
|
|
153
|
+
const cacheKey = IdempotencyCache.composeKey(projectName, toolName, clientKey);
|
|
154
|
+
const cached = cache.get(cacheKey);
|
|
155
|
+
if (cached) {
|
|
156
|
+
return buildReplayResponse(cached, currentRequestId);
|
|
157
|
+
}
|
|
158
|
+
const response = await fn();
|
|
159
|
+
if (isSuccessfulResponse(response)) {
|
|
160
|
+
cache.put(cacheKey, {
|
|
161
|
+
response,
|
|
162
|
+
originalRequestId: currentRequestId,
|
|
163
|
+
originalCompletedAt: new Date().toISOString(),
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
return response;
|
|
167
|
+
}
|
|
168
|
+
//# sourceMappingURL=idempotency.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"idempotency.js","sourceRoot":"","sources":["../../src/mcp/idempotency.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Result } from '../errors.js';
|
|
2
|
+
import type { InstanceCtx } from './ctx.js';
|
|
3
|
+
export type InstanceReloadSource = 'tool' | 'sighup';
|
|
4
|
+
export interface InstanceReloadResult {
|
|
5
|
+
source: InstanceReloadSource;
|
|
6
|
+
projectsAdded: string[];
|
|
7
|
+
projectsRemoved: string[];
|
|
8
|
+
sessionsCancelled: string[];
|
|
9
|
+
sessionsPruned: string[];
|
|
10
|
+
durationMs: number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Run a full instance reload from the config file recorded on the live
|
|
14
|
+
* InstanceConfig. Emits progress + audit log lines around the critical steps.
|
|
15
|
+
*
|
|
16
|
+
* Error codes that flow back to the caller (tool wraps them as MCP responses):
|
|
17
|
+
* - INSTANCE_RELOAD_SYNTHETIC (synthetic instance, no config file)
|
|
18
|
+
* - INSTANCE_RELOAD_IN_PROGRESS (another reload is active)
|
|
19
|
+
* - INSTANCE_RELOAD_FAILED (parse error, file read error, etc.)
|
|
20
|
+
* - INSTANCE_MUTATION_UNSUPPORTED (existing project path/role changed)
|
|
21
|
+
*/
|
|
22
|
+
export declare function performInstanceReload(ctx: InstanceCtx, source: InstanceReloadSource): Promise<Result<InstanceReloadResult>>;
|
|
23
|
+
//# sourceMappingURL=instance-reload.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"instance-reload.d.ts","sourceRoot":"","sources":["../../src/mcp/instance-reload.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAE3C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAI5C,MAAM,MAAM,oBAAoB,GAAG,MAAM,GAAG,QAAQ,CAAC;AAErD,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,oBAAoB,CAAC;IAC7B,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;GASG;AACH,wBAAsB,qBAAqB,CACzC,GAAG,EAAE,WAAW,EAChB,MAAM,EAAE,oBAAoB,GAC3B,OAAO,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC,CA4GvC"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Instance reload orchestrator — shared by maad_instance_reload tool and the
|
|
3
|
+
// SIGHUP handler. Re-parses the instance config from disk, delegates the diff
|
|
4
|
+
// + eviction to EnginePool.reloadInstance, then propagates the new config to
|
|
5
|
+
// SessionRegistry and cancels / prunes sessions whose projects were removed.
|
|
6
|
+
//
|
|
7
|
+
// Both call sites (tool handler, SIGHUP handler) invoke this with a `source`
|
|
8
|
+
// string that flows into the audit event so operators can tell apart
|
|
9
|
+
// operator-initiated reloads from OS-signal reloads.
|
|
10
|
+
//
|
|
11
|
+
// Failure modes never leave the runtime half-applied: diff mutations are
|
|
12
|
+
// detected + rejected before any eviction, and the instance ref swap happens
|
|
13
|
+
// only after the pool has finished its part.
|
|
14
|
+
// ============================================================================
|
|
15
|
+
import { ok, singleErr } from '../errors.js';
|
|
16
|
+
import { loadInstance } from '../instance/config.js';
|
|
17
|
+
import { logInstanceReload, logInstanceReloadProgress } from '../logging.js';
|
|
18
|
+
/**
|
|
19
|
+
* Run a full instance reload from the config file recorded on the live
|
|
20
|
+
* InstanceConfig. Emits progress + audit log lines around the critical steps.
|
|
21
|
+
*
|
|
22
|
+
* Error codes that flow back to the caller (tool wraps them as MCP responses):
|
|
23
|
+
* - INSTANCE_RELOAD_SYNTHETIC (synthetic instance, no config file)
|
|
24
|
+
* - INSTANCE_RELOAD_IN_PROGRESS (another reload is active)
|
|
25
|
+
* - INSTANCE_RELOAD_FAILED (parse error, file read error, etc.)
|
|
26
|
+
* - INSTANCE_MUTATION_UNSUPPORTED (existing project path/role changed)
|
|
27
|
+
*/
|
|
28
|
+
export async function performInstanceReload(ctx, source) {
|
|
29
|
+
const startedAt = Date.now();
|
|
30
|
+
// Acquire the reload slot FIRST, before any I/O — otherwise concurrent
|
|
31
|
+
// callers can both pass the in-progress check while one is still parsing
|
|
32
|
+
// its yaml. tryBeginReload also handles the synthetic-instance fast path
|
|
33
|
+
// so both checks live in one place.
|
|
34
|
+
const begin = ctx.pool.tryBeginReload();
|
|
35
|
+
if (!begin.ok) {
|
|
36
|
+
const message = begin.code === 'INSTANCE_RELOAD_SYNTHETIC'
|
|
37
|
+
? 'Synthetic (legacy --project) instances cannot be reloaded. Start the server with --instance <path> to enable reload.'
|
|
38
|
+
: 'Another instance reload is already running. Retry once it completes.';
|
|
39
|
+
return singleErr(begin.code, message);
|
|
40
|
+
}
|
|
41
|
+
// From here on, every return path must call ctx.pool.endReload to release
|
|
42
|
+
// the slot and record success/failure stats.
|
|
43
|
+
let outcome = { ok: false };
|
|
44
|
+
try {
|
|
45
|
+
const configPath = ctx.instance.configPath;
|
|
46
|
+
if (!configPath) {
|
|
47
|
+
// Defense: file-source instances should always carry configPath. Treat
|
|
48
|
+
// missing as a failed reload rather than a silent no-op.
|
|
49
|
+
return singleErr('INSTANCE_RELOAD_FAILED', 'Instance has source="file" but no configPath recorded; cannot re-parse.');
|
|
50
|
+
}
|
|
51
|
+
logInstanceReloadProgress({ source, phase: 'start' });
|
|
52
|
+
// Re-parse the config file. Parse errors leave the running instance intact.
|
|
53
|
+
const loaded = await loadInstance(configPath);
|
|
54
|
+
if (!loaded.ok) {
|
|
55
|
+
const first = loaded.errors[0];
|
|
56
|
+
const detail = loaded.errors.map(e => `${e.code}: ${e.message}`).join('; ');
|
|
57
|
+
logInstanceReloadProgress({
|
|
58
|
+
source,
|
|
59
|
+
phase: 'failed',
|
|
60
|
+
code: first?.code ?? 'INSTANCE_RELOAD_FAILED',
|
|
61
|
+
message: detail,
|
|
62
|
+
durationMs: Date.now() - startedAt,
|
|
63
|
+
});
|
|
64
|
+
return singleErr('INSTANCE_RELOAD_FAILED', `Failed to load instance config from ${configPath}: ${detail}`);
|
|
65
|
+
}
|
|
66
|
+
const newInstance = loaded.value;
|
|
67
|
+
// Pool applies the diff: mutation check + eviction of removed engines.
|
|
68
|
+
const diffResult = await ctx.pool.applyDiff(newInstance);
|
|
69
|
+
if (!diffResult.ok) {
|
|
70
|
+
const first = diffResult.errors[0];
|
|
71
|
+
logInstanceReloadProgress({
|
|
72
|
+
source,
|
|
73
|
+
phase: 'failed',
|
|
74
|
+
code: first?.code ?? 'INSTANCE_RELOAD_FAILED',
|
|
75
|
+
message: first?.message ?? 'Apply diff failed',
|
|
76
|
+
durationMs: Date.now() - startedAt,
|
|
77
|
+
});
|
|
78
|
+
return diffResult;
|
|
79
|
+
}
|
|
80
|
+
const diff = diffResult.value;
|
|
81
|
+
// Propagate the new config to downstream refs that hold their own copy.
|
|
82
|
+
ctx.instance = newInstance;
|
|
83
|
+
ctx.sessions.setInstance(newInstance);
|
|
84
|
+
// Session fallout: single-mode sessions on removed projects → cancelled;
|
|
85
|
+
// multi-mode sessions → whitelist pruned (cancelled only if it drains).
|
|
86
|
+
const cancelled = [];
|
|
87
|
+
const pruned = [];
|
|
88
|
+
for (const name of diff.removed) {
|
|
89
|
+
cancelled.push(...ctx.sessions.cancelByProject(name));
|
|
90
|
+
const { prunedSessions, cancelledSessions } = ctx.sessions.pruneProjectFromWhitelist(name);
|
|
91
|
+
pruned.push(...prunedSessions);
|
|
92
|
+
cancelled.push(...cancelledSessions);
|
|
93
|
+
}
|
|
94
|
+
const durationMs = Date.now() - startedAt;
|
|
95
|
+
logInstanceReload({
|
|
96
|
+
source,
|
|
97
|
+
projectsAdded: diff.added.length,
|
|
98
|
+
projectsRemoved: diff.removed.length,
|
|
99
|
+
projectsAddedNames: diff.added,
|
|
100
|
+
projectsRemovedNames: diff.removed,
|
|
101
|
+
sessionsCancelled: cancelled.length,
|
|
102
|
+
sessionsPruned: pruned.length,
|
|
103
|
+
durationMs,
|
|
104
|
+
});
|
|
105
|
+
logInstanceReloadProgress({
|
|
106
|
+
source,
|
|
107
|
+
phase: 'complete',
|
|
108
|
+
projectsAdded: diff.added.length,
|
|
109
|
+
projectsRemoved: diff.removed.length,
|
|
110
|
+
durationMs,
|
|
111
|
+
});
|
|
112
|
+
outcome = { ok: true, diff };
|
|
113
|
+
return ok({
|
|
114
|
+
source,
|
|
115
|
+
projectsAdded: diff.added,
|
|
116
|
+
projectsRemoved: diff.removed,
|
|
117
|
+
sessionsCancelled: cancelled,
|
|
118
|
+
sessionsPruned: pruned,
|
|
119
|
+
durationMs,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
finally {
|
|
123
|
+
ctx.pool.endReload(outcome);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=instance-reload.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"instance-reload.js","sourceRoot":"","sources":["../../src/mcp/instance-reload.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type OperationKind = 'read' | 'write';
|
|
2
|
+
export declare const READ_TOOLS: ReadonlySet<string>;
|
|
3
|
+
export declare const WRITE_TOOLS: ReadonlySet<string>;
|
|
4
|
+
export declare const ENGINE_LESS_TOOLS: ReadonlySet<string>;
|
|
5
|
+
/**
|
|
6
|
+
* Returns the operation kind for a tool that flows through `withEngine`.
|
|
7
|
+
* Returns null for engine-less tools OR unknown names — callers must
|
|
8
|
+
* distinguish via {@link isEngineLess}.
|
|
9
|
+
*/
|
|
10
|
+
export declare function getKindForTool(name: string): OperationKind | null;
|
|
11
|
+
/**
|
|
12
|
+
* True if the tool is instance/session-scoped and does not pass through
|
|
13
|
+
* `withEngine`. Callers using {@link getKindForTool} should check this
|
|
14
|
+
* before treating a null return as "missing annotation".
|
|
15
|
+
*/
|
|
16
|
+
export declare function isEngineLess(name: string): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Every engine-bound tool name currently registered. Used by CI tests that
|
|
19
|
+
* cross-check against the MCP server's actual registrations to catch drift.
|
|
20
|
+
*/
|
|
21
|
+
export declare function allEngineToolNames(): string[];
|
|
22
|
+
//# sourceMappingURL=kinds.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kinds.d.ts","sourceRoot":"","sources":["../../src/mcp/kinds.ts"],"names":[],"mappings":"AAgBA,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,OAAO,CAAC;AAI7C,eAAO,MAAM,UAAU,EAAE,WAAW,CAAC,MAAM,CAiBzC,CAAC;AAIH,eAAO,MAAM,WAAW,EAAE,WAAW,CAAC,MAAM,CAQ1C,CAAC;AAKH,eAAO,MAAM,iBAAiB,EAAE,WAAW,CAAC,MAAM,CAchD,CAAC;AAqBH;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAIjE;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAElD;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,EAAE,CAE7C"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Operation kind metadata — every MCP tool that flows through `withEngine`
|
|
3
|
+
// declares read vs write. Reads invoke the handler directly; writes wrap it
|
|
4
|
+
// in `engine.runExclusive` so the per-engine FIFO mutex serializes them.
|
|
5
|
+
//
|
|
6
|
+
// The classification is the source of truth at the request boundary. The
|
|
7
|
+
// engine itself no longer self-wraps its mutation methods (0.5.0 R4) — the
|
|
8
|
+
// mutex lives at the MCP entry point where concurrency policy belongs.
|
|
9
|
+
//
|
|
10
|
+
// Missing annotations are caught at:
|
|
11
|
+
// - module load (disjointness assertion below)
|
|
12
|
+
// - runtime in `withEngine` (MISSING_OPERATION_KIND for any tool that
|
|
13
|
+
// passes through without a registered kind)
|
|
14
|
+
// - CI (tests/mcp/kinds.test.ts cross-checks against registered tools)
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// Tools that flow through withEngine and do not mutate git / SQLite /
|
|
17
|
+
// in-memory cache / filesystem state.
|
|
18
|
+
export const READ_TOOLS = new Set([
|
|
19
|
+
'maad_get',
|
|
20
|
+
'maad_query',
|
|
21
|
+
'maad_search',
|
|
22
|
+
'maad_related',
|
|
23
|
+
'maad_schema',
|
|
24
|
+
'maad_aggregate',
|
|
25
|
+
'maad_verify',
|
|
26
|
+
'maad_join',
|
|
27
|
+
'maad_changes_since',
|
|
28
|
+
'maad_history',
|
|
29
|
+
'maad_audit',
|
|
30
|
+
'maad_scan',
|
|
31
|
+
'maad_summary',
|
|
32
|
+
'maad_describe',
|
|
33
|
+
'maad_validate',
|
|
34
|
+
'maad_health',
|
|
35
|
+
]);
|
|
36
|
+
// Tools that flow through withEngine and may mutate engine state. Must
|
|
37
|
+
// acquire the write mutex.
|
|
38
|
+
export const WRITE_TOOLS = new Set([
|
|
39
|
+
'maad_create',
|
|
40
|
+
'maad_update',
|
|
41
|
+
'maad_bulk_create',
|
|
42
|
+
'maad_bulk_update',
|
|
43
|
+
'maad_delete',
|
|
44
|
+
'maad_reindex',
|
|
45
|
+
'maad_reload',
|
|
46
|
+
]);
|
|
47
|
+
// Tools that do not flow through withEngine — they operate on session /
|
|
48
|
+
// instance state only and never resolve an engine. OperationKind does not
|
|
49
|
+
// apply. Listed here so kind-coverage assertions know to skip them.
|
|
50
|
+
export const ENGINE_LESS_TOOLS = new Set([
|
|
51
|
+
'maad_projects',
|
|
52
|
+
'maad_use_project',
|
|
53
|
+
'maad_use_projects',
|
|
54
|
+
'maad_current_session',
|
|
55
|
+
'maad_instance_reload',
|
|
56
|
+
'maad_subscribe',
|
|
57
|
+
'maad_unsubscribe',
|
|
58
|
+
'maad_subscriptions',
|
|
59
|
+
'maad_issue_token',
|
|
60
|
+
'maad_revoke_token',
|
|
61
|
+
'maad_rotate_token',
|
|
62
|
+
'maad_list_tokens',
|
|
63
|
+
'maad_show_token',
|
|
64
|
+
]);
|
|
65
|
+
// Assert the three sets are disjoint. A tool belongs to exactly one bucket.
|
|
66
|
+
// Runs at module load — a bad entry crashes the process at startup rather
|
|
67
|
+
// than silently shipping.
|
|
68
|
+
(function assertDisjoint() {
|
|
69
|
+
const seen = new Map();
|
|
70
|
+
const register = (bucket, names) => {
|
|
71
|
+
for (const name of names) {
|
|
72
|
+
const prev = seen.get(name);
|
|
73
|
+
if (prev) {
|
|
74
|
+
throw new Error(`kinds.ts: tool "${name}" listed in both "${prev}" and "${bucket}"`);
|
|
75
|
+
}
|
|
76
|
+
seen.set(name, bucket);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
register('READ_TOOLS', READ_TOOLS);
|
|
80
|
+
register('WRITE_TOOLS', WRITE_TOOLS);
|
|
81
|
+
register('ENGINE_LESS_TOOLS', ENGINE_LESS_TOOLS);
|
|
82
|
+
})();
|
|
83
|
+
/**
|
|
84
|
+
* Returns the operation kind for a tool that flows through `withEngine`.
|
|
85
|
+
* Returns null for engine-less tools OR unknown names — callers must
|
|
86
|
+
* distinguish via {@link isEngineLess}.
|
|
87
|
+
*/
|
|
88
|
+
export function getKindForTool(name) {
|
|
89
|
+
if (READ_TOOLS.has(name))
|
|
90
|
+
return 'read';
|
|
91
|
+
if (WRITE_TOOLS.has(name))
|
|
92
|
+
return 'write';
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* True if the tool is instance/session-scoped and does not pass through
|
|
97
|
+
* `withEngine`. Callers using {@link getKindForTool} should check this
|
|
98
|
+
* before treating a null return as "missing annotation".
|
|
99
|
+
*/
|
|
100
|
+
export function isEngineLess(name) {
|
|
101
|
+
return ENGINE_LESS_TOOLS.has(name);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Every engine-bound tool name currently registered. Used by CI tests that
|
|
105
|
+
* cross-check against the MCP server's actual registrations to catch drift.
|
|
106
|
+
*/
|
|
107
|
+
export function allEngineToolNames() {
|
|
108
|
+
return [...READ_TOOLS, ...WRITE_TOOLS];
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=kinds.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kinds.js","sourceRoot":"","sources":["../../src/mcp/kinds.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,2EAA2E;AAC3E,4EAA4E;AAC5E,yEAAyE;AACzE,EAAE;AACF,yEAAyE;AACzE,2EAA2E;AAC3E,uEAAuE;AACvE,EAAE;AACF,qCAAqC;AACrC,iDAAiD;AACjD,wEAAwE;AACxE,gDAAgD;AAChD,yEAAyE;AACzE,+EAA+E;AAI/E,sEAAsE;AACtE,sCAAsC;AACtC,MAAM,CAAC,MAAM,UAAU,GAAwB,IAAI,GAAG,CAAC;IACrD,UAAU;IACV,YAAY;IACZ,aAAa;IACb,cAAc;IACd,aAAa;IACb,gBAAgB;IAChB,aAAa;IACb,WAAW;IACX,oBAAoB;IACpB,cAAc;IACd,YAAY;IACZ,WAAW;IACX,cAAc;IACd,eAAe;IACf,eAAe;IACf,aAAa;CACd,CAAC,CAAC;AAEH,uEAAuE;AACvE,2BAA2B;AAC3B,MAAM,CAAC,MAAM,WAAW,GAAwB,IAAI,GAAG,CAAC;IACtD,aAAa;IACb,aAAa;IACb,kBAAkB;IAClB,kBAAkB;IAClB,aAAa;IACb,cAAc;IACd,aAAa;CACd,CAAC,CAAC;AAEH,wEAAwE;AACxE,0EAA0E;AAC1E,oEAAoE;AACpE,MAAM,CAAC,MAAM,iBAAiB,GAAwB,IAAI,GAAG,CAAC;IAC5D,eAAe;IACf,kBAAkB;IAClB,mBAAmB;IACnB,sBAAsB;IACtB,sBAAsB;IACtB,gBAAgB;IAChB,kBAAkB;IAClB,oBAAoB;IACpB,kBAAkB;IAClB,mBAAmB;IACnB,mBAAmB;IACnB,kBAAkB;IAClB,iBAAiB;CAClB,CAAC,CAAC;AAEH,4EAA4E;AAC5E,0EAA0E;AAC1E,0BAA0B;AAC1B,CAAC,SAAS,cAAc;IACtB,MAAM,IAAI,GAAG,IAAI,GAAG,EAAkB,CAAC;IACvC,MAAM,QAAQ,GAAG,CAAC,MAAc,EAAE,KAA0B,EAAE,EAAE;QAC9D,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAC5B,IAAI,IAAI,EAAE,CAAC;gBACT,MAAM,IAAI,KAAK,CAAC,mBAAmB,IAAI,qBAAqB,IAAI,UAAU,MAAM,GAAG,CAAC,CAAC;YACvF,CAAC;YACD,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACzB,CAAC;IACH,CAAC,CAAC;IACF,QAAQ,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;IACnC,QAAQ,CAAC,aAAa,EAAE,WAAW,CAAC,CAAC;IACrC,QAAQ,CAAC,mBAAmB,EAAE,iBAAiB,CAAC,CAAC;AACnD,CAAC,CAAC,EAAE,CAAC;AAEL;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,IAAI,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,OAAO,MAAM,CAAC;IACxC,IAAI,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,OAAO,OAAO,CAAC;IAC1C,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,OAAO,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AACrC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB;IAChC,OAAO,CAAC,GAAG,UAAU,EAAE,GAAG,WAAW,CAAC,CAAC;AACzC,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { MaadEngine } from '../engine.js';
|
|
2
|
+
export interface StartupResult {
|
|
3
|
+
engine: MaadEngine;
|
|
4
|
+
warnings: string[];
|
|
5
|
+
}
|
|
6
|
+
export declare function startupEngine(projectRoot: string): Promise<StartupResult>;
|
|
7
|
+
export declare function registerShutdownHooks(engine: MaadEngine | null, cleanup?: () => Promise<void>): void;
|
|
8
|
+
//# sourceMappingURL=lifecycle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../../src/mcp/lifecycle.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAI1C,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,UAAU,CAAC;IACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,wBAAsB,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CA6C/E;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,EAAE,OAAO,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAYpG"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// MCP Lifecycle — startup validation, engine init, shutdown hooks
|
|
3
|
+
// Extracted from server.ts to keep server focused on transport.
|
|
4
|
+
// ============================================================================
|
|
5
|
+
import { existsSync } from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { MaadEngine } from '../engine.js';
|
|
8
|
+
import { logger } from '../engine/logger.js';
|
|
9
|
+
import { ensureProjectSkills } from '../skills-scaffold.js';
|
|
10
|
+
export async function startupEngine(projectRoot) {
|
|
11
|
+
const resolved = path.resolve(projectRoot);
|
|
12
|
+
const warnings = [];
|
|
13
|
+
// Validate project exists
|
|
14
|
+
if (!existsSync(resolved)) {
|
|
15
|
+
throw new Error(`Project directory not found: ${resolved}`);
|
|
16
|
+
}
|
|
17
|
+
// Init engine — self-heals _registry, _schema, _backend on empty projects
|
|
18
|
+
const engine = new MaadEngine();
|
|
19
|
+
const initResult = await engine.init(resolved);
|
|
20
|
+
if (!initResult.ok) {
|
|
21
|
+
const messages = initResult.errors.map(e => `${e.code}: ${e.message}`).join('; ');
|
|
22
|
+
throw new Error(`Engine initialization failed: ${messages}`);
|
|
23
|
+
}
|
|
24
|
+
// Ensure _skills/ guide files exist for the agent. Non-blocking: a failed
|
|
25
|
+
// write (permissions, read-only FS) logs a warning but does not crash the
|
|
26
|
+
// server — engine is already up and usable without the skills.
|
|
27
|
+
if (!engine.isReadOnly()) {
|
|
28
|
+
const skillsResult = ensureProjectSkills(resolved);
|
|
29
|
+
if (skillsResult.created.length > 0) {
|
|
30
|
+
logger.info('lifecycle', 'startup', `Scaffolded skills: ${skillsResult.created.join(', ')}`);
|
|
31
|
+
}
|
|
32
|
+
for (const e of skillsResult.errors) {
|
|
33
|
+
logger.bestEffort('lifecycle', 'startup', `Failed to write ${e.file}: ${e.message}`);
|
|
34
|
+
warnings.push(`skills scaffold: ${e.file} — ${e.message}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
// Check for recovery actions
|
|
38
|
+
const recovery = engine.getStartupRecovery();
|
|
39
|
+
if (recovery.length > 0) {
|
|
40
|
+
for (const action of recovery) {
|
|
41
|
+
logger.info('lifecycle', 'startup', action);
|
|
42
|
+
warnings.push(action);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// Log health
|
|
46
|
+
const health = engine.health();
|
|
47
|
+
logger.info('lifecycle', 'startup', `Engine ready: ${health.totalDocuments} docs, ${health.registeredTypes} types, git: ${health.gitAvailable}`);
|
|
48
|
+
return { engine, warnings };
|
|
49
|
+
}
|
|
50
|
+
export function registerShutdownHooks(engine, cleanup) {
|
|
51
|
+
// Legacy shim retained for any caller that still uses the single-engine
|
|
52
|
+
// flow. The modern path is installSignalHandlers(target, opts) from
|
|
53
|
+
// src/mcp/shutdown.ts with pool + rate limiter. Server.ts uses that path.
|
|
54
|
+
const shutdown = async () => {
|
|
55
|
+
logger.info('lifecycle', 'shutdown', 'Shutting down...');
|
|
56
|
+
if (engine)
|
|
57
|
+
engine.close();
|
|
58
|
+
if (cleanup)
|
|
59
|
+
await cleanup();
|
|
60
|
+
process.exit(0);
|
|
61
|
+
};
|
|
62
|
+
process.on('SIGINT', shutdown);
|
|
63
|
+
process.on('SIGTERM', shutdown);
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=lifecycle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lifecycle.js","sourceRoot":"","sources":["../../src/mcp/lifecycle.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { InstanceCtx } from './ctx.js';
|
|
2
|
+
/**
|
|
3
|
+
* Event shape emitted on durable writes. Used both as the payload mapped into
|
|
4
|
+
* MCP's `notifications/resources/updated` and as the match target for
|
|
5
|
+
* per-session filters.
|
|
6
|
+
*/
|
|
7
|
+
export interface ChangeEvent {
|
|
8
|
+
action: 'create' | 'update' | 'delete';
|
|
9
|
+
docId: string;
|
|
10
|
+
docType: string;
|
|
11
|
+
project: string;
|
|
12
|
+
updatedAt: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Callback that pushes a ChangeEvent to a single MCP session's client. The
|
|
16
|
+
* transport layer (HTTP or stdio) registers this closure when it builds the
|
|
17
|
+
* per-session McpServer. Failures are logged but never thrown — a broken
|
|
18
|
+
* notifier channel must not kill the write that triggered it.
|
|
19
|
+
*/
|
|
20
|
+
export type Notifier = (event: ChangeEvent) => Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* Register a notifier for a session. Called by the transport once the
|
|
23
|
+
* per-session McpServer is connected and ready to emit. Overwrites any
|
|
24
|
+
* prior registration for the same sessionId (transport reconnect path).
|
|
25
|
+
*/
|
|
26
|
+
export declare function registerNotifier(sessionId: string, fn: Notifier): void;
|
|
27
|
+
/**
|
|
28
|
+
* Remove a notifier. Called when a session is destroyed (SessionRegistry
|
|
29
|
+
* close handler fans this out). Safe to call with an unknown sessionId.
|
|
30
|
+
*/
|
|
31
|
+
export declare function unregisterNotifier(sessionId: string): void;
|
|
32
|
+
/**
|
|
33
|
+
* Current count of registered notifiers. Used by maad_health telemetry and
|
|
34
|
+
* by zero-overhead fast-path detection in `notifyWrite`.
|
|
35
|
+
*/
|
|
36
|
+
export declare function notifierCount(): number;
|
|
37
|
+
/**
|
|
38
|
+
* Test hook — drops every registered notifier. Production code never calls
|
|
39
|
+
* this; vitest suites that reuse the process need it between cases.
|
|
40
|
+
*/
|
|
41
|
+
export declare function __resetNotifiers(): void;
|
|
42
|
+
/**
|
|
43
|
+
* Fan-out entry point called by MCP write tool handlers after a successful
|
|
44
|
+
* durable commit. Iterates sessions with subscriptions, matches the filter,
|
|
45
|
+
* and invokes each session's notifier. Zero overhead when no session has a
|
|
46
|
+
* subscription (the count check bypasses the iteration).
|
|
47
|
+
*
|
|
48
|
+
* Caller MUST only invoke this when `writeDurable === true` on the engine
|
|
49
|
+
* result — the durability gate is upstream, not here. Delete operations
|
|
50
|
+
* should pass `action: 'delete'` and the docType recorded before deletion.
|
|
51
|
+
*/
|
|
52
|
+
export declare function notifyWrite(ctx: InstanceCtx, event: ChangeEvent): Promise<void>;
|
|
53
|
+
/**
|
|
54
|
+
* 0.6.12 — Inventory of active subscriptions across an instance. Produced
|
|
55
|
+
* by `maad_subscriptions` (admin-only). Extracted here so the aggregation
|
|
56
|
+
* logic is unit-testable without spinning up a full MCP server.
|
|
57
|
+
*
|
|
58
|
+
* `byProject` reflects "who would receive an event for project X": single-mode
|
|
59
|
+
* sessions bin under their activeProject; multi-mode sessions without an
|
|
60
|
+
* explicit project filter bin under every whitelisted project; sessions with
|
|
61
|
+
* an explicit filter bin only under that project.
|
|
62
|
+
*
|
|
63
|
+
* `byDocType` uses `*` as the any-type bucket (subscribers that omitted the
|
|
64
|
+
* docTypes filter) so admins can tell at a glance how many "firehose" vs
|
|
65
|
+
* "type-narrowed" subscribers are active.
|
|
66
|
+
*/
|
|
67
|
+
export interface SubscriptionInventoryEntry {
|
|
68
|
+
sessionId: string;
|
|
69
|
+
mode: string | null;
|
|
70
|
+
activeProject: string | null;
|
|
71
|
+
whitelist: string[] | null;
|
|
72
|
+
subscription: {
|
|
73
|
+
docTypes: string[] | null;
|
|
74
|
+
project: string | null;
|
|
75
|
+
createdAt: string;
|
|
76
|
+
};
|
|
77
|
+
bindingSource: string | null;
|
|
78
|
+
lastActivityAt: string;
|
|
79
|
+
}
|
|
80
|
+
export interface SubscriptionInventory {
|
|
81
|
+
totalSubscriptions: number;
|
|
82
|
+
subscriptions: SubscriptionInventoryEntry[];
|
|
83
|
+
byProject: Record<string, number>;
|
|
84
|
+
byDocType: Record<string, number>;
|
|
85
|
+
}
|
|
86
|
+
export declare function collectSubscriptions(sessions: InstanceCtx['sessions']): SubscriptionInventory;
|
|
87
|
+
//# sourceMappingURL=notifications.d.ts.map
|