@ductape/mcp 0.2.30 → 0.2.32

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/CHANGELOG.md CHANGED
@@ -6,6 +6,7 @@
6
6
 
7
7
  ## Unreleased
8
8
 
9
+ - Added `cli-authentication` guidance for automatic Google/GitHub browser OAuth, validated loopback callbacks, trusted-terminal handoff, credential non-disclosure, and external session refresh.
9
10
  - Added source-verified graph/vector projection guidance to the `features`, `events`, `graphs`, and `vector` docs topics, including exact graph/vector payloads, NestJS consumer routing, replay/idempotency, projection-state ordering, rebuild/drift repair, and an explicit stop condition for the currently missing code-first `ctx.vector` API.
10
11
  - Added read-only `ductape_events_topic_setup` and `ductape_events_validate_project` tools and made the canonical one-topic-per-file `ductape/events/<topic-tag>.topic.json` layout explicit and enforceable through the CLI.
11
12
  - Mark `ductape_function_setup` as read-only, non-destructive, idempotent, and closed-world in both MCP registration APIs so hosts do not incorrectly require mutation approval for its pure setup-plan generation.
package/dist/index.js CHANGED
@@ -909,12 +909,13 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
909
909
  vector.deleteIndex [{ product, env, vector, name }]
910
910
  vector.listIndexes [{ product, env, vector }]
911
911
  vector.count [{ product, env, vector, namespace? }]
912
- vector.actions.create [{ product, vector, actionTag, name, operation, template, description?, parameters? }]
913
- vector.actions.update [{ product, vector, actionTag, name?, description?, template?, parameters? }]
914
- vector.actions.fetch [{ product, vector, actionTag }]
915
- vector.actions.fetchAll [{ product, vector }]
916
- vector.actions.delete [{ product, vector, actionTag }]
917
- vector.actions.execute [{ product, env, vector, action, input }]
912
+ vector.action.create [{ product, vector, actionTag, name, operation, template, description?, parameters? }]
913
+ vector.action.update [{ product, vector, actionTag, name?, description?, template?, parameters? }]
914
+ vector.action.fetch [{ product, vector, actionTag }]
915
+ vector.action.fetchAll [{ product, vector }]
916
+ vector.action.delete [{ product, vector, actionTag }]
917
+ vector.action.execute [{ product, env, vector, action, input, session? }]
918
+ ← synchronous saved-action execution. Do not use vector.query for a saved action.
918
919
 
919
920
  ━━━ MODULE: features ━━━
920
921
  Feature definitions are code-first. Use features.define in application source; do not call
@@ -944,6 +945,9 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
944
945
  // ctx.input – typed runtime input; always compiles to $Input{} operators
945
946
  // ctx.sampleInput – compile-time sample for loop/branch discovery only
946
947
  // ctx.step(tag, fn, rollback?, opts?) – define a durable step
948
+ // ctx.when.eq/ne/gt/gte/lt/lte/truthy/falsy(...) – build portable runtime conditions
949
+ // ctx.branch(condition, { then, else? }) – record both paths as conditioned steps
950
+ // ctx.each(ctx.sampleInput.items, async (item, index) => ...) – deterministic compile-time expansion
947
951
  // ctx.api.run({ app, action, input }) – call an app action (NOT 'event' -- that field name
948
952
  // only applies to ctx.database/ctx.notification/ctx.storage below, never ctx.api/ctx.action)
949
953
  // ctx.database.execute({ database, action, input }) for a saved database action
@@ -1500,6 +1504,7 @@ function runCli(command) {
1500
1504
  'or use an OAuth flow:',
1501
1505
  ' ductape login --browser google',
1502
1506
  ' ductape login --browser github',
1507
+ 'Browser OAuth returns to the CLI automatically through a validated 127.0.0.1 callback.',
1503
1508
  '',
1504
1509
  'Do not paste a password, OAuth callback token, or stored CLI credential into an agent prompt.',
1505
1510
  'After login succeeds, retry the same ductape_cli command.',
@@ -1551,7 +1556,7 @@ const docsInputSchema = z.object({
1551
1556
  topic: z.string().describe('Feature topic to look up. Supported: ' +
1552
1557
  'transactions, presave, triggers, aggregations, migrations, indexes, performance, actions, ' +
1553
1558
  'graphs, storage, cloud, vector, warehouse, secrets, apps, products, sessions, caches, ' +
1554
- 'notifications, resilience, features, portable-functions, events, logs, migration, frontend, frontend-analytics, client, react, vue'),
1559
+ 'notifications, resilience, features, portable-functions, events, logs, migration, cli-authentication, frontend, frontend-analytics, client, react, vue'),
1555
1560
  });
1556
1561
  const eventsDiscoveryInputSchema = z.object({
1557
1562
  query: z.string().optional().describe('Capability or recovery search, including DLQ, dead letter, failed messages, retry, poison message, replay, or consumer failure.'),
@@ -1607,6 +1612,34 @@ const migrationInputSchema = z.object({
1607
1612
  write: z.boolean().optional().default(false).describe('Write redacted advisory artifacts only. This never writes application code or executable Ductape assets.'),
1608
1613
  });
1609
1614
  const DOCS = {
1615
+ 'cli-authentication': `
1616
+ DUCTAPE CLI AUTHENTICATION
1617
+
1618
+ For an interactive local login, prefer browser OAuth:
1619
+ ductape login --browser google
1620
+ ductape login --browser github
1621
+
1622
+ The CLI opens the provider in the user's browser, starts a temporary loopback listener on
1623
+ 127.0.0.1, validates the returned OAuth state, exchanges the callback token, stores the resulting
1624
+ session in the local Ductape credential store, and then continues workspace selection. The user
1625
+ does not copy a callback token or share credentials with an agent.
1626
+
1627
+ Run browser login only in the user's trusted local terminal. An MCP tool, agent subprocess, CI
1628
+ runner, container, SSH session, or remote development host may not own the browser's loopback
1629
+ interface. Do not invoke interactive login through ductape_cli and do not ask the user to paste a
1630
+ password, OAuth callback token, auth token, or stored credential into chat.
1631
+
1632
+ Use email/password login only when the user deliberately chooses it in their own terminal:
1633
+ ductape login
1634
+
1635
+ Use --token only as a compatibility or automation fallback when the user already possesses a
1636
+ valid OAuth callback token through a trusted flow. Never solicit that token in an agent prompt.
1637
+
1638
+ After login succeeds, retry the original ductape_cli operation. The MCP re-reads a previously
1639
+ missing or expired external CLI session without requiring an MCP restart. If a command returns
1640
+ HTTP 401 while workspace reads still succeed, treat it as endpoint authorization or command
1641
+ routing failure, not as a reason to re-authenticate.
1642
+ `.trim(),
1610
1643
  'portable-functions': `
1611
1644
  PORTABLE APPLICATION FUNCTIONS
1612
1645
 
@@ -2551,10 +2584,12 @@ Schema management:
2551
2584
  graph.dropIndex / dropConstraint / listIndexes / listConstraints
2552
2585
 
2553
2586
  Saved actions (parameterized queries stored on the product):
2554
- graph.createAction [{ graphTag?, name, description?, operation, query, parameters: [{ name, type, required?, defaultValue?, description? }] }, productTag?]
2555
- graph.listActions [graphTag?, productTag?]
2556
- graph.execute [{ product, env, graph, action, input?, session?, cache? }]
2557
- graph.dispatch [data] ← call ductape_generate_payload FIRST
2587
+ graph.action.create [{ product, graph|graphTag, name, description?, operation, query, parameters? }]
2588
+ graph.action.fetchAll [{ product, graph|graphTag }]
2589
+ graph.action.fetch [{ product, graph|graphTag, action|actionTag }]
2590
+ graph.action.execute [{ product, env, graph, action, input, session? }]
2591
+ graph.action.dispatch [{ product, env, graph, event, input, schedule? }] ← call ductape_generate_payload FIRST
2592
+ Use graph.action.execute for a saved action. graph.query is only for a direct provider-native query.
2558
2593
 
2559
2594
  Supported index types: btree | fulltext | vector | range | point | text
2560
2595
  Supported constraint types: UNIQUE | EXISTS | NODE_KEY
@@ -3341,6 +3376,20 @@ CONFIGURATION BOUNDARY
3341
3376
  Workbench is also supported. Never route their administrative create/update methods through
3342
3377
  ductape_execute: its publishable-key runtime proxy will fail.
3343
3378
 
3379
+ CLI COMMANDS (aliases are normalized; use these exact shapes):
3380
+ ductape resources feature list --product <product> --json
3381
+ ductape resources feature get -t <feature> --product <product> --json
3382
+ ductape resources quota list --product <product> --json
3383
+ ductape resources quota get -t <quota> --product <product> --json
3384
+ ductape resources fallback list --product <product> --json
3385
+ ductape resources fallback get -t <fallback> --product <product> --json
3386
+ ductape resources health list --product <product> --json
3387
+ ductape resources health get -t <healthcheck> --product <product> --json
3388
+ Create/update use the same resource type plus -f <asset.json>. Singular/plural feature(s),
3389
+ fallback(s), and healthcheck(s) aliases are accepted, but canonical generated commands should
3390
+ use feature, quota, fallback, and health. These commands are administrative catalogue CRUD;
3391
+ runtime run/execute operations belong to the SDK or authenticated execution surface.
3392
+
3344
3393
  QUOTAS — weighted/provider-capacity routing pools (NOT request rate limiting):
3345
3394
  Workbench definition shape:
3346
3395
  {
@@ -3897,15 +3946,83 @@ STEP 6 — WRITE the feature into the project codebase
3897
3946
  script does, invoked only through the explicit CLI command.
3898
3947
 
3899
3948
  STEP 7 — HANDLE conditionals, loops, and branching (when applicable)
3900
- Branch on step result (early return in handler):
3901
- Add branchOverrides: { stepTag: { field: value } } so all branches are recorded
3902
- At runtime the executor evaluates the real result and skips or runs later steps accordingly
3949
+ MANDATORY CONTROL-FLOW RULES:
3950
+ 1. A stored Feature executes its compiled steps. The JavaScript handler is NOT rerun at runtime.
3951
+ 2. Never use if/else, switch, ?:, ??, &&, ||, optional chaining, map, filter, find, some,
3952
+ every, reduce, for, for..of, forEach, or while on a ctx.step() result or ctx.input proxy.
3953
+ JavaScript would evaluate the recording proxy, not the future runtime value.
3954
+ 3. Use ctx.branch(ctx.when.*, { then, else }) for runtime decisions.
3955
+ 4. Use ctx.each only with a concrete ctx.sampleInput array to expand a fixed step graph.
3956
+ 5. Put runtime-sized collection algorithms and arbitrary business logic in a registered
3957
+ portable Function, then invoke it through ctx.functions inside a step.
3958
+ 6. Direct Date.now() and Math.random() are forbidden in handlers. Use ctx.transform.now(),
3959
+ ctx.transform.uuid(), and ctx.transform.concat/replace/substring/upper/lower/trim so values
3960
+ are generated at execution time. Use a portable Function for domain-specific generators.
3961
+ 7. Never use branchOverrides in newly generated code. It exists only to migrate old handlers.
3962
+
3963
+ Prefer explicit portable branching for step results:
3964
+ const result = await ctx.step("find", () => ctx.database.execute(...));
3965
+ await ctx.branch(ctx.when.eq(result.count, 0), {
3966
+ then: () => ctx.step("create", () => ctx.database.execute(...)),
3967
+ else: () => ctx.step("reuse", () => ctx.database.execute(...)),
3968
+ });
3969
+ → ctx.when also supports ne, gt, gte, lt, lte, truthy, falsy, and/or.
3970
+ → Do not use ordinary if/else, ternary, or ?? directly on an unresolved step result.
3971
+ → branchOverrides remains a compatibility escape hatch for older Feature source.
3972
+ → If both paths must feed a later common step, do not assume ctx.branch returns a selected
3973
+ value. Either record the downstream operation inside each path, persist a shared result that
3974
+ the later step can read, or move the find-or-create algorithm into one portable Function.
3903
3975
  Loop over input array:
3904
- → Add recordInput: { items: [{ id: "1" }, { id: "2" }] } and iterate ctx.sampleInput.items
3976
+ → Add recordInput: { items: [{ id: "1" }, { id: "2" }] } and call
3977
+ ctx.each(ctx.sampleInput.items, async (item, index) => { ... })
3905
3978
  → Use unique step tags per iteration (e.g. "process-" + item.id)
3979
+ → map/filter/find/indexing on ctx.sampleInput are compile-time graph discovery only.
3980
+ → Runtime-sized map/filter/find/some/every and arbitrary loops belong in a registered portable
3981
+ Function invoked through ctx.functions; never guess or freeze their results during recording.
3906
3982
  Switch/if-else on feature input values:
3907
- Add recordScenarios: [{ type: "a" }, { type: "b" }] — handler runs once per scenario
3908
- Only the scenario whose input matches runs at execution time
3983
+ Prefer ctx.branch(ctx.when.eq(ctx.input.type, "a"), { then, else }).
3984
+ recordScenarios is for legacy compile-time graph discovery, not a runtime branch primitive.
3985
+
3986
+ SAFE COPY-READY PATTERNS:
3987
+ Runtime input branch:
3988
+ await ctx.branch(ctx.when.eq(ctx.input.kind, "refund"), {
3989
+ then: () => ctx.step("refund", () => ctx.api.run({ app, action: "refund", input: {...} })),
3990
+ else: () => ctx.step("charge", () => ctx.api.run({ app, action: "charge", input: {...} })),
3991
+ });
3992
+ Nested/indexed result check:
3993
+ await ctx.branch(ctx.when.eq(search.data.length, 0), { then: ..., else: ... });
3994
+ Compound condition:
3995
+ await ctx.branch(ctx.when.and(
3996
+ ctx.when.eq(result.status, "ready"),
3997
+ ctx.when.gt(result.count, 0),
3998
+ ), { then: ..., else: ... });
3999
+ Runtime payment/reference string (never frozen at sync time):
4000
+ const reference = ctx.transform.concat(
4001
+ "pay_", ctx.transform.now(), "_", ctx.transform.uuid(),
4002
+ );
4003
+ await ctx.step("charge", () => ctx.fallback.execute({
4004
+ fallback: "charge-payment", input: { reference, ... },
4005
+ }));
4006
+ Fixed graph expansion:
4007
+ recordInput: { regions: ["ng", "gh"] },
4008
+ handler: async (ctx) => ctx.each(ctx.sampleInput.regions, async (region, index) => {
4009
+ await ctx.step("sync-region-" + index, () => ctx.functions.invoke(syncFn, "run", { region }));
4010
+ });
4011
+
4012
+ UNSAFE — NEVER GENERATE:
4013
+ if (result.count === 0) await ctx.step(...);
4014
+ const customer = found[0] ?? await ctx.step(...);
4015
+ ctx.input.items.map(item => ctx.step(...));
4016
+ for (const item of ctx.input.items) await ctx.step(...);
4017
+ const reference = "pay_" + Date.now() + "_" + Math.random();
4018
+
4019
+ BEFORE SYNCING A FEATURE:
4020
+ → Confirm every runtime decision uses ctx.branch + ctx.when.
4021
+ → Confirm every ctx.step tag is unique across both paths and every expanded iteration.
4022
+ → Confirm no runtime proxy is consumed by native JavaScript control flow or array iteration.
4023
+ → Confirm every ctx.step contains a portable ctx component/function call.
4024
+ → Compile locally, inspect schema.steps, and verify both branch paths, condition, depends_on,
4025
+ and operator-valued inputs are present before persisting.
3909
4026
 
3910
4027
  STEP 8 — SET rollbacks for reversible steps
3911
4028
  Any step that allocates a resource should undo it if a later step fails.
@@ -4015,22 +4132,36 @@ Feature statuses: pending | running | completed | failed | rolled_back | rolling
4015
4132
 
4016
4133
  ━━━ FEATURE RECORDING SEMANTICS ━━━
4017
4134
 
4018
- When you call features.define({ handler }), the handler runs TWICE:
4135
+ When you call features.define({ handler }), there are two distinct phases, but the handler itself
4136
+ runs only during compilation/recording:
4019
4137
 
4020
4138
  1. RECORDING PHASE (at define time) — handler is called with a RecordingContext.
4021
4139
  All ctx.step() calls return lightweight proxy objects, not real data.
4022
4140
  This phase captures the step graph: which steps exist, their types, tags, and declared
4023
4141
  inputs/outputs. No real API calls, DB queries, or side effects occur.
4024
4142
  Arbitrary JS code OUTSIDE ctx.step() ALSO runs during recording — with proxy values.
4025
- For loops: supply recordInput and iterate ctx.sampleInput so all iterations are recorded.
4143
+ For loops: supply recordInput and use ctx.each(ctx.sampleInput.items, ...) so all iterations are recorded.
4026
4144
  ctx.input is always the runtime operator surface and must never expose recordInput literals.
4027
- For branches: use branchOverrides so each path is captured.
4028
-
4029
- 2. EXECUTION PHASE (at runtime) handler is called with a real ExecutionContext.
4030
- ctx.step() actually executes. All real Ductape component calls happen.
4031
- Arbitrary JS logic (math, string ops, conditionals on step results) runs for real.
4032
-
4033
- Implication: put all meaningful business logic INSIDE ctx.step() handlers, not in the
4145
+ For branches: use ctx.branch(ctx.when.*, { then, else }) so both paths are captured. Use
4146
+ branchOverrides only for compatibility with existing Feature handlers.
4147
+ Never make an authorization, validation, tenancy, or other security decision by branching on
4148
+ ctx.input during recording. Use ctx.sampleInput only to discover graph shape; enforce security
4149
+ invariants inside a runtime portable Function or recorded step.
4150
+
4151
+ ductape features sync [filter] sets DUCTAPE_SYNC_MODE=1 and, when filtered,
4152
+ DUCTAPE_FEATURE_FILTER=<filter>. This is an administrative catalogue-only lifecycle: framework
4153
+ integrations and project readiness hooks must not start HTTP listeners, provider probes,
4154
+ schedulers, or Event consumers. Apply the filter before booting unrelated service modules.
4155
+
4156
+ 2. EXECUTION PHASE (at runtime) — the stored schema.steps graph is interpreted by FeatureExecutor.
4157
+ The original JavaScript handler is NOT called. Step inputs and conditions resolve operators
4158
+ such as $Input{}, $Sequence{}, $Step{}, and $Now against runtime state. Only recorded portable
4159
+ component/function operations execute.
4160
+
4161
+ Implication: arbitrary JavaScript control flow never becomes runtime behavior merely because it
4162
+ appears in the handler. Express runtime branches through ctx.branch/ctx.when, fixed graph expansion
4163
+ through ctx.each(ctx.sampleInput...), and runtime-sized algorithms through ctx.functions. Put all
4164
+ meaningful business logic INSIDE recorded portable operations, not in the
4034
4165
  outer handler body. Code in the outer body runs during recording with proxy values and
4035
4166
  may behave unexpectedly (e.g. typeof proxy === 'object' is true but .someField is a proxy).
4036
4167
 
@@ -5100,8 +5231,10 @@ EXACT CODE-FIRST VECTOR CONTEXT:
5100
5231
  ductape vector actions get <action-tag> --vector <vector-tag> --json
5101
5232
  Always list and inspect first. Reuse an existing action when operation, namespace/filter/template,
5102
5233
  parameters, and output semantics match; do not create a duplicate action under a new name.
5103
- Update/delete/list use the same \`ductape vector actions\` surface. At runtime the SDK namespace is
5104
- \`ductape.vector.actions.*\` (plural), while Feature reuse is singular \`ctx.vector.execute(...)\`.
5234
+ Update/delete/list use the same plural \`ductape vector actions\` CLI surface. Runtime SDK/MCP calls
5235
+ use the singular \`ductape.vector.action.*\` namespace; execute saved actions with
5236
+ \`ductape.vector.action.execute({ product, env, vector, action, input })\`. Feature handlers instead
5237
+ use \`ctx.vector.execute({ vector, action, input })\` because product/env/session are inherited.
5105
5238
  Vertex AI Vector Search stores and searches supplied vectors. It does NOT generate embeddings.
5106
5239
  Do not claim that a Vertex Vector Search index embeds text.
5107
5240
 
@@ -5461,7 +5594,9 @@ const cliInputSchema = z.object({
5461
5594
  '"features sync" (runs the project\'s own "features:sync" npm script) — never call ' +
5462
5595
  'features.define from the app\'s normal startup path, since that blocks every boot on ' +
5463
5596
  'Ductape API reachability. See ductape_docs for the full convention.\n\n' +
5464
- 'The CLI uses the user\'s local logged-in session (ductape login) no key is required.'),
5597
+ 'The CLI uses the user\'s local logged-in session. Prefer browser OAuth in a trusted local terminal: ' +
5598
+ 'ductape login --browser google (or github). It returns automatically through a validated loopback callback. ' +
5599
+ 'Never invoke interactive login through MCP or ask the user for credentials.'),
5465
5600
  });
5466
5601
  async function loadMcpSdk() {
5467
5602
  try {
@@ -5626,6 +5761,25 @@ async function main() {
5626
5761
  const isLocalMigrationGuidance = (firstWord === 'migrate-codebase' && !args.command.includes('--ensure-product')) ||
5627
5762
  firstWord.startsWith('migration-');
5628
5763
  const isDiagnostic = firstWord === 'doctor';
5764
+ if (firstWord === 'login') {
5765
+ return {
5766
+ content: [{
5767
+ type: 'text',
5768
+ text: [
5769
+ 'Ductape login must be completed by the user in a trusted local terminal, not through an MCP tool process.',
5770
+ '',
5771
+ 'Preferred browser OAuth:',
5772
+ ' ductape login --browser google',
5773
+ ' ductape login --browser github',
5774
+ '',
5775
+ 'The browser returns to the CLI automatically through a validated 127.0.0.1 callback; no token copying is required.',
5776
+ 'Never ask the user to paste a password, OAuth callback token, auth token, or stored CLI credential into the agent.',
5777
+ 'After login succeeds, retry the original ductape_cli operation. The MCP will detect the new session.',
5778
+ ].join('\n'),
5779
+ }],
5780
+ isError: true,
5781
+ };
5782
+ }
5629
5783
  if (!isAuthCommand && !isLocalMigrationGuidance && !isDiagnostic) {
5630
5784
  // Cache successful authentication, but re-check a missing/expired session on every call.
5631
5785
  // The user may complete `ductape login` in another terminal while this MCP process remains
@@ -5648,6 +5802,7 @@ async function main() {
5648
5802
  'or:',
5649
5803
  ` ductape login --browser google${wsFlag}`,
5650
5804
  ` ductape login --browser github${wsFlag}`,
5805
+ 'Browser OAuth returns to the CLI automatically through a validated 127.0.0.1 callback.',
5651
5806
  '',
5652
5807
  'Never ask the user to paste their password, OAuth callback token, or stored CLI credential into the agent.',
5653
5808
  'After login succeeds, retry the original ductape_cli command.',
@@ -6082,7 +6237,7 @@ async function main() {
6082
6237
  'index strategy, operation types) that should be confirmed with the user first.\n\n' +
6083
6238
  'Available topics: transactions, presave, triggers, aggregations, migrations, indexes, performance, actions, ' +
6084
6239
  'graphs, storage, cloud, vector, warehouse, secrets, apps, products, sessions, caches, ' +
6085
- 'notifications, resilience, features, portable-functions, events, logs, migration, frontend, frontend-analytics, client, react, vue',
6240
+ 'notifications, resilience, features, portable-functions, events, logs, migration, cli-authentication, frontend, frontend-analytics, client, react, vue',
6086
6241
  inputSchema: docsInputSchema,
6087
6242
  }, docsHandler);
6088
6243
  server.registerTool('ductape_events_discover', {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ductape/mcp",
3
- "version": "0.2.30",
3
+ "version": "0.2.32",
4
4
  "description": "MCP server that exposes Ductape SDK operations via the backend proxy",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -15,7 +15,7 @@
15
15
  ],
16
16
  "scripts": {
17
17
  "build": "tsc",
18
- "test": "npm run build && node scripts/check-cli-command-security.mjs && node scripts/check-frontend-analytics-guidance.mjs && node scripts/check-events-discovery.mjs && node scripts/check-schema-fallback.mjs && node scripts/check-paystack-action-schema.mjs && node scripts/check-portable-functions.mjs && node scripts/check-project-link-guidance.mjs && node scripts/check-graph-vector-projection-guidance.mjs && node scripts/check-asset-file-guidance.mjs",
18
+ "test": "npm run build && node scripts/check-cli-command-security.mjs && node scripts/check-frontend-analytics-guidance.mjs && node scripts/check-events-discovery.mjs && node scripts/check-schema-fallback.mjs && node scripts/check-paystack-action-schema.mjs && node scripts/check-portable-functions.mjs && node scripts/check-feature-control-flow.mjs && node scripts/check-project-link-guidance.mjs && node scripts/check-graph-vector-projection-guidance.mjs && node scripts/check-asset-file-guidance.mjs",
19
19
  "start": "node dist/index.js",
20
20
  "dev": "tsx src/index.ts"
21
21
  },