Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 17 additions & 11 deletions .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,12 @@ To help you navigate the project, here is a breakdown of the key directories:
- `src/Analysis`: Core logic for model loading and script execution.
- `src/Workspace`: Model persistence and management.
- `src/Plugins`: Logic for reading, installing and loading plugins.
- **`Integrations/VS-Code`**: VS Code extension
- **`Integrations/MCP`**: MCP Server
- **`integrations/`**: Integration packages and SDKs.
- `sdks/csharp/DxWorks.ScriptBee.Analysis.Sdk`: The C# **Server SDK** for building custom Analysis Services. It
provides the endpoint definitions, contracts, validators, use-case interfaces, and a registration extension so
implementors can focus solely on business logic.
- `MCP/`: The MCP Server integration.
- `VS-Code/`: The VS Code extension.
- **`docs/`**: Documentation source files, built with **VitePress**.
- **`scripts/`**: Utility scripts for building, packing, and maintaining the project.

Expand Down Expand Up @@ -88,22 +92,24 @@ ScriptBee uses a namespaced tagging strategy to manage independent component rel

### Tag Naming Convention

| Tag Format | Component | Artifact(s) | GitHub Release |
|:-----------------------|:-----------------------------------|:--------------------------------------|:---------------|
| `v<version>` | Unified Application (Backend + UI) | Docker: `dxworks/scriptbee` | **Yes (Main)** |
| `analysis@<version>` | Analysis Microservice | Docker: `dxworks/scriptbee-analysis` | No (Silent) |
| `plugin-api@<version>` | Plugin API | NuGet: `DxWorks.ScriptBee.Plugin.Api` | **Yes** |
| `bundle@<version>` | Default Plugin Bundle | Zip Archive | **Yes** |
| `vs-code@<version>` | VS Code Extension | vsix file | **Yes** |
| `mcp@<version>` | MCP Server | Docker: `dxworks/scriptbee-mcp` | **Yes** |
| Tag Format | Component | Artifact(s) | GitHub Release |
|:-------------------------|:-----------------------------------|:----------------------------------------|:---------------|
| `v<version>` | Unified Application (Backend + UI) | Docker: `dxworks/scriptbee` | **Yes (Main)** |
| `analysis@<version>` | Analysis Microservice | Docker: `dxworks/scriptbee-analysis` | No (Silent) |
| `analysis-sdk@<version>` | Analysis Server SDK (C#) | NuGet: `DxWorks.ScriptBee.Analysis.Sdk` | **Yes** |
| `plugin-api@<version>` | Plugin API | NuGet: `DxWorks.ScriptBee.Plugin.Api` | **Yes** |
| `bundle@<version>` | Default Plugin Bundle | Zip Archive | **Yes** |
| `vs-code@<version>` | VS Code Extension | vsix file | **Yes** |
| `mcp@<version>` | MCP Server | Docker: `dxworks/scriptbee-mcp` | **Yes** |

### Release Process

1. **Tagging**: Push a tag matching one of the patterns above (e.g., `git tag v1.2.3 && git push origin v1.2.3`).
2. **Automation**: GitHub Actions will automatically:
- Run the relevant test suites.
- Build and publish the artifacts to Docker Hub or NuGet.
- Draft a GitHub Release with auto-generated release notes (for `v*`, `plugin-api@`, and `bundle@`).
- Draft a GitHub Release with auto-generated release notes (for `v*`, `analysis-sdk@`, `plugin-api@`, and
`bundle@`).
3. **Manual Finalization**: The maintainer should review the drafted GitHub Release, refine the notes if necessary, and
publish it.

Expand Down
125 changes: 121 additions & 4 deletions Integrations/MCP/ScriptBee.MCP/Generated/GatewayApi.g.cs
Original file line number Diff line number Diff line change
Expand Up @@ -754,6 +754,53 @@ public partial interface IGatewayApi
[Delete("/api/projects/{projectId}/instances/{instanceId}")]
Task InstancesDelete(string projectId, string instanceId, CancellationToken cancellationToken = default);

/// <summary>Delete a saved file</summary>
/// <remarks>Deletes a saved file from the project and cleans up references.</remarks>
/// <param name="projectId">projectId parameter</param>
/// <param name="fileId">fileId parameter</param>
/// <param name="cancellationToken">The cancellation token to cancel the request.</param>
/// <returns>A <see cref="Task"/> that completes when the request is finished.</returns>
/// <exception cref="ApiException">
/// Thrown when the request returns a non-success status code:
/// <list type="table">
/// <listheader>
/// <term>Status</term>
/// <description>Description</description>
/// </listheader>
/// <item>
/// <term>404</term>
/// <description>Not Found</description>
/// </item>
/// </list>
/// </exception>
[Headers("Accept: application/json")]
[Delete("/api/projects/{projectId}/saved-files/{fileId}")]
Task SavedFilesDelete(string projectId, string fileId, CancellationToken cancellationToken = default);

/// <summary>Upload saved files</summary>
/// <remarks>Uploads one or more files to the project without requiring a loader.</remarks>
/// <param name="projectId">projectId parameter</param>
/// <param name="files">files parameter</param>
/// <param name="cancellationToken">The cancellation token to cancel the request.</param>
/// <returns>OK</returns>
/// <exception cref="ApiException">
/// Thrown when the request returns a non-success status code:
/// <list type="table">
/// <listheader>
/// <term>Status</term>
/// <description>Description</description>
/// </listheader>
/// <item>
/// <term>404</term>
/// <description>Not Found</description>
/// </item>
/// </list>
/// </exception>
[Multipart]
[Headers("Accept: application/json")]
[Post("/api/projects/{projectId}/saved-files")]
Task<UploadSavedFilesResponse> SavedFilesPost(string projectId, IEnumerable<byte[]> files, CancellationToken cancellationToken = default);

/// <summary>Get available linkers for an instance</summary>
/// <remarks>Retrieves a list of all linkers available for the specified project instance.</remarks>
/// <param name="projectId">projectId parameter</param>
Expand All @@ -774,7 +821,7 @@ public partial interface IGatewayApi
/// <exception cref="ApiException">Thrown when the request returns a non-success status code.</exception>
[Headers("Accept: application/json")]
[Get("/api/projects/{projectId}/instances/{instanceId}/loaders")]
Task<GetLoadersResponse> Loaders(string projectId, string instanceId, CancellationToken cancellationToken = default);
Task<GetLoadersResponse> LoadersGet(string projectId, string instanceId, CancellationToken cancellationToken = default);

/// <summary>Get current instance context</summary>
/// <remarks>Retrieves the current data context for the specified project instance.</remarks>
Expand Down Expand Up @@ -975,6 +1022,55 @@ public partial interface IGatewayApi
[Post("/api/projects/{projectId}/instances/{instanceId}/context/reload")]
Task Reload(string projectId, string instanceId, CancellationToken cancellationToken = default);

/// <summary>Unload a model file from instance context</summary>
/// <remarks>Unloads a specific model file for a loader from the instance context and project state.</remarks>
/// <param name="projectId">projectId parameter</param>
/// <param name="instanceId">instanceId parameter</param>
/// <param name="loaderId">loaderId parameter</param>
/// <param name="fileId">fileId parameter</param>
/// <param name="cancellationToken">The cancellation token to cancel the request.</param>
/// <returns>A <see cref="Task"/> that completes when the request is finished.</returns>
/// <exception cref="ApiException">
/// Thrown when the request returns a non-success status code:
/// <list type="table">
/// <listheader>
/// <term>Status</term>
/// <description>Description</description>
/// </listheader>
/// <item>
/// <term>404</term>
/// <description>Not Found</description>
/// </item>
/// </list>
/// </exception>
[Headers("Accept: application/json")]
[Delete("/api/projects/{projectId}/instances/{instanceId}/context/loaders/{loaderId}/files/{fileId}")]
Task Files(string projectId, string instanceId, string loaderId, string fileId, CancellationToken cancellationToken = default);

/// <summary>Unload all model files for a loader from instance context</summary>
/// <remarks>Unloads all model files for a specific loader from the instance context and project state.</remarks>
/// <param name="projectId">projectId parameter</param>
/// <param name="instanceId">instanceId parameter</param>
/// <param name="loaderId">loaderId parameter</param>
/// <param name="cancellationToken">The cancellation token to cancel the request.</param>
/// <returns>A <see cref="Task"/> that completes when the request is finished.</returns>
/// <exception cref="ApiException">
/// Thrown when the request returns a non-success status code:
/// <list type="table">
/// <listheader>
/// <term>Status</term>
/// <description>Description</description>
/// </listheader>
/// <item>
/// <term>404</term>
/// <description>Not Found</description>
/// </item>
/// </list>
/// </exception>
[Headers("Accept: application/json")]
[Delete("/api/projects/{projectId}/instances/{instanceId}/context/loaders/{loaderId}")]
Task LoadersDelete(string projectId, string instanceId, string loaderId, CancellationToken cancellationToken = default);

/// <summary>Get authentication configuration</summary>
/// <remarks>Retrieves the authentication configuration for the application.</remarks>
/// <param name="cancellationToken">The cancellation token to cancel the request.</param>
Expand Down Expand Up @@ -2463,9 +2559,11 @@ public IDictionary<string, object> AdditionalProperties
public partial class LoadContextCommand
{

[JsonPropertyName("filesToLoad")]
public IDictionary<string, ICollection<string>> FilesToLoad { get; set; }

[JsonPropertyName("loaderIds")]
[System.ComponentModel.DataAnnotations.Required]
public ICollection<string> LoaderIds { get; set; } = new System.Collections.ObjectModel.Collection<string>();
public ICollection<string> LoaderIds { get; set; }

private IDictionary<string, object> _additionalProperties;

Expand Down Expand Up @@ -2756,7 +2854,7 @@ public partial class ProjectDetails

[JsonPropertyName("savedFiles")]
[System.ComponentModel.DataAnnotations.Required]
public IDictionary<string, ICollection<FileData>> SavedFiles { get; set; } = new Dictionary<string, ICollection<FileData>>();
public ICollection<FileData> SavedFiles { get; set; } = new System.Collections.ObjectModel.Collection<FileData>();

[JsonPropertyName("loadedFiles")]
[System.ComponentModel.DataAnnotations.Required]
Expand Down Expand Up @@ -3099,6 +3197,25 @@ public IDictionary<string, object> AdditionalProperties

}

[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.7.1.0 (NJsonSchema v11.6.1.0 (Newtonsoft.Json v13.0.0.0))")]
public partial class UploadSavedFilesResponse
{

[JsonPropertyName("files")]
[System.ComponentModel.DataAnnotations.Required]
public ICollection<FileData> Files { get; set; } = new System.Collections.ObjectModel.Collection<FileData>();

private IDictionary<string, object> _additionalProperties;

[JsonExtensionData]
public IDictionary<string, object> AdditionalProperties
{
get { return _additionalProperties ?? (_additionalProperties = new Dictionary<string, object>()); }
set { _additionalProperties = value; }
}

}

[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.7.1.0 (NJsonSchema v11.6.1.0 (Newtonsoft.Json v13.0.0.0))")]
public partial class UserInfo
{
Expand Down
2 changes: 1 addition & 1 deletion Integrations/MCP/ScriptBee.MCP/Tools/ContextTools.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public async Task<GetLoadersResponse> GetLoaders(
[Description("The unique identifier of the project.")] string projectId,
[Description("The unique identifier of the instance.")] string instanceId,
CancellationToken cancellationToken
) => await gatewayApi.Loaders(projectId, instanceId, cancellationToken);
) => await gatewayApi.LoadersGet(projectId, instanceId, cancellationToken);

[McpServerTool]
[Description(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<RootNamespace>DxWorks.ScriptBee.Analysis.Sdk.Tests</RootNamespace>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\DxWorks.ScriptBee.Analysis.Sdk\DxWorks.ScriptBee.Analysis.Sdk.csproj" />
<ProjectReference Include="..\..\..\..\ScriptBeeWebApp\src\Workspace\Analysis\Analysis\Analysis.csproj" />
<ProjectReference Include="..\..\..\..\ScriptBeeWebApp\src\Workspace\Artifacts\Artifacts\Artifacts.csproj" />
<ProjectReference Include="..\..\..\..\ScriptBeeWebApp\test\Common\Tests.Common\Tests.Common.csproj" />
</ItemGroup>

<ItemGroup>
<Content Include="**\TestData\**">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
using System.Buffers.Binary;
using System.Text;
using ScriptBee.Common.CodeGeneration;
using ScriptBee.Service.Analysis;

namespace ScriptBee.Analysis.Service.Tests;
namespace DxWorks.ScriptBee.Analysis.Sdk.Tests;

public class FileBundlerTests
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using DxWorks.ScriptBee.Analysis.Sdk.Model;
using NSubstitute;
using ScriptBee.Artifacts;
using ScriptBee.Domain.Model.File;

namespace DxWorks.ScriptBee.Analysis.Sdk.Tests.Model;

public class ModelFileLoaderTests
{
private readonly IFileModelService _fileModelService = Substitute.For<IFileModelService>();

private readonly ModelFileLoader _loader;

public ModelFileLoaderTests()
{
_loader = new ModelFileLoader(_fileModelService);
}

[Fact]
public async Task LoadModelFileStreamAsync_WhenCalled_UsesFileModelService()
{
var fileId = new FileId("d245c7e2-1ec6-4b65-8672-d37ec5d31311");
using var expectedStream = new MemoryStream([1, 2, 3, 4]);
_fileModelService
.GetFileAsync(fileId, Arg.Any<CancellationToken>())
.Returns(expectedStream);

var result = await _loader.LoadModelFileStreamAsync(
fileId,
TestContext.Current.CancellationToken
);

result.ShouldBeSameAs(expectedStream);
await _fileModelService.Received(1).GetFileAsync(fileId, Arg.Any<CancellationToken>());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using DxWorks.ScriptBee.Analysis.Sdk.Results;
using NSubstitute;
using ScriptBee.Artifacts;
using ScriptBee.Domain.Model.File;

namespace DxWorks.ScriptBee.Analysis.Sdk.Tests.Results;

public class ScriptResultsStoreTests
{
private readonly IFileModelService _fileModelService = Substitute.For<IFileModelService>();

private readonly ScriptResultsStore _scriptResultsStore;

public ScriptResultsStoreTests()
{
_scriptResultsStore = new ScriptResultsStore(_fileModelService);
}

[Fact]
public async Task UploadFileAsync_WhenCalled_DelegatesToFileModelService()
{
var fileId = new FileId("0fd7a3a6-733a-4f1e-9bd0-7b4b981400c3");
using var fileStream = new MemoryStream([9, 8, 7]);
var metadata = new ScriptResultMetadata { };

await _scriptResultsStore.UploadFileAsync(
fileId,
fileStream,
metadata,
TestContext.Current.CancellationToken
);

await _fileModelService
.Received(1)
.UploadFileAsync(fileId, fileStream, metadata, Arg.Any<CancellationToken>());
}

private sealed class ScriptResultMetadata;
}
Loading
Loading