@amistio/cli 0.1.10 → 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/dist/index.js CHANGED
@@ -20,6 +20,10 @@ var itemTypeSchema = z.enum([
20
20
  "repositoryLink",
21
21
  "brainDocument",
22
22
  "generatedDraft",
23
+ "issue",
24
+ "securityScan",
25
+ "securityFinding",
26
+ "securityPostureSnapshot",
23
27
  "syncCursor",
24
28
  "syncConflict",
25
29
  "workItem",
@@ -66,11 +70,22 @@ var workStatusSchema = z.enum([
66
70
  ]);
67
71
  var sourceSchema = z.enum(["web", "repo", "generated", "runner"]);
68
72
  var executionModeSchema = z.enum(["localRunner", "cloudSandbox"]);
69
- var workKindSchema = z.enum(["brainGeneration", "implementation", "planRevision", "assistantQuestion", "impactPreview"]);
73
+ var workKindSchema = z.enum(["brainGeneration", "implementation", "planRevision", "assistantQuestion", "impactPreview", "issueDiagnosis", "securityPostureScan"]);
70
74
  var generatedDraftStatusSchema = z.enum(["queued", "generating", "reviewing", "approved", "rejected", "changesRequested", "failed"]);
71
75
  var assistantQuestionModeSchema = z.enum(["brainOnly", "sourceAware"]);
72
76
  var impactRiskLevelSchema = z.enum(["low", "medium", "high", "critical"]);
73
77
  var impactReportStatusSchema = z.enum(["queued", "running", "completed", "failed", "stale"]);
78
+ var issueCategorySchema = z.enum(["bug", "regression", "brokenWorkflow", "security", "operational", "other"]);
79
+ var issueSeveritySchema = z.enum(["low", "medium", "high", "critical"]);
80
+ var issueStatusSchema = z.enum(["queued", "diagnosing", "analysisReady", "approved", "changesRequested", "rejected", "implementationQueued", "implemented", "failed"]);
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"]);
74
89
  var activityEventTypeSchema = z.enum([
75
90
  "commandSubmitted",
76
91
  "generationQueued",
@@ -89,6 +104,19 @@ var activityEventTypeSchema = z.enum([
89
104
  "impactPreviewCompleted",
90
105
  "impactPreviewFailed",
91
106
  "impactPreviewStale",
107
+ "issueDiagnosisQueued",
108
+ "issueDiagnosisCompleted",
109
+ "issueDiagnosisFailed",
110
+ "issueReview",
111
+ "issueImplementationQueued",
112
+ "securityScanQueued",
113
+ "securityScanStarted",
114
+ "securityScanCompleted",
115
+ "securityScanFailed",
116
+ "securityFindingOpened",
117
+ "securityFindingReviewed",
118
+ "securityRemediationPlanCreated",
119
+ "securityRemediationQueued",
92
120
  "handoffExported"
93
121
  ]);
94
122
  var activityEventActorSchema = z.enum(["webUser", "runner", "cli", "system"]);
@@ -338,6 +366,9 @@ var workItemSchema = baseItemSchema.extend({
338
366
  approvedByUserId: z.string().min(1).optional(),
339
367
  sourceWish: z.string().min(1).optional(),
340
368
  generatedDraftId: z.string().min(1).optional(),
369
+ issueId: z.string().min(1).optional(),
370
+ securityScanId: z.string().min(1).optional(),
371
+ securityFindingId: z.string().min(1).optional(),
341
372
  repositoryLinkId: z.string().min(1).optional(),
342
373
  reviewThreadId: z.string().min(1).optional(),
343
374
  reviewDocumentId: z.string().min(1).optional(),
@@ -386,6 +417,7 @@ var runnerHeartbeatItemSchema = baseItemSchema.extend({
386
417
  mode: z.enum(["foreground", "background"]).optional(),
387
418
  hostname: z.string().min(1).optional(),
388
419
  runnerName: z.string().min(1).optional(),
420
+ supportedWorkKinds: z.array(workKindSchema).optional(),
389
421
  supportsBranchIsolation: z.boolean().optional(),
390
422
  supportsGitWorktreeIsolation: z.boolean().optional(),
391
423
  currentWorkItemId: z.string().min(1).optional(),
@@ -720,6 +752,148 @@ var impactPreviewResultSchema = z.object({
720
752
  analyzedRepoRevision: z.number().int().nonnegative().optional(),
721
753
  repoFingerprint: z.string().min(1).optional()
722
754
  });
755
+ var issueDiagnosisResultSchema = z.object({
756
+ summary: z.string().trim().min(1),
757
+ impact: z.string().trim().min(1),
758
+ evidence: z.array(z.string().trim().min(1)).default([]),
759
+ affectedSurfaces: z.array(z.string().trim().min(1)).default([]),
760
+ rootCauseAnalysis: z.string().trim().min(1),
761
+ falsifiableHypothesis: z.string().trim().min(1).optional(),
762
+ nextCheck: z.string().trim().min(1).optional(),
763
+ proposedFix: z.string().trim().min(1),
764
+ expectedBehavior: z.string().trim().min(1),
765
+ verificationPlan: z.array(z.string().trim().min(1)).min(1),
766
+ riskLevel: impactRiskLevelSchema.default("medium"),
767
+ likelyPaths: z.array(impactPathSchema).default([]),
768
+ approvalState: issueApprovalStateSchema.default("proposed")
769
+ });
770
+ var issueItemSchema = baseItemSchema.extend({
771
+ type: z.literal("issue"),
772
+ projectId: z.string().min(1),
773
+ issueId: z.string().min(1),
774
+ title: z.string().trim().min(1).max(200),
775
+ description: z.string().trim().min(1).max(12e3),
776
+ category: issueCategorySchema.default("bug"),
777
+ severity: issueSeveritySchema.default("medium"),
778
+ status: issueStatusSchema,
779
+ approvalState: issueApprovalStateSchema.default("proposed"),
780
+ submittedByUserId: z.string().min(1).optional(),
781
+ repositoryLinkId: z.string().min(1).optional(),
782
+ diagnosisWorkItemId: z.string().min(1).optional(),
783
+ implementationWorkItemId: z.string().min(1).optional(),
784
+ diagnosis: issueDiagnosisResultSchema.optional(),
785
+ reviewNotes: z.string().trim().min(1).optional(),
786
+ approvedByUserId: z.string().min(1).optional(),
787
+ approvedAt: isoDateTimeSchema.optional(),
788
+ requestedChangesByUserId: z.string().min(1).optional(),
789
+ requestedChangesAt: isoDateTimeSchema.optional(),
790
+ rejectedByUserId: z.string().min(1).optional(),
791
+ rejectedAt: isoDateTimeSchema.optional(),
792
+ diagnosedAt: isoDateTimeSchema.optional(),
793
+ runnerId: z.string().min(1).optional(),
794
+ lastDiagnosisError: z.string().optional()
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
+ });
723
897
  var activityEventItemSchema = baseItemSchema.extend({
724
898
  type: z.literal("activityEvent"),
725
899
  projectId: z.string().min(1),
@@ -734,6 +908,9 @@ var activityEventItemSchema = baseItemSchema.extend({
734
908
  occurredAt: isoDateTimeSchema,
735
909
  relatedWorkItemId: z.string().min(1).optional(),
736
910
  relatedDocumentId: z.string().min(1).optional(),
911
+ relatedIssueId: z.string().min(1).optional(),
912
+ relatedSecurityScanId: z.string().min(1).optional(),
913
+ relatedSecurityFindingId: z.string().min(1).optional(),
737
914
  generatedDraftId: z.string().min(1).optional(),
738
915
  runnerId: z.string().min(1).optional(),
739
916
  repositoryLinkId: z.string().min(1).optional(),
@@ -790,6 +967,10 @@ var projectItemUnionSchema = z.discriminatedUnion("type", [
790
967
  repositoryLinkItemSchema,
791
968
  brainDocumentItemSchema,
792
969
  generatedDraftItemSchema,
970
+ issueItemSchema,
971
+ securityScanItemSchema,
972
+ securityFindingItemSchema,
973
+ securityPostureSnapshotItemSchema,
793
974
  syncCursorItemSchema,
794
975
  syncConflictItemSchema,
795
976
  workItemSchema,
@@ -1527,6 +1708,13 @@ var ApiClient = class {
1527
1708
  { method: "GET" }
1528
1709
  );
1529
1710
  }
1711
+ async listIssues(projectId) {
1712
+ return this.request(
1713
+ `/projects/${projectId}/issues`,
1714
+ z3.object({ issues: z3.array(issueItemSchema) }),
1715
+ { method: "GET" }
1716
+ );
1717
+ }
1530
1718
  async pushBrainDocuments(projectId, documents) {
1531
1719
  return this.request(
1532
1720
  `/projects/${projectId}/brain-documents`,
@@ -1684,6 +1872,26 @@ var ApiClient = class {
1684
1872
  }
1685
1873
  );
1686
1874
  }
1875
+ async submitIssueDiagnosisResult(projectId, workItemId, result) {
1876
+ return this.request(
1877
+ `/projects/${projectId}/work-items/${workItemId}/issue-result`,
1878
+ z3.object({ issue: issueItemSchema, workItem: workItemSchema }),
1879
+ {
1880
+ method: "POST",
1881
+ body: JSON.stringify(result)
1882
+ }
1883
+ );
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
+ }
1687
1895
  async request(urlPath, schema, init) {
1688
1896
  const response = await fetch(resolveApiUrl(this.options.apiUrl, urlPath), {
1689
1897
  ...init,
@@ -3652,6 +3860,10 @@ var assistantAnswerStart = "AMISTIO_ASSISTANT_ANSWER_START";
3652
3860
  var assistantAnswerEnd = "AMISTIO_ASSISTANT_ANSWER_END";
3653
3861
  var impactPreviewStart = "AMISTIO_IMPACT_PREVIEW_START";
3654
3862
  var impactPreviewEnd = "AMISTIO_IMPACT_PREVIEW_END";
3863
+ var issueDiagnosisStart = "AMISTIO_ISSUE_DIAGNOSIS_START";
3864
+ var issueDiagnosisEnd = "AMISTIO_ISSUE_DIAGNOSIS_END";
3865
+ var securityPostureStart = "AMISTIO_SECURITY_POSTURE_START";
3866
+ var securityPostureEnd = "AMISTIO_SECURITY_POSTURE_END";
3655
3867
  function createWorkExecutionPrompt(workItem, context) {
3656
3868
  if (workItem.workKind === "brainGeneration") {
3657
3869
  return createBrainGenerationPrompt(workItem);
@@ -3665,6 +3877,12 @@ function createWorkExecutionPrompt(workItem, context) {
3665
3877
  if (workItem.workKind === "impactPreview") {
3666
3878
  return createImpactPreviewPrompt(workItem, context?.impactPreview);
3667
3879
  }
3880
+ if (workItem.workKind === "issueDiagnosis") {
3881
+ return createIssueDiagnosisPrompt(workItem, context?.issueDiagnosis);
3882
+ }
3883
+ if (workItem.workKind === "securityPostureScan") {
3884
+ return createSecurityPostureScanPrompt(workItem, context?.securityPostureScan);
3885
+ }
3668
3886
  return [
3669
3887
  "# Amistio Work Execution",
3670
3888
  "",
@@ -3693,6 +3911,113 @@ function createWorkExecutionPrompt(workItem, context) {
3693
3911
  "- Run relevant verification commands when feasible and summarize results."
3694
3912
  ].join("\n");
3695
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
+ }
3971
+ function createIssueDiagnosisPrompt(workItem, context) {
3972
+ const issue = context?.issue;
3973
+ const approvedContext = (context?.documents ?? []).filter((document) => document.status === "approved" || document.syncState === "approved" || document.syncState === "synced").slice(0, 16).map((document) => [
3974
+ `### ${document.title}`,
3975
+ `documentId: ${document.documentId}`,
3976
+ `documentType: ${document.documentType}`,
3977
+ `repoPath: ${document.repoPath}`,
3978
+ `revision: ${document.revision}`,
3979
+ document.content.slice(0, 3e3)
3980
+ ].join("\n")).join("\n\n");
3981
+ return [
3982
+ "# Amistio Issue Diagnosis",
3983
+ "",
3984
+ "You are running locally through the Amistio CLI inside the user's repository.",
3985
+ "Diagnose the submitted bug or operational issue and prepare the approval-gated fix analysis.",
3986
+ "This is a diagnosis-only task. Do not modify files, create branches, run implementation commands, or commit changes.",
3987
+ "",
3988
+ "## Submitted Issue",
3989
+ "",
3990
+ `Issue ID: ${workItem.issueId ?? issue?.issueId ?? "unknown"}`,
3991
+ `Title: ${issue?.title ?? workItem.title}`,
3992
+ `Category: ${issue?.category ?? "bug"}`,
3993
+ `Severity: ${issue?.severity ?? "medium"}`,
3994
+ "",
3995
+ issue?.description ?? workItem.sourceWish ?? workItem.title,
3996
+ "",
3997
+ "## Approved Project Brain Context",
3998
+ "",
3999
+ approvedContext || "No approved project-brain records were loaded. Inspect local repository files as needed and explain the gap in evidence.",
4000
+ "",
4001
+ "## Analysis Requirements",
4002
+ "",
4003
+ "- Document user-visible impact and concrete evidence or reproduction status.",
4004
+ "- Name affected surfaces and likely paths using concise path summaries only.",
4005
+ "- Provide rootCauseAnalysis. If unconfirmed, include falsifiableHypothesis and nextCheck.",
4006
+ "- Propose the fix and expected behavior, then provide a verification plan.",
4007
+ "- Keep repository source and secrets local. Do not include raw source dumps, credentials, local secret paths, or provider session references.",
4008
+ "- Leave approvalState as proposed. Implementation must wait for user approval in Amistio.",
4009
+ "",
4010
+ "## Output Contract",
4011
+ "",
4012
+ "Print exactly one JSON object between the markers below. The CLI will submit only this structured diagnosis back to Amistio.",
4013
+ "",
4014
+ issueDiagnosisStart,
4015
+ '{"summary":"The issue is caused by a missing state transition in the workspace action flow.","impact":"Users see a submitted action but no follow-up analysis appears.","evidence":["Reproduced by submitting an issue from the workspace UI"],"affectedSurfaces":["Workspace UI","Runner claim API"],"rootCauseAnalysis":"The issue path queues normal implementation work instead of diagnosis work.","falsifiableHypothesis":"Adding issueDiagnosis work and a dedicated result route will produce analysis without source mutation.","nextCheck":"Run focused web and CLI typechecks after wiring the route.","proposedFix":"Add a first-class issue diagnosis work kind, result parser, and approval-gated implementation queue.","expectedBehavior":"Submitted issues show a complete analysis and only queue implementation after approval.","verificationPlan":["Run shared, web, and CLI typechecks","Submit an issue with a compatible runner and verify analysis appears"],"riskLevel":"medium","likelyPaths":[{"repoPath":"src/apps/web/legacy/api/projects/[projectId]/issues.ts","reason":"Issue submission and approval route"}],"approvalState":"proposed"}',
4016
+ issueDiagnosisEnd,
4017
+ "",
4018
+ "Do not put Markdown fences around the markers. Do not implement the fix."
4019
+ ].join("\n");
4020
+ }
3696
4021
  function createImpactPreviewPrompt(workItem, context) {
3697
4022
  const implementationPrompt = context?.implementationPrompt;
3698
4023
  const approvedContext = (context?.documents ?? []).filter((document) => document.status === "approved" || document.syncState === "approved" || document.syncState === "synced").slice(0, 16).map((document) => [
@@ -3869,6 +4194,26 @@ function parseImpactPreviewResult(output) {
3869
4194
  const parsed = JSON.parse(stripJsonFence(payload));
3870
4195
  return impactPreviewResultSchema.parse(parsed);
3871
4196
  }
4197
+ function parseIssueDiagnosisResult(output) {
4198
+ const start = output.indexOf(issueDiagnosisStart);
4199
+ const end = output.indexOf(issueDiagnosisEnd, start + issueDiagnosisStart.length);
4200
+ if (start === -1 || end === -1 || end <= start) {
4201
+ throw new Error("Local AI diagnosis did not return an Amistio issue diagnosis block.");
4202
+ }
4203
+ const payload = output.slice(start + issueDiagnosisStart.length, end).trim();
4204
+ const parsed = JSON.parse(stripJsonFence(payload));
4205
+ return issueDiagnosisResultSchema.parse(parsed);
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
+ }
3872
4217
  function createBrainGenerationPrompt(workItem) {
3873
4218
  const wish = workItem.sourceWish ?? workItem.title;
3874
4219
  return [
@@ -4412,6 +4757,39 @@ async function runOfficialCliUpdate() {
4412
4757
  }
4413
4758
  return { succeeded: false, message: "Official Amistio CLI update command failed.", error: result.output || `npm exited with code ${result.exitCode}.` };
4414
4759
  }
4760
+ async function runOfficialCliUpdateWithRuntimeRefresh(options) {
4761
+ const updateResult = await (options.runUpdate ?? runOfficialCliUpdate)();
4762
+ if (!updateResult.succeeded) {
4763
+ return updateResult;
4764
+ }
4765
+ if (options.mode === "foreground") {
4766
+ return {
4767
+ succeeded: true,
4768
+ stopRunner: true,
4769
+ message: `${updateResult.message} Restart the local runner command to load the updated CLI version.`
4770
+ };
4771
+ }
4772
+ if (!options.restartBackgroundRunner) {
4773
+ return {
4774
+ succeeded: false,
4775
+ message: `${updateResult.message} Background runner restart was not available after update. Restart the runner manually to load the updated CLI version.`,
4776
+ error: "Background runner restart hook was not provided."
4777
+ };
4778
+ }
4779
+ const restartResult = await options.restartBackgroundRunner();
4780
+ if (restartResult.succeeded) {
4781
+ return {
4782
+ succeeded: true,
4783
+ stopRunner: true,
4784
+ message: `${updateResult.message} ${restartResult.message}`
4785
+ };
4786
+ }
4787
+ return {
4788
+ succeeded: false,
4789
+ message: `${updateResult.message} Background runner restart failed after update. Restart the runner manually to load the updated CLI version.`,
4790
+ error: restartResult.error ?? restartResult.message
4791
+ };
4792
+ }
4415
4793
  function runOfficialUpdateProcess(command, args, timeoutMs) {
4416
4794
  return new Promise((resolve) => {
4417
4795
  const child = spawn4(command, args, { stdio: ["ignore", "pipe", "pipe"] });
@@ -4548,6 +4926,7 @@ var DEFAULT_MAX_PREFLIGHT_ATTEMPTS = 3;
4548
4926
  var DEFAULT_TOOL_TIMEOUT_SECONDS = 30 * 60;
4549
4927
  var RUNNER_WORK_LEASE_SECONDS = 300;
4550
4928
  var RUNNER_WORK_LEASE_RENEWAL_MS = 12e4;
4929
+ var runnerSupportedWorkKinds = ["brainGeneration", "implementation", "planRevision", "assistantQuestion", "impactPreview", "issueDiagnosis", "securityPostureScan"];
4551
4930
  program.name("amistio").description("Amistio project brain CLI").version(CLI_VERSION);
4552
4931
  program.command("init").description("Create Amistio control-plane folders for a new project").option("--root <path>", "Repository root", defaultRoot).action(async (options) => {
4553
4932
  const created = await initControlPlane(options.root);
@@ -5510,6 +5889,42 @@ async function runNextWorkItem({
5510
5889
  return recordFinalizationFailure({ apiClient, error, isolationTelemetry, projectId, repositoryLinkId, runnerId, sessionContext, toolConfig, toolName: preview.toolName, workItem: result.workItem, durationMs: Date.now() - startedAt });
5511
5890
  }
5512
5891
  }
5892
+ if (result.workItem.workKind === "issueDiagnosis") {
5893
+ try {
5894
+ return await finalizeIssueDiagnosisWork({
5895
+ apiClient,
5896
+ durationMs: Date.now() - startedAt,
5897
+ projectId,
5898
+ repositoryLinkId,
5899
+ runnerId,
5900
+ sessionContext,
5901
+ toolConfig,
5902
+ toolName: preview.toolName,
5903
+ toolResult,
5904
+ workItem: result.workItem
5905
+ });
5906
+ } catch (error) {
5907
+ return recordFinalizationFailure({ apiClient, error, isolationTelemetry, projectId, repositoryLinkId, runnerId, sessionContext, toolConfig, toolName: preview.toolName, workItem: result.workItem, durationMs: Date.now() - startedAt });
5908
+ }
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
+ }
5513
5928
  const finalStatus = toolResult.exitCode === 0 ? "completed" : "failed";
5514
5929
  const durationMs = Date.now() - startedAt;
5515
5930
  const failureExcerpt = toolResult.exitCode === 0 ? void 0 : truncateLogExcerpt(toolResult.stderr || toolResult.stdout);
@@ -5712,6 +6127,7 @@ async function recordRunnerMilestone(apiClient, projectId, workItem, runnerId, r
5712
6127
  repositoryLinkId,
5713
6128
  relatedWorkItemId: workItem.workItemId,
5714
6129
  ...workItem.reviewDocumentId ? { relatedDocumentId: workItem.reviewDocumentId } : workItem.impactDocumentId ? { relatedDocumentId: workItem.impactDocumentId } : {},
6130
+ ...workItem.issueId ? { relatedIssueId: workItem.issueId } : {},
5715
6131
  ...workItem.generatedDraftId ? { generatedDraftId: workItem.generatedDraftId } : {},
5716
6132
  ...input
5717
6133
  }).catch(() => void 0);
@@ -5759,7 +6175,10 @@ async function executeRunnerCommand(command, context) {
5759
6175
  if (command.commandKind === "restart") {
5760
6176
  return restartCurrentRunner(context);
5761
6177
  }
5762
- return runOfficialCliUpdate();
6178
+ return runOfficialCliUpdateWithRuntimeRefresh({
6179
+ mode: currentRunnerMode(),
6180
+ restartBackgroundRunner: () => restartCurrentRunner(context)
6181
+ });
5763
6182
  }
5764
6183
  async function restartCurrentRunner(context) {
5765
6184
  if (currentRunnerMode() !== "background") {
@@ -6047,6 +6466,178 @@ ${toolResult.stderr}`);
6047
6466
  console.error(previewError ?? "Local runner impact preview failed.");
6048
6467
  return { status: "failed", exitCode: toolResult.exitCode || 1 };
6049
6468
  }
6469
+ async function finalizeIssueDiagnosisWork({
6470
+ apiClient,
6471
+ durationMs,
6472
+ projectId,
6473
+ repositoryLinkId,
6474
+ runnerId,
6475
+ sessionContext,
6476
+ toolConfig,
6477
+ toolName,
6478
+ toolResult,
6479
+ workItem
6480
+ }) {
6481
+ let diagnosis = void 0;
6482
+ let diagnosisError;
6483
+ if (toolResult.exitCode === 0) {
6484
+ try {
6485
+ diagnosis = parseIssueDiagnosisResult(`${toolResult.stdout}
6486
+ ${toolResult.stderr}`);
6487
+ } catch (error) {
6488
+ diagnosisError = errorMessage3(error);
6489
+ }
6490
+ } else {
6491
+ diagnosisError = truncateLogExcerpt(toolResult.stderr || toolResult.stdout) || `${toolName} exited with code ${toolResult.exitCode}.`;
6492
+ }
6493
+ const finalStatus = diagnosis ? "completed" : "failed";
6494
+ const updatedToolSession = await finalizeToolSession({
6495
+ apiClient,
6496
+ projectId,
6497
+ status: finalStatus,
6498
+ runnerId,
6499
+ workItemId: workItem.workItemId,
6500
+ stdout: toolResult.stdout,
6501
+ ...sessionContext.toolSession ? { session: sessionContext.toolSession } : {},
6502
+ ...toolResult.messageCount !== void 0 ? { messageCount: toolResult.messageCount } : {},
6503
+ ...toolResult.tokensIn !== void 0 ? { tokensIn: toolResult.tokensIn } : {},
6504
+ ...toolResult.tokensOut !== void 0 ? { tokensOut: toolResult.tokensOut } : {},
6505
+ ...toolResult.costUsd !== void 0 ? { costUsd: toolResult.costUsd } : {}
6506
+ });
6507
+ const sessionTelemetry = {
6508
+ sessionPolicy: sessionContext.policy,
6509
+ sessionDecision: sessionContext.decision,
6510
+ sessionDecisionReason: sessionContext.reason,
6511
+ ...updatedToolSession ? { toolSessionId: updatedToolSession.toolSessionId } : {},
6512
+ ...updatedToolSession?.sessionGroupKey ? { sessionGroupKey: updatedToolSession.sessionGroupKey } : {}
6513
+ };
6514
+ if (diagnosis) {
6515
+ const result = await apiClient.submitIssueDiagnosisResult(projectId, workItem.workItemId, {
6516
+ status: "completed",
6517
+ runnerId,
6518
+ idempotencyKey: `issue_${workItem.workItemId}_${randomUUID()}`,
6519
+ diagnosis,
6520
+ tool: toolName,
6521
+ durationMs,
6522
+ ...sessionTelemetry,
6523
+ message: `${toolName} returned an issue root-cause analysis.`
6524
+ });
6525
+ await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
6526
+ status: "completed",
6527
+ summary: `${toolName} returned an issue root-cause analysis.`,
6528
+ idempotencyKey: `runner_milestone_issue_completed_${workItem.workItemId}_${result.workItem.idempotencyKey}`,
6529
+ metadata: { tool: toolName, durationMs, riskLevel: diagnosis.riskLevel, likelyPathCount: diagnosis.likelyPaths.length, verificationSummary: diagnosis.verificationPlan.join(" | ") }
6530
+ });
6531
+ await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
6532
+ console.log("Issue diagnosis returned for approval.");
6533
+ return { status: "completed", exitCode: 0 };
6534
+ }
6535
+ const failedResult = await apiClient.submitIssueDiagnosisResult(projectId, workItem.workItemId, {
6536
+ status: "failed",
6537
+ runnerId,
6538
+ idempotencyKey: `issue_${workItem.workItemId}_${randomUUID()}`,
6539
+ tool: toolName,
6540
+ durationMs,
6541
+ ...sessionTelemetry,
6542
+ message: `${toolName} did not produce a valid issue diagnosis.`,
6543
+ ...diagnosisError ? { error: diagnosisError } : {}
6544
+ });
6545
+ await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
6546
+ status: "failed",
6547
+ summary: diagnosisError ?? `${toolName} did not produce a valid issue diagnosis.`,
6548
+ idempotencyKey: `runner_milestone_issue_failed_${workItem.workItemId}_${failedResult.workItem.idempotencyKey}`,
6549
+ metadata: { tool: toolName, durationMs, verificationSummary: "Issue diagnosis output did not include valid structured JSON." }
6550
+ });
6551
+ await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
6552
+ console.error(diagnosisError ?? "Local runner issue diagnosis failed.");
6553
+ return { status: "failed", exitCode: toolResult.exitCode || 1 };
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
+ }
6050
6641
  async function createRunnerWorkPrompt(apiClient, projectId, workItem) {
6051
6642
  if (workItem.workKind === "assistantQuestion") {
6052
6643
  const [{ documents: documents2 }, { messages: messages2 }] = await Promise.all([
@@ -6068,6 +6659,25 @@ async function createRunnerWorkPrompt(apiClient, projectId, workItem) {
6068
6659
  }
6069
6660
  });
6070
6661
  }
6662
+ if (workItem.workKind === "issueDiagnosis") {
6663
+ const [{ documents: documents2 }, { issues }] = await Promise.all([
6664
+ apiClient.listBrainDocuments(projectId),
6665
+ apiClient.listIssues(projectId)
6666
+ ]);
6667
+ const issue = issues.find((item) => item.issueId === workItem.issueId || item.id === workItem.issueId);
6668
+ return createWorkExecutionPrompt(workItem, {
6669
+ issueDiagnosis: {
6670
+ documents: documents2,
6671
+ ...issue ? { issue } : {}
6672
+ }
6673
+ });
6674
+ }
6675
+ if (workItem.workKind === "securityPostureScan") {
6676
+ const { documents: documents2 } = await apiClient.listBrainDocuments(projectId);
6677
+ return createWorkExecutionPrompt(workItem, {
6678
+ securityPostureScan: { documents: documents2 }
6679
+ });
6680
+ }
6071
6681
  if (workItem.workKind !== "planRevision" || !workItem.reviewDocumentId) {
6072
6682
  return createWorkExecutionPrompt(workItem);
6073
6683
  }
@@ -6545,6 +7155,7 @@ function toRunnerToolCapabilities(tools) {
6545
7155
  function runnerIsolationCapabilityMetadata() {
6546
7156
  return {
6547
7157
  machineId: runnerMachineId(),
7158
+ supportedWorkKinds: runnerSupportedWorkKinds,
6548
7159
  supportsBranchIsolation: true,
6549
7160
  supportsGitWorktreeIsolation: true
6550
7161
  };