@amistio/cli 0.1.11 → 0.1.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -39,7 +39,7 @@ When `--tool copilot` uses the GitHub Copilot SDK, Amistio approves read-only pe
39
39
 
40
40
  `amistio runner status` reports local background runner state, latest heartbeat, and bounded resource usage when available. Resource usage is latest-sample runner process memory/CPU plus safe aggregate system memory/load signals; it does not include source files, environment variables, command lines, process lists, credentials, or arbitrary local paths.
41
41
 
42
- The runner advertises its supported work kinds in heartbeats. Current runners can claim read-only issue diagnosis jobs from the web Issues panel, generate root-cause analysis and a proposed fix, and submit that result without modifying source. Implementation is queued separately only after the user approves the analysis in the app.
42
+ The runner advertises its supported work kinds in heartbeats. Current runners can claim read-only issue diagnosis jobs from the web Issues panel, generate root-cause analysis and a proposed fix, and submit that result without modifying source. They can also claim read-only `securityPostureScan` jobs from the workspace Security panel or the hosted nightly cron scheduler. Security scan results contain bounded summaries, standard references, safe evidence, and repo-relative paths only; source, secrets, environment variables, command lines, process lists, credentials, and arbitrary local paths stay local. Implementation is queued separately only after the user approves an issue analysis or security remediation plan in the app.
43
43
 
44
44
  Runner setup and local-tool execution use bounded failure controls. `amistio run --watch` retries Git worktree preflight failures by releasing the claim for another attempt, then fails the work item after `--max-preflight-attempts` attempts, defaulting to 3. Active local-tool runs renew the work lease, and `--tool-timeout-seconds` caps tool execution, defaulting to 1800 seconds.
45
45
 
package/dist/index.js CHANGED
@@ -21,6 +21,9 @@ var itemTypeSchema = z.enum([
21
21
  "brainDocument",
22
22
  "generatedDraft",
23
23
  "issue",
24
+ "securityScan",
25
+ "securityFinding",
26
+ "securityPostureSnapshot",
24
27
  "syncCursor",
25
28
  "syncConflict",
26
29
  "workItem",
@@ -67,7 +70,7 @@ var workStatusSchema = z.enum([
67
70
  ]);
68
71
  var sourceSchema = z.enum(["web", "repo", "generated", "runner"]);
69
72
  var executionModeSchema = z.enum(["localRunner", "cloudSandbox"]);
70
- var workKindSchema = z.enum(["brainGeneration", "implementation", "planRevision", "assistantQuestion", "impactPreview", "issueDiagnosis"]);
73
+ var workKindSchema = z.enum(["brainGeneration", "implementation", "planRevision", "assistantQuestion", "impactPreview", "issueDiagnosis", "securityPostureScan"]);
71
74
  var generatedDraftStatusSchema = z.enum(["queued", "generating", "reviewing", "approved", "rejected", "changesRequested", "failed"]);
72
75
  var assistantQuestionModeSchema = z.enum(["brainOnly", "sourceAware"]);
73
76
  var impactRiskLevelSchema = z.enum(["low", "medium", "high", "critical"]);
@@ -76,6 +79,13 @@ var issueCategorySchema = z.enum(["bug", "regression", "brokenWorkflow", "securi
76
79
  var issueSeveritySchema = z.enum(["low", "medium", "high", "critical"]);
77
80
  var issueStatusSchema = z.enum(["queued", "diagnosing", "analysisReady", "approved", "changesRequested", "rejected", "implementationQueued", "implemented", "failed"]);
78
81
  var issueApprovalStateSchema = z.enum(["proposed", "approved", "changesRequested", "rejected", "implemented", "blocked"]);
82
+ var securityScanStatusSchema = z.enum(["queued", "running", "completed", "failed", "stale", "skipped"]);
83
+ var securityScanSourceSchema = z.enum(["nightly", "manual"]);
84
+ var securityFindingCategorySchema = z.enum(["owasp", "dependency", "supplyChain", "secretHygiene", "authentication", "authorization", "tenantIsolation", "headers", "redirects", "csrf", "cors", "ciCd", "logging", "runnerBoundary", "other"]);
85
+ var securityFindingSeveritySchema = z.enum(["info", "low", "medium", "high", "critical"]);
86
+ var securityFindingConfidenceSchema = z.enum(["low", "medium", "high"]);
87
+ var securityFindingStatusSchema = z.enum(["open", "planReady", "approved", "changesRequested", "dismissed", "acceptedRisk", "remediationQueued", "resolved", "failed"]);
88
+ var securityApprovalStateSchema = z.enum(["proposed", "approved", "changesRequested", "dismissed", "acceptedRisk", "implemented", "blocked"]);
79
89
  var activityEventTypeSchema = z.enum([
80
90
  "commandSubmitted",
81
91
  "generationQueued",
@@ -99,6 +109,14 @@ var activityEventTypeSchema = z.enum([
99
109
  "issueDiagnosisFailed",
100
110
  "issueReview",
101
111
  "issueImplementationQueued",
112
+ "securityScanQueued",
113
+ "securityScanStarted",
114
+ "securityScanCompleted",
115
+ "securityScanFailed",
116
+ "securityFindingOpened",
117
+ "securityFindingReviewed",
118
+ "securityRemediationPlanCreated",
119
+ "securityRemediationQueued",
102
120
  "handoffExported"
103
121
  ]);
104
122
  var activityEventActorSchema = z.enum(["webUser", "runner", "cli", "system"]);
@@ -349,6 +367,8 @@ var workItemSchema = baseItemSchema.extend({
349
367
  sourceWish: z.string().min(1).optional(),
350
368
  generatedDraftId: z.string().min(1).optional(),
351
369
  issueId: z.string().min(1).optional(),
370
+ securityScanId: z.string().min(1).optional(),
371
+ securityFindingId: z.string().min(1).optional(),
352
372
  repositoryLinkId: z.string().min(1).optional(),
353
373
  reviewThreadId: z.string().min(1).optional(),
354
374
  reviewDocumentId: z.string().min(1).optional(),
@@ -773,6 +793,107 @@ var issueItemSchema = baseItemSchema.extend({
773
793
  runnerId: z.string().min(1).optional(),
774
794
  lastDiagnosisError: z.string().optional()
775
795
  });
796
+ var securityStandardReferenceSchema = z.object({
797
+ standard: z.string().trim().min(1).max(120),
798
+ control: z.string().trim().min(1).max(160),
799
+ url: z.string().url().optional()
800
+ });
801
+ var securitySeverityCountsSchema = z.object({
802
+ info: z.number().int().nonnegative().default(0),
803
+ low: z.number().int().nonnegative().default(0),
804
+ medium: z.number().int().nonnegative().default(0),
805
+ high: z.number().int().nonnegative().default(0),
806
+ critical: z.number().int().nonnegative().default(0)
807
+ });
808
+ var defaultSecuritySeverityCounts = { info: 0, low: 0, medium: 0, high: 0, critical: 0 };
809
+ var securityCategorySummarySchema = z.object({
810
+ category: securityFindingCategorySchema,
811
+ status: z.enum(["pass", "warning", "fail", "unknown"]),
812
+ summary: z.string().trim().min(1).max(600)
813
+ });
814
+ var securityFindingResultSchema = z.object({
815
+ title: z.string().trim().min(1).max(200),
816
+ category: securityFindingCategorySchema,
817
+ severity: securityFindingSeveritySchema,
818
+ confidence: securityFindingConfidenceSchema.default("medium"),
819
+ summary: z.string().trim().min(1).max(2e3),
820
+ standardReferences: z.array(securityStandardReferenceSchema).default([]),
821
+ affectedSurfaces: z.array(z.string().trim().min(1).max(300)).default([]),
822
+ evidence: z.array(z.string().trim().min(1).max(600)).default([]),
823
+ recommendedRemediation: z.string().trim().min(1).max(4e3),
824
+ verificationPlan: z.array(z.string().trim().min(1).max(300)).min(1),
825
+ safePaths: z.array(z.string().trim().min(1).max(300)).default([]),
826
+ status: securityFindingStatusSchema.default("open")
827
+ });
828
+ var securityPostureScanResultSchema = z.object({
829
+ summary: z.string().trim().min(1).max(2e3),
830
+ baselineVersion: z.string().trim().min(1).max(80),
831
+ postureScore: z.number().min(0).max(100).optional(),
832
+ postureGrade: z.enum(["A", "B", "C", "D", "F", "Unknown"]).default("Unknown"),
833
+ categorySummaries: z.array(securityCategorySummarySchema).default([]),
834
+ findings: z.array(securityFindingResultSchema).default([]),
835
+ verificationPlan: z.array(z.string().trim().min(1).max(300)).min(1)
836
+ });
837
+ var securityScanItemSchema = baseItemSchema.extend({
838
+ type: z.literal("securityScan"),
839
+ projectId: z.string().min(1),
840
+ securityScanId: z.string().min(1),
841
+ scheduledDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
842
+ source: securityScanSourceSchema,
843
+ status: securityScanStatusSchema,
844
+ baselineVersion: z.string().trim().min(1).max(80).default("amistio-security-baseline-v1"),
845
+ workItemId: z.string().min(1).optional(),
846
+ runnerId: z.string().min(1).optional(),
847
+ findingIds: z.array(z.string().min(1)).default([]),
848
+ summary: z.string().trim().min(1).max(2e3).optional(),
849
+ severityCounts: securitySeverityCountsSchema.default(defaultSecuritySeverityCounts),
850
+ startedAt: isoDateTimeSchema.optional(),
851
+ completedAt: isoDateTimeSchema.optional(),
852
+ failedAt: isoDateTimeSchema.optional(),
853
+ error: z.string().max(1e3).optional()
854
+ });
855
+ var securityFindingItemSchema = baseItemSchema.extend({
856
+ type: z.literal("securityFinding"),
857
+ projectId: z.string().min(1),
858
+ securityFindingId: z.string().min(1),
859
+ securityScanId: z.string().min(1),
860
+ title: z.string().trim().min(1).max(200),
861
+ category: securityFindingCategorySchema,
862
+ severity: securityFindingSeveritySchema,
863
+ confidence: securityFindingConfidenceSchema.default("medium"),
864
+ status: securityFindingStatusSchema,
865
+ approvalState: securityApprovalStateSchema.default("proposed"),
866
+ summary: z.string().trim().min(1).max(2e3),
867
+ standardReferences: z.array(securityStandardReferenceSchema).default([]),
868
+ affectedSurfaces: z.array(z.string().trim().min(1).max(300)).default([]),
869
+ evidence: z.array(z.string().trim().min(1).max(600)).default([]),
870
+ safePaths: z.array(z.string().trim().min(1).max(300)).default([]),
871
+ recommendedRemediation: z.string().trim().min(1).max(4e3),
872
+ verificationPlan: z.array(z.string().trim().min(1).max(300)).min(1),
873
+ remediationPlanDocumentId: z.string().min(1).optional(),
874
+ remediationPromptDocumentId: z.string().min(1).optional(),
875
+ implementationWorkItemId: z.string().min(1).optional(),
876
+ reviewNotes: z.string().trim().min(1).optional(),
877
+ reviewedByUserId: z.string().min(1).optional(),
878
+ reviewedAt: isoDateTimeSchema.optional(),
879
+ resolvedAt: isoDateTimeSchema.optional(),
880
+ lastReviewAction: z.enum(["approve", "requestChanges", "dismiss", "acceptRisk", "reopen"]).optional()
881
+ });
882
+ var securityPostureSnapshotItemSchema = baseItemSchema.extend({
883
+ type: z.literal("securityPostureSnapshot"),
884
+ projectId: z.string().min(1),
885
+ securityPostureSnapshotId: z.string().min(1),
886
+ latestScanId: z.string().min(1).optional(),
887
+ status: z.enum(["unknown", "fresh", "stale", "running", "failed"]),
888
+ baselineVersion: z.string().trim().min(1).max(80).default("amistio-security-baseline-v1"),
889
+ postureScore: z.number().min(0).max(100).optional(),
890
+ postureGrade: z.enum(["A", "B", "C", "D", "F", "Unknown"]).default("Unknown"),
891
+ severityCounts: securitySeverityCountsSchema.default(defaultSecuritySeverityCounts),
892
+ categorySummaries: z.array(securityCategorySummarySchema).default([]),
893
+ lastScannedAt: isoDateTimeSchema.optional(),
894
+ nextScheduledAt: isoDateTimeSchema.optional(),
895
+ summary: z.string().trim().min(1).max(2e3).optional()
896
+ });
776
897
  var activityEventItemSchema = baseItemSchema.extend({
777
898
  type: z.literal("activityEvent"),
778
899
  projectId: z.string().min(1),
@@ -788,6 +909,8 @@ var activityEventItemSchema = baseItemSchema.extend({
788
909
  relatedWorkItemId: z.string().min(1).optional(),
789
910
  relatedDocumentId: z.string().min(1).optional(),
790
911
  relatedIssueId: z.string().min(1).optional(),
912
+ relatedSecurityScanId: z.string().min(1).optional(),
913
+ relatedSecurityFindingId: z.string().min(1).optional(),
791
914
  generatedDraftId: z.string().min(1).optional(),
792
915
  runnerId: z.string().min(1).optional(),
793
916
  repositoryLinkId: z.string().min(1).optional(),
@@ -845,6 +968,9 @@ var projectItemUnionSchema = z.discriminatedUnion("type", [
845
968
  brainDocumentItemSchema,
846
969
  generatedDraftItemSchema,
847
970
  issueItemSchema,
971
+ securityScanItemSchema,
972
+ securityFindingItemSchema,
973
+ securityPostureSnapshotItemSchema,
848
974
  syncCursorItemSchema,
849
975
  syncConflictItemSchema,
850
976
  workItemSchema,
@@ -1756,6 +1882,16 @@ var ApiClient = class {
1756
1882
  }
1757
1883
  );
1758
1884
  }
1885
+ async submitSecurityPostureScanResult(projectId, workItemId, result) {
1886
+ return this.request(
1887
+ `/projects/${projectId}/work-items/${workItemId}/security-result`,
1888
+ z3.object({ scan: securityScanItemSchema, findings: z3.array(securityFindingItemSchema), snapshot: securityPostureSnapshotItemSchema, workItem: workItemSchema }),
1889
+ {
1890
+ method: "POST",
1891
+ body: JSON.stringify(result)
1892
+ }
1893
+ );
1894
+ }
1759
1895
  async request(urlPath, schema, init) {
1760
1896
  const response = await fetch(resolveApiUrl(this.options.apiUrl, urlPath), {
1761
1897
  ...init,
@@ -3726,6 +3862,8 @@ var impactPreviewStart = "AMISTIO_IMPACT_PREVIEW_START";
3726
3862
  var impactPreviewEnd = "AMISTIO_IMPACT_PREVIEW_END";
3727
3863
  var issueDiagnosisStart = "AMISTIO_ISSUE_DIAGNOSIS_START";
3728
3864
  var issueDiagnosisEnd = "AMISTIO_ISSUE_DIAGNOSIS_END";
3865
+ var securityPostureStart = "AMISTIO_SECURITY_POSTURE_START";
3866
+ var securityPostureEnd = "AMISTIO_SECURITY_POSTURE_END";
3729
3867
  function createWorkExecutionPrompt(workItem, context) {
3730
3868
  if (workItem.workKind === "brainGeneration") {
3731
3869
  return createBrainGenerationPrompt(workItem);
@@ -3742,6 +3880,9 @@ function createWorkExecutionPrompt(workItem, context) {
3742
3880
  if (workItem.workKind === "issueDiagnosis") {
3743
3881
  return createIssueDiagnosisPrompt(workItem, context?.issueDiagnosis);
3744
3882
  }
3883
+ if (workItem.workKind === "securityPostureScan") {
3884
+ return createSecurityPostureScanPrompt(workItem, context?.securityPostureScan);
3885
+ }
3745
3886
  return [
3746
3887
  "# Amistio Work Execution",
3747
3888
  "",
@@ -3770,6 +3911,63 @@ function createWorkExecutionPrompt(workItem, context) {
3770
3911
  "- Run relevant verification commands when feasible and summarize results."
3771
3912
  ].join("\n");
3772
3913
  }
3914
+ function createSecurityPostureScanPrompt(workItem, context) {
3915
+ const approvedContext = (context?.documents ?? []).filter((document) => document.status === "approved" || document.syncState === "approved" || document.syncState === "synced").slice(0, 20).map((document) => [
3916
+ `### ${document.title}`,
3917
+ `documentId: ${document.documentId}`,
3918
+ `documentType: ${document.documentType}`,
3919
+ `repoPath: ${document.repoPath}`,
3920
+ `revision: ${document.revision}`,
3921
+ document.content.slice(0, 3e3)
3922
+ ].join("\n")).join("\n\n");
3923
+ return [
3924
+ "# Amistio Security Posture Scan",
3925
+ "",
3926
+ "You are running locally through the Amistio CLI inside the user's repository.",
3927
+ "Run a read-only security posture scan and produce approval-gated remediation findings.",
3928
+ "Do not modify files, create branches, commit, run implementation prompts, install packages, perform exploit attempts, call external services, or make unaudited network calls.",
3929
+ "Use only safe local inspection and focused diagnostic commands that do not expose secrets.",
3930
+ "",
3931
+ "## Work Item",
3932
+ "",
3933
+ `Title: ${workItem.title}`,
3934
+ `Work item ID: ${workItem.workItemId}`,
3935
+ `Project ID: ${workItem.projectId}`,
3936
+ `Security scan ID: ${workItem.securityScanId ?? "unknown"}`,
3937
+ "",
3938
+ "## Scan Request",
3939
+ "",
3940
+ workItem.sourceWish ?? "Run the Amistio security baseline.",
3941
+ "",
3942
+ "## Approved Project Brain Context",
3943
+ "",
3944
+ approvedContext || "No approved project-brain records were loaded. Inspect local repository files as needed and explain the gap in the summary.",
3945
+ "",
3946
+ "## Baseline Requirements",
3947
+ "",
3948
+ "- Cover OWASP Top 10 and OWASP ASVS Level 1 style controls.",
3949
+ "- Check dependency and supply-chain posture: audit advisories, lockfiles, CI verification, release provenance, and trusted publishing where applicable.",
3950
+ "- Check secret and credential hygiene using redacted evidence only.",
3951
+ "- Check authentication, authorization, tenant isolation, redirects/callbacks, CSRF, CORS, security headers, logging redaction, and rate limiting.",
3952
+ "- Check Amistio controls: local-runner-only execution, supported work-kind gates, approval gates, worktree isolation, runner credential binding, and redacted telemetry.",
3953
+ "",
3954
+ "## Data Safety",
3955
+ "",
3956
+ "- Persist summaries, controls, repository-relative paths, and redacted evidence only.",
3957
+ "- Do not include raw source, secrets, env vars, command lines, process lists, absolute local paths, credential values, tokens, provider sessions, or exploit payloads.",
3958
+ "- Use safePaths for repo-relative paths only; never include /absolute paths or ../ traversal.",
3959
+ "",
3960
+ "## Output Contract",
3961
+ "",
3962
+ "Print exactly one JSON object between the markers below. The CLI will submit only this structured scan result back to Amistio.",
3963
+ "",
3964
+ securityPostureStart,
3965
+ '{"summary":"Security posture is mostly healthy, but dependency advisory review is not automated.","baselineVersion":"amistio-security-baseline-v1","postureScore":82,"postureGrade":"B","categorySummaries":[{"category":"dependency","status":"warning","summary":"Dependency audit automation is incomplete."}],"findings":[{"title":"Dependency audit is not enforced in CI","category":"dependency","severity":"medium","confidence":"high","summary":"The repository has a lockfile, but CI does not appear to fail on known vulnerable dependencies.","standardReferences":[{"standard":"OWASP ASVS","control":"V14.2 Dependency"}],"affectedSurfaces":["CI verification"],"evidence":["No dependency audit step was found in the CI verification summary."],"recommendedRemediation":"Add a dependency advisory check to the existing verification pipeline and document how failures are handled.","verificationPlan":["Run the updated CI verification command locally","Confirm a known advisory fails the check in a controlled test"],"safePaths":["package.json","pnpm-lock.yaml"]}],"verificationPlan":["Run focused dependency and CI checks","Review Security panel findings for approval"]}',
3966
+ securityPostureEnd,
3967
+ "",
3968
+ "Do not put Markdown fences around the markers. Do not implement remediation."
3969
+ ].join("\n");
3970
+ }
3773
3971
  function createIssueDiagnosisPrompt(workItem, context) {
3774
3972
  const issue = context?.issue;
3775
3973
  const approvedContext = (context?.documents ?? []).filter((document) => document.status === "approved" || document.syncState === "approved" || document.syncState === "synced").slice(0, 16).map((document) => [
@@ -4006,6 +4204,16 @@ function parseIssueDiagnosisResult(output) {
4006
4204
  const parsed = JSON.parse(stripJsonFence(payload));
4007
4205
  return issueDiagnosisResultSchema.parse(parsed);
4008
4206
  }
4207
+ function parseSecurityPostureScanResult(output) {
4208
+ const start = output.indexOf(securityPostureStart);
4209
+ const end = output.indexOf(securityPostureEnd, start + securityPostureStart.length);
4210
+ if (start === -1 || end === -1 || end <= start) {
4211
+ throw new Error("Local AI scan did not return an Amistio security posture block.");
4212
+ }
4213
+ const payload = output.slice(start + securityPostureStart.length, end).trim();
4214
+ const parsed = JSON.parse(stripJsonFence(payload));
4215
+ return securityPostureScanResultSchema.parse(parsed);
4216
+ }
4009
4217
  function createBrainGenerationPrompt(workItem) {
4010
4218
  const wish = workItem.sourceWish ?? workItem.title;
4011
4219
  return [
@@ -4718,7 +4926,7 @@ var DEFAULT_MAX_PREFLIGHT_ATTEMPTS = 3;
4718
4926
  var DEFAULT_TOOL_TIMEOUT_SECONDS = 30 * 60;
4719
4927
  var RUNNER_WORK_LEASE_SECONDS = 300;
4720
4928
  var RUNNER_WORK_LEASE_RENEWAL_MS = 12e4;
4721
- var runnerSupportedWorkKinds = ["brainGeneration", "implementation", "planRevision", "assistantQuestion", "impactPreview", "issueDiagnosis"];
4929
+ var runnerSupportedWorkKinds = ["brainGeneration", "implementation", "planRevision", "assistantQuestion", "impactPreview", "issueDiagnosis", "securityPostureScan"];
4722
4930
  program.name("amistio").description("Amistio project brain CLI").version(CLI_VERSION);
4723
4931
  program.command("init").description("Create Amistio control-plane folders for a new project").option("--root <path>", "Repository root", defaultRoot).action(async (options) => {
4724
4932
  const created = await initControlPlane(options.root);
@@ -5699,6 +5907,24 @@ async function runNextWorkItem({
5699
5907
  return recordFinalizationFailure({ apiClient, error, isolationTelemetry, projectId, repositoryLinkId, runnerId, sessionContext, toolConfig, toolName: preview.toolName, workItem: result.workItem, durationMs: Date.now() - startedAt });
5700
5908
  }
5701
5909
  }
5910
+ if (result.workItem.workKind === "securityPostureScan") {
5911
+ try {
5912
+ return await finalizeSecurityPostureScanWork({
5913
+ apiClient,
5914
+ durationMs: Date.now() - startedAt,
5915
+ projectId,
5916
+ repositoryLinkId,
5917
+ runnerId,
5918
+ sessionContext,
5919
+ toolConfig,
5920
+ toolName: preview.toolName,
5921
+ toolResult,
5922
+ workItem: result.workItem
5923
+ });
5924
+ } catch (error) {
5925
+ return recordFinalizationFailure({ apiClient, error, isolationTelemetry, projectId, repositoryLinkId, runnerId, sessionContext, toolConfig, toolName: preview.toolName, workItem: result.workItem, durationMs: Date.now() - startedAt });
5926
+ }
5927
+ }
5702
5928
  const finalStatus = toolResult.exitCode === 0 ? "completed" : "failed";
5703
5929
  const durationMs = Date.now() - startedAt;
5704
5930
  const failureExcerpt = toolResult.exitCode === 0 ? void 0 : truncateLogExcerpt(toolResult.stderr || toolResult.stdout);
@@ -6326,6 +6552,92 @@ ${toolResult.stderr}`);
6326
6552
  console.error(diagnosisError ?? "Local runner issue diagnosis failed.");
6327
6553
  return { status: "failed", exitCode: toolResult.exitCode || 1 };
6328
6554
  }
6555
+ async function finalizeSecurityPostureScanWork({
6556
+ apiClient,
6557
+ durationMs,
6558
+ projectId,
6559
+ repositoryLinkId,
6560
+ runnerId,
6561
+ sessionContext,
6562
+ toolConfig,
6563
+ toolName,
6564
+ toolResult,
6565
+ workItem
6566
+ }) {
6567
+ let scanResult = void 0;
6568
+ let scanError;
6569
+ if (toolResult.exitCode === 0) {
6570
+ try {
6571
+ scanResult = parseSecurityPostureScanResult(`${toolResult.stdout}
6572
+ ${toolResult.stderr}`);
6573
+ } catch (error) {
6574
+ scanError = errorMessage3(error);
6575
+ }
6576
+ } else {
6577
+ scanError = truncateLogExcerpt(toolResult.stderr || toolResult.stdout) || `${toolName} exited with code ${toolResult.exitCode}.`;
6578
+ }
6579
+ const finalStatus = scanResult ? "completed" : "failed";
6580
+ const updatedToolSession = await finalizeToolSession({
6581
+ apiClient,
6582
+ projectId,
6583
+ status: finalStatus,
6584
+ runnerId,
6585
+ workItemId: workItem.workItemId,
6586
+ stdout: toolResult.stdout,
6587
+ ...sessionContext.toolSession ? { session: sessionContext.toolSession } : {},
6588
+ ...toolResult.messageCount !== void 0 ? { messageCount: toolResult.messageCount } : {},
6589
+ ...toolResult.tokensIn !== void 0 ? { tokensIn: toolResult.tokensIn } : {},
6590
+ ...toolResult.tokensOut !== void 0 ? { tokensOut: toolResult.tokensOut } : {},
6591
+ ...toolResult.costUsd !== void 0 ? { costUsd: toolResult.costUsd } : {}
6592
+ });
6593
+ const sessionTelemetry = {
6594
+ sessionPolicy: sessionContext.policy,
6595
+ sessionDecision: sessionContext.decision,
6596
+ sessionDecisionReason: sessionContext.reason,
6597
+ ...updatedToolSession ? { toolSessionId: updatedToolSession.toolSessionId } : {},
6598
+ ...updatedToolSession?.sessionGroupKey ? { sessionGroupKey: updatedToolSession.sessionGroupKey } : {}
6599
+ };
6600
+ if (scanResult) {
6601
+ const result = await apiClient.submitSecurityPostureScanResult(projectId, workItem.workItemId, {
6602
+ status: "completed",
6603
+ runnerId,
6604
+ idempotencyKey: `security_${workItem.workItemId}_${randomUUID()}`,
6605
+ result: scanResult,
6606
+ tool: toolName,
6607
+ durationMs,
6608
+ ...sessionTelemetry,
6609
+ message: `${toolName} returned a security posture scan.`
6610
+ });
6611
+ await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
6612
+ status: "completed",
6613
+ summary: `${toolName} returned a security posture scan.`,
6614
+ idempotencyKey: `runner_milestone_security_completed_${workItem.workItemId}_${result.workItem.idempotencyKey}`,
6615
+ metadata: { tool: toolName, durationMs, findingCount: scanResult.findings.length, critical: result.scan.severityCounts.critical, high: result.scan.severityCounts.high, verificationSummary: scanResult.verificationPlan.join(" | ") }
6616
+ });
6617
+ await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
6618
+ console.log("Security posture scan returned for review.");
6619
+ return { status: "completed", exitCode: 0 };
6620
+ }
6621
+ const failedResult = await apiClient.submitSecurityPostureScanResult(projectId, workItem.workItemId, {
6622
+ status: "failed",
6623
+ runnerId,
6624
+ idempotencyKey: `security_${workItem.workItemId}_${randomUUID()}`,
6625
+ tool: toolName,
6626
+ durationMs,
6627
+ ...sessionTelemetry,
6628
+ message: `${toolName} did not produce a valid security posture scan.`,
6629
+ ...scanError ? { error: scanError } : {}
6630
+ });
6631
+ await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
6632
+ status: "failed",
6633
+ summary: scanError ?? `${toolName} did not produce a valid security posture scan.`,
6634
+ idempotencyKey: `runner_milestone_security_failed_${workItem.workItemId}_${failedResult.workItem.idempotencyKey}`,
6635
+ metadata: { tool: toolName, durationMs, verificationSummary: "Security posture output did not include valid structured JSON." }
6636
+ });
6637
+ await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
6638
+ console.error(scanError ?? "Local runner security posture scan failed.");
6639
+ return { status: "failed", exitCode: toolResult.exitCode || 1 };
6640
+ }
6329
6641
  async function createRunnerWorkPrompt(apiClient, projectId, workItem) {
6330
6642
  if (workItem.workKind === "assistantQuestion") {
6331
6643
  const [{ documents: documents2 }, { messages: messages2 }] = await Promise.all([
@@ -6360,6 +6672,12 @@ async function createRunnerWorkPrompt(apiClient, projectId, workItem) {
6360
6672
  }
6361
6673
  });
6362
6674
  }
6675
+ if (workItem.workKind === "securityPostureScan") {
6676
+ const { documents: documents2 } = await apiClient.listBrainDocuments(projectId);
6677
+ return createWorkExecutionPrompt(workItem, {
6678
+ securityPostureScan: { documents: documents2 }
6679
+ });
6680
+ }
6363
6681
  if (workItem.workKind !== "planRevision" || !workItem.reviewDocumentId) {
6364
6682
  return createWorkExecutionPrompt(workItem);
6365
6683
  }