Add PHP Analyzer Plugin and Composer Lock Data Handling
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled

- Implemented the PhpAnalyzerPlugin to analyze PHP projects.
- Created ComposerLockData class to represent data from composer.lock files.
- Developed ComposerLockReader to load and parse composer.lock files asynchronously.
- Introduced ComposerPackage class to encapsulate package details.
- Added PhpPackage class to represent PHP packages with metadata and evidence.
- Implemented PhpPackageCollector to gather packages from ComposerLockData.
- Created PhpLanguageAnalyzer to perform analysis and emit results.
- Added capability signals for known PHP frameworks and CMS.
- Developed unit tests for the PHP language analyzer and its components.
- Included sample composer.lock and expected output for testing.
- Updated project files for the new PHP analyzer library and tests.
This commit is contained in:
StellaOps Bot
2025-11-22 14:02:49 +02:00
parent a7f3c7869a
commit b6b9ffc050
158 changed files with 16272 additions and 809 deletions

View File

@@ -749,6 +749,8 @@ public sealed class CommandHandlersTests
new[] { "impact", "impact " },
forceRefresh: false,
timeoutSeconds: 0,
outputFormat: AdvisoryOutputFormat.Table,
outputPath: null,
verbose: false,
cancellationToken: CancellationToken.None);
@@ -777,6 +779,104 @@ public sealed class CommandHandlersTests
}
}
[Fact]
public async Task HandleAdviseRunAsync_WritesMarkdownWithCitations()
{
var originalExit = Environment.ExitCode;
var originalConsole = AnsiConsole.Console;
using var tempDir = new TempDirectory();
var outputPath = Path.Combine(tempDir.Path, "advisory.md");
var testConsole = new TestConsole();
try
{
Environment.ExitCode = 0;
AnsiConsole.Console = testConsole;
var planResponse = new AdvisoryPipelinePlanResponseModel
{
TaskType = AdvisoryAiTaskType.Summary.ToString(),
CacheKey = "cache-markdown",
PromptTemplate = "prompts/advisory/summary.liquid",
Budget = new AdvisoryTaskBudgetModel
{
PromptTokens = 256,
CompletionTokens = 64
},
Chunks = Array.Empty<PipelineChunkSummaryModel>(),
Vectors = Array.Empty<PipelineVectorSummaryModel>(),
Metadata = new Dictionary<string, string>()
};
var outputResponse = new AdvisoryPipelineOutputModel
{
CacheKey = planResponse.CacheKey,
TaskType = planResponse.TaskType,
Profile = "default",
Prompt = "Sanitized prompt",
Response = "Rendered summary body.",
Citations = new[]
{
new AdvisoryOutputCitationModel { Index = 1, DocumentId = "doc-9", ChunkId = "chunk-9" }
},
Metadata = new Dictionary<string, string>(),
Guardrail = new AdvisoryOutputGuardrailModel
{
Blocked = false,
SanitizedPrompt = "Sanitized prompt",
Violations = Array.Empty<AdvisoryOutputGuardrailViolationModel>(),
Metadata = new Dictionary<string, string>()
},
Provenance = new AdvisoryOutputProvenanceModel
{
InputDigest = "sha256:markdown-in",
OutputHash = "sha256:markdown-out",
Signatures = Array.Empty<string>()
},
GeneratedAtUtc = DateTimeOffset.Parse("2025-11-06T12:00:00Z", CultureInfo.InvariantCulture),
PlanFromCache = false
};
var backend = new StubBackendClient(new JobTriggerResult(true, "ok", null, null))
{
AdvisoryPlanResponse = planResponse,
AdvisoryOutputResponse = outputResponse
};
var provider = BuildServiceProvider(backend);
await CommandHandlers.HandleAdviseRunAsync(
provider,
AdvisoryAiTaskType.Summary,
"ADV-4",
null,
null,
null,
"default",
Array.Empty<string>(),
forceRefresh: false,
timeoutSeconds: 0,
outputFormat: AdvisoryOutputFormat.Markdown,
outputPath: outputPath,
verbose: false,
cancellationToken: CancellationToken.None);
var markdown = await File.ReadAllTextAsync(outputPath);
Assert.Contains("Citations", markdown, StringComparison.OrdinalIgnoreCase);
Assert.Contains("doc-9", markdown, StringComparison.OrdinalIgnoreCase);
Assert.Contains("chunk-9", markdown, StringComparison.OrdinalIgnoreCase);
Assert.True(File.Exists(outputPath));
Assert.Contains("Rendered summary body", markdown, StringComparison.OrdinalIgnoreCase);
Assert.Equal(0, Environment.ExitCode);
Assert.Contains("Citations", testConsole.Output, StringComparison.OrdinalIgnoreCase);
}
finally
{
AnsiConsole.Console = originalConsole;
Environment.ExitCode = originalExit;
}
}
[Fact]
public async Task HandleAdviseRunAsync_ReturnsGuardrailExitCodeOnBlock()
{
@@ -855,6 +955,8 @@ public sealed class CommandHandlersTests
Array.Empty<string>(),
forceRefresh: true,
timeoutSeconds: 0,
outputFormat: AdvisoryOutputFormat.Table,
outputPath: null,
verbose: false,
cancellationToken: CancellationToken.None);
@@ -913,6 +1015,8 @@ public sealed class CommandHandlersTests
Array.Empty<string>(),
forceRefresh: false,
timeoutSeconds: 0,
outputFormat: AdvisoryOutputFormat.Table,
outputPath: null,
verbose: false,
cancellationToken: CancellationToken.None);