@amistio/cli 0.1.11 → 0.1.13
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 +9 -3
- package/dist/index.js +1667 -134
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { createHash as createHash7, randomUUID } from "node:crypto";
|
|
5
5
|
import { writeFile as writeFile9 } from "node:fs/promises";
|
|
6
6
|
import os7 from "node:os";
|
|
7
|
-
import
|
|
7
|
+
import path14 from "node:path";
|
|
8
8
|
import { Command } from "commander";
|
|
9
9
|
|
|
10
10
|
// ../shared/src/schemas.ts
|
|
@@ -21,6 +21,15 @@ var itemTypeSchema = z.enum([
|
|
|
21
21
|
"brainDocument",
|
|
22
22
|
"generatedDraft",
|
|
23
23
|
"issue",
|
|
24
|
+
"securityScan",
|
|
25
|
+
"securityFinding",
|
|
26
|
+
"securityPostureSnapshot",
|
|
27
|
+
"appEvaluationScan",
|
|
28
|
+
"appEvaluationFinding",
|
|
29
|
+
"projectContextMap",
|
|
30
|
+
"projectContextRefresh",
|
|
31
|
+
"projectSystemDiagram",
|
|
32
|
+
"contextMiss",
|
|
24
33
|
"syncCursor",
|
|
25
34
|
"syncConflict",
|
|
26
35
|
"workItem",
|
|
@@ -45,6 +54,8 @@ var documentTypeSchema = z.enum([
|
|
|
45
54
|
"prompt",
|
|
46
55
|
"workflow"
|
|
47
56
|
]);
|
|
57
|
+
var documentContentFormatSchema = z.enum(["markdown", "html"]);
|
|
58
|
+
var artifactFormatPreferenceSchema = z.enum(["markdown", "html", "both", "auto"]);
|
|
48
59
|
var syncStateSchema = z.enum([
|
|
49
60
|
"draft",
|
|
50
61
|
"approved",
|
|
@@ -67,7 +78,23 @@ var workStatusSchema = z.enum([
|
|
|
67
78
|
]);
|
|
68
79
|
var sourceSchema = z.enum(["web", "repo", "generated", "runner"]);
|
|
69
80
|
var executionModeSchema = z.enum(["localRunner", "cloudSandbox"]);
|
|
70
|
-
var workKindSchema = z.enum(["brainGeneration", "implementation", "planRevision", "assistantQuestion", "impactPreview", "issueDiagnosis"]);
|
|
81
|
+
var workKindSchema = z.enum(["brainGeneration", "implementation", "planRevision", "assistantQuestion", "impactPreview", "issueDiagnosis", "securityPostureScan", "appEvaluationScan", "projectContextRefresh"]);
|
|
82
|
+
var implementationHandoffStatusSchema = z.enum(["notStarted", "noChanges", "prReady", "blocked", "failed"]);
|
|
83
|
+
var implementationHandoffCleanupStatusSchema = z.enum(["notApplicable", "pending", "completed", "failed"]);
|
|
84
|
+
var implementationHandoffSchema = z.object({
|
|
85
|
+
provider: z.string().trim().min(1).max(80).optional(),
|
|
86
|
+
status: implementationHandoffStatusSchema,
|
|
87
|
+
baseBranch: z.string().trim().min(1).max(200).optional(),
|
|
88
|
+
headBranch: z.string().trim().min(1).max(200).optional(),
|
|
89
|
+
remoteName: z.string().trim().min(1).max(80).optional(),
|
|
90
|
+
commitSha: z.string().trim().min(7).max(64).optional(),
|
|
91
|
+
prNumber: z.number().int().positive().optional(),
|
|
92
|
+
prUrl: z.string().trim().url().max(500).optional(),
|
|
93
|
+
cleanupStatus: implementationHandoffCleanupStatusSchema.optional(),
|
|
94
|
+
cleanupMessage: z.string().trim().min(1).max(600).optional(),
|
|
95
|
+
message: z.string().trim().min(1).max(600).optional(),
|
|
96
|
+
error: z.string().trim().min(1).max(1200).optional()
|
|
97
|
+
}).strict();
|
|
71
98
|
var generatedDraftStatusSchema = z.enum(["queued", "generating", "reviewing", "approved", "rejected", "changesRequested", "failed"]);
|
|
72
99
|
var assistantQuestionModeSchema = z.enum(["brainOnly", "sourceAware"]);
|
|
73
100
|
var impactRiskLevelSchema = z.enum(["low", "medium", "high", "critical"]);
|
|
@@ -76,6 +103,20 @@ var issueCategorySchema = z.enum(["bug", "regression", "brokenWorkflow", "securi
|
|
|
76
103
|
var issueSeveritySchema = z.enum(["low", "medium", "high", "critical"]);
|
|
77
104
|
var issueStatusSchema = z.enum(["queued", "diagnosing", "analysisReady", "approved", "changesRequested", "rejected", "implementationQueued", "implemented", "failed"]);
|
|
78
105
|
var issueApprovalStateSchema = z.enum(["proposed", "approved", "changesRequested", "rejected", "implemented", "blocked"]);
|
|
106
|
+
var securityScanStatusSchema = z.enum(["queued", "running", "completed", "failed", "stale", "skipped"]);
|
|
107
|
+
var securityScanSourceSchema = z.enum(["nightly", "manual", "runner"]);
|
|
108
|
+
var securityFindingCategorySchema = z.enum(["owasp", "dependency", "supplyChain", "secretHygiene", "authentication", "authorization", "tenantIsolation", "headers", "redirects", "csrf", "cors", "ciCd", "logging", "runnerBoundary", "other"]);
|
|
109
|
+
var securityFindingSeveritySchema = z.enum(["info", "low", "medium", "high", "critical"]);
|
|
110
|
+
var securityFindingConfidenceSchema = z.enum(["low", "medium", "high"]);
|
|
111
|
+
var securityFindingStatusSchema = z.enum(["open", "planReady", "approved", "changesRequested", "dismissed", "acceptedRisk", "remediationQueued", "resolved", "failed"]);
|
|
112
|
+
var securityApprovalStateSchema = z.enum(["proposed", "approved", "changesRequested", "dismissed", "acceptedRisk", "implemented", "blocked"]);
|
|
113
|
+
var appEvaluationScanStatusSchema = z.enum(["queued", "running", "completed", "failed", "stale", "skipped"]);
|
|
114
|
+
var appEvaluationFindingCategorySchema = z.enum(["verification", "productDrift", "planCleanup", "promptRot", "missingMemory", "releaseReadiness", "ux", "accessibility", "performance", "reliability", "securityPosture", "other"]);
|
|
115
|
+
var appEvaluationFindingSeveritySchema = z.enum(["info", "low", "medium", "high", "critical"]);
|
|
116
|
+
var appEvaluationFindingConfidenceSchema = z.enum(["low", "medium", "high"]);
|
|
117
|
+
var appEvaluationFindingStatusSchema = z.enum(["open", "planReady", "approved", "changesRequested", "dismissed", "acceptedRisk", "implementationQueued", "resolved", "failed"]);
|
|
118
|
+
var appEvaluationApprovalStateSchema = z.enum(["proposed", "approved", "changesRequested", "dismissed", "acceptedRisk", "implemented", "blocked"]);
|
|
119
|
+
var appEvaluationPlanLifecycleActionSchema = z.enum(["createPlan", "updatePlan", "markCompleted", "markImplemented", "markSuperseded", "markBlocked", "archive", "keepActive", "none"]);
|
|
79
120
|
var activityEventTypeSchema = z.enum([
|
|
80
121
|
"commandSubmitted",
|
|
81
122
|
"generationQueued",
|
|
@@ -99,6 +140,30 @@ var activityEventTypeSchema = z.enum([
|
|
|
99
140
|
"issueDiagnosisFailed",
|
|
100
141
|
"issueReview",
|
|
101
142
|
"issueImplementationQueued",
|
|
143
|
+
"securityScanQueued",
|
|
144
|
+
"securityScanStarted",
|
|
145
|
+
"securityScanCompleted",
|
|
146
|
+
"securityScanFailed",
|
|
147
|
+
"securityFindingOpened",
|
|
148
|
+
"securityFindingReviewed",
|
|
149
|
+
"securityRemediationPlanCreated",
|
|
150
|
+
"securityRemediationQueued",
|
|
151
|
+
"appEvaluationQueued",
|
|
152
|
+
"appEvaluationStarted",
|
|
153
|
+
"appEvaluationCompleted",
|
|
154
|
+
"appEvaluationFailed",
|
|
155
|
+
"appEvaluationFindingOpened",
|
|
156
|
+
"appEvaluationFindingReviewed",
|
|
157
|
+
"appEvaluationPlanCreated",
|
|
158
|
+
"appEvaluationImplementationQueued",
|
|
159
|
+
"projectContextRefreshQueued",
|
|
160
|
+
"projectContextRefreshStarted",
|
|
161
|
+
"projectContextRefreshCompleted",
|
|
162
|
+
"projectContextRefreshFailed",
|
|
163
|
+
"projectContextMissRecorded",
|
|
164
|
+
"projectSystemDiagramGenerated",
|
|
165
|
+
"projectSystemDiagramReviewed",
|
|
166
|
+
"projectSystemDiagramMissRecorded",
|
|
102
167
|
"handoffExported"
|
|
103
168
|
]);
|
|
104
169
|
var activityEventActorSchema = z.enum(["webUser", "runner", "cli", "system"]);
|
|
@@ -113,6 +178,7 @@ var activityMetadataValueSchema = z.union([
|
|
|
113
178
|
var activityMetadataSchema = z.record(z.string().min(1).max(80), activityMetadataValueSchema).default({});
|
|
114
179
|
var generatedBrainArtifactSchema = z.object({
|
|
115
180
|
documentType: documentTypeSchema,
|
|
181
|
+
contentFormat: documentContentFormatSchema.default("markdown"),
|
|
116
182
|
title: z.string().trim().min(1),
|
|
117
183
|
repoPath: z.string().trim().min(1),
|
|
118
184
|
content: z.string().min(1)
|
|
@@ -225,6 +291,7 @@ var organizationWorkspaceScopeSchema = z.object({
|
|
|
225
291
|
kind: z.literal("organization"),
|
|
226
292
|
accountId: z.string().min(1),
|
|
227
293
|
organizationId: z.string().min(1),
|
|
294
|
+
userId: z.string().min(1).optional(),
|
|
228
295
|
organizationSlug: z.string().trim().min(1).optional(),
|
|
229
296
|
organizationRole: z.string().trim().min(1).optional(),
|
|
230
297
|
label: z.string().trim().min(1).default("Organization workspace")
|
|
@@ -264,6 +331,7 @@ var projectItemSchema = baseItemSchema.extend({
|
|
|
264
331
|
name: z.string().min(1),
|
|
265
332
|
slug: z.string().min(1),
|
|
266
333
|
description: z.string().optional(),
|
|
334
|
+
artifactFormatPreference: artifactFormatPreferenceSchema.optional(),
|
|
267
335
|
status: projectStatusSchema.default("active"),
|
|
268
336
|
archivedAt: isoDateTimeSchema.optional(),
|
|
269
337
|
archivedByUserId: z.string().min(1).optional(),
|
|
@@ -284,6 +352,7 @@ var repositoryLinkItemSchema = baseItemSchema.extend({
|
|
|
284
352
|
linkSource: repositoryLinkSourceSchema.optional(),
|
|
285
353
|
cloneStatus: repositoryCloneStatusSchema.optional(),
|
|
286
354
|
autoSyncEnabled: z.boolean().optional(),
|
|
355
|
+
appEvaluationEnabled: z.boolean().optional(),
|
|
287
356
|
lastValidatedAt: isoDateTimeSchema.optional(),
|
|
288
357
|
status: z.enum(["active", "revoked"]).default("active")
|
|
289
358
|
});
|
|
@@ -292,6 +361,7 @@ var brainDocumentItemSchema = baseItemSchema.extend({
|
|
|
292
361
|
projectId: z.string().min(1),
|
|
293
362
|
documentId: z.string().min(1),
|
|
294
363
|
documentType: documentTypeSchema,
|
|
364
|
+
contentFormat: documentContentFormatSchema.default("markdown"),
|
|
295
365
|
title: z.string().min(1),
|
|
296
366
|
status: z.string().min(1),
|
|
297
367
|
repoPath: z.string().min(1),
|
|
@@ -349,6 +419,12 @@ var workItemSchema = baseItemSchema.extend({
|
|
|
349
419
|
sourceWish: z.string().min(1).optional(),
|
|
350
420
|
generatedDraftId: z.string().min(1).optional(),
|
|
351
421
|
issueId: z.string().min(1).optional(),
|
|
422
|
+
securityScanId: z.string().min(1).optional(),
|
|
423
|
+
securityFindingId: z.string().min(1).optional(),
|
|
424
|
+
appEvaluationScanId: z.string().min(1).optional(),
|
|
425
|
+
appEvaluationFindingId: z.string().min(1).optional(),
|
|
426
|
+
projectContextRefreshId: z.string().min(1).optional(),
|
|
427
|
+
contextMissId: z.string().min(1).optional(),
|
|
352
428
|
repositoryLinkId: z.string().min(1).optional(),
|
|
353
429
|
reviewThreadId: z.string().min(1).optional(),
|
|
354
430
|
reviewDocumentId: z.string().min(1).optional(),
|
|
@@ -356,6 +432,7 @@ var workItemSchema = baseItemSchema.extend({
|
|
|
356
432
|
reviewMessageId: z.string().min(1).optional(),
|
|
357
433
|
assistantMessageId: z.string().min(1).optional(),
|
|
358
434
|
assistantQuestionMode: assistantQuestionModeSchema.optional(),
|
|
435
|
+
artifactFormatPreference: artifactFormatPreferenceSchema.optional(),
|
|
359
436
|
impactReportId: z.string().min(1).optional(),
|
|
360
437
|
impactDocumentId: z.string().min(1).optional(),
|
|
361
438
|
impactDocumentRevision: z.number().int().nonnegative().optional(),
|
|
@@ -381,6 +458,7 @@ var workItemSchema = baseItemSchema.extend({
|
|
|
381
458
|
toolSessionId: z.string().min(1).optional(),
|
|
382
459
|
sessionDecision: sessionDecisionSchema.optional(),
|
|
383
460
|
sessionDecisionReason: z.string().min(1).optional(),
|
|
461
|
+
implementationHandoff: implementationHandoffSchema.optional(),
|
|
384
462
|
lastStatusMessage: z.string().optional(),
|
|
385
463
|
lastStatusAt: isoDateTimeSchema
|
|
386
464
|
});
|
|
@@ -475,6 +553,7 @@ var runnerExecutionLogItemSchema = baseItemSchema.extend({
|
|
|
475
553
|
toolSessionId: z.string().min(1).optional(),
|
|
476
554
|
sessionDecision: sessionDecisionSchema.optional(),
|
|
477
555
|
sessionDecisionReason: z.string().min(1).optional(),
|
|
556
|
+
implementationHandoff: implementationHandoffSchema.optional(),
|
|
478
557
|
message: z.string().optional(),
|
|
479
558
|
error: z.string().optional()
|
|
480
559
|
});
|
|
@@ -571,7 +650,7 @@ var assistantAnswerResultSchema = z.object({
|
|
|
571
650
|
summary: z.string().optional()
|
|
572
651
|
});
|
|
573
652
|
var knowledgeSearchScopeSchema = z.enum(["project", "organization"]);
|
|
574
|
-
var knowledgeSourceTypeSchema = z.enum(["brainDocument", "generatedArtifact", "runnerSummary", "knowledgeEntity", "knowledgeRelation"]);
|
|
653
|
+
var knowledgeSourceTypeSchema = z.enum(["brainDocument", "generatedArtifact", "runnerSummary", "knowledgeEntity", "knowledgeRelation", "projectContextMap", "projectContextSlice", "projectSystemDiagram", "projectSystemDiagramNode", "projectSystemDiagramEdge"]);
|
|
575
654
|
var knowledgeChunkStatusSchema = z.enum(["approved", "synced", "stale"]);
|
|
576
655
|
var knowledgeEntityTypeSchema = z.enum(["project", "system", "component", "domain", "tool", "decision", "feature", "risk", "team", "workflow", "unknown"]);
|
|
577
656
|
var knowledgeRelationTypeSchema = z.enum(["uses", "depends_on", "decides", "supersedes", "touches", "blocks", "implements", "mentions"]);
|
|
@@ -683,6 +762,221 @@ var knowledgeDiagramResultSchema = z.object({
|
|
|
683
762
|
freshness: knowledgeIndexFreshnessSchema,
|
|
684
763
|
warnings: z.array(z.string().trim().min(1).max(600)).default([])
|
|
685
764
|
});
|
|
765
|
+
var projectContextSliceKindSchema = z.enum(["overview", "architecture", "domain", "data", "api", "frontend", "backend", "cli", "workflow", "operations", "security", "testing", "unknown"]);
|
|
766
|
+
var projectContextFreshnessSchema = z.enum(["fresh", "stale", "partial", "missing"]);
|
|
767
|
+
var projectContextMapStatusSchema = z.enum(["draft", "proposed", "approved", "stale", "archived"]);
|
|
768
|
+
var projectContextRefreshStatusSchema = z.enum(["queued", "running", "completed", "failed", "skipped"]);
|
|
769
|
+
var contextMissStatusSchema = z.enum(["open", "resolved", "dismissed"]);
|
|
770
|
+
var projectContextRepoPathSchema = z.string().trim().min(1).max(300).refine((value) => {
|
|
771
|
+
if (value.startsWith("/") || /^[A-Za-z]:[\\/]/.test(value)) {
|
|
772
|
+
return false;
|
|
773
|
+
}
|
|
774
|
+
return !value.split(/[\\/]+/).includes("..");
|
|
775
|
+
}, "Path must be repository-relative without traversal");
|
|
776
|
+
var projectContextCoverageSchema = z.object({
|
|
777
|
+
status: projectContextFreshnessSchema.default("missing"),
|
|
778
|
+
sliceCount: z.number().int().nonnegative().default(0),
|
|
779
|
+
staleSliceCount: z.number().int().nonnegative().default(0),
|
|
780
|
+
missingAreas: z.array(z.string().trim().min(1).max(160)).default([]),
|
|
781
|
+
warnings: z.array(z.string().trim().min(1).max(300)).default([])
|
|
782
|
+
});
|
|
783
|
+
var defaultProjectContextCoverage = { status: "missing", sliceCount: 0, staleSliceCount: 0, missingAreas: [], warnings: [] };
|
|
784
|
+
var projectContextSliceSchema = z.object({
|
|
785
|
+
sliceId: z.string().trim().min(1).max(160),
|
|
786
|
+
kind: projectContextSliceKindSchema.default("unknown"),
|
|
787
|
+
title: z.string().trim().min(1).max(200),
|
|
788
|
+
summary: z.string().trim().min(1).max(4e3),
|
|
789
|
+
repoPaths: z.array(projectContextRepoPathSchema).max(80).default([]),
|
|
790
|
+
ownerHints: z.array(z.string().trim().min(1).max(160)).max(20).default([]),
|
|
791
|
+
tags: z.array(z.string().trim().min(1).max(80)).max(30).default([]),
|
|
792
|
+
citations: z.array(assistantCitationSchema).default([]),
|
|
793
|
+
dependsOn: z.array(z.string().trim().min(1).max(160)).max(50).default([]),
|
|
794
|
+
freshness: projectContextFreshnessSchema.default("fresh"),
|
|
795
|
+
confidence: z.number().min(0).max(1).default(0.6),
|
|
796
|
+
contentHash: z.string().trim().min(1).max(160).optional(),
|
|
797
|
+
lastVerifiedAt: isoDateTimeSchema.optional()
|
|
798
|
+
});
|
|
799
|
+
var projectContextEntitySchema = z.object({
|
|
800
|
+
entityId: z.string().trim().min(1).max(200),
|
|
801
|
+
name: z.string().trim().min(1).max(200),
|
|
802
|
+
entityType: knowledgeEntityTypeSchema.default("unknown"),
|
|
803
|
+
summary: z.string().trim().min(1).max(1200).optional(),
|
|
804
|
+
aliases: z.array(z.string().trim().min(1).max(200)).default([]),
|
|
805
|
+
sliceIds: z.array(z.string().trim().min(1).max(160)).default([]),
|
|
806
|
+
citations: z.array(assistantCitationSchema).default([]),
|
|
807
|
+
confidence: z.number().min(0).max(1).default(0.6)
|
|
808
|
+
});
|
|
809
|
+
var projectContextRelationSchema = z.object({
|
|
810
|
+
relationId: z.string().trim().min(1).max(200),
|
|
811
|
+
relationType: knowledgeRelationTypeSchema.default("mentions"),
|
|
812
|
+
fromId: z.string().trim().min(1).max(200),
|
|
813
|
+
toId: z.string().trim().min(1).max(200),
|
|
814
|
+
summary: z.string().trim().min(1).max(1200),
|
|
815
|
+
citations: z.array(assistantCitationSchema).default([]),
|
|
816
|
+
confidence: z.number().min(0).max(1).default(0.6)
|
|
817
|
+
});
|
|
818
|
+
var projectSystemDiagramStatusSchema = z.enum(["draft", "proposed", "approved", "stale", "archived"]);
|
|
819
|
+
var projectSystemDiagramViewKindSchema = z.enum(["wholeSystem", "projectBrainLifecycle", "contextRefresh", "runtimeTopology", "featureFlow"]);
|
|
820
|
+
var projectSystemDiagramNodeKindSchema = z.enum(["actor", "app", "api", "cli", "runner", "dataStore", "searchIndex", "authBoundary", "repository", "externalService", "workflow", "component", "unknown"]);
|
|
821
|
+
var projectSystemDiagramGroupKindSchema = z.enum(["boundary", "lane", "group"]);
|
|
822
|
+
var projectSystemDiagramDirectionSchema = z.enum(["TD", "LR"]);
|
|
823
|
+
var projectSystemDiagramNodeSchema = z.object({
|
|
824
|
+
nodeId: z.string().trim().min(1).max(200),
|
|
825
|
+
label: z.string().trim().min(1).max(120),
|
|
826
|
+
kind: projectSystemDiagramNodeKindSchema.default("unknown"),
|
|
827
|
+
summary: z.string().trim().min(1).max(1200).optional(),
|
|
828
|
+
groupId: z.string().trim().min(1).max(160).optional(),
|
|
829
|
+
lane: z.string().trim().min(1).max(120).optional(),
|
|
830
|
+
sliceIds: z.array(z.string().trim().min(1).max(160)).default([]),
|
|
831
|
+
entityIds: z.array(z.string().trim().min(1).max(200)).default([]),
|
|
832
|
+
citations: z.array(assistantCitationSchema).min(1),
|
|
833
|
+
confidence: z.number().min(0).max(1).default(0.6),
|
|
834
|
+
freshness: projectContextFreshnessSchema.default("fresh")
|
|
835
|
+
});
|
|
836
|
+
var projectSystemDiagramEdgeSchema = z.object({
|
|
837
|
+
edgeId: z.string().trim().min(1).max(220),
|
|
838
|
+
fromNodeId: z.string().trim().min(1).max(200),
|
|
839
|
+
toNodeId: z.string().trim().min(1).max(200),
|
|
840
|
+
relationType: knowledgeRelationTypeSchema.default("mentions"),
|
|
841
|
+
label: z.string().trim().min(1).max(120).optional(),
|
|
842
|
+
summary: z.string().trim().min(1).max(1200),
|
|
843
|
+
citations: z.array(assistantCitationSchema).min(1),
|
|
844
|
+
confidence: z.number().min(0).max(1).default(0.6),
|
|
845
|
+
freshness: projectContextFreshnessSchema.default("fresh")
|
|
846
|
+
});
|
|
847
|
+
var projectSystemDiagramGroupSchema = z.object({
|
|
848
|
+
groupId: z.string().trim().min(1).max(160),
|
|
849
|
+
label: z.string().trim().min(1).max(120),
|
|
850
|
+
kind: projectSystemDiagramGroupKindSchema.default("group"),
|
|
851
|
+
summary: z.string().trim().min(1).max(1200).optional(),
|
|
852
|
+
citations: z.array(assistantCitationSchema).default([])
|
|
853
|
+
});
|
|
854
|
+
var projectSystemDiagramViewSchema = z.object({
|
|
855
|
+
viewId: z.string().trim().min(1).max(160),
|
|
856
|
+
kind: projectSystemDiagramViewKindSchema,
|
|
857
|
+
title: z.string().trim().min(1).max(160),
|
|
858
|
+
summary: z.string().trim().min(1).max(1200),
|
|
859
|
+
nodeIds: z.array(z.string().trim().min(1).max(200)).min(1),
|
|
860
|
+
edgeIds: z.array(z.string().trim().min(1).max(220)).default([]),
|
|
861
|
+
groupIds: z.array(z.string().trim().min(1).max(160)).default([]),
|
|
862
|
+
mermaid: z.string().trim().min(1).max(12e3),
|
|
863
|
+
warnings: z.array(z.string().trim().min(1).max(600)).default([]),
|
|
864
|
+
freshness: projectContextFreshnessSchema.default("fresh")
|
|
865
|
+
});
|
|
866
|
+
var projectSystemDiagramGraphManifestSchema = z.object({
|
|
867
|
+
manifestId: z.string().trim().min(1).max(200),
|
|
868
|
+
layoutVersion: z.number().int().positive().default(1),
|
|
869
|
+
direction: projectSystemDiagramDirectionSchema.default("LR"),
|
|
870
|
+
nodes: z.array(projectSystemDiagramNodeSchema).min(1),
|
|
871
|
+
edges: z.array(projectSystemDiagramEdgeSchema).default([]),
|
|
872
|
+
groups: z.array(projectSystemDiagramGroupSchema).default([]),
|
|
873
|
+
views: z.array(projectSystemDiagramViewSchema).min(1),
|
|
874
|
+
citations: z.array(assistantCitationSchema).default([]),
|
|
875
|
+
unknowns: z.array(z.string().trim().min(1).max(300)).default([]),
|
|
876
|
+
warnings: z.array(z.string().trim().min(1).max(600)).default([])
|
|
877
|
+
});
|
|
878
|
+
var projectSystemDiagramItemSchema = baseItemSchema.extend({
|
|
879
|
+
type: z.literal("projectSystemDiagram"),
|
|
880
|
+
projectId: z.string().min(1),
|
|
881
|
+
projectSystemDiagramId: z.string().min(1),
|
|
882
|
+
version: z.number().int().positive(),
|
|
883
|
+
status: projectSystemDiagramStatusSchema,
|
|
884
|
+
source: sourceSchema.default("generated"),
|
|
885
|
+
repositoryLinkId: z.string().min(1).optional(),
|
|
886
|
+
projectContextMapId: z.string().min(1),
|
|
887
|
+
projectContextMapVersion: z.number().int().positive(),
|
|
888
|
+
sourceBrainRevision: z.number().int().nonnegative().optional(),
|
|
889
|
+
summary: z.string().trim().min(1).max(4e3),
|
|
890
|
+
graph: projectSystemDiagramGraphManifestSchema,
|
|
891
|
+
coverage: projectContextCoverageSchema.default(defaultProjectContextCoverage),
|
|
892
|
+
mermaid: z.string().trim().min(1).max(12e3),
|
|
893
|
+
approvedByUserId: z.string().min(1).optional(),
|
|
894
|
+
approvedAt: isoDateTimeSchema.optional(),
|
|
895
|
+
generatedAt: isoDateTimeSchema,
|
|
896
|
+
supersedesDiagramId: z.string().min(1).optional(),
|
|
897
|
+
error: z.string().max(1e3).optional()
|
|
898
|
+
});
|
|
899
|
+
var projectContextMapItemSchema = baseItemSchema.extend({
|
|
900
|
+
type: z.literal("projectContextMap"),
|
|
901
|
+
projectId: z.string().min(1),
|
|
902
|
+
projectContextMapId: z.string().min(1),
|
|
903
|
+
version: z.number().int().positive(),
|
|
904
|
+
status: projectContextMapStatusSchema,
|
|
905
|
+
source: sourceSchema.default("runner"),
|
|
906
|
+
repositoryLinkId: z.string().min(1).optional(),
|
|
907
|
+
refreshId: z.string().min(1).optional(),
|
|
908
|
+
summary: z.string().trim().min(1).max(4e3),
|
|
909
|
+
slices: z.array(projectContextSliceSchema).default([]),
|
|
910
|
+
entities: z.array(projectContextEntitySchema).default([]),
|
|
911
|
+
relations: z.array(projectContextRelationSchema).default([]),
|
|
912
|
+
coverage: projectContextCoverageSchema.default(defaultProjectContextCoverage),
|
|
913
|
+
approvedByUserId: z.string().min(1).optional(),
|
|
914
|
+
approvedAt: isoDateTimeSchema.optional(),
|
|
915
|
+
generatedAt: isoDateTimeSchema,
|
|
916
|
+
supersedesMapId: z.string().min(1).optional(),
|
|
917
|
+
error: z.string().max(1e3).optional()
|
|
918
|
+
});
|
|
919
|
+
var projectContextRefreshResultSchema = z.object({
|
|
920
|
+
summary: z.string().trim().min(1).max(4e3),
|
|
921
|
+
slices: z.array(projectContextSliceSchema).default([]),
|
|
922
|
+
entities: z.array(projectContextEntitySchema).default([]),
|
|
923
|
+
relations: z.array(projectContextRelationSchema).default([]),
|
|
924
|
+
coverage: projectContextCoverageSchema.default(defaultProjectContextCoverage),
|
|
925
|
+
changedSliceIds: z.array(z.string().trim().min(1).max(160)).default([]),
|
|
926
|
+
staleSliceIds: z.array(z.string().trim().min(1).max(160)).default([]),
|
|
927
|
+
verificationPlan: z.array(z.string().trim().min(1).max(300)).min(1),
|
|
928
|
+
warnings: z.array(z.string().trim().min(1).max(600)).default([])
|
|
929
|
+
});
|
|
930
|
+
var projectContextRefreshItemSchema = baseItemSchema.extend({
|
|
931
|
+
type: z.literal("projectContextRefresh"),
|
|
932
|
+
projectId: z.string().min(1),
|
|
933
|
+
projectContextRefreshId: z.string().min(1),
|
|
934
|
+
status: projectContextRefreshStatusSchema,
|
|
935
|
+
source: z.enum(["manual", "runner", "scheduled"]).default("manual"),
|
|
936
|
+
repositoryLinkId: z.string().min(1).optional(),
|
|
937
|
+
workItemId: z.string().min(1).optional(),
|
|
938
|
+
runnerId: z.string().min(1).optional(),
|
|
939
|
+
previousMapId: z.string().min(1).optional(),
|
|
940
|
+
proposedMapId: z.string().min(1).optional(),
|
|
941
|
+
summary: z.string().trim().min(1).max(4e3).optional(),
|
|
942
|
+
changedSliceIds: z.array(z.string().trim().min(1).max(160)).default([]),
|
|
943
|
+
staleSliceIds: z.array(z.string().trim().min(1).max(160)).default([]),
|
|
944
|
+
startedAt: isoDateTimeSchema.optional(),
|
|
945
|
+
completedAt: isoDateTimeSchema.optional(),
|
|
946
|
+
failedAt: isoDateTimeSchema.optional(),
|
|
947
|
+
error: z.string().max(1e3).optional()
|
|
948
|
+
});
|
|
949
|
+
var projectContextPackSchema = z.object({
|
|
950
|
+
packId: z.string().min(1),
|
|
951
|
+
accountId: z.string().min(1),
|
|
952
|
+
projectId: z.string().min(1),
|
|
953
|
+
workItemId: z.string().min(1).optional(),
|
|
954
|
+
query: z.string().trim().min(1).max(2e3),
|
|
955
|
+
mapId: z.string().min(1).optional(),
|
|
956
|
+
mapVersion: z.number().int().positive().optional(),
|
|
957
|
+
slices: z.array(projectContextSliceSchema).default([]),
|
|
958
|
+
entities: z.array(projectContextEntitySchema).default([]),
|
|
959
|
+
relations: z.array(projectContextRelationSchema).default([]),
|
|
960
|
+
citations: z.array(assistantCitationSchema).default([]),
|
|
961
|
+
freshnessWarnings: z.array(z.string().trim().min(1).max(600)).default([]),
|
|
962
|
+
tokenEstimate: z.number().int().nonnegative().default(0),
|
|
963
|
+
generatedAt: isoDateTimeSchema
|
|
964
|
+
});
|
|
965
|
+
var contextMissItemSchema = baseItemSchema.extend({
|
|
966
|
+
type: z.literal("contextMiss"),
|
|
967
|
+
projectId: z.string().min(1),
|
|
968
|
+
contextMissId: z.string().min(1),
|
|
969
|
+
status: contextMissStatusSchema.default("open"),
|
|
970
|
+
source: z.enum(["runner", "web", "system"]).default("runner"),
|
|
971
|
+
workItemId: z.string().min(1).optional(),
|
|
972
|
+
projectContextMapId: z.string().min(1).optional(),
|
|
973
|
+
query: z.string().trim().min(1).max(2e3),
|
|
974
|
+
missingSurface: z.string().trim().min(1).max(200),
|
|
975
|
+
reason: z.string().trim().min(1).max(1200),
|
|
976
|
+
suggestedPaths: z.array(projectContextRepoPathSchema).default([]),
|
|
977
|
+
resolvedAt: isoDateTimeSchema.optional(),
|
|
978
|
+
dismissedAt: isoDateTimeSchema.optional()
|
|
979
|
+
});
|
|
686
980
|
var impactAffectedAreaSchema = z.object({
|
|
687
981
|
name: z.string().trim().min(1),
|
|
688
982
|
description: z.string().trim().min(1).optional()
|
|
@@ -773,6 +1067,177 @@ var issueItemSchema = baseItemSchema.extend({
|
|
|
773
1067
|
runnerId: z.string().min(1).optional(),
|
|
774
1068
|
lastDiagnosisError: z.string().optional()
|
|
775
1069
|
});
|
|
1070
|
+
var securityStandardReferenceSchema = z.object({
|
|
1071
|
+
standard: z.string().trim().min(1).max(120),
|
|
1072
|
+
control: z.string().trim().min(1).max(160),
|
|
1073
|
+
url: z.string().url().optional()
|
|
1074
|
+
});
|
|
1075
|
+
var securitySeverityCountsSchema = z.object({
|
|
1076
|
+
info: z.number().int().nonnegative().default(0),
|
|
1077
|
+
low: z.number().int().nonnegative().default(0),
|
|
1078
|
+
medium: z.number().int().nonnegative().default(0),
|
|
1079
|
+
high: z.number().int().nonnegative().default(0),
|
|
1080
|
+
critical: z.number().int().nonnegative().default(0)
|
|
1081
|
+
});
|
|
1082
|
+
var defaultSecuritySeverityCounts = { info: 0, low: 0, medium: 0, high: 0, critical: 0 };
|
|
1083
|
+
var securityCategorySummarySchema = z.object({
|
|
1084
|
+
category: securityFindingCategorySchema,
|
|
1085
|
+
status: z.enum(["pass", "warning", "fail", "unknown"]),
|
|
1086
|
+
summary: z.string().trim().min(1).max(600)
|
|
1087
|
+
});
|
|
1088
|
+
var securityFindingResultSchema = z.object({
|
|
1089
|
+
title: z.string().trim().min(1).max(200),
|
|
1090
|
+
category: securityFindingCategorySchema,
|
|
1091
|
+
severity: securityFindingSeveritySchema,
|
|
1092
|
+
confidence: securityFindingConfidenceSchema.default("medium"),
|
|
1093
|
+
summary: z.string().trim().min(1).max(2e3),
|
|
1094
|
+
standardReferences: z.array(securityStandardReferenceSchema).default([]),
|
|
1095
|
+
affectedSurfaces: z.array(z.string().trim().min(1).max(300)).default([]),
|
|
1096
|
+
evidence: z.array(z.string().trim().min(1).max(600)).default([]),
|
|
1097
|
+
recommendedRemediation: z.string().trim().min(1).max(4e3),
|
|
1098
|
+
verificationPlan: z.array(z.string().trim().min(1).max(300)).min(1),
|
|
1099
|
+
safePaths: z.array(z.string().trim().min(1).max(300)).default([]),
|
|
1100
|
+
status: securityFindingStatusSchema.default("open")
|
|
1101
|
+
});
|
|
1102
|
+
var securityPostureScanResultSchema = z.object({
|
|
1103
|
+
summary: z.string().trim().min(1).max(2e3),
|
|
1104
|
+
baselineVersion: z.string().trim().min(1).max(80),
|
|
1105
|
+
postureScore: z.number().min(0).max(100).optional(),
|
|
1106
|
+
postureGrade: z.enum(["A", "B", "C", "D", "F", "Unknown"]).default("Unknown"),
|
|
1107
|
+
categorySummaries: z.array(securityCategorySummarySchema).default([]),
|
|
1108
|
+
findings: z.array(securityFindingResultSchema).default([]),
|
|
1109
|
+
verificationPlan: z.array(z.string().trim().min(1).max(300)).min(1)
|
|
1110
|
+
});
|
|
1111
|
+
var securityScanItemSchema = baseItemSchema.extend({
|
|
1112
|
+
type: z.literal("securityScan"),
|
|
1113
|
+
projectId: z.string().min(1),
|
|
1114
|
+
securityScanId: z.string().min(1),
|
|
1115
|
+
scheduledDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
|
|
1116
|
+
source: securityScanSourceSchema,
|
|
1117
|
+
status: securityScanStatusSchema,
|
|
1118
|
+
baselineVersion: z.string().trim().min(1).max(80).default("amistio-security-baseline-v1"),
|
|
1119
|
+
workItemId: z.string().min(1).optional(),
|
|
1120
|
+
repositoryLinkId: z.string().min(1).optional(),
|
|
1121
|
+
runnerId: z.string().min(1).optional(),
|
|
1122
|
+
findingIds: z.array(z.string().min(1)).default([]),
|
|
1123
|
+
summary: z.string().trim().min(1).max(2e3).optional(),
|
|
1124
|
+
severityCounts: securitySeverityCountsSchema.default(defaultSecuritySeverityCounts),
|
|
1125
|
+
startedAt: isoDateTimeSchema.optional(),
|
|
1126
|
+
completedAt: isoDateTimeSchema.optional(),
|
|
1127
|
+
failedAt: isoDateTimeSchema.optional(),
|
|
1128
|
+
error: z.string().max(1e3).optional()
|
|
1129
|
+
});
|
|
1130
|
+
var securityFindingItemSchema = baseItemSchema.extend({
|
|
1131
|
+
type: z.literal("securityFinding"),
|
|
1132
|
+
projectId: z.string().min(1),
|
|
1133
|
+
securityFindingId: z.string().min(1),
|
|
1134
|
+
securityScanId: z.string().min(1),
|
|
1135
|
+
title: z.string().trim().min(1).max(200),
|
|
1136
|
+
category: securityFindingCategorySchema,
|
|
1137
|
+
severity: securityFindingSeveritySchema,
|
|
1138
|
+
confidence: securityFindingConfidenceSchema.default("medium"),
|
|
1139
|
+
status: securityFindingStatusSchema,
|
|
1140
|
+
approvalState: securityApprovalStateSchema.default("proposed"),
|
|
1141
|
+
summary: z.string().trim().min(1).max(2e3),
|
|
1142
|
+
standardReferences: z.array(securityStandardReferenceSchema).default([]),
|
|
1143
|
+
affectedSurfaces: z.array(z.string().trim().min(1).max(300)).default([]),
|
|
1144
|
+
evidence: z.array(z.string().trim().min(1).max(600)).default([]),
|
|
1145
|
+
safePaths: z.array(z.string().trim().min(1).max(300)).default([]),
|
|
1146
|
+
recommendedRemediation: z.string().trim().min(1).max(4e3),
|
|
1147
|
+
verificationPlan: z.array(z.string().trim().min(1).max(300)).min(1),
|
|
1148
|
+
remediationPlanDocumentId: z.string().min(1).optional(),
|
|
1149
|
+
remediationPromptDocumentId: z.string().min(1).optional(),
|
|
1150
|
+
implementationWorkItemId: z.string().min(1).optional(),
|
|
1151
|
+
reviewNotes: z.string().trim().min(1).optional(),
|
|
1152
|
+
reviewedByUserId: z.string().min(1).optional(),
|
|
1153
|
+
reviewedAt: isoDateTimeSchema.optional(),
|
|
1154
|
+
resolvedAt: isoDateTimeSchema.optional(),
|
|
1155
|
+
lastReviewAction: z.enum(["approve", "requestChanges", "dismiss", "acceptRisk", "reopen"]).optional()
|
|
1156
|
+
});
|
|
1157
|
+
var securityPostureSnapshotItemSchema = baseItemSchema.extend({
|
|
1158
|
+
type: z.literal("securityPostureSnapshot"),
|
|
1159
|
+
projectId: z.string().min(1),
|
|
1160
|
+
securityPostureSnapshotId: z.string().min(1),
|
|
1161
|
+
latestScanId: z.string().min(1).optional(),
|
|
1162
|
+
status: z.enum(["unknown", "fresh", "stale", "running", "failed"]),
|
|
1163
|
+
baselineVersion: z.string().trim().min(1).max(80).default("amistio-security-baseline-v1"),
|
|
1164
|
+
postureScore: z.number().min(0).max(100).optional(),
|
|
1165
|
+
postureGrade: z.enum(["A", "B", "C", "D", "F", "Unknown"]).default("Unknown"),
|
|
1166
|
+
severityCounts: securitySeverityCountsSchema.default(defaultSecuritySeverityCounts),
|
|
1167
|
+
categorySummaries: z.array(securityCategorySummarySchema).default([]),
|
|
1168
|
+
lastScannedAt: isoDateTimeSchema.optional(),
|
|
1169
|
+
nextScheduledAt: isoDateTimeSchema.optional(),
|
|
1170
|
+
summary: z.string().trim().min(1).max(2e3).optional()
|
|
1171
|
+
});
|
|
1172
|
+
var appEvaluationFindingResultSchema = z.object({
|
|
1173
|
+
title: z.string().trim().min(1).max(200),
|
|
1174
|
+
category: appEvaluationFindingCategorySchema,
|
|
1175
|
+
severity: appEvaluationFindingSeveritySchema,
|
|
1176
|
+
confidence: appEvaluationFindingConfidenceSchema.default("medium"),
|
|
1177
|
+
summary: z.string().trim().min(1).max(2e3),
|
|
1178
|
+
affectedSurfaces: z.array(z.string().trim().min(1).max(300)).default([]),
|
|
1179
|
+
evidence: z.array(z.string().trim().min(1).max(600)).default([]),
|
|
1180
|
+
suggestedAction: z.string().trim().min(1).max(4e3),
|
|
1181
|
+
verificationPlan: z.array(z.string().trim().min(1).max(300)).min(1),
|
|
1182
|
+
safePaths: z.array(z.string().trim().min(1).max(300)).default([]),
|
|
1183
|
+
proposedLifecycleAction: appEvaluationPlanLifecycleActionSchema.default("none"),
|
|
1184
|
+
relatedArtifactIds: z.array(z.string().trim().min(1).max(160)).default([]),
|
|
1185
|
+
proposedPlanTitle: z.string().trim().min(1).max(200).optional(),
|
|
1186
|
+
proposedPlanRepoPath: z.string().trim().min(1).max(300).optional(),
|
|
1187
|
+
proposedPlanContent: z.string().trim().min(1).max(3e4).optional(),
|
|
1188
|
+
status: appEvaluationFindingStatusSchema.default("open")
|
|
1189
|
+
});
|
|
1190
|
+
var appEvaluationScanResultSchema = z.object({
|
|
1191
|
+
summary: z.string().trim().min(1).max(2e3),
|
|
1192
|
+
baselineVersion: z.string().trim().min(1).max(80),
|
|
1193
|
+
findings: z.array(appEvaluationFindingResultSchema).default([]),
|
|
1194
|
+
verificationPlan: z.array(z.string().trim().min(1).max(300)).min(1)
|
|
1195
|
+
});
|
|
1196
|
+
var appEvaluationScanItemSchema = baseItemSchema.extend({
|
|
1197
|
+
type: z.literal("appEvaluationScan"),
|
|
1198
|
+
projectId: z.string().min(1),
|
|
1199
|
+
appEvaluationScanId: z.string().min(1),
|
|
1200
|
+
hourBucket: z.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:00:00\.000Z$/),
|
|
1201
|
+
source: z.enum(["runner", "manual"]),
|
|
1202
|
+
status: appEvaluationScanStatusSchema,
|
|
1203
|
+
baselineVersion: z.string().trim().min(1).max(80).default("amistio-app-evaluation-v1"),
|
|
1204
|
+
workItemId: z.string().min(1).optional(),
|
|
1205
|
+
repositoryLinkId: z.string().min(1).optional(),
|
|
1206
|
+
runnerId: z.string().min(1).optional(),
|
|
1207
|
+
findingIds: z.array(z.string().min(1)).default([]),
|
|
1208
|
+
summary: z.string().trim().min(1).max(2e3).optional(),
|
|
1209
|
+
startedAt: isoDateTimeSchema.optional(),
|
|
1210
|
+
completedAt: isoDateTimeSchema.optional(),
|
|
1211
|
+
failedAt: isoDateTimeSchema.optional(),
|
|
1212
|
+
error: z.string().max(1e3).optional()
|
|
1213
|
+
});
|
|
1214
|
+
var appEvaluationFindingItemSchema = baseItemSchema.extend({
|
|
1215
|
+
type: z.literal("appEvaluationFinding"),
|
|
1216
|
+
projectId: z.string().min(1),
|
|
1217
|
+
appEvaluationFindingId: z.string().min(1),
|
|
1218
|
+
appEvaluationScanId: z.string().min(1),
|
|
1219
|
+
title: z.string().trim().min(1).max(200),
|
|
1220
|
+
category: appEvaluationFindingCategorySchema,
|
|
1221
|
+
severity: appEvaluationFindingSeveritySchema,
|
|
1222
|
+
confidence: appEvaluationFindingConfidenceSchema.default("medium"),
|
|
1223
|
+
status: appEvaluationFindingStatusSchema,
|
|
1224
|
+
approvalState: appEvaluationApprovalStateSchema.default("proposed"),
|
|
1225
|
+
summary: z.string().trim().min(1).max(2e3),
|
|
1226
|
+
affectedSurfaces: z.array(z.string().trim().min(1).max(300)).default([]),
|
|
1227
|
+
evidence: z.array(z.string().trim().min(1).max(600)).default([]),
|
|
1228
|
+
safePaths: z.array(z.string().trim().min(1).max(300)).default([]),
|
|
1229
|
+
suggestedAction: z.string().trim().min(1).max(4e3),
|
|
1230
|
+
verificationPlan: z.array(z.string().trim().min(1).max(300)).min(1),
|
|
1231
|
+
proposedLifecycleAction: appEvaluationPlanLifecycleActionSchema.default("none"),
|
|
1232
|
+
relatedArtifactIds: z.array(z.string().trim().min(1).max(160)).default([]),
|
|
1233
|
+
proposedPlanDocumentId: z.string().min(1).optional(),
|
|
1234
|
+
implementationWorkItemId: z.string().min(1).optional(),
|
|
1235
|
+
reviewNotes: z.string().trim().min(1).optional(),
|
|
1236
|
+
reviewedByUserId: z.string().min(1).optional(),
|
|
1237
|
+
reviewedAt: isoDateTimeSchema.optional(),
|
|
1238
|
+
resolvedAt: isoDateTimeSchema.optional(),
|
|
1239
|
+
lastReviewAction: z.enum(["approve", "requestChanges", "dismiss", "acceptRisk", "reopen"]).optional()
|
|
1240
|
+
});
|
|
776
1241
|
var activityEventItemSchema = baseItemSchema.extend({
|
|
777
1242
|
type: z.literal("activityEvent"),
|
|
778
1243
|
projectId: z.string().min(1),
|
|
@@ -788,6 +1253,13 @@ var activityEventItemSchema = baseItemSchema.extend({
|
|
|
788
1253
|
relatedWorkItemId: z.string().min(1).optional(),
|
|
789
1254
|
relatedDocumentId: z.string().min(1).optional(),
|
|
790
1255
|
relatedIssueId: z.string().min(1).optional(),
|
|
1256
|
+
relatedSecurityScanId: z.string().min(1).optional(),
|
|
1257
|
+
relatedSecurityFindingId: z.string().min(1).optional(),
|
|
1258
|
+
relatedAppEvaluationScanId: z.string().min(1).optional(),
|
|
1259
|
+
relatedAppEvaluationFindingId: z.string().min(1).optional(),
|
|
1260
|
+
relatedProjectContextRefreshId: z.string().min(1).optional(),
|
|
1261
|
+
relatedProjectSystemDiagramId: z.string().min(1).optional(),
|
|
1262
|
+
relatedContextMissId: z.string().min(1).optional(),
|
|
791
1263
|
generatedDraftId: z.string().min(1).optional(),
|
|
792
1264
|
runnerId: z.string().min(1).optional(),
|
|
793
1265
|
repositoryLinkId: z.string().min(1).optional(),
|
|
@@ -845,6 +1317,15 @@ var projectItemUnionSchema = z.discriminatedUnion("type", [
|
|
|
845
1317
|
brainDocumentItemSchema,
|
|
846
1318
|
generatedDraftItemSchema,
|
|
847
1319
|
issueItemSchema,
|
|
1320
|
+
securityScanItemSchema,
|
|
1321
|
+
securityFindingItemSchema,
|
|
1322
|
+
securityPostureSnapshotItemSchema,
|
|
1323
|
+
appEvaluationScanItemSchema,
|
|
1324
|
+
appEvaluationFindingItemSchema,
|
|
1325
|
+
projectContextMapItemSchema,
|
|
1326
|
+
projectContextRefreshItemSchema,
|
|
1327
|
+
projectSystemDiagramItemSchema,
|
|
1328
|
+
contextMissItemSchema,
|
|
848
1329
|
syncCursorItemSchema,
|
|
849
1330
|
syncConflictItemSchema,
|
|
850
1331
|
workItemSchema,
|
|
@@ -884,16 +1365,43 @@ function isOfficialAmistioApiUrl(value) {
|
|
|
884
1365
|
|
|
885
1366
|
// ../shared/src/brain-documents.ts
|
|
886
1367
|
var controlPlaneRoots = /* @__PURE__ */ new Set(["architecture", "context", "decisions", "features", "memory", "plans", "prompts", "workflows"]);
|
|
1368
|
+
var documentTypeFolders = {
|
|
1369
|
+
architecture: "architecture",
|
|
1370
|
+
context: "context",
|
|
1371
|
+
decision: "decisions",
|
|
1372
|
+
feature: "features",
|
|
1373
|
+
memory: "memory",
|
|
1374
|
+
plan: "plans",
|
|
1375
|
+
prompt: "prompts",
|
|
1376
|
+
workflow: "workflows"
|
|
1377
|
+
};
|
|
1378
|
+
var documentTypeByFolder = Object.fromEntries(Object.entries(documentTypeFolders).map(([documentType, folder]) => [folder, documentType]));
|
|
887
1379
|
function isControlPlaneTemplateRepoPath(repoPath) {
|
|
888
1380
|
const normalized = normalizeRepoPath(repoPath);
|
|
889
1381
|
const segments = normalized.split("/").filter(Boolean);
|
|
890
|
-
const root = segments
|
|
1382
|
+
const root = controlPlaneRootFromSegments(segments);
|
|
891
1383
|
const basename = segments[segments.length - 1]?.toLowerCase();
|
|
892
1384
|
return Boolean(root && controlPlaneRoots.has(root) && (basename === "_template.md" || basename === "_template.mdx"));
|
|
893
1385
|
}
|
|
1386
|
+
function documentTypeForBrainRepoPath(repoPath) {
|
|
1387
|
+
const normalized = normalizeRepoPath(repoPath);
|
|
1388
|
+
const segments = normalized.split("/").filter(Boolean);
|
|
1389
|
+
const root = controlPlaneRootFromSegments(segments);
|
|
1390
|
+
return root ? documentTypeByFolder[root] : void 0;
|
|
1391
|
+
}
|
|
1392
|
+
function inferContentFormatFromRepoPath(repoPath) {
|
|
1393
|
+
if (/\.html?$/i.test(repoPath)) return "html";
|
|
1394
|
+
if (/\.mdx?$/i.test(repoPath)) return "markdown";
|
|
1395
|
+
return void 0;
|
|
1396
|
+
}
|
|
894
1397
|
function normalizeRepoPath(repoPath) {
|
|
895
1398
|
return repoPath.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "");
|
|
896
1399
|
}
|
|
1400
|
+
function controlPlaneRootFromSegments(segments) {
|
|
1401
|
+
if (segments[0] === "docs" && segments[1] === "html") return segments[2];
|
|
1402
|
+
if (segments[0] === "docs") return segments[1];
|
|
1403
|
+
return segments[0];
|
|
1404
|
+
}
|
|
897
1405
|
|
|
898
1406
|
// ../shared/src/repo-metadata.ts
|
|
899
1407
|
import { z as z2 } from "zod";
|
|
@@ -907,6 +1415,7 @@ var repoLinkMetadataSchema = z2.object({
|
|
|
907
1415
|
var syncedDocumentFrontmatterSchema = z2.object({
|
|
908
1416
|
amistioDocumentId: z2.string().min(1),
|
|
909
1417
|
amistioDocumentType: z2.string().min(1),
|
|
1418
|
+
amistioContentFormat: z2.enum(["markdown", "html"]).default("markdown"),
|
|
910
1419
|
amistioRevision: z2.coerce.number().int().nonnegative(),
|
|
911
1420
|
amistioContentHash: z2.string().min(1),
|
|
912
1421
|
status: z2.string().optional()
|
|
@@ -1057,6 +1566,7 @@ function decideSyncState(state) {
|
|
|
1057
1566
|
|
|
1058
1567
|
// ../shared/src/next-action.ts
|
|
1059
1568
|
var nextActionRunnerHeartbeatFreshMs = 15 * 60 * 1e3;
|
|
1569
|
+
var nextActionCompletedWorkFreshMs = 60 * 60 * 1e3;
|
|
1060
1570
|
function computeProjectNextAction(input) {
|
|
1061
1571
|
const nowMs = input.nowMs ?? Date.now();
|
|
1062
1572
|
if (!input.projectId) {
|
|
@@ -1154,7 +1664,7 @@ function computeProjectNextAction(input) {
|
|
|
1154
1664
|
if (!readiness.ready) {
|
|
1155
1665
|
return runnerWaitAction(readiness, "Runner setup needed");
|
|
1156
1666
|
}
|
|
1157
|
-
const completedWork = latestWorkItem(input.workItems.filter((item) => item.status === "completed"));
|
|
1667
|
+
const completedWork = latestWorkItem(input.workItems.filter((item) => item.status === "completed" && isFreshCompletedWork(item, nowMs)));
|
|
1158
1668
|
if (completedWork) {
|
|
1159
1669
|
return workAction(completedWork, "workCompleted", "none", "success", "Work completed", completedWork.lastStatusMessage ?? "The latest runner work is complete.");
|
|
1160
1670
|
}
|
|
@@ -1248,6 +1758,10 @@ function workAction(workItem, kind, actor, tone, title, message) {
|
|
|
1248
1758
|
function latestWorkItem(workItems) {
|
|
1249
1759
|
return [...workItems].sort((first, second) => Date.parse(second.updatedAt) - Date.parse(first.updatedAt))[0];
|
|
1250
1760
|
}
|
|
1761
|
+
function isFreshCompletedWork(workItem, nowMs) {
|
|
1762
|
+
const timestamp = Date.parse(workItem.lastStatusAt ?? workItem.updatedAt);
|
|
1763
|
+
return timestamp > 0 && nowMs - timestamp <= nextActionCompletedWorkFreshMs;
|
|
1764
|
+
}
|
|
1251
1765
|
function latestTimestamp(values) {
|
|
1252
1766
|
return values.sort((first, second) => Date.parse(second) - Date.parse(first))[0];
|
|
1253
1767
|
}
|
|
@@ -1589,6 +2103,27 @@ var ApiClient = class {
|
|
|
1589
2103
|
{ method: "GET" }
|
|
1590
2104
|
);
|
|
1591
2105
|
}
|
|
2106
|
+
async listProjectContext(projectId) {
|
|
2107
|
+
return this.request(
|
|
2108
|
+
`/projects/${projectId}/project-context`,
|
|
2109
|
+
z3.object({
|
|
2110
|
+
maps: z3.array(projectContextMapItemSchema),
|
|
2111
|
+
refreshes: z3.array(projectContextRefreshItemSchema),
|
|
2112
|
+
misses: z3.array(contextMissItemSchema),
|
|
2113
|
+
activeMap: projectContextMapItemSchema.optional()
|
|
2114
|
+
}),
|
|
2115
|
+
{ method: "GET" }
|
|
2116
|
+
);
|
|
2117
|
+
}
|
|
2118
|
+
async buildProjectContextPack(projectId, query, workItemId) {
|
|
2119
|
+
const params = new URLSearchParams({ packQuery: query });
|
|
2120
|
+
if (workItemId) params.set("workItemId", workItemId);
|
|
2121
|
+
return this.request(
|
|
2122
|
+
`/projects/${projectId}/project-context?${params.toString()}`,
|
|
2123
|
+
projectContextPackSchema,
|
|
2124
|
+
{ method: "GET" }
|
|
2125
|
+
);
|
|
2126
|
+
}
|
|
1592
2127
|
async pushBrainDocuments(projectId, documents) {
|
|
1593
2128
|
return this.request(
|
|
1594
2129
|
`/projects/${projectId}/brain-documents`,
|
|
@@ -1756,6 +2291,36 @@ var ApiClient = class {
|
|
|
1756
2291
|
}
|
|
1757
2292
|
);
|
|
1758
2293
|
}
|
|
2294
|
+
async submitSecurityPostureScanResult(projectId, workItemId, result) {
|
|
2295
|
+
return this.request(
|
|
2296
|
+
`/projects/${projectId}/work-items/${workItemId}/security-result`,
|
|
2297
|
+
z3.object({ scan: securityScanItemSchema, findings: z3.array(securityFindingItemSchema), snapshot: securityPostureSnapshotItemSchema, workItem: workItemSchema }),
|
|
2298
|
+
{
|
|
2299
|
+
method: "POST",
|
|
2300
|
+
body: JSON.stringify(result)
|
|
2301
|
+
}
|
|
2302
|
+
);
|
|
2303
|
+
}
|
|
2304
|
+
async submitAppEvaluationScanResult(projectId, workItemId, result) {
|
|
2305
|
+
return this.request(
|
|
2306
|
+
`/projects/${projectId}/work-items/${workItemId}/app-evaluation-result`,
|
|
2307
|
+
z3.object({ scan: appEvaluationScanItemSchema, findings: z3.array(appEvaluationFindingItemSchema), workItem: workItemSchema }),
|
|
2308
|
+
{
|
|
2309
|
+
method: "POST",
|
|
2310
|
+
body: JSON.stringify(result)
|
|
2311
|
+
}
|
|
2312
|
+
);
|
|
2313
|
+
}
|
|
2314
|
+
async submitProjectContextRefreshResult(projectId, workItemId, result) {
|
|
2315
|
+
return this.request(
|
|
2316
|
+
`/projects/${projectId}/work-items/${workItemId}/project-context-result`,
|
|
2317
|
+
z3.object({ refresh: projectContextRefreshItemSchema, map: projectContextMapItemSchema.optional(), workItem: workItemSchema }),
|
|
2318
|
+
{
|
|
2319
|
+
method: "POST",
|
|
2320
|
+
body: JSON.stringify(result)
|
|
2321
|
+
}
|
|
2322
|
+
);
|
|
2323
|
+
}
|
|
1759
2324
|
async request(urlPath, schema, init) {
|
|
1760
2325
|
const response = await fetch(resolveApiUrl(this.options.apiUrl, urlPath), {
|
|
1761
2326
|
...init,
|
|
@@ -1802,8 +2367,8 @@ var toolSessionMutationSchema = z3.object({
|
|
|
1802
2367
|
});
|
|
1803
2368
|
function resolveApiUrl(apiUrl, urlPath) {
|
|
1804
2369
|
const base = apiUrl.endsWith("/") ? apiUrl.slice(0, -1) : apiUrl;
|
|
1805
|
-
const
|
|
1806
|
-
return new URL(`${base}${
|
|
2370
|
+
const path15 = urlPath.startsWith("/") ? urlPath : `/${urlPath}`;
|
|
2371
|
+
return new URL(`${base}${path15}`);
|
|
1807
2372
|
}
|
|
1808
2373
|
|
|
1809
2374
|
// src/orchestrator.ts
|
|
@@ -2695,6 +3260,15 @@ function currentRunnerMode() {
|
|
|
2695
3260
|
function defaultRunnerMetadataDir() {
|
|
2696
3261
|
return path6.join(os3.homedir(), ".config", "amistio", "runners");
|
|
2697
3262
|
}
|
|
3263
|
+
function updatedCliRunnerLaunchOptions() {
|
|
3264
|
+
return { executablePath: "amistio", directExecutable: true };
|
|
3265
|
+
}
|
|
3266
|
+
function resolveRunnerDaemonLaunch(input) {
|
|
3267
|
+
if (input.directExecutable) {
|
|
3268
|
+
return { executablePath: input.executablePath ?? "amistio", args: input.args };
|
|
3269
|
+
}
|
|
3270
|
+
return { executablePath: input.executablePath ?? process.execPath, args: [input.scriptPath ?? process.argv[1], ...input.args] };
|
|
3271
|
+
}
|
|
2698
3272
|
async function startRunnerDaemon(input) {
|
|
2699
3273
|
const metadataDir = input.metadataDir ?? defaultRunnerMetadataDir();
|
|
2700
3274
|
const existing = await readRunnerDaemonMetadata(input, metadataDir);
|
|
@@ -2704,7 +3278,12 @@ async function startRunnerDaemon(input) {
|
|
|
2704
3278
|
await mkdir5(metadataDir, { recursive: true });
|
|
2705
3279
|
const logPath = path6.join(metadataDir, `${runnerDaemonKey(input)}.log`);
|
|
2706
3280
|
const logFd = openSync(logPath, "a");
|
|
2707
|
-
const
|
|
3281
|
+
const launch = resolveRunnerDaemonLaunch({
|
|
3282
|
+
args: input.args,
|
|
3283
|
+
...input.executablePath ? { executablePath: input.executablePath } : {},
|
|
3284
|
+
...input.scriptPath ? { scriptPath: input.scriptPath } : {}
|
|
3285
|
+
});
|
|
3286
|
+
const child = spawn2(launch.executablePath, launch.args, {
|
|
2708
3287
|
cwd: input.rootDir,
|
|
2709
3288
|
detached: true,
|
|
2710
3289
|
env: {
|
|
@@ -2741,7 +3320,13 @@ async function restartRunnerDaemonProcess(metadata, args, input = {}) {
|
|
|
2741
3320
|
await mkdir5(metadataDir, { recursive: true });
|
|
2742
3321
|
const logPath = metadata.logPath ?? path6.join(metadataDir, `${runnerDaemonKey(metadata)}.log`);
|
|
2743
3322
|
const logFd = openSync(logPath, "a");
|
|
2744
|
-
const
|
|
3323
|
+
const launch = resolveRunnerDaemonLaunch({
|
|
3324
|
+
args,
|
|
3325
|
+
...input.executablePath ? { executablePath: input.executablePath } : {},
|
|
3326
|
+
...input.scriptPath ? { scriptPath: input.scriptPath } : {},
|
|
3327
|
+
...input.directExecutable ? { directExecutable: true } : {}
|
|
3328
|
+
});
|
|
3329
|
+
const child = spawn2(launch.executablePath, launch.args, {
|
|
2745
3330
|
cwd: metadata.rootDir,
|
|
2746
3331
|
detached: true,
|
|
2747
3332
|
env: {
|
|
@@ -3220,6 +3805,7 @@ import { promisify as promisify3 } from "node:util";
|
|
|
3220
3805
|
var execFileAsync3 = promisify3(execFile3);
|
|
3221
3806
|
var legacySyncRoots = ["architecture", "context", "decisions", "features", "memory", "plans", "prompts", "workflows"];
|
|
3222
3807
|
var syncRoots = legacySyncRoots.map((syncRoot) => `docs/${syncRoot}`);
|
|
3808
|
+
var htmlSyncRoot = "docs/html";
|
|
3223
3809
|
var documentTypeByRoot = {
|
|
3224
3810
|
architecture: "architecture",
|
|
3225
3811
|
context: "context",
|
|
@@ -3307,15 +3893,15 @@ async function collectSyncStatus(rootDir, webDocuments = []) {
|
|
|
3307
3893
|
return { status, items, counts };
|
|
3308
3894
|
}
|
|
3309
3895
|
async function readLocalSyncedDocuments(rootDir) {
|
|
3310
|
-
const
|
|
3896
|
+
const documentFiles = await findBrainDocumentFiles(rootDir);
|
|
3311
3897
|
const documents = [];
|
|
3312
|
-
for (const fullPath of
|
|
3898
|
+
for (const fullPath of documentFiles) {
|
|
3313
3899
|
const raw = await readFile6(fullPath, "utf8");
|
|
3314
|
-
const
|
|
3900
|
+
const repoPath = toRepoPath(rootDir, fullPath);
|
|
3901
|
+
const parsed = parseSyncedDocument(raw, repoPath);
|
|
3315
3902
|
if (!parsed) {
|
|
3316
3903
|
continue;
|
|
3317
3904
|
}
|
|
3318
|
-
const repoPath = toRepoPath(rootDir, fullPath);
|
|
3319
3905
|
if (isControlPlaneTemplateRepoPath(repoPath)) {
|
|
3320
3906
|
continue;
|
|
3321
3907
|
}
|
|
@@ -3361,7 +3947,7 @@ async function materializeBrainDocuments(rootDir, documents, options = {}) {
|
|
|
3361
3947
|
continue;
|
|
3362
3948
|
}
|
|
3363
3949
|
await mkdir7(path8.dirname(fullPath), { recursive: true });
|
|
3364
|
-
await writeFile7(fullPath,
|
|
3950
|
+
await writeFile7(fullPath, createSyncedDocumentContent(document), "utf8");
|
|
3365
3951
|
result.written.push(document.repoPath);
|
|
3366
3952
|
}
|
|
3367
3953
|
return result;
|
|
@@ -3379,6 +3965,7 @@ async function collectDirtyDocumentsForPush(rootDir, metadata) {
|
|
|
3379
3965
|
projectId: metadata.amistioProjectId,
|
|
3380
3966
|
documentId: document.frontmatter.amistioDocumentId,
|
|
3381
3967
|
documentType,
|
|
3968
|
+
contentFormat: document.frontmatter.amistioContentFormat,
|
|
3382
3969
|
title: inferTitle(document.content, document.repoPath),
|
|
3383
3970
|
status: "reviewing",
|
|
3384
3971
|
repoPath: document.repoPath,
|
|
@@ -3416,11 +4003,15 @@ function autoSyncSkipCounts(skipped) {
|
|
|
3416
4003
|
unreadable: skipped.filter((item) => item.reason === "unreadable").length
|
|
3417
4004
|
};
|
|
3418
4005
|
}
|
|
3419
|
-
function
|
|
4006
|
+
function createSyncedDocumentContent(document) {
|
|
4007
|
+
return (document.contentFormat ?? inferContentFormatFromRepoPath(document.repoPath) ?? "markdown") === "html" ? createSyncedDocumentHtml(document) : createSyncedDocumentMarkdownContent(document);
|
|
4008
|
+
}
|
|
4009
|
+
function createSyncedDocumentMarkdownContent(document) {
|
|
3420
4010
|
return [
|
|
3421
4011
|
"---",
|
|
3422
4012
|
`amistioDocumentId: ${document.documentId}`,
|
|
3423
4013
|
`amistioDocumentType: ${document.documentType}`,
|
|
4014
|
+
`amistioContentFormat: ${document.contentFormat ?? "markdown"}`,
|
|
3424
4015
|
`amistioRevision: ${document.revision}`,
|
|
3425
4016
|
`amistioContentHash: ${document.contentHash}`,
|
|
3426
4017
|
`status: ${document.status}`,
|
|
@@ -3429,6 +4020,24 @@ function createSyncedDocumentMarkdown(document) {
|
|
|
3429
4020
|
""
|
|
3430
4021
|
].join("\n");
|
|
3431
4022
|
}
|
|
4023
|
+
function createSyncedDocumentHtml(document) {
|
|
4024
|
+
return [
|
|
4025
|
+
"<!--",
|
|
4026
|
+
`amistioDocumentId: ${document.documentId}`,
|
|
4027
|
+
`amistioDocumentType: ${document.documentType}`,
|
|
4028
|
+
"amistioContentFormat: html",
|
|
4029
|
+
`amistioRevision: ${document.revision}`,
|
|
4030
|
+
`amistioContentHash: ${document.contentHash}`,
|
|
4031
|
+
`status: ${document.status}`,
|
|
4032
|
+
"-->",
|
|
4033
|
+
document.content,
|
|
4034
|
+
""
|
|
4035
|
+
].join("\n");
|
|
4036
|
+
}
|
|
4037
|
+
function parseSyncedDocument(content, repoPath) {
|
|
4038
|
+
const format = inferContentFormatFromRepoPath(repoPath);
|
|
4039
|
+
return format === "html" ? parseSyncedHtml(content) : parseSyncedMarkdown(content);
|
|
4040
|
+
}
|
|
3432
4041
|
function parseSyncedMarkdown(content) {
|
|
3433
4042
|
const rawFrontmatter = parseFrontmatter(content);
|
|
3434
4043
|
const frontmatter = syncedDocumentFrontmatterSchema.safeParse(rawFrontmatter);
|
|
@@ -3443,10 +4052,32 @@ function parseSyncedMarkdown(content) {
|
|
|
3443
4052
|
const bodyStart = closingLineEnd === -1 ? content.length : closingLineEnd + 1;
|
|
3444
4053
|
return { frontmatter: frontmatter.data, content: content.slice(bodyStart).replace(/\n$/, "") };
|
|
3445
4054
|
}
|
|
4055
|
+
function parseSyncedHtml(content) {
|
|
4056
|
+
const trimmedStart = content.trimStart();
|
|
4057
|
+
if (!trimmedStart.startsWith("<!--")) {
|
|
4058
|
+
return void 0;
|
|
4059
|
+
}
|
|
4060
|
+
const startOffset = content.indexOf("<!--");
|
|
4061
|
+
const closingMarker = content.indexOf("-->", startOffset + 4);
|
|
4062
|
+
if (closingMarker === -1) {
|
|
4063
|
+
return void 0;
|
|
4064
|
+
}
|
|
4065
|
+
const metadataLines = content.slice(startOffset + 4, closingMarker).split(/\r?\n/);
|
|
4066
|
+
const metadata = Object.fromEntries(metadataLines.map((line) => {
|
|
4067
|
+
const separator = line.indexOf(":");
|
|
4068
|
+
return separator === -1 ? void 0 : [line.slice(0, separator).trim(), line.slice(separator + 1).trim()];
|
|
4069
|
+
}).filter((entry) => Boolean(entry?.[0])));
|
|
4070
|
+
const frontmatter = syncedDocumentFrontmatterSchema.safeParse({ ...metadata, amistioContentFormat: "html" });
|
|
4071
|
+
if (!frontmatter.success) {
|
|
4072
|
+
return void 0;
|
|
4073
|
+
}
|
|
4074
|
+
const bodyStart = content.indexOf("\n", closingMarker + 3);
|
|
4075
|
+
return { frontmatter: frontmatter.data, content: content.slice(bodyStart === -1 ? closingMarker + 3 : bodyStart + 1).replace(/\n$/, "") };
|
|
4076
|
+
}
|
|
3446
4077
|
async function readExistingSyncedDocument(fullPath) {
|
|
3447
4078
|
try {
|
|
3448
4079
|
const raw = await readFile6(fullPath, "utf8");
|
|
3449
|
-
const parsed =
|
|
4080
|
+
const parsed = parseSyncedDocument(raw, fullPath);
|
|
3450
4081
|
if (!parsed) {
|
|
3451
4082
|
return { exists: true };
|
|
3452
4083
|
}
|
|
@@ -3467,23 +4098,23 @@ async function readExistingSyncedDocument(fullPath) {
|
|
|
3467
4098
|
throw error;
|
|
3468
4099
|
}
|
|
3469
4100
|
}
|
|
3470
|
-
async function
|
|
4101
|
+
async function findBrainDocumentFiles(rootDir) {
|
|
3471
4102
|
const files = [];
|
|
3472
|
-
for (const syncRoot of syncRoots) {
|
|
4103
|
+
for (const syncRoot of [...syncRoots, htmlSyncRoot]) {
|
|
3473
4104
|
const fullRoot = path8.join(rootDir, syncRoot);
|
|
3474
4105
|
if (!await exists2(fullRoot)) {
|
|
3475
4106
|
continue;
|
|
3476
4107
|
}
|
|
3477
|
-
await
|
|
4108
|
+
await walkBrainDocumentFiles(fullRoot, files);
|
|
3478
4109
|
}
|
|
3479
4110
|
return files;
|
|
3480
4111
|
}
|
|
3481
|
-
async function
|
|
4112
|
+
async function walkBrainDocumentFiles(directory, files) {
|
|
3482
4113
|
for (const entry of await readdir3(directory, { withFileTypes: true })) {
|
|
3483
4114
|
const fullPath = path8.join(directory, entry.name);
|
|
3484
4115
|
if (entry.isDirectory()) {
|
|
3485
|
-
await
|
|
3486
|
-
} else if (entry.isFile() && entry.name
|
|
4116
|
+
await walkBrainDocumentFiles(fullPath, files);
|
|
4117
|
+
} else if (entry.isFile() && /\.(md|mdx|html?)$/i.test(entry.name)) {
|
|
3487
4118
|
files.push(fullPath);
|
|
3488
4119
|
}
|
|
3489
4120
|
}
|
|
@@ -3505,7 +4136,7 @@ function safeRepoPath(rootDir, repoPath) {
|
|
|
3505
4136
|
}
|
|
3506
4137
|
function isControlPlanePath(repoPath) {
|
|
3507
4138
|
const normalized = path8.normalize(repoPath);
|
|
3508
|
-
return syncRoots.some((syncRoot) => normalized === syncRoot || normalized.startsWith(`${syncRoot}${path8.sep}`));
|
|
4139
|
+
return syncRoots.some((syncRoot) => normalized === syncRoot || normalized.startsWith(`${syncRoot}${path8.sep}`)) || normalized === htmlSyncRoot || normalized.startsWith(`${htmlSyncRoot}${path8.sep}`);
|
|
3509
4140
|
}
|
|
3510
4141
|
function canonicalControlPlaneRepoPath(repoPath) {
|
|
3511
4142
|
const normalized = repoPath.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "");
|
|
@@ -3520,7 +4151,9 @@ function toRepoPath(rootDir, fullPath) {
|
|
|
3520
4151
|
}
|
|
3521
4152
|
function inferTitle(content, repoPath) {
|
|
3522
4153
|
const heading = content.split("\n").find((line) => line.startsWith("# "))?.replace(/^#\s+/, "").trim();
|
|
3523
|
-
|
|
4154
|
+
if (heading) return heading;
|
|
4155
|
+
const htmlHeading = content.match(/<h1\b[^>]*>([\s\S]*?)<\/h1>/i)?.[1]?.replace(/<[^>]+>/g, "").trim();
|
|
4156
|
+
return htmlHeading || path8.basename(repoPath, path8.extname(repoPath));
|
|
3524
4157
|
}
|
|
3525
4158
|
async function collectExternalBrainDocumentsForPush(rootDir, metadata, existingDocuments, options) {
|
|
3526
4159
|
const root = path8.resolve(rootDir);
|
|
@@ -3553,7 +4186,7 @@ async function collectExternalBrainDocumentsForPush(rootDir, metadata, existingD
|
|
|
3553
4186
|
skipped.push({ repoPath: normalizedRepoPath, reason: "unreadable" });
|
|
3554
4187
|
continue;
|
|
3555
4188
|
}
|
|
3556
|
-
if (
|
|
4189
|
+
if (parseSyncedDocument(content, normalizedRepoPath)) {
|
|
3557
4190
|
skipped.push({ repoPath: normalizedRepoPath, reason: "alreadyManaged" });
|
|
3558
4191
|
continue;
|
|
3559
4192
|
}
|
|
@@ -3583,6 +4216,7 @@ async function collectExternalBrainDocumentsForPush(rootDir, metadata, existingD
|
|
|
3583
4216
|
projectId: metadata.amistioProjectId,
|
|
3584
4217
|
documentId,
|
|
3585
4218
|
documentType,
|
|
4219
|
+
contentFormat: inferContentFormatFromRepoPath(canonicalRepoPath) ?? "markdown",
|
|
3586
4220
|
title: inferTitle(content, canonicalRepoPath),
|
|
3587
4221
|
status: "reviewing",
|
|
3588
4222
|
repoPath: canonicalRepoPath,
|
|
@@ -3611,7 +4245,7 @@ async function listAutoSyncCandidatePaths(rootDir) {
|
|
|
3611
4245
|
return uniqueSortedRepoPaths(gitFiles.split("\n").map(normalizeRepoPath3).filter((repoPath) => repoPath && isRecognizedBrainRepoPath(repoPath)));
|
|
3612
4246
|
}
|
|
3613
4247
|
const files = [];
|
|
3614
|
-
for (const syncRoot of [...syncRoots, ...legacySyncRoots]) {
|
|
4248
|
+
for (const syncRoot of [...syncRoots, htmlSyncRoot, ...legacySyncRoots]) {
|
|
3615
4249
|
const fullRoot = path8.join(rootDir, syncRoot);
|
|
3616
4250
|
if (await exists2(fullRoot)) {
|
|
3617
4251
|
await walkAutoSyncFiles(rootDir, fullRoot, files);
|
|
@@ -3635,7 +4269,7 @@ async function walkAutoSyncFiles(rootDir, directory, files) {
|
|
|
3635
4269
|
function autoSyncPathSkipReason(repoPath) {
|
|
3636
4270
|
if (autoSyncMetadataPaths.has(repoPath)) return "metadata";
|
|
3637
4271
|
if (isControlPlaneTemplateRepoPath(repoPath)) return "template";
|
|
3638
|
-
if (!/\.(md|mdx)$/i.test(repoPath)) return "unsupported";
|
|
4272
|
+
if (!/\.(md|mdx|html?)$/i.test(repoPath)) return "unsupported";
|
|
3639
4273
|
const basename = repoPath.split("/").at(-1)?.toLowerCase() ?? "";
|
|
3640
4274
|
if (basename.startsWith(".") || basename.endsWith(".lock") || basename.includes(".env") || basename.includes("secret") || basename.includes("credential")) return "excluded";
|
|
3641
4275
|
if (repoPath.split("/").some((segment) => autoSyncExcludedDirectoryNames.has(segment) || autoSyncGeneratedPathSegments.has(segment))) return "excluded";
|
|
@@ -3643,10 +4277,13 @@ function autoSyncPathSkipReason(repoPath) {
|
|
|
3643
4277
|
}
|
|
3644
4278
|
function isRecognizedBrainRepoPath(repoPath) {
|
|
3645
4279
|
const normalized = normalizeRepoPath3(repoPath);
|
|
3646
|
-
const [firstSegment, secondSegment] = normalized.split("/");
|
|
3647
|
-
return Boolean(firstSegment === "docs" && secondSegment && legacySyncRoots.includes(secondSegment) || firstSegment && legacySyncRoots.includes(firstSegment));
|
|
4280
|
+
const [firstSegment, secondSegment, thirdSegment] = normalized.split("/");
|
|
4281
|
+
return Boolean(firstSegment === "docs" && secondSegment === "html" && thirdSegment && legacySyncRoots.includes(thirdSegment) || firstSegment === "docs" && secondSegment && legacySyncRoots.includes(secondSegment) || firstSegment && legacySyncRoots.includes(firstSegment));
|
|
3648
4282
|
}
|
|
3649
4283
|
function documentTypeForRepoPath(repoPath) {
|
|
4284
|
+
return documentTypeForBrainRepoPath(repoPath) ?? legacyDocumentTypeForRepoPath(repoPath);
|
|
4285
|
+
}
|
|
4286
|
+
function legacyDocumentTypeForRepoPath(repoPath) {
|
|
3650
4287
|
const normalized = normalizeRepoPath3(repoPath);
|
|
3651
4288
|
const segments = normalized.split("/");
|
|
3652
4289
|
const root = segments[0] === "docs" ? segments[1] : segments[0];
|
|
@@ -3665,6 +4302,7 @@ function parseFrontmatterFromSyncedDocument(frontmatter) {
|
|
|
3665
4302
|
return {
|
|
3666
4303
|
amistioDocumentId: frontmatter.amistioDocumentId,
|
|
3667
4304
|
amistioDocumentType: frontmatter.amistioDocumentType,
|
|
4305
|
+
amistioContentFormat: frontmatter.amistioContentFormat,
|
|
3668
4306
|
amistioRevision: frontmatter.amistioRevision,
|
|
3669
4307
|
amistioContentHash: frontmatter.amistioContentHash,
|
|
3670
4308
|
...frontmatter.status ? { status: frontmatter.status } : {}
|
|
@@ -3726,6 +4364,12 @@ var impactPreviewStart = "AMISTIO_IMPACT_PREVIEW_START";
|
|
|
3726
4364
|
var impactPreviewEnd = "AMISTIO_IMPACT_PREVIEW_END";
|
|
3727
4365
|
var issueDiagnosisStart = "AMISTIO_ISSUE_DIAGNOSIS_START";
|
|
3728
4366
|
var issueDiagnosisEnd = "AMISTIO_ISSUE_DIAGNOSIS_END";
|
|
4367
|
+
var securityPostureStart = "AMISTIO_SECURITY_POSTURE_START";
|
|
4368
|
+
var securityPostureEnd = "AMISTIO_SECURITY_POSTURE_END";
|
|
4369
|
+
var appEvaluationStart = "AMISTIO_APP_EVALUATION_START";
|
|
4370
|
+
var appEvaluationEnd = "AMISTIO_APP_EVALUATION_END";
|
|
4371
|
+
var projectContextRefreshStart = "AMISTIO_PROJECT_CONTEXT_REFRESH_START";
|
|
4372
|
+
var projectContextRefreshEnd = "AMISTIO_PROJECT_CONTEXT_REFRESH_END";
|
|
3729
4373
|
function createWorkExecutionPrompt(workItem, context) {
|
|
3730
4374
|
if (workItem.workKind === "brainGeneration") {
|
|
3731
4375
|
return createBrainGenerationPrompt(workItem);
|
|
@@ -3742,6 +4386,15 @@ function createWorkExecutionPrompt(workItem, context) {
|
|
|
3742
4386
|
if (workItem.workKind === "issueDiagnosis") {
|
|
3743
4387
|
return createIssueDiagnosisPrompt(workItem, context?.issueDiagnosis);
|
|
3744
4388
|
}
|
|
4389
|
+
if (workItem.workKind === "securityPostureScan") {
|
|
4390
|
+
return createSecurityPostureScanPrompt(workItem, context?.securityPostureScan);
|
|
4391
|
+
}
|
|
4392
|
+
if (workItem.workKind === "appEvaluationScan") {
|
|
4393
|
+
return createAppEvaluationScanPrompt(workItem, context?.appEvaluationScan);
|
|
4394
|
+
}
|
|
4395
|
+
if (workItem.workKind === "projectContextRefresh") {
|
|
4396
|
+
return createProjectContextRefreshPrompt(workItem, context?.projectContextRefresh);
|
|
4397
|
+
}
|
|
3745
4398
|
return [
|
|
3746
4399
|
"# Amistio Work Execution",
|
|
3747
4400
|
"",
|
|
@@ -3770,9 +4423,8 @@ function createWorkExecutionPrompt(workItem, context) {
|
|
|
3770
4423
|
"- Run relevant verification commands when feasible and summarize results."
|
|
3771
4424
|
].join("\n");
|
|
3772
4425
|
}
|
|
3773
|
-
function
|
|
3774
|
-
const
|
|
3775
|
-
const approvedContext = (context?.documents ?? []).filter((document) => document.status === "approved" || document.syncState === "approved" || document.syncState === "synced").slice(0, 16).map((document) => [
|
|
4426
|
+
function createProjectContextRefreshPrompt(workItem, context) {
|
|
4427
|
+
const approvedContext = (context?.documents ?? []).filter((document) => document.status === "approved" || document.syncState === "approved" || document.syncState === "synced").slice(0, 24).map((document) => [
|
|
3776
4428
|
`### ${document.title}`,
|
|
3777
4429
|
`documentId: ${document.documentId}`,
|
|
3778
4430
|
`documentType: ${document.documentType}`,
|
|
@@ -3780,49 +4432,70 @@ function createIssueDiagnosisPrompt(workItem, context) {
|
|
|
3780
4432
|
`revision: ${document.revision}`,
|
|
3781
4433
|
document.content.slice(0, 3e3)
|
|
3782
4434
|
].join("\n")).join("\n\n");
|
|
4435
|
+
const activeMap = context?.activeMap;
|
|
4436
|
+
const previousMap = activeMap ? [
|
|
4437
|
+
`Map ID: ${activeMap.projectContextMapId}`,
|
|
4438
|
+
`Version: ${activeMap.version}`,
|
|
4439
|
+
`Status: ${activeMap.status}`,
|
|
4440
|
+
`Coverage: ${activeMap.coverage.status}`,
|
|
4441
|
+
`Summary: ${activeMap.summary}`,
|
|
4442
|
+
"Slices:",
|
|
4443
|
+
...activeMap.slices.slice(0, 20).map((slice) => `- ${slice.sliceId} / ${slice.kind} / ${slice.freshness}: ${slice.title} - ${slice.summary.slice(0, 500)}`)
|
|
4444
|
+
].join("\n") : "No approved project context map was loaded. Build the initial map.";
|
|
3783
4445
|
return [
|
|
3784
|
-
"# Amistio
|
|
4446
|
+
"# Amistio Living Project Context Refresh",
|
|
3785
4447
|
"",
|
|
3786
4448
|
"You are running locally through the Amistio CLI inside the user's repository.",
|
|
3787
|
-
"
|
|
3788
|
-
"
|
|
4449
|
+
"Run a read-only project context refresh that captures the whole app as bounded, reviewable knowledge.",
|
|
4450
|
+
"Do not modify files, create branches, commit, install packages, run implementation prompts, call external services, or make unaudited network calls.",
|
|
4451
|
+
"Use only safe local inspection and focused diagnostic commands that do not expose secrets.",
|
|
3789
4452
|
"",
|
|
3790
|
-
"##
|
|
4453
|
+
"## Work Item",
|
|
3791
4454
|
"",
|
|
3792
|
-
`
|
|
3793
|
-
`
|
|
3794
|
-
`
|
|
3795
|
-
`
|
|
4455
|
+
`Title: ${workItem.title}`,
|
|
4456
|
+
`Work item ID: ${workItem.workItemId}`,
|
|
4457
|
+
`Project ID: ${workItem.projectId}`,
|
|
4458
|
+
`Project context refresh ID: ${workItem.projectContextRefreshId ?? "unknown"}`,
|
|
3796
4459
|
"",
|
|
3797
|
-
|
|
4460
|
+
"## Refresh Request",
|
|
4461
|
+
"",
|
|
4462
|
+
workItem.sourceWish ?? "Refresh the living project context map.",
|
|
3798
4463
|
"",
|
|
3799
4464
|
"## Approved Project Brain Context",
|
|
3800
4465
|
"",
|
|
3801
|
-
approvedContext || "No approved project-brain records were loaded. Inspect local repository files as needed and explain the gap in
|
|
4466
|
+
approvedContext || "No approved project-brain records were loaded. Inspect local repository files as needed and explain the gap in the coverage warnings.",
|
|
3802
4467
|
"",
|
|
3803
|
-
"##
|
|
4468
|
+
"## Previous Context Map",
|
|
3804
4469
|
"",
|
|
3805
|
-
|
|
3806
|
-
"
|
|
3807
|
-
"
|
|
3808
|
-
"
|
|
3809
|
-
"-
|
|
3810
|
-
"-
|
|
4470
|
+
previousMap,
|
|
4471
|
+
"",
|
|
4472
|
+
"## Mapping Requirements",
|
|
4473
|
+
"",
|
|
4474
|
+
"- Create slices for architecture, domain, data, API, frontend, backend, CLI, workflows, operations, security, and testing when those surfaces exist.",
|
|
4475
|
+
"- Capture entities and relations that explain how the app is put together and where future work should look first.",
|
|
4476
|
+
"- Prefer summaries, repository-relative paths, short citations, tags, and freshness status over raw source excerpts.",
|
|
4477
|
+
"- Mark stale or missing areas explicitly instead of guessing.",
|
|
4478
|
+
"- Keep repoPaths repository-relative; never include /absolute paths or ../ traversal.",
|
|
4479
|
+
"",
|
|
4480
|
+
"## Data Safety",
|
|
4481
|
+
"",
|
|
4482
|
+
"- Persist summaries, entities, relations, citations, and repository-relative paths only.",
|
|
4483
|
+
"- Do not include raw source dumps, secrets, env vars, process lists, absolute local paths, credential values, tokens, provider sessions, or destructive shell output.",
|
|
4484
|
+
"- Citation excerpts must be short evidence snippets, not code payloads.",
|
|
3811
4485
|
"",
|
|
3812
4486
|
"## Output Contract",
|
|
3813
4487
|
"",
|
|
3814
|
-
"Print exactly one JSON object between the markers below. The CLI will submit only this structured
|
|
4488
|
+
"Print exactly one JSON object between the markers below. The CLI will submit only this structured refresh result back to Amistio.",
|
|
3815
4489
|
"",
|
|
3816
|
-
|
|
3817
|
-
'{"summary":"The
|
|
3818
|
-
|
|
4490
|
+
projectContextRefreshStart,
|
|
4491
|
+
'{"summary":"The app uses docs/ as the orchestration control plane and a local runner for source-aware work.","slices":[{"sliceId":"slice_control_plane","kind":"architecture","title":"Docs control plane","summary":"ADRs, plans, prompts, context, and memory under docs/ guide implementation work.","repoPaths":["AGENTS.md","docs/architecture/overview.md"],"tags":["orchestrator","docs"],"citations":[{"source":"localSource","repoPath":"AGENTS.md","excerpt":"docs/ control-plane folders are the source of truth."}],"freshness":"fresh","confidence":0.9}],"entities":[{"entityId":"entity_local_runner","name":"Local runner","entityType":"component","summary":"Executes approved work locally and returns redacted structured results.","sliceIds":["slice_control_plane"],"confidence":0.8}],"relations":[{"relationId":"rel_runner_uses_docs","relationType":"uses","fromId":"entity_local_runner","toId":"slice_control_plane","summary":"The runner reads approved docs context before executing work.","confidence":0.8}],"coverage":{"status":"partial","sliceCount":1,"staleSliceCount":0,"missingAreas":["runtime data flow"],"warnings":[]},"changedSliceIds":["slice_control_plane"],"staleSliceIds":[],"verificationPlan":["Review the proposed context map in Amistio","Run focused checks for any stale areas"]}',
|
|
4492
|
+
projectContextRefreshEnd,
|
|
3819
4493
|
"",
|
|
3820
|
-
"Do not put Markdown fences around the markers. Do not implement
|
|
4494
|
+
"Do not put Markdown fences around the markers. Do not implement any changes."
|
|
3821
4495
|
].join("\n");
|
|
3822
4496
|
}
|
|
3823
|
-
function
|
|
3824
|
-
const
|
|
3825
|
-
const approvedContext = (context?.documents ?? []).filter((document) => document.status === "approved" || document.syncState === "approved" || document.syncState === "synced").slice(0, 16).map((document) => [
|
|
4497
|
+
function createSecurityPostureScanPrompt(workItem, context) {
|
|
4498
|
+
const approvedContext = (context?.documents ?? []).filter((document) => document.status === "approved" || document.syncState === "approved" || document.syncState === "synced").slice(0, 20).map((document) => [
|
|
3826
4499
|
`### ${document.title}`,
|
|
3827
4500
|
`documentId: ${document.documentId}`,
|
|
3828
4501
|
`documentType: ${document.documentType}`,
|
|
@@ -3831,91 +4504,324 @@ function createImpactPreviewPrompt(workItem, context) {
|
|
|
3831
4504
|
document.content.slice(0, 3e3)
|
|
3832
4505
|
].join("\n")).join("\n\n");
|
|
3833
4506
|
return [
|
|
3834
|
-
"# Amistio
|
|
4507
|
+
"# Amistio Security Posture Scan",
|
|
3835
4508
|
"",
|
|
3836
4509
|
"You are running locally through the Amistio CLI inside the user's repository.",
|
|
3837
|
-
"
|
|
3838
|
-
"Do not modify files,
|
|
4510
|
+
"Run a read-only security posture scan and produce approval-gated remediation findings.",
|
|
4511
|
+
"Do not modify files, create branches, commit, run implementation prompts, install packages, perform exploit attempts, call external services, or make unaudited network calls.",
|
|
4512
|
+
"Use only safe local inspection and focused diagnostic commands that do not expose secrets.",
|
|
3839
4513
|
"",
|
|
3840
4514
|
"## Work Item",
|
|
3841
4515
|
"",
|
|
3842
4516
|
`Title: ${workItem.title}`,
|
|
3843
4517
|
`Work item ID: ${workItem.workItemId}`,
|
|
3844
4518
|
`Project ID: ${workItem.projectId}`,
|
|
3845
|
-
`
|
|
3846
|
-
`Impact report ID: ${workItem.impactReportId ?? "unknown"}`,
|
|
3847
|
-
`Analyzed repo revision: ${context?.analyzedRepoRevision ?? "unknown"}`,
|
|
4519
|
+
`Security scan ID: ${workItem.securityScanId ?? "unknown"}`,
|
|
3848
4520
|
"",
|
|
3849
|
-
"##
|
|
4521
|
+
"## Scan Request",
|
|
3850
4522
|
"",
|
|
3851
|
-
|
|
4523
|
+
workItem.sourceWish ?? "Run the Amistio security baseline.",
|
|
3852
4524
|
"",
|
|
3853
4525
|
"## Approved Project Brain Context",
|
|
3854
4526
|
"",
|
|
3855
|
-
approvedContext || "No approved project-brain records were loaded. Inspect
|
|
4527
|
+
approvedContext || "No approved project-brain records were loaded. Inspect local repository files as needed and explain the gap in the summary.",
|
|
3856
4528
|
"",
|
|
3857
|
-
"##
|
|
4529
|
+
"## Baseline Requirements",
|
|
3858
4530
|
"",
|
|
3859
|
-
"-
|
|
3860
|
-
"-
|
|
3861
|
-
"-
|
|
3862
|
-
"-
|
|
3863
|
-
"-
|
|
4531
|
+
"- Cover OWASP Top 10 and OWASP ASVS Level 1 style controls.",
|
|
4532
|
+
"- Check dependency and supply-chain posture: audit advisories, lockfiles, CI verification, release provenance, and trusted publishing where applicable.",
|
|
4533
|
+
"- Check secret and credential hygiene using redacted evidence only.",
|
|
4534
|
+
"- Check authentication, authorization, tenant isolation, redirects/callbacks, CSRF, CORS, security headers, logging redaction, and rate limiting.",
|
|
4535
|
+
"- Check Amistio controls: local-runner-only execution, supported work-kind gates, approval gates, worktree isolation, runner credential binding, and redacted telemetry.",
|
|
4536
|
+
"",
|
|
4537
|
+
"## Data Safety",
|
|
4538
|
+
"",
|
|
4539
|
+
"- Persist summaries, controls, repository-relative paths, and redacted evidence only.",
|
|
4540
|
+
"- Do not include raw source, secrets, env vars, command lines, process lists, absolute local paths, credential values, tokens, provider sessions, or exploit payloads.",
|
|
4541
|
+
"- Use safePaths for repo-relative paths only; never include /absolute paths or ../ traversal.",
|
|
3864
4542
|
"",
|
|
3865
4543
|
"## Output Contract",
|
|
3866
4544
|
"",
|
|
3867
|
-
"Print exactly one JSON object between the markers below. The CLI will submit only this structured
|
|
4545
|
+
"Print exactly one JSON object between the markers below. The CLI will submit only this structured scan result back to Amistio.",
|
|
3868
4546
|
"",
|
|
3869
|
-
|
|
3870
|
-
'{"
|
|
3871
|
-
|
|
4547
|
+
securityPostureStart,
|
|
4548
|
+
'{"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"]}',
|
|
4549
|
+
securityPostureEnd,
|
|
3872
4550
|
"",
|
|
3873
|
-
"Do not put Markdown fences around the markers. Do not implement
|
|
4551
|
+
"Do not put Markdown fences around the markers. Do not implement remediation."
|
|
3874
4552
|
].join("\n");
|
|
3875
4553
|
}
|
|
3876
|
-
function
|
|
3877
|
-
const
|
|
3878
|
-
const priorMessages = (context?.messages ?? []).filter((message) => message.messageId !== context?.question.messageId).slice(-12).map((message) => `- ${message.role} / ${message.status}: ${message.content}`).join("\n");
|
|
3879
|
-
const brainContext = (context?.documents ?? []).filter((document) => document.status === "approved" || document.syncState === "approved" || document.syncState === "synced").slice(0, 12).map((document) => [
|
|
4554
|
+
function createAppEvaluationScanPrompt(workItem, context) {
|
|
4555
|
+
const approvedContext = (context?.documents ?? []).filter((document) => document.status === "approved" || document.syncState === "approved" || document.syncState === "synced").slice(0, 24).map((document) => [
|
|
3880
4556
|
`### ${document.title}`,
|
|
3881
4557
|
`documentId: ${document.documentId}`,
|
|
4558
|
+
`documentType: ${document.documentType}`,
|
|
3882
4559
|
`repoPath: ${document.repoPath}`,
|
|
3883
4560
|
`revision: ${document.revision}`,
|
|
3884
|
-
document.content.slice(0,
|
|
4561
|
+
document.content.slice(0, 3e3)
|
|
3885
4562
|
].join("\n")).join("\n\n");
|
|
3886
4563
|
return [
|
|
3887
|
-
"# Amistio
|
|
4564
|
+
"# Amistio App Evaluation Scan",
|
|
3888
4565
|
"",
|
|
3889
4566
|
"You are running locally through the Amistio CLI inside the user's repository.",
|
|
3890
|
-
"
|
|
3891
|
-
"
|
|
3892
|
-
"",
|
|
3893
|
-
"## User Question",
|
|
3894
|
-
"",
|
|
3895
|
-
question,
|
|
4567
|
+
"Run a read-only app evaluation scan and produce approval-gated improvement and cleanup findings.",
|
|
4568
|
+
"Do not modify files, create branches, commit, install packages, run implementation prompts, delete or archive plans, call external services, or make unaudited network calls.",
|
|
4569
|
+
"Use only safe local inspection and focused diagnostic commands that do not expose secrets.",
|
|
3896
4570
|
"",
|
|
3897
4571
|
"## Work Item",
|
|
3898
4572
|
"",
|
|
3899
4573
|
`Title: ${workItem.title}`,
|
|
3900
4574
|
`Work item ID: ${workItem.workItemId}`,
|
|
3901
4575
|
`Project ID: ${workItem.projectId}`,
|
|
3902
|
-
`
|
|
4576
|
+
`App evaluation scan ID: ${workItem.appEvaluationScanId ?? "unknown"}`,
|
|
4577
|
+
"",
|
|
4578
|
+
"## Scan Request",
|
|
4579
|
+
"",
|
|
4580
|
+
workItem.sourceWish ?? "Run the Amistio app evaluation baseline.",
|
|
3903
4581
|
"",
|
|
3904
4582
|
"## Approved Project Brain Context",
|
|
3905
4583
|
"",
|
|
3906
|
-
|
|
4584
|
+
approvedContext || "No approved project-brain records were loaded. Inspect local repository files as needed and explain the gap in the summary.",
|
|
3907
4585
|
"",
|
|
3908
|
-
"##
|
|
4586
|
+
"## Baseline Requirements",
|
|
3909
4587
|
"",
|
|
3910
|
-
|
|
4588
|
+
"- Check app verification health: lint, typecheck, test, build, CI references, flaky or missing gates.",
|
|
4589
|
+
"- Check product and docs drift between approved context, plans, prompts, and current implementation.",
|
|
4590
|
+
"- Check old plans and prompts for cleanup candidates, but only propose lifecycle actions such as markCompleted, markSuperseded, archive, keepActive, or none.",
|
|
4591
|
+
"- Check missing memory or workflow updates when repeated lessons or operational rules are visible.",
|
|
4592
|
+
"- Check release readiness, UX, accessibility, performance, reliability, and security-posture follow-through at a summary level.",
|
|
3911
4593
|
"",
|
|
3912
|
-
"##
|
|
4594
|
+
"## Data Safety",
|
|
3913
4595
|
"",
|
|
3914
|
-
"-
|
|
3915
|
-
"-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
4596
|
+
"- Persist summaries, recommended actions, repository-relative paths, and redacted evidence only.",
|
|
4597
|
+
"- Do not include raw source dumps, secrets, env vars, process lists, absolute local paths, credential values, tokens, provider sessions, or destructive shell output.",
|
|
4598
|
+
"- Use safePaths for repo-relative paths only; never include /absolute paths or ../ traversal.",
|
|
4599
|
+
"",
|
|
4600
|
+
"## Output Contract",
|
|
4601
|
+
"",
|
|
4602
|
+
"Print exactly one JSON object between the markers below. The CLI will submit only this structured scan result back to Amistio.",
|
|
4603
|
+
"",
|
|
4604
|
+
appEvaluationStart,
|
|
4605
|
+
'{"summary":"The app is generally healthy, but one release-readiness plan needs cleanup and verification evidence should be refreshed.","baselineVersion":"amistio-app-evaluation-v1","findings":[{"title":"Stale release plan should be marked superseded","category":"planCleanup","severity":"low","confidence":"high","summary":"A release plan appears to describe a workflow that has since been replaced by a newer accepted plan.","affectedSurfaces":["docs/plans"],"evidence":["A newer plan references the same scope and status, while the older plan remains proposed."],"suggestedAction":"Prepare a cleanup update that marks the stale plan superseded and links to the newer plan.","verificationPlan":["Review both plan files","Confirm the newer plan is accepted before changing lifecycle state"],"safePaths":["docs/plans/PLAN-example.md"],"proposedLifecycleAction":"markSuperseded","relatedArtifactIds":[]}],"verificationPlan":["Review App Evaluation findings for approval","Run the canonical verify gate before implementing approved actions"]}',
|
|
4606
|
+
appEvaluationEnd,
|
|
4607
|
+
"",
|
|
4608
|
+
"Do not put Markdown fences around the markers. Do not implement improvements or cleanup."
|
|
4609
|
+
].join("\n");
|
|
4610
|
+
}
|
|
4611
|
+
function createIssueDiagnosisPrompt(workItem, context) {
|
|
4612
|
+
const issue = context?.issue;
|
|
4613
|
+
const approvedContext = (context?.documents ?? []).filter((document) => document.status === "approved" || document.syncState === "approved" || document.syncState === "synced").slice(0, 16).map((document) => [
|
|
4614
|
+
`### ${document.title}`,
|
|
4615
|
+
`documentId: ${document.documentId}`,
|
|
4616
|
+
`documentType: ${document.documentType}`,
|
|
4617
|
+
`repoPath: ${document.repoPath}`,
|
|
4618
|
+
`revision: ${document.revision}`,
|
|
4619
|
+
document.content.slice(0, 3e3)
|
|
4620
|
+
].join("\n")).join("\n\n");
|
|
4621
|
+
return [
|
|
4622
|
+
"# Amistio Issue Diagnosis",
|
|
4623
|
+
"",
|
|
4624
|
+
"You are running locally through the Amistio CLI inside the user's repository.",
|
|
4625
|
+
"Diagnose the submitted bug or operational issue and prepare the approval-gated fix analysis.",
|
|
4626
|
+
"This is a diagnosis-only task. Do not modify files, create branches, run implementation commands, or commit changes.",
|
|
4627
|
+
"",
|
|
4628
|
+
"## Submitted Issue",
|
|
4629
|
+
"",
|
|
4630
|
+
`Issue ID: ${workItem.issueId ?? issue?.issueId ?? "unknown"}`,
|
|
4631
|
+
`Title: ${issue?.title ?? workItem.title}`,
|
|
4632
|
+
`Category: ${issue?.category ?? "bug"}`,
|
|
4633
|
+
`Severity: ${issue?.severity ?? "medium"}`,
|
|
4634
|
+
"",
|
|
4635
|
+
issue?.description ?? workItem.sourceWish ?? workItem.title,
|
|
4636
|
+
"",
|
|
4637
|
+
"## Approved Project Brain Context",
|
|
4638
|
+
"",
|
|
4639
|
+
approvedContext || "No approved project-brain records were loaded. Inspect local repository files as needed and explain the gap in evidence.",
|
|
4640
|
+
"",
|
|
4641
|
+
"## Analysis Requirements",
|
|
4642
|
+
"",
|
|
4643
|
+
"- Document user-visible impact and concrete evidence or reproduction status.",
|
|
4644
|
+
"- Name affected surfaces and likely paths using concise path summaries only.",
|
|
4645
|
+
"- Provide rootCauseAnalysis. If unconfirmed, include falsifiableHypothesis and nextCheck.",
|
|
4646
|
+
"- Propose the fix and expected behavior, then provide a verification plan.",
|
|
4647
|
+
"- Keep repository source and secrets local. Do not include raw source dumps, credentials, local secret paths, or provider session references.",
|
|
4648
|
+
"- Leave approvalState as proposed. Implementation must wait for user approval in Amistio.",
|
|
4649
|
+
"",
|
|
4650
|
+
"## Output Contract",
|
|
4651
|
+
"",
|
|
4652
|
+
"Print exactly one JSON object between the markers below. The CLI will submit only this structured diagnosis back to Amistio.",
|
|
4653
|
+
"",
|
|
4654
|
+
issueDiagnosisStart,
|
|
4655
|
+
'{"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"}',
|
|
4656
|
+
issueDiagnosisEnd,
|
|
4657
|
+
"",
|
|
4658
|
+
"Do not put Markdown fences around the markers. Do not implement the fix."
|
|
4659
|
+
].join("\n");
|
|
4660
|
+
}
|
|
4661
|
+
function formatProjectContextMap(map, query, contextPack) {
|
|
4662
|
+
if ((!map || map.status !== "approved") && !contextPack) {
|
|
4663
|
+
return "No approved living context map was loaded. Use approved brain records and inspect local source only where needed.";
|
|
4664
|
+
}
|
|
4665
|
+
if (!map || map.status !== "approved") {
|
|
4666
|
+
return [
|
|
4667
|
+
`Context pack ID: ${contextPack?.packId ?? "unknown"}`,
|
|
4668
|
+
contextPack?.freshnessWarnings.length ? `Freshness warnings: ${contextPack.freshnessWarnings.join(" | ")}` : "Freshness warnings: none recorded",
|
|
4669
|
+
"No approved living context map was loaded. Use approved brain records and inspect local source only where needed."
|
|
4670
|
+
].join("\n");
|
|
4671
|
+
}
|
|
4672
|
+
const tokens2 = tokenizeForContext(query);
|
|
4673
|
+
const candidateSlices = contextPack?.slices.length ? contextPack.slices : map.slices;
|
|
4674
|
+
const rankedSlices = [...candidateSlices].map((slice) => ({ slice, score: scoreContextSlice(tokens2, slice.title, slice.summary, [...slice.repoPaths, ...slice.tags, ...slice.ownerHints].join(" ")) })).sort((first, second) => second.score - first.score || first.slice.title.localeCompare(second.slice.title));
|
|
4675
|
+
const selectedSlices = rankedSlices.filter((entry) => entry.score > 0).map((entry) => entry.slice).slice(0, 8);
|
|
4676
|
+
const slices = (selectedSlices.length ? selectedSlices : candidateSlices.slice(0, 8)).map((slice) => [
|
|
4677
|
+
`### ${slice.title}`,
|
|
4678
|
+
`sliceId: ${slice.sliceId}`,
|
|
4679
|
+
`kind: ${slice.kind}`,
|
|
4680
|
+
`freshness: ${slice.freshness}`,
|
|
4681
|
+
`confidence: ${Math.round(slice.confidence * 100)}%`,
|
|
4682
|
+
slice.repoPaths.length ? `repoPaths: ${slice.repoPaths.slice(0, 12).join(", ")}` : "repoPaths: none recorded",
|
|
4683
|
+
slice.summary
|
|
4684
|
+
].join("\n")).join("\n\n");
|
|
4685
|
+
const entities = (contextPack?.entities.length ? contextPack.entities : map.entities).slice(0, 12).map((entity) => `- ${entity.name} (${entity.entityType}): ${entity.summary ?? entity.entityId}`).join("\n");
|
|
4686
|
+
const relations = (contextPack?.relations.length ? contextPack.relations : map.relations).slice(0, 12).map((relation) => `- ${relation.fromId} ${relation.relationType} ${relation.toId}: ${relation.summary}`).join("\n");
|
|
4687
|
+
return [
|
|
4688
|
+
contextPack ? `Context pack ID: ${contextPack.packId}` : "Context pack ID: not recorded",
|
|
4689
|
+
`Map ID: ${map.projectContextMapId}`,
|
|
4690
|
+
`Version: ${map.version}`,
|
|
4691
|
+
contextPack?.tokenEstimate !== void 0 ? `Pack token estimate: ${contextPack.tokenEstimate}` : "Pack token estimate: not recorded",
|
|
4692
|
+
`Coverage: ${map.coverage.status}; slices ${map.coverage.sliceCount}; stale ${map.coverage.staleSliceCount}`,
|
|
4693
|
+
contextPack?.freshnessWarnings.length ? `Freshness warnings: ${contextPack.freshnessWarnings.join(" | ")}` : "Freshness warnings: none recorded",
|
|
4694
|
+
map.coverage.missingAreas.length ? `Missing areas: ${map.coverage.missingAreas.join(", ")}` : "Missing areas: none recorded",
|
|
4695
|
+
map.coverage.warnings.length ? `Warnings: ${map.coverage.warnings.join(" | ")}` : "Warnings: none recorded",
|
|
4696
|
+
"",
|
|
4697
|
+
map.summary,
|
|
4698
|
+
"",
|
|
4699
|
+
"### Relevant Context Slices",
|
|
4700
|
+
slices || "No slices recorded.",
|
|
4701
|
+
"",
|
|
4702
|
+
"### Entities",
|
|
4703
|
+
entities || "No entities recorded.",
|
|
4704
|
+
"",
|
|
4705
|
+
"### Relations",
|
|
4706
|
+
relations || "No relations recorded."
|
|
4707
|
+
].join("\n");
|
|
4708
|
+
}
|
|
4709
|
+
function tokenizeForContext(value) {
|
|
4710
|
+
return [...new Set(value.toLowerCase().match(/[a-z0-9][a-z0-9_-]{2,}/g) ?? [])].slice(0, 24);
|
|
4711
|
+
}
|
|
4712
|
+
function scoreContextSlice(tokens2, title, summary, metadata) {
|
|
4713
|
+
const haystackTitle = title.toLowerCase();
|
|
4714
|
+
const haystackSummary = summary.toLowerCase();
|
|
4715
|
+
const haystackMetadata = metadata.toLowerCase();
|
|
4716
|
+
return tokens2.reduce((score, token) => score + (haystackTitle.includes(token) ? 5 : 0) + (haystackMetadata.includes(token) ? 3 : 0) + (haystackSummary.includes(token) ? 1 : 0), 0);
|
|
4717
|
+
}
|
|
4718
|
+
function createImpactPreviewPrompt(workItem, context) {
|
|
4719
|
+
const implementationPrompt = context?.implementationPrompt;
|
|
4720
|
+
const livingContext = formatProjectContextMap(context?.activeMap, workItem.sourceWish ?? workItem.title, context?.contextPack);
|
|
4721
|
+
const approvedContext = (context?.documents ?? []).filter((document) => document.status === "approved" || document.syncState === "approved" || document.syncState === "synced").slice(0, 16).map((document) => [
|
|
4722
|
+
`### ${document.title}`,
|
|
4723
|
+
`documentId: ${document.documentId}`,
|
|
4724
|
+
`documentType: ${document.documentType}`,
|
|
4725
|
+
`repoPath: ${document.repoPath}`,
|
|
4726
|
+
`revision: ${document.revision}`,
|
|
4727
|
+
document.content.slice(0, 3e3)
|
|
4728
|
+
].join("\n")).join("\n\n");
|
|
4729
|
+
return [
|
|
4730
|
+
"# Amistio Implementation Impact Preview",
|
|
4731
|
+
"",
|
|
4732
|
+
"You are running locally through the Amistio CLI inside the user's repository.",
|
|
4733
|
+
"Analyze the likely impact of the approved implementation prompt. This is a preview-only task.",
|
|
4734
|
+
"Do not modify files, do not create branches, do not run implementation commands, and do not commit changes.",
|
|
4735
|
+
"",
|
|
4736
|
+
"## Work Item",
|
|
4737
|
+
"",
|
|
4738
|
+
`Title: ${workItem.title}`,
|
|
4739
|
+
`Work item ID: ${workItem.workItemId}`,
|
|
4740
|
+
`Project ID: ${workItem.projectId}`,
|
|
4741
|
+
`Generated draft ID: ${workItem.generatedDraftId ?? "unknown"}`,
|
|
4742
|
+
`Impact report ID: ${workItem.impactReportId ?? "unknown"}`,
|
|
4743
|
+
`Analyzed repo revision: ${context?.analyzedRepoRevision ?? "unknown"}`,
|
|
4744
|
+
"",
|
|
4745
|
+
"## Approved Implementation Prompt",
|
|
4746
|
+
"",
|
|
4747
|
+
implementationPrompt ? implementationPrompt.content : workItem.sourceWish ?? workItem.title,
|
|
4748
|
+
"",
|
|
4749
|
+
"## Approved Living Project Context Map",
|
|
4750
|
+
"",
|
|
4751
|
+
livingContext,
|
|
4752
|
+
"",
|
|
4753
|
+
"## Approved Project Brain Context",
|
|
4754
|
+
"",
|
|
4755
|
+
approvedContext || "No approved project-brain records were loaded. Inspect the local repository and explain the gap in the report.",
|
|
4756
|
+
"",
|
|
4757
|
+
"## Report Requirements",
|
|
4758
|
+
"",
|
|
4759
|
+
"- Identify affected product areas and likely modules/files using safe path summaries.",
|
|
4760
|
+
"- Assign riskLevel as low, medium, high, or critical with operational reasoning in summary.",
|
|
4761
|
+
"- Include data/schema/migration impact, security/privacy/access-control considerations, verification plan, and rollback plan.",
|
|
4762
|
+
"- Keep repository source and secrets local. Do not include raw source dumps, credentials, local secret paths, or provider session references.",
|
|
4763
|
+
"- If you inspect files, cite paths only through likelyPaths and concise reasons.",
|
|
4764
|
+
"",
|
|
4765
|
+
"## Output Contract",
|
|
4766
|
+
"",
|
|
4767
|
+
"Print exactly one JSON object between the markers below. The CLI will submit only this structured report back to Amistio.",
|
|
4768
|
+
"",
|
|
4769
|
+
impactPreviewStart,
|
|
4770
|
+
'{"riskLevel":"medium","summary":"Touches the workspace command flow and runner claim gate.","affectedAreas":[{"name":"Workspace UI","description":"Shows impact before execution"}],"likelyPaths":[{"repoPath":"src/apps/web/components/workspace-client.tsx","reason":"Workspace action wiring"}],"dependencies":["local runner"],"dataSchemaImpact":"Adds an impact report project item.","securityPrivacyImpact":"Source remains local; SaaS stores summaries and paths only.","verificationPlan":["Run shared, web, and CLI tests","Run root verify"],"rollbackPlan":"Disable the impact gate and remove queued preview work if needed."}',
|
|
4771
|
+
impactPreviewEnd,
|
|
4772
|
+
"",
|
|
4773
|
+
"Do not put Markdown fences around the markers. Do not implement the work."
|
|
4774
|
+
].join("\n");
|
|
4775
|
+
}
|
|
4776
|
+
function createAssistantQuestionPrompt(workItem, context) {
|
|
4777
|
+
const question = context?.question.content ?? workItem.sourceWish ?? workItem.title;
|
|
4778
|
+
const livingContext = formatProjectContextMap(context?.activeMap, question, context?.contextPack);
|
|
4779
|
+
const priorMessages = (context?.messages ?? []).filter((message) => message.messageId !== context?.question.messageId).slice(-12).map((message) => `- ${message.role} / ${message.status}: ${message.content}`).join("\n");
|
|
4780
|
+
const brainContext = (context?.documents ?? []).filter((document) => document.status === "approved" || document.syncState === "approved" || document.syncState === "synced").slice(0, 12).map((document) => [
|
|
4781
|
+
`### ${document.title}`,
|
|
4782
|
+
`documentId: ${document.documentId}`,
|
|
4783
|
+
`repoPath: ${document.repoPath}`,
|
|
4784
|
+
`revision: ${document.revision}`,
|
|
4785
|
+
document.content.slice(0, 2400)
|
|
4786
|
+
].join("\n")).join("\n\n");
|
|
4787
|
+
return [
|
|
4788
|
+
"# Amistio Project Knowledge Assistant",
|
|
4789
|
+
"",
|
|
4790
|
+
"You are running locally through the Amistio CLI inside the user's repository.",
|
|
4791
|
+
"Answer the user's project question using approved project-brain context and local repository inspection when useful.",
|
|
4792
|
+
"This is an answer-only task. Do not modify files, do not create branches, and do not commit changes.",
|
|
4793
|
+
"",
|
|
4794
|
+
"## User Question",
|
|
4795
|
+
"",
|
|
4796
|
+
question,
|
|
4797
|
+
"",
|
|
4798
|
+
"## Work Item",
|
|
4799
|
+
"",
|
|
4800
|
+
`Title: ${workItem.title}`,
|
|
4801
|
+
`Work item ID: ${workItem.workItemId}`,
|
|
4802
|
+
`Project ID: ${workItem.projectId}`,
|
|
4803
|
+
`Assistant message ID: ${workItem.assistantMessageId ?? context?.question.messageId ?? "unknown"}`,
|
|
4804
|
+
"",
|
|
4805
|
+
"## Approved Living Project Context Map",
|
|
4806
|
+
"",
|
|
4807
|
+
livingContext,
|
|
4808
|
+
"",
|
|
4809
|
+
"## Approved Project Brain Context",
|
|
4810
|
+
"",
|
|
4811
|
+
brainContext || "No approved project-brain records were loaded. Say what you inspected locally and avoid pretending citations exist.",
|
|
4812
|
+
"",
|
|
4813
|
+
"## Recent Assistant Conversation",
|
|
4814
|
+
"",
|
|
4815
|
+
priorMessages || "No prior assistant messages were loaded.",
|
|
4816
|
+
"",
|
|
4817
|
+
"## Source Boundary Rules",
|
|
4818
|
+
"",
|
|
4819
|
+
"- Keep repository source, secrets, tokens, local credential paths, and provider session references local.",
|
|
4820
|
+
"- Summarize findings instead of dumping source code. Tiny identifiers, filenames, and short excerpts are acceptable when necessary.",
|
|
4821
|
+
'- Use sourceBoundary "projectBrain" when the answer only uses project-brain records.',
|
|
4822
|
+
"- Treat the approved living context map as project-brain context; use it to avoid broad repository rereads when it is fresh and relevant.",
|
|
4823
|
+
'- Use sourceBoundary "localSource" when you inspected local repository files.',
|
|
4824
|
+
'- Use sourceBoundary "mixed" when both project-brain records and local files shaped the answer.',
|
|
3919
4825
|
"- Include citations for project-brain records with documentId/title/repoPath where relevant.",
|
|
3920
4826
|
"- Include local file citations with repoPath only; do not include raw source dumps.",
|
|
3921
4827
|
"",
|
|
@@ -4006,8 +4912,39 @@ function parseIssueDiagnosisResult(output) {
|
|
|
4006
4912
|
const parsed = JSON.parse(stripJsonFence(payload));
|
|
4007
4913
|
return issueDiagnosisResultSchema.parse(parsed);
|
|
4008
4914
|
}
|
|
4915
|
+
function parseSecurityPostureScanResult(output) {
|
|
4916
|
+
const start = output.indexOf(securityPostureStart);
|
|
4917
|
+
const end = output.indexOf(securityPostureEnd, start + securityPostureStart.length);
|
|
4918
|
+
if (start === -1 || end === -1 || end <= start) {
|
|
4919
|
+
throw new Error("Local AI scan did not return an Amistio security posture block.");
|
|
4920
|
+
}
|
|
4921
|
+
const payload = output.slice(start + securityPostureStart.length, end).trim();
|
|
4922
|
+
const parsed = JSON.parse(stripJsonFence(payload));
|
|
4923
|
+
return securityPostureScanResultSchema.parse(parsed);
|
|
4924
|
+
}
|
|
4925
|
+
function parseAppEvaluationScanResult(output) {
|
|
4926
|
+
const start = output.indexOf(appEvaluationStart);
|
|
4927
|
+
const end = output.indexOf(appEvaluationEnd, start + appEvaluationStart.length);
|
|
4928
|
+
if (start === -1 || end === -1 || end <= start) {
|
|
4929
|
+
throw new Error("Local AI scan did not return an Amistio app evaluation block.");
|
|
4930
|
+
}
|
|
4931
|
+
const payload = output.slice(start + appEvaluationStart.length, end).trim();
|
|
4932
|
+
const parsed = JSON.parse(stripJsonFence(payload));
|
|
4933
|
+
return appEvaluationScanResultSchema.parse(parsed);
|
|
4934
|
+
}
|
|
4935
|
+
function parseProjectContextRefreshResult(output) {
|
|
4936
|
+
const start = output.indexOf(projectContextRefreshStart);
|
|
4937
|
+
const end = output.indexOf(projectContextRefreshEnd, start + projectContextRefreshStart.length);
|
|
4938
|
+
if (start === -1 || end === -1 || end <= start) {
|
|
4939
|
+
throw new Error("Local AI refresh did not return an Amistio project context block.");
|
|
4940
|
+
}
|
|
4941
|
+
const payload = output.slice(start + projectContextRefreshStart.length, end).trim();
|
|
4942
|
+
const parsed = JSON.parse(stripJsonFence(payload));
|
|
4943
|
+
return projectContextRefreshResultSchema.parse(parsed);
|
|
4944
|
+
}
|
|
4009
4945
|
function createBrainGenerationPrompt(workItem) {
|
|
4010
4946
|
const wish = workItem.sourceWish ?? workItem.title;
|
|
4947
|
+
const artifactFormatPreference = workItem.artifactFormatPreference ?? "markdown";
|
|
4011
4948
|
return [
|
|
4012
4949
|
"# Amistio Brain Generation",
|
|
4013
4950
|
"",
|
|
@@ -4024,6 +4961,7 @@ function createBrainGenerationPrompt(workItem) {
|
|
|
4024
4961
|
`Work item ID: ${workItem.workItemId}`,
|
|
4025
4962
|
`Project ID: ${workItem.projectId}`,
|
|
4026
4963
|
`Generated draft ID: ${workItem.generatedDraftId ?? "unknown"}`,
|
|
4964
|
+
`Artifact format preference: ${artifactFormatPreference}`,
|
|
4027
4965
|
"",
|
|
4028
4966
|
"## Read First",
|
|
4029
4967
|
"",
|
|
@@ -4033,7 +4971,7 @@ function createBrainGenerationPrompt(workItem) {
|
|
|
4033
4971
|
"",
|
|
4034
4972
|
"## Generate Artifacts",
|
|
4035
4973
|
"",
|
|
4036
|
-
"Return
|
|
4974
|
+
"Return artifacts that should enter human review before implementation. Use only these document types and matching repo roots:",
|
|
4037
4975
|
"",
|
|
4038
4976
|
"- architecture -> docs/architecture/",
|
|
4039
4977
|
"- context -> docs/context/",
|
|
@@ -4043,20 +4981,35 @@ function createBrainGenerationPrompt(workItem) {
|
|
|
4043
4981
|
"- plan -> docs/plans/",
|
|
4044
4982
|
"- prompt -> docs/prompts/",
|
|
4045
4983
|
"- workflow -> docs/workflows/",
|
|
4984
|
+
"- HTML companions -> docs/html/<same-folder>/ using .html, for example docs/html/plans/PLAN-example.html",
|
|
4985
|
+
"",
|
|
4986
|
+
artifactFormatPreferenceInstructions(artifactFormatPreference),
|
|
4046
4987
|
"",
|
|
4047
|
-
"Each artifact needs documentType, title, repoPath, and content. Include at least a plan and an implementation prompt when implementation work is implied. Use model-agnostic prompt wording.",
|
|
4988
|
+
"Each artifact needs documentType, contentFormat, title, repoPath, and content. contentFormat may be omitted only for Markdown. Include at least a plan and an implementation prompt when implementation work is implied. Use model-agnostic prompt wording.",
|
|
4048
4989
|
"",
|
|
4049
4990
|
"## Output Contract",
|
|
4050
4991
|
"",
|
|
4051
4992
|
"Print exactly one JSON object between the markers below. The CLI will submit only this structured result back to Amistio.",
|
|
4052
4993
|
"",
|
|
4053
4994
|
generationResultStart,
|
|
4054
|
-
'{"artifacts":[{"documentType":"plan","title":"Plan: Example","repoPath":"docs/plans/PLAN-example.md","content":"# Plan: Example\\n\\n## Goal\\n..."}]}',
|
|
4995
|
+
artifactFormatPreference === "html" ? '{"artifacts":[{"documentType":"plan","contentFormat":"html","title":"Plan: Example","repoPath":"docs/html/plans/PLAN-example.html","content":"<!doctype html><html><body><h1>Plan: Example</h1></body></html>"}]}' : '{"artifacts":[{"documentType":"plan","contentFormat":"markdown","title":"Plan: Example","repoPath":"docs/plans/PLAN-example.md","content":"# Plan: Example\\n\\n## Goal\\n..."}]}',
|
|
4055
4996
|
generationResultEnd,
|
|
4056
4997
|
"",
|
|
4057
4998
|
"Do not put Markdown fences around the markers. Do not claim implementation is complete."
|
|
4058
4999
|
].join("\n");
|
|
4059
5000
|
}
|
|
5001
|
+
function artifactFormatPreferenceInstructions(preference) {
|
|
5002
|
+
if (preference === "html") {
|
|
5003
|
+
return "The user chose HTML. Generate .html artifacts under docs/html/<folder>/ and set contentFormat to html.";
|
|
5004
|
+
}
|
|
5005
|
+
if (preference === "both") {
|
|
5006
|
+
return "The user chose both formats. Generate Markdown artifacts under docs/<folder>/ and matching HTML companions under docs/html/<folder>/ with contentFormat values markdown and html.";
|
|
5007
|
+
}
|
|
5008
|
+
if (preference === "auto") {
|
|
5009
|
+
return "Choose Markdown or HTML based on the artifact content. Use Markdown for text-first docs and HTML for visual or layout-sensitive artifacts.";
|
|
5010
|
+
}
|
|
5011
|
+
return "The user chose Markdown. Generate Markdown artifacts under docs/<folder>/ and set contentFormat to markdown or omit it.";
|
|
5012
|
+
}
|
|
4060
5013
|
function stripJsonFence(value) {
|
|
4061
5014
|
const trimmed = value.trim();
|
|
4062
5015
|
if (!trimmed.startsWith("```")) {
|
|
@@ -4081,7 +5034,13 @@ function formatWatchIdleLine(action, intervalSeconds) {
|
|
|
4081
5034
|
}
|
|
4082
5035
|
function shouldPrintWatchState(action, previous, nowMs, reminderMs = watchStateReminderMs) {
|
|
4083
5036
|
const key = watchStateKey(action);
|
|
4084
|
-
|
|
5037
|
+
if (!previous || previous.key !== key) {
|
|
5038
|
+
return true;
|
|
5039
|
+
}
|
|
5040
|
+
if (action.kind === "workCompleted") {
|
|
5041
|
+
return false;
|
|
5042
|
+
}
|
|
5043
|
+
return nowMs - previous.printedAtMs >= reminderMs;
|
|
4085
5044
|
}
|
|
4086
5045
|
function watchStateKey(action) {
|
|
4087
5046
|
return [action.kind, action.message, action.workItemId, action.documentId, action.runnerId].filter(Boolean).join(":");
|
|
@@ -4261,7 +5220,8 @@ async function scanLegacyDocuments(options) {
|
|
|
4261
5220
|
skipped.push({ repoPath, reason: "excluded" });
|
|
4262
5221
|
continue;
|
|
4263
5222
|
}
|
|
4264
|
-
|
|
5223
|
+
const contentFormat = inferContentFormatFromRepoPath(repoPath);
|
|
5224
|
+
if (!contentFormat) {
|
|
4265
5225
|
skipped.push({ repoPath, reason: "notMarkdown" });
|
|
4266
5226
|
continue;
|
|
4267
5227
|
}
|
|
@@ -4284,16 +5244,17 @@ async function scanLegacyDocuments(options) {
|
|
|
4284
5244
|
skipped.push({ repoPath, reason: "unreadable" });
|
|
4285
5245
|
continue;
|
|
4286
5246
|
}
|
|
4287
|
-
if (
|
|
5247
|
+
if (isAmistioManagedDocument(content)) {
|
|
4288
5248
|
skipped.push({ repoPath, reason: "alreadyManaged" });
|
|
4289
5249
|
continue;
|
|
4290
5250
|
}
|
|
4291
5251
|
const documentType = classifyLegacyDocument(repoPath, content);
|
|
4292
|
-
const destinationPath = uniqueDestinationPath(canonicalImportPath(repoPath, documentType), repoPath, usedDestinationPaths);
|
|
5252
|
+
const destinationPath = uniqueDestinationPath(canonicalImportPath(repoPath, documentType, contentFormat), repoPath, usedDestinationPaths);
|
|
4293
5253
|
candidates.push({
|
|
4294
5254
|
sourcePath: repoPath,
|
|
4295
5255
|
repoPath: destinationPath,
|
|
4296
5256
|
documentType,
|
|
5257
|
+
contentFormat,
|
|
4297
5258
|
title: inferTitle2(content, repoPath),
|
|
4298
5259
|
content,
|
|
4299
5260
|
contentHash: sha256ContentHash(content)
|
|
@@ -4316,6 +5277,7 @@ function buildImportedBrainDocuments(options) {
|
|
|
4316
5277
|
projectId: options.projectId,
|
|
4317
5278
|
documentId,
|
|
4318
5279
|
documentType: candidate.documentType,
|
|
5280
|
+
contentFormat: candidate.contentFormat,
|
|
4319
5281
|
title: candidate.title,
|
|
4320
5282
|
status: "approved",
|
|
4321
5283
|
repoPath: candidate.repoPath,
|
|
@@ -4394,9 +5356,6 @@ function wildcardMatch(pattern, repoPath) {
|
|
|
4394
5356
|
const escaped = normalizedPattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*\//g, "::DOUBLE_STAR_SLASH::").replace(/\*\*/g, "::DOUBLE_STAR::").replace(/\?/g, "[^/]").replace(/\*/g, "[^/]*").replace(/::DOUBLE_STAR_SLASH::/g, "(?:.*/)?").replace(/::DOUBLE_STAR::/g, ".*");
|
|
4395
5357
|
return new RegExp(`^${escaped}$`).test(repoPath);
|
|
4396
5358
|
}
|
|
4397
|
-
function isMarkdownDocument(repoPath) {
|
|
4398
|
-
return /\.(md|mdx)$/i.test(repoPath);
|
|
4399
|
-
}
|
|
4400
5359
|
function isExcludedRepoPath(repoPath) {
|
|
4401
5360
|
if (isControlPlaneTemplateRepoPath(repoPath)) return true;
|
|
4402
5361
|
if (excludedFileNames.has(repoPath)) return true;
|
|
@@ -4405,9 +5364,12 @@ function isExcludedRepoPath(repoPath) {
|
|
|
4405
5364
|
const basename = segments[segments.length - 1]?.toLowerCase() ?? "";
|
|
4406
5365
|
return basename.startsWith(".env") || basename.includes("secret") || basename.includes("token") || basename.includes("credential") || basename.endsWith(".lock");
|
|
4407
5366
|
}
|
|
4408
|
-
function
|
|
5367
|
+
function isAmistioManagedDocument(content) {
|
|
4409
5368
|
const frontmatter = parseFrontmatter(content);
|
|
4410
|
-
|
|
5369
|
+
if (typeof frontmatter.amistioDocumentId === "string" && frontmatter.amistioDocumentId.trim().length > 0) {
|
|
5370
|
+
return true;
|
|
5371
|
+
}
|
|
5372
|
+
return /^\s*<!--[\s\S]*?amistioDocumentId\s*:/i.test(content);
|
|
4411
5373
|
}
|
|
4412
5374
|
function classifyLegacyDocument(repoPath, content) {
|
|
4413
5375
|
const haystack = `${repoPath} ${inferTitle2(content, repoPath)}`.toLowerCase();
|
|
@@ -4420,7 +5382,7 @@ function classifyLegacyDocument(repoPath, content) {
|
|
|
4420
5382
|
if (/\b(workflow|workflows|runbook|playbook|process|procedure|ops)\b/.test(haystack)) return "workflow";
|
|
4421
5383
|
return "context";
|
|
4422
5384
|
}
|
|
4423
|
-
function canonicalImportPath(sourcePath, documentType) {
|
|
5385
|
+
function canonicalImportPath(sourcePath, documentType, contentFormat) {
|
|
4424
5386
|
if (isCanonicalControlPlanePath(sourcePath)) {
|
|
4425
5387
|
return sourcePath;
|
|
4426
5388
|
}
|
|
@@ -4428,7 +5390,8 @@ function canonicalImportPath(sourcePath, documentType) {
|
|
|
4428
5390
|
return `docs/${sourcePath}`;
|
|
4429
5391
|
}
|
|
4430
5392
|
const baseSlug = slugFromPath(sourcePath);
|
|
4431
|
-
|
|
5393
|
+
const extension = contentFormat === "html" ? "html" : "md";
|
|
5394
|
+
return `${documentFolderByType[documentType]}/imported/${baseSlug}.${extension}`;
|
|
4432
5395
|
}
|
|
4433
5396
|
function uniqueDestinationPath(basePath, sourcePath, usedPaths) {
|
|
4434
5397
|
if (!usedPaths.has(basePath)) {
|
|
@@ -4443,8 +5406,8 @@ function uniqueDestinationPath(basePath, sourcePath, usedPaths) {
|
|
|
4443
5406
|
return uniquePath;
|
|
4444
5407
|
}
|
|
4445
5408
|
function isCanonicalControlPlanePath(repoPath) {
|
|
4446
|
-
const [firstSegment, secondSegment] = normalizeRepoPath4(repoPath).split("/");
|
|
4447
|
-
return firstSegment === "docs" && Boolean(secondSegment && controlPlaneRoots2.includes(secondSegment));
|
|
5409
|
+
const [firstSegment, secondSegment, thirdSegment] = normalizeRepoPath4(repoPath).split("/");
|
|
5410
|
+
return firstSegment === "docs" && Boolean(secondSegment === "html" && thirdSegment && controlPlaneRoots2.includes(thirdSegment) || secondSegment && controlPlaneRoots2.includes(secondSegment));
|
|
4448
5411
|
}
|
|
4449
5412
|
function isLegacyControlPlanePath(repoPath) {
|
|
4450
5413
|
const [firstSegment] = repoPath.split("/");
|
|
@@ -4454,6 +5417,8 @@ function inferTitle2(content, repoPath) {
|
|
|
4454
5417
|
const body = stripFrontmatter(content);
|
|
4455
5418
|
const heading = body.split("\n").find((line) => /^#\s+/.test(line))?.replace(/^#\s+/, "").trim();
|
|
4456
5419
|
if (heading) return heading;
|
|
5420
|
+
const htmlHeading = body.match(/<h1\b[^>]*>([\s\S]*?)<\/h1>/i)?.[1]?.replace(/<[^>]+>/g, "").trim();
|
|
5421
|
+
if (htmlHeading) return htmlHeading;
|
|
4457
5422
|
const basename = path10.posix.basename(repoPath, path10.posix.extname(repoPath)).replace(/[-_]+/g, " ").trim();
|
|
4458
5423
|
return titleCase(basename || "Imported Document");
|
|
4459
5424
|
}
|
|
@@ -4465,7 +5430,7 @@ function stripFrontmatter(content) {
|
|
|
4465
5430
|
return closingLineEnd === -1 ? "" : content.slice(closingLineEnd + 1);
|
|
4466
5431
|
}
|
|
4467
5432
|
function slugFromPath(repoPath) {
|
|
4468
|
-
const withoutExtension = repoPath.replace(/\.(md|mdx)$/i, "");
|
|
5433
|
+
const withoutExtension = repoPath.replace(/\.(md|mdx|html?)$/i, "");
|
|
4469
5434
|
const slug = withoutExtension.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 90);
|
|
4470
5435
|
return slug || "imported-document";
|
|
4471
5436
|
}
|
|
@@ -4564,6 +5529,7 @@ async function runOfficialCliUpdateWithRuntimeRefresh(options) {
|
|
|
4564
5529
|
if (!options.restartBackgroundRunner) {
|
|
4565
5530
|
return {
|
|
4566
5531
|
succeeded: false,
|
|
5532
|
+
stopRunner: true,
|
|
4567
5533
|
message: `${updateResult.message} Background runner restart was not available after update. Restart the runner manually to load the updated CLI version.`,
|
|
4568
5534
|
error: "Background runner restart hook was not provided."
|
|
4569
5535
|
};
|
|
@@ -4578,6 +5544,7 @@ async function runOfficialCliUpdateWithRuntimeRefresh(options) {
|
|
|
4578
5544
|
}
|
|
4579
5545
|
return {
|
|
4580
5546
|
succeeded: false,
|
|
5547
|
+
stopRunner: true,
|
|
4581
5548
|
message: `${updateResult.message} Background runner restart failed after update. Restart the runner manually to load the updated CLI version.`,
|
|
4582
5549
|
error: restartResult.error ?? restartResult.message
|
|
4583
5550
|
};
|
|
@@ -4699,6 +5666,186 @@ function errorMessage2(error) {
|
|
|
4699
5666
|
return error instanceof Error ? error.message : String(error);
|
|
4700
5667
|
}
|
|
4701
5668
|
|
|
5669
|
+
// src/implementation-handoff.ts
|
|
5670
|
+
import { execFile as execFile6 } from "node:child_process";
|
|
5671
|
+
import path13 from "node:path";
|
|
5672
|
+
import { promisify as promisify6 } from "node:util";
|
|
5673
|
+
var execFileAsync6 = promisify6(execFile6);
|
|
5674
|
+
async function completeImplementationHandoff(input) {
|
|
5675
|
+
const run = input.commandRunner ?? defaultCommandRunner;
|
|
5676
|
+
const headBranch = input.worktreeIsolation?.branch ?? input.workItem.executionBranch;
|
|
5677
|
+
const baseBranch = input.baseBranch ?? input.repositoryLink?.defaultBranch ?? "main";
|
|
5678
|
+
if (!headBranch) {
|
|
5679
|
+
return blockedHandoff({ baseBranch, message: "Implementation handoff needs an execution branch before it can create a pull request." });
|
|
5680
|
+
}
|
|
5681
|
+
const common = {
|
|
5682
|
+
provider: input.repositoryLink?.provider ?? "github",
|
|
5683
|
+
baseBranch,
|
|
5684
|
+
headBranch
|
|
5685
|
+
};
|
|
5686
|
+
try {
|
|
5687
|
+
const unmergedFiles = await gitOutput2(run, input.worktreePath, ["diff", "--name-only", "--diff-filter=U"]);
|
|
5688
|
+
if (unmergedFiles.trim()) {
|
|
5689
|
+
return blockedHandoff({ ...common, message: "Implementation handoff is blocked because the worktree has unresolved merge conflicts." });
|
|
5690
|
+
}
|
|
5691
|
+
const status = await gitOutput2(run, input.worktreePath, ["status", "--porcelain=v1", "-z", "--untracked-files=all"]);
|
|
5692
|
+
if (!status) {
|
|
5693
|
+
return {
|
|
5694
|
+
...common,
|
|
5695
|
+
status: "noChanges",
|
|
5696
|
+
cleanupStatus: "notApplicable",
|
|
5697
|
+
message: "Local execution completed with no repository changes to hand off."
|
|
5698
|
+
};
|
|
5699
|
+
}
|
|
5700
|
+
const remoteName = await resolveRemoteName(run, input.worktreePath);
|
|
5701
|
+
const remoteUrl = await gitOutput2(run, input.worktreePath, ["remote", "get-url", remoteName]);
|
|
5702
|
+
const provider = input.repositoryLink?.provider ?? inferProvider(remoteUrl);
|
|
5703
|
+
if (provider !== "github") {
|
|
5704
|
+
return blockedHandoff({
|
|
5705
|
+
...common,
|
|
5706
|
+
provider,
|
|
5707
|
+
remoteName,
|
|
5708
|
+
message: "Automated pull request handoff currently requires a GitHub remote. Commit and push manually, or link a GitHub repository."
|
|
5709
|
+
});
|
|
5710
|
+
}
|
|
5711
|
+
await gitOutput2(run, input.worktreePath, ["add", "-A"]);
|
|
5712
|
+
await gitOutput2(run, input.worktreePath, ["commit", "-m", commitSubject(input.workItem), "-m", commitBody(input)]);
|
|
5713
|
+
const commitSha = await gitOutput2(run, input.worktreePath, ["rev-parse", "HEAD"]);
|
|
5714
|
+
await gitOutput2(run, input.worktreePath, ["push", "--set-upstream", remoteName, headBranch]);
|
|
5715
|
+
const pullRequest = await ensureGithubPullRequest(run, input.worktreePath, { baseBranch, headBranch, workItem: input.workItem, ...input.verificationSummary ? { verificationSummary: input.verificationSummary } : {} });
|
|
5716
|
+
const cleanup = await cleanupWorktree(run, input);
|
|
5717
|
+
return {
|
|
5718
|
+
provider: "github",
|
|
5719
|
+
status: "prReady",
|
|
5720
|
+
baseBranch,
|
|
5721
|
+
headBranch,
|
|
5722
|
+
remoteName,
|
|
5723
|
+
commitSha,
|
|
5724
|
+
prNumber: pullRequest.number,
|
|
5725
|
+
prUrl: pullRequest.url,
|
|
5726
|
+
cleanupStatus: cleanup.status,
|
|
5727
|
+
...cleanup.message ? { cleanupMessage: cleanup.message } : {},
|
|
5728
|
+
message: cleanup.status === "completed" ? "GitHub pull request is ready for review and the local worktree was cleaned up." : "GitHub pull request is ready for review; local worktree cleanup needs attention."
|
|
5729
|
+
};
|
|
5730
|
+
} catch (error) {
|
|
5731
|
+
return blockedHandoff({ ...common, message: "Implementation handoff is blocked and the local worktree was preserved for recovery.", error: safeErrorMessage(error) });
|
|
5732
|
+
}
|
|
5733
|
+
}
|
|
5734
|
+
async function ensureGithubPullRequest(run, cwd, input) {
|
|
5735
|
+
const existing = await run("gh", ["pr", "list", "--head", input.headBranch, "--base", input.baseBranch, "--state", "open", "--json", "number,url", "--limit", "1"], { cwd });
|
|
5736
|
+
const parsed = parsePullRequestList(existing.stdout);
|
|
5737
|
+
if (parsed) {
|
|
5738
|
+
return parsed;
|
|
5739
|
+
}
|
|
5740
|
+
const created = await run("gh", ["pr", "create", "--base", input.baseBranch, "--head", input.headBranch, "--title", pullRequestTitle(input.workItem), "--body", pullRequestBody(input)], { cwd });
|
|
5741
|
+
const url = extractPullRequestUrl(created.stdout);
|
|
5742
|
+
if (!url) {
|
|
5743
|
+
throw new Error("GitHub CLI did not return a pull request URL.");
|
|
5744
|
+
}
|
|
5745
|
+
const number = pullRequestNumber(url);
|
|
5746
|
+
if (!number) {
|
|
5747
|
+
throw new Error("GitHub CLI returned a pull request URL without a parseable number.");
|
|
5748
|
+
}
|
|
5749
|
+
return { number, url };
|
|
5750
|
+
}
|
|
5751
|
+
async function cleanupWorktree(run, input) {
|
|
5752
|
+
const status = await gitOutput2(run, input.worktreePath, ["status", "--porcelain=v1", "-z", "--untracked-files=all"]);
|
|
5753
|
+
if (status) {
|
|
5754
|
+
return { status: "failed", message: "Cleanup skipped because the worktree is not clean after PR handoff." };
|
|
5755
|
+
}
|
|
5756
|
+
try {
|
|
5757
|
+
await gitOutput2(run, input.primaryRepoRoot || path13.dirname(input.worktreePath), ["worktree", "remove", input.worktreePath]);
|
|
5758
|
+
return { status: "completed" };
|
|
5759
|
+
} catch (error) {
|
|
5760
|
+
return { status: "failed", message: `Cleanup failed: ${safeErrorMessage(error)}` };
|
|
5761
|
+
}
|
|
5762
|
+
}
|
|
5763
|
+
async function resolveRemoteName(run, cwd) {
|
|
5764
|
+
const remotes = (await gitOutput2(run, cwd, ["remote"])).split(/\r?\n/g).map((remote) => remote.trim()).filter(Boolean);
|
|
5765
|
+
if (!remotes.length) {
|
|
5766
|
+
throw new Error("No Git remote is configured for PR handoff.");
|
|
5767
|
+
}
|
|
5768
|
+
return remotes.includes("origin") ? "origin" : remotes[0];
|
|
5769
|
+
}
|
|
5770
|
+
async function gitOutput2(run, cwd, args) {
|
|
5771
|
+
const result = await run("git", args, { cwd });
|
|
5772
|
+
return result.stdout.trim();
|
|
5773
|
+
}
|
|
5774
|
+
function blockedHandoff(input) {
|
|
5775
|
+
return {
|
|
5776
|
+
status: "blocked",
|
|
5777
|
+
cleanupStatus: "pending",
|
|
5778
|
+
...input
|
|
5779
|
+
};
|
|
5780
|
+
}
|
|
5781
|
+
function inferProvider(remoteUrl) {
|
|
5782
|
+
try {
|
|
5783
|
+
return parseRepositoryCloneUrl(remoteUrl).provider ?? "other";
|
|
5784
|
+
} catch {
|
|
5785
|
+
return "other";
|
|
5786
|
+
}
|
|
5787
|
+
}
|
|
5788
|
+
function parsePullRequestList(stdout) {
|
|
5789
|
+
const parsed = JSON.parse(stdout || "[]");
|
|
5790
|
+
const first = parsed[0];
|
|
5791
|
+
if (typeof first?.number === "number" && typeof first.url === "string" && first.url) {
|
|
5792
|
+
return { number: first.number, url: first.url };
|
|
5793
|
+
}
|
|
5794
|
+
return void 0;
|
|
5795
|
+
}
|
|
5796
|
+
function extractPullRequestUrl(stdout) {
|
|
5797
|
+
return stdout.split(/\s+/g).find((value) => /^https:\/\/github\.com\/[^\s]+\/pull\/\d+$/i.test(value));
|
|
5798
|
+
}
|
|
5799
|
+
function pullRequestNumber(url) {
|
|
5800
|
+
const match = /\/pull\/(\d+)(?:$|[?#])/.exec(url);
|
|
5801
|
+
return match ? Number.parseInt(match[1], 10) : void 0;
|
|
5802
|
+
}
|
|
5803
|
+
function commitSubject(workItem) {
|
|
5804
|
+
return truncate(`Amistio: ${workItem.title}`, 72);
|
|
5805
|
+
}
|
|
5806
|
+
function commitBody(input) {
|
|
5807
|
+
return [
|
|
5808
|
+
`Work item: ${input.workItem.workItemId}`,
|
|
5809
|
+
`Implementation scope: ${input.workItem.controllingAdrId ?? input.workItem.implementationScopeId ?? "unspecified"}`,
|
|
5810
|
+
"",
|
|
5811
|
+
"Generated by Amistio local runner.",
|
|
5812
|
+
"Do not include secrets or source patches in Amistio SaaS metadata."
|
|
5813
|
+
].join("\n");
|
|
5814
|
+
}
|
|
5815
|
+
function pullRequestTitle(workItem) {
|
|
5816
|
+
return truncate(workItem.title, 120);
|
|
5817
|
+
}
|
|
5818
|
+
function pullRequestBody(input) {
|
|
5819
|
+
return [
|
|
5820
|
+
`Amistio work item: ${input.workItem.workItemId}`,
|
|
5821
|
+
`Implementation scope: ${input.workItem.controllingAdrId ?? input.workItem.implementationScopeId ?? "unspecified"}`,
|
|
5822
|
+
`Base branch: ${input.baseBranch}`,
|
|
5823
|
+
`Head branch: ${input.headBranch}`,
|
|
5824
|
+
"",
|
|
5825
|
+
input.verificationSummary ?? "Verification summary was not reported by the local runner."
|
|
5826
|
+
].join("\n");
|
|
5827
|
+
}
|
|
5828
|
+
function truncate(value, maxLength) {
|
|
5829
|
+
return value.length <= maxLength ? value : value.slice(0, maxLength - 1).trimEnd();
|
|
5830
|
+
}
|
|
5831
|
+
async function defaultCommandRunner(command, args, options) {
|
|
5832
|
+
const { stdout, stderr } = await execFileAsync6(command, args, { cwd: options.cwd, maxBuffer: 1024 * 1024 });
|
|
5833
|
+
return { stdout, stderr };
|
|
5834
|
+
}
|
|
5835
|
+
function safeErrorMessage(error) {
|
|
5836
|
+
const scrub = (value) => redactLocalPaths(value.replace(/^Command failed:[^\n]*(\n)?/i, "").trim() || value);
|
|
5837
|
+
if (typeof error === "object" && error && "stderr" in error && typeof error.stderr === "string" && error.stderr.trim()) {
|
|
5838
|
+
return truncate(scrub(error.stderr.trim()), 600);
|
|
5839
|
+
}
|
|
5840
|
+
if (error instanceof Error) {
|
|
5841
|
+
return truncate(scrub(error.message), 600);
|
|
5842
|
+
}
|
|
5843
|
+
return truncate(scrub(String(error)), 600);
|
|
5844
|
+
}
|
|
5845
|
+
function redactLocalPaths(value) {
|
|
5846
|
+
return value.replace(/(^|[\s'"`])(\/{1,2}(?:Users|home|private|tmp|var|Volumes)\/[^\s'"`]+)/g, "$1<local-path>").replace(/(^|[\s'"`])([A-Za-z]:\\[^\s'"`]+)/g, "$1<local-path>");
|
|
5847
|
+
}
|
|
5848
|
+
|
|
4702
5849
|
// src/version.ts
|
|
4703
5850
|
import { readFileSync } from "node:fs";
|
|
4704
5851
|
function readCliPackageVersion() {
|
|
@@ -4718,7 +5865,7 @@ var DEFAULT_MAX_PREFLIGHT_ATTEMPTS = 3;
|
|
|
4718
5865
|
var DEFAULT_TOOL_TIMEOUT_SECONDS = 30 * 60;
|
|
4719
5866
|
var RUNNER_WORK_LEASE_SECONDS = 300;
|
|
4720
5867
|
var RUNNER_WORK_LEASE_RENEWAL_MS = 12e4;
|
|
4721
|
-
var runnerSupportedWorkKinds = ["brainGeneration", "implementation", "planRevision", "assistantQuestion", "impactPreview", "issueDiagnosis"];
|
|
5868
|
+
var runnerSupportedWorkKinds = ["brainGeneration", "implementation", "planRevision", "assistantQuestion", "impactPreview", "issueDiagnosis", "securityPostureScan", "appEvaluationScan", "projectContextRefresh"];
|
|
4722
5869
|
program.name("amistio").description("Amistio project brain CLI").version(CLI_VERSION);
|
|
4723
5870
|
program.command("init").description("Create Amistio control-plane folders for a new project").option("--root <path>", "Repository root", defaultRoot).action(async (options) => {
|
|
4724
5871
|
const created = await initControlPlane(options.root);
|
|
@@ -5096,7 +6243,7 @@ program.command("run").description("Claim and run approved Amistio work locally"
|
|
|
5096
6243
|
projectId: context.metadata.amistioProjectId,
|
|
5097
6244
|
repositoryLinkId: context.metadata.repositoryLinkId,
|
|
5098
6245
|
runnerId,
|
|
5099
|
-
rootDir:
|
|
6246
|
+
rootDir: path14.resolve(options.root),
|
|
5100
6247
|
apiUrl: options.apiUrl,
|
|
5101
6248
|
args: buildBackgroundRunnerArgs(resolvedOptions)
|
|
5102
6249
|
});
|
|
@@ -5268,7 +6415,7 @@ runnerService.command("install").description("Install a user-level startup servi
|
|
|
5268
6415
|
projectId: context.metadata.amistioProjectId,
|
|
5269
6416
|
repositoryLinkId: context.metadata.repositoryLinkId,
|
|
5270
6417
|
runnerId,
|
|
5271
|
-
rootDir:
|
|
6418
|
+
rootDir: path14.resolve(options.root),
|
|
5272
6419
|
apiUrl: options.apiUrl,
|
|
5273
6420
|
args,
|
|
5274
6421
|
platform
|
|
@@ -5699,13 +6846,90 @@ async function runNextWorkItem({
|
|
|
5699
6846
|
return recordFinalizationFailure({ apiClient, error, isolationTelemetry, projectId, repositoryLinkId, runnerId, sessionContext, toolConfig, toolName: preview.toolName, workItem: result.workItem, durationMs: Date.now() - startedAt });
|
|
5700
6847
|
}
|
|
5701
6848
|
}
|
|
5702
|
-
|
|
6849
|
+
if (result.workItem.workKind === "securityPostureScan") {
|
|
6850
|
+
try {
|
|
6851
|
+
return await finalizeSecurityPostureScanWork({
|
|
6852
|
+
apiClient,
|
|
6853
|
+
durationMs: Date.now() - startedAt,
|
|
6854
|
+
projectId,
|
|
6855
|
+
repositoryLinkId,
|
|
6856
|
+
runnerId,
|
|
6857
|
+
sessionContext,
|
|
6858
|
+
toolConfig,
|
|
6859
|
+
toolName: preview.toolName,
|
|
6860
|
+
toolResult,
|
|
6861
|
+
workItem: result.workItem
|
|
6862
|
+
});
|
|
6863
|
+
} catch (error) {
|
|
6864
|
+
return recordFinalizationFailure({ apiClient, error, isolationTelemetry, projectId, repositoryLinkId, runnerId, sessionContext, toolConfig, toolName: preview.toolName, workItem: result.workItem, durationMs: Date.now() - startedAt });
|
|
6865
|
+
}
|
|
6866
|
+
}
|
|
6867
|
+
if (result.workItem.workKind === "appEvaluationScan") {
|
|
6868
|
+
try {
|
|
6869
|
+
return await finalizeAppEvaluationScanWork({
|
|
6870
|
+
apiClient,
|
|
6871
|
+
durationMs: Date.now() - startedAt,
|
|
6872
|
+
projectId,
|
|
6873
|
+
repositoryLinkId,
|
|
6874
|
+
runnerId,
|
|
6875
|
+
sessionContext,
|
|
6876
|
+
toolConfig,
|
|
6877
|
+
toolName: preview.toolName,
|
|
6878
|
+
toolResult,
|
|
6879
|
+
workItem: result.workItem
|
|
6880
|
+
});
|
|
6881
|
+
} catch (error) {
|
|
6882
|
+
return recordFinalizationFailure({ apiClient, error, isolationTelemetry, projectId, repositoryLinkId, runnerId, sessionContext, toolConfig, toolName: preview.toolName, workItem: result.workItem, durationMs: Date.now() - startedAt });
|
|
6883
|
+
}
|
|
6884
|
+
}
|
|
6885
|
+
if (result.workItem.workKind === "projectContextRefresh") {
|
|
6886
|
+
try {
|
|
6887
|
+
return await finalizeProjectContextRefreshWork({
|
|
6888
|
+
apiClient,
|
|
6889
|
+
durationMs: Date.now() - startedAt,
|
|
6890
|
+
projectId,
|
|
6891
|
+
repositoryLinkId,
|
|
6892
|
+
runnerId,
|
|
6893
|
+
sessionContext,
|
|
6894
|
+
toolConfig,
|
|
6895
|
+
toolName: preview.toolName,
|
|
6896
|
+
toolResult,
|
|
6897
|
+
workItem: result.workItem
|
|
6898
|
+
});
|
|
6899
|
+
} catch (error) {
|
|
6900
|
+
return recordFinalizationFailure({ apiClient, error, isolationTelemetry, projectId, repositoryLinkId, runnerId, sessionContext, toolConfig, toolName: preview.toolName, workItem: result.workItem, durationMs: Date.now() - startedAt });
|
|
6901
|
+
}
|
|
6902
|
+
}
|
|
6903
|
+
let finalStatus = toolResult.exitCode === 0 ? "completed" : "failed";
|
|
5703
6904
|
const durationMs = Date.now() - startedAt;
|
|
5704
6905
|
const failureExcerpt = toolResult.exitCode === 0 ? void 0 : truncateLogExcerpt(toolResult.stderr || toolResult.stdout);
|
|
6906
|
+
let implementationHandoff;
|
|
6907
|
+
let finalMessage = `${preview.toolName} exited with code ${toolResult.exitCode}.`;
|
|
6908
|
+
let finalError = failureExcerpt;
|
|
6909
|
+
if (toolResult.exitCode === 0 && (result.workItem.workKind ?? "implementation") === "implementation") {
|
|
6910
|
+
await recordRunnerMilestone(apiClient, projectId, result.workItem, runnerId, repositoryLinkId, {
|
|
6911
|
+
status: "running",
|
|
6912
|
+
summary: "Preparing GitHub PR handoff for implementation work.",
|
|
6913
|
+
idempotencyKey: `runner_milestone_handoff_started_${result.workItem.workItemId}_${result.workItem.attempt}`,
|
|
6914
|
+
metadata: { executionWorktreeKey: isolationTelemetry.executionWorktreeKey ?? "", executionBranch: isolationTelemetry.executionBranch ?? "" }
|
|
6915
|
+
});
|
|
6916
|
+
const repositoryLink = await loadWorkItemRepositoryLink(apiClient, projectId, result.workItem.repositoryLinkId ?? repositoryLinkId);
|
|
6917
|
+
implementationHandoff = await completeImplementationHandoff({
|
|
6918
|
+
primaryRepoRoot: root,
|
|
6919
|
+
...repositoryLink ? { repositoryLink } : {},
|
|
6920
|
+
verificationSummary: "Local execution reported completion.",
|
|
6921
|
+
workItem: result.workItem,
|
|
6922
|
+
...worktreeIsolation.isolation ? { worktreeIsolation: worktreeIsolation.isolation } : {},
|
|
6923
|
+
worktreePath: executionRoot
|
|
6924
|
+
});
|
|
6925
|
+
finalStatus = implementationHandoff.status === "prReady" || implementationHandoff.status === "noChanges" ? "completed" : "blocked";
|
|
6926
|
+
finalMessage = implementationHandoff.message ?? "Implementation handoff finished.";
|
|
6927
|
+
finalError = implementationHandoff.error;
|
|
6928
|
+
}
|
|
5705
6929
|
const updatedToolSession = await finalizeToolSession({
|
|
5706
6930
|
apiClient,
|
|
5707
6931
|
projectId,
|
|
5708
|
-
status:
|
|
6932
|
+
status: toolResult.exitCode === 0 ? "completed" : "failed",
|
|
5709
6933
|
runnerId,
|
|
5710
6934
|
workItemId: result.workItem.workItemId,
|
|
5711
6935
|
stdout: toolResult.stdout,
|
|
@@ -5725,8 +6949,9 @@ async function runNextWorkItem({
|
|
|
5725
6949
|
tool: preview.toolName,
|
|
5726
6950
|
...toolResult.model ? { model: toolResult.model } : {},
|
|
5727
6951
|
durationMs,
|
|
5728
|
-
message:
|
|
6952
|
+
message: finalMessage,
|
|
5729
6953
|
...isolationTelemetry,
|
|
6954
|
+
...finalStatus === "blocked" ? { blockerReason: finalMessage } : {},
|
|
5730
6955
|
sessionPolicy: sessionContext.policy,
|
|
5731
6956
|
sessionDecision: sessionContext.decision,
|
|
5732
6957
|
sessionDecisionReason: sessionContext.reason,
|
|
@@ -5735,19 +6960,34 @@ async function runNextWorkItem({
|
|
|
5735
6960
|
...toolResult.tokensIn !== void 0 ? { tokensIn: toolResult.tokensIn } : {},
|
|
5736
6961
|
...toolResult.tokensOut !== void 0 ? { tokensOut: toolResult.tokensOut } : {},
|
|
5737
6962
|
...toolResult.costUsd !== void 0 ? { costUsd: toolResult.costUsd } : {},
|
|
5738
|
-
...
|
|
6963
|
+
...implementationHandoff ? { implementationHandoff } : {},
|
|
6964
|
+
...finalError ? { error: finalError } : {}
|
|
5739
6965
|
}
|
|
5740
6966
|
);
|
|
5741
6967
|
await recordRunnerMilestone(apiClient, projectId, result.workItem, runnerId, repositoryLinkId, {
|
|
5742
6968
|
status: finalStatus,
|
|
5743
|
-
summary:
|
|
6969
|
+
summary: finalMessage,
|
|
5744
6970
|
idempotencyKey: `runner_milestone_finished_${result.workItem.workItemId}_${statusResult.workItem.idempotencyKey}`,
|
|
5745
|
-
metadata: {
|
|
6971
|
+
metadata: {
|
|
6972
|
+
tool: preview.toolName,
|
|
6973
|
+
durationMs,
|
|
6974
|
+
exitCode: toolResult.exitCode,
|
|
6975
|
+
verificationSummary: finalStatus === "completed" ? "Local execution reported completion." : "Local execution reported failure.",
|
|
6976
|
+
executionWorktreeKey: isolationTelemetry.executionWorktreeKey ?? "",
|
|
6977
|
+
executionBranch: isolationTelemetry.executionBranch ?? "",
|
|
6978
|
+
...implementationHandoff?.status ? { handoffStatus: implementationHandoff.status } : {},
|
|
6979
|
+
...implementationHandoff?.prUrl ? { prUrl: implementationHandoff.prUrl } : {},
|
|
6980
|
+
...implementationHandoff?.cleanupStatus ? { cleanupStatus: implementationHandoff.cleanupStatus } : {}
|
|
6981
|
+
}
|
|
5746
6982
|
});
|
|
5747
6983
|
await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
|
|
5748
6984
|
const durationSeconds = Math.round(durationMs / 1e3);
|
|
5749
6985
|
console.log(`Marked ${statusResult.workItem.workItemId} ${statusResult.workItem.status} after ${durationSeconds}s.`);
|
|
5750
|
-
return { status: finalStatus, exitCode: toolResult.exitCode };
|
|
6986
|
+
return { status: finalStatus, exitCode: finalStatus === "completed" ? toolResult.exitCode : 1 };
|
|
6987
|
+
}
|
|
6988
|
+
async function loadWorkItemRepositoryLink(apiClient, projectId, repositoryLinkId) {
|
|
6989
|
+
const { repositoryLinks } = await apiClient.listRepositoryLinks(projectId);
|
|
6990
|
+
return repositoryLinks.find((link) => link.repositoryLinkId === repositoryLinkId && link.status !== "revoked");
|
|
5751
6991
|
}
|
|
5752
6992
|
async function prepareWorktreeForClaimedItem({ apiClient, maxPreflightAttempts, projectId, repositoryLinkId, root, runnerId, toolConfig, workItem }) {
|
|
5753
6993
|
if (!needsGitWorktreeIsolation(workItem)) {
|
|
@@ -5951,10 +7191,10 @@ async function executeRunnerCommand(command, context) {
|
|
|
5951
7191
|
}
|
|
5952
7192
|
return runOfficialCliUpdateWithRuntimeRefresh({
|
|
5953
7193
|
mode: currentRunnerMode(),
|
|
5954
|
-
restartBackgroundRunner: () => restartCurrentRunner(context)
|
|
7194
|
+
restartBackgroundRunner: () => restartCurrentRunner(context, { useUpdatedCliExecutable: true })
|
|
5955
7195
|
});
|
|
5956
7196
|
}
|
|
5957
|
-
async function restartCurrentRunner(context) {
|
|
7197
|
+
async function restartCurrentRunner(context, options = {}) {
|
|
5958
7198
|
if (currentRunnerMode() !== "background") {
|
|
5959
7199
|
return { succeeded: false, message: "Foreground runners cannot be restarted remotely. Stop and start the local command manually." };
|
|
5960
7200
|
}
|
|
@@ -5963,7 +7203,7 @@ async function restartCurrentRunner(context) {
|
|
|
5963
7203
|
return { succeeded: false, message: "Background runner metadata was not found, so restart needs manual action." };
|
|
5964
7204
|
}
|
|
5965
7205
|
try {
|
|
5966
|
-
const replacement = await restartRunnerDaemonProcess(metadata, context.backgroundArgs);
|
|
7206
|
+
const replacement = await restartRunnerDaemonProcess(metadata, context.backgroundArgs, options.useUpdatedCliExecutable ? updatedCliRunnerLaunchOptions() : {});
|
|
5967
7207
|
return { succeeded: true, stopRunner: true, message: `Replacement background runner started with PID ${replacement.pid}.` };
|
|
5968
7208
|
} catch (error) {
|
|
5969
7209
|
return { succeeded: false, message: "Background restart failed.", error: errorMessage3(error) };
|
|
@@ -6326,23 +7566,291 @@ ${toolResult.stderr}`);
|
|
|
6326
7566
|
console.error(diagnosisError ?? "Local runner issue diagnosis failed.");
|
|
6327
7567
|
return { status: "failed", exitCode: toolResult.exitCode || 1 };
|
|
6328
7568
|
}
|
|
7569
|
+
async function finalizeSecurityPostureScanWork({
|
|
7570
|
+
apiClient,
|
|
7571
|
+
durationMs,
|
|
7572
|
+
projectId,
|
|
7573
|
+
repositoryLinkId,
|
|
7574
|
+
runnerId,
|
|
7575
|
+
sessionContext,
|
|
7576
|
+
toolConfig,
|
|
7577
|
+
toolName,
|
|
7578
|
+
toolResult,
|
|
7579
|
+
workItem
|
|
7580
|
+
}) {
|
|
7581
|
+
let scanResult = void 0;
|
|
7582
|
+
let scanError;
|
|
7583
|
+
if (toolResult.exitCode === 0) {
|
|
7584
|
+
try {
|
|
7585
|
+
scanResult = parseSecurityPostureScanResult(`${toolResult.stdout}
|
|
7586
|
+
${toolResult.stderr}`);
|
|
7587
|
+
} catch (error) {
|
|
7588
|
+
scanError = errorMessage3(error);
|
|
7589
|
+
}
|
|
7590
|
+
} else {
|
|
7591
|
+
scanError = truncateLogExcerpt(toolResult.stderr || toolResult.stdout) || `${toolName} exited with code ${toolResult.exitCode}.`;
|
|
7592
|
+
}
|
|
7593
|
+
const finalStatus = scanResult ? "completed" : "failed";
|
|
7594
|
+
const updatedToolSession = await finalizeToolSession({
|
|
7595
|
+
apiClient,
|
|
7596
|
+
projectId,
|
|
7597
|
+
status: finalStatus,
|
|
7598
|
+
runnerId,
|
|
7599
|
+
workItemId: workItem.workItemId,
|
|
7600
|
+
stdout: toolResult.stdout,
|
|
7601
|
+
...sessionContext.toolSession ? { session: sessionContext.toolSession } : {},
|
|
7602
|
+
...toolResult.messageCount !== void 0 ? { messageCount: toolResult.messageCount } : {},
|
|
7603
|
+
...toolResult.tokensIn !== void 0 ? { tokensIn: toolResult.tokensIn } : {},
|
|
7604
|
+
...toolResult.tokensOut !== void 0 ? { tokensOut: toolResult.tokensOut } : {},
|
|
7605
|
+
...toolResult.costUsd !== void 0 ? { costUsd: toolResult.costUsd } : {}
|
|
7606
|
+
});
|
|
7607
|
+
const sessionTelemetry = {
|
|
7608
|
+
sessionPolicy: sessionContext.policy,
|
|
7609
|
+
sessionDecision: sessionContext.decision,
|
|
7610
|
+
sessionDecisionReason: sessionContext.reason,
|
|
7611
|
+
...updatedToolSession ? { toolSessionId: updatedToolSession.toolSessionId } : {},
|
|
7612
|
+
...updatedToolSession?.sessionGroupKey ? { sessionGroupKey: updatedToolSession.sessionGroupKey } : {}
|
|
7613
|
+
};
|
|
7614
|
+
if (scanResult) {
|
|
7615
|
+
const result = await apiClient.submitSecurityPostureScanResult(projectId, workItem.workItemId, {
|
|
7616
|
+
status: "completed",
|
|
7617
|
+
runnerId,
|
|
7618
|
+
idempotencyKey: `security_${workItem.workItemId}_${randomUUID()}`,
|
|
7619
|
+
result: scanResult,
|
|
7620
|
+
tool: toolName,
|
|
7621
|
+
durationMs,
|
|
7622
|
+
...sessionTelemetry,
|
|
7623
|
+
message: `${toolName} returned a security posture scan.`
|
|
7624
|
+
});
|
|
7625
|
+
await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
|
|
7626
|
+
status: "completed",
|
|
7627
|
+
summary: `${toolName} returned a security posture scan.`,
|
|
7628
|
+
idempotencyKey: `runner_milestone_security_completed_${workItem.workItemId}_${result.workItem.idempotencyKey}`,
|
|
7629
|
+
metadata: { tool: toolName, durationMs, findingCount: scanResult.findings.length, critical: result.scan.severityCounts.critical, high: result.scan.severityCounts.high, verificationSummary: scanResult.verificationPlan.join(" | ") }
|
|
7630
|
+
});
|
|
7631
|
+
await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
|
|
7632
|
+
console.log("Security posture scan returned for review.");
|
|
7633
|
+
return { status: "completed", exitCode: 0 };
|
|
7634
|
+
}
|
|
7635
|
+
const failedResult = await apiClient.submitSecurityPostureScanResult(projectId, workItem.workItemId, {
|
|
7636
|
+
status: "failed",
|
|
7637
|
+
runnerId,
|
|
7638
|
+
idempotencyKey: `security_${workItem.workItemId}_${randomUUID()}`,
|
|
7639
|
+
tool: toolName,
|
|
7640
|
+
durationMs,
|
|
7641
|
+
...sessionTelemetry,
|
|
7642
|
+
message: `${toolName} did not produce a valid security posture scan.`,
|
|
7643
|
+
...scanError ? { error: scanError } : {}
|
|
7644
|
+
});
|
|
7645
|
+
await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
|
|
7646
|
+
status: "failed",
|
|
7647
|
+
summary: scanError ?? `${toolName} did not produce a valid security posture scan.`,
|
|
7648
|
+
idempotencyKey: `runner_milestone_security_failed_${workItem.workItemId}_${failedResult.workItem.idempotencyKey}`,
|
|
7649
|
+
metadata: { tool: toolName, durationMs, verificationSummary: "Security posture output did not include valid structured JSON." }
|
|
7650
|
+
});
|
|
7651
|
+
await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
|
|
7652
|
+
console.error(scanError ?? "Local runner security posture scan failed.");
|
|
7653
|
+
return { status: "failed", exitCode: toolResult.exitCode || 1 };
|
|
7654
|
+
}
|
|
7655
|
+
async function finalizeAppEvaluationScanWork({
|
|
7656
|
+
apiClient,
|
|
7657
|
+
durationMs,
|
|
7658
|
+
projectId,
|
|
7659
|
+
repositoryLinkId,
|
|
7660
|
+
runnerId,
|
|
7661
|
+
sessionContext,
|
|
7662
|
+
toolConfig,
|
|
7663
|
+
toolName,
|
|
7664
|
+
toolResult,
|
|
7665
|
+
workItem
|
|
7666
|
+
}) {
|
|
7667
|
+
let scanResult = void 0;
|
|
7668
|
+
let scanError;
|
|
7669
|
+
if (toolResult.exitCode === 0) {
|
|
7670
|
+
try {
|
|
7671
|
+
scanResult = parseAppEvaluationScanResult(`${toolResult.stdout}
|
|
7672
|
+
${toolResult.stderr}`);
|
|
7673
|
+
} catch (error) {
|
|
7674
|
+
scanError = errorMessage3(error);
|
|
7675
|
+
}
|
|
7676
|
+
} else {
|
|
7677
|
+
scanError = truncateLogExcerpt(toolResult.stderr || toolResult.stdout) || `${toolName} exited with code ${toolResult.exitCode}.`;
|
|
7678
|
+
}
|
|
7679
|
+
const finalStatus = scanResult ? "completed" : "failed";
|
|
7680
|
+
const updatedToolSession = await finalizeToolSession({
|
|
7681
|
+
apiClient,
|
|
7682
|
+
projectId,
|
|
7683
|
+
status: finalStatus,
|
|
7684
|
+
runnerId,
|
|
7685
|
+
workItemId: workItem.workItemId,
|
|
7686
|
+
stdout: toolResult.stdout,
|
|
7687
|
+
...sessionContext.toolSession ? { session: sessionContext.toolSession } : {},
|
|
7688
|
+
...toolResult.messageCount !== void 0 ? { messageCount: toolResult.messageCount } : {},
|
|
7689
|
+
...toolResult.tokensIn !== void 0 ? { tokensIn: toolResult.tokensIn } : {},
|
|
7690
|
+
...toolResult.tokensOut !== void 0 ? { tokensOut: toolResult.tokensOut } : {},
|
|
7691
|
+
...toolResult.costUsd !== void 0 ? { costUsd: toolResult.costUsd } : {}
|
|
7692
|
+
});
|
|
7693
|
+
const sessionTelemetry = {
|
|
7694
|
+
sessionPolicy: sessionContext.policy,
|
|
7695
|
+
sessionDecision: sessionContext.decision,
|
|
7696
|
+
sessionDecisionReason: sessionContext.reason,
|
|
7697
|
+
...updatedToolSession ? { toolSessionId: updatedToolSession.toolSessionId } : {},
|
|
7698
|
+
...updatedToolSession?.sessionGroupKey ? { sessionGroupKey: updatedToolSession.sessionGroupKey } : {}
|
|
7699
|
+
};
|
|
7700
|
+
if (scanResult) {
|
|
7701
|
+
const result = await apiClient.submitAppEvaluationScanResult(projectId, workItem.workItemId, {
|
|
7702
|
+
status: "completed",
|
|
7703
|
+
runnerId,
|
|
7704
|
+
idempotencyKey: `app_evaluation_${workItem.workItemId}_${randomUUID()}`,
|
|
7705
|
+
result: scanResult,
|
|
7706
|
+
tool: toolName,
|
|
7707
|
+
durationMs,
|
|
7708
|
+
...sessionTelemetry,
|
|
7709
|
+
message: `${toolName} returned an app evaluation scan.`
|
|
7710
|
+
});
|
|
7711
|
+
await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
|
|
7712
|
+
status: "completed",
|
|
7713
|
+
summary: `${toolName} returned an app evaluation scan.`,
|
|
7714
|
+
idempotencyKey: `runner_milestone_app_evaluation_completed_${workItem.workItemId}_${result.workItem.idempotencyKey}`,
|
|
7715
|
+
metadata: { tool: toolName, durationMs, findingCount: scanResult.findings.length, verificationSummary: scanResult.verificationPlan.join(" | ") }
|
|
7716
|
+
});
|
|
7717
|
+
await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
|
|
7718
|
+
console.log("App evaluation scan returned for review.");
|
|
7719
|
+
return { status: "completed", exitCode: 0 };
|
|
7720
|
+
}
|
|
7721
|
+
const failedResult = await apiClient.submitAppEvaluationScanResult(projectId, workItem.workItemId, {
|
|
7722
|
+
status: "failed",
|
|
7723
|
+
runnerId,
|
|
7724
|
+
idempotencyKey: `app_evaluation_${workItem.workItemId}_${randomUUID()}`,
|
|
7725
|
+
tool: toolName,
|
|
7726
|
+
durationMs,
|
|
7727
|
+
...sessionTelemetry,
|
|
7728
|
+
message: `${toolName} did not produce a valid app evaluation scan.`,
|
|
7729
|
+
...scanError ? { error: scanError } : {}
|
|
7730
|
+
});
|
|
7731
|
+
await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
|
|
7732
|
+
status: "failed",
|
|
7733
|
+
summary: scanError ?? `${toolName} did not produce a valid app evaluation scan.`,
|
|
7734
|
+
idempotencyKey: `runner_milestone_app_evaluation_failed_${workItem.workItemId}_${failedResult.workItem.idempotencyKey}`,
|
|
7735
|
+
metadata: { tool: toolName, durationMs, verificationSummary: "App evaluation output did not include valid structured JSON." }
|
|
7736
|
+
});
|
|
7737
|
+
await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
|
|
7738
|
+
console.error(scanError ?? "Local runner app evaluation scan failed.");
|
|
7739
|
+
return { status: "failed", exitCode: toolResult.exitCode || 1 };
|
|
7740
|
+
}
|
|
7741
|
+
async function finalizeProjectContextRefreshWork({
|
|
7742
|
+
apiClient,
|
|
7743
|
+
durationMs,
|
|
7744
|
+
projectId,
|
|
7745
|
+
repositoryLinkId,
|
|
7746
|
+
runnerId,
|
|
7747
|
+
sessionContext,
|
|
7748
|
+
toolConfig,
|
|
7749
|
+
toolName,
|
|
7750
|
+
toolResult,
|
|
7751
|
+
workItem
|
|
7752
|
+
}) {
|
|
7753
|
+
let refreshResult = void 0;
|
|
7754
|
+
let refreshError;
|
|
7755
|
+
if (toolResult.exitCode === 0) {
|
|
7756
|
+
try {
|
|
7757
|
+
refreshResult = parseProjectContextRefreshResult(`${toolResult.stdout}
|
|
7758
|
+
${toolResult.stderr}`);
|
|
7759
|
+
} catch (error) {
|
|
7760
|
+
refreshError = errorMessage3(error);
|
|
7761
|
+
}
|
|
7762
|
+
} else {
|
|
7763
|
+
refreshError = truncateLogExcerpt(toolResult.stderr || toolResult.stdout) || `${toolName} exited with code ${toolResult.exitCode}.`;
|
|
7764
|
+
}
|
|
7765
|
+
const finalStatus = refreshResult ? "completed" : "failed";
|
|
7766
|
+
const updatedToolSession = await finalizeToolSession({
|
|
7767
|
+
apiClient,
|
|
7768
|
+
projectId,
|
|
7769
|
+
status: finalStatus,
|
|
7770
|
+
runnerId,
|
|
7771
|
+
workItemId: workItem.workItemId,
|
|
7772
|
+
stdout: toolResult.stdout,
|
|
7773
|
+
...sessionContext.toolSession ? { session: sessionContext.toolSession } : {},
|
|
7774
|
+
...toolResult.messageCount !== void 0 ? { messageCount: toolResult.messageCount } : {},
|
|
7775
|
+
...toolResult.tokensIn !== void 0 ? { tokensIn: toolResult.tokensIn } : {},
|
|
7776
|
+
...toolResult.tokensOut !== void 0 ? { tokensOut: toolResult.tokensOut } : {},
|
|
7777
|
+
...toolResult.costUsd !== void 0 ? { costUsd: toolResult.costUsd } : {}
|
|
7778
|
+
});
|
|
7779
|
+
const sessionTelemetry = {
|
|
7780
|
+
sessionPolicy: sessionContext.policy,
|
|
7781
|
+
sessionDecision: sessionContext.decision,
|
|
7782
|
+
sessionDecisionReason: sessionContext.reason,
|
|
7783
|
+
...updatedToolSession ? { toolSessionId: updatedToolSession.toolSessionId } : {},
|
|
7784
|
+
...updatedToolSession?.sessionGroupKey ? { sessionGroupKey: updatedToolSession.sessionGroupKey } : {}
|
|
7785
|
+
};
|
|
7786
|
+
if (refreshResult) {
|
|
7787
|
+
const result = await apiClient.submitProjectContextRefreshResult(projectId, workItem.workItemId, {
|
|
7788
|
+
status: "completed",
|
|
7789
|
+
runnerId,
|
|
7790
|
+
idempotencyKey: `project_context_${workItem.workItemId}_${randomUUID()}`,
|
|
7791
|
+
result: refreshResult,
|
|
7792
|
+
tool: toolName,
|
|
7793
|
+
durationMs,
|
|
7794
|
+
...sessionTelemetry,
|
|
7795
|
+
message: `${toolName} returned a project context refresh.`
|
|
7796
|
+
});
|
|
7797
|
+
await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
|
|
7798
|
+
status: "completed",
|
|
7799
|
+
summary: `${toolName} returned a project context refresh.`,
|
|
7800
|
+
idempotencyKey: `runner_milestone_project_context_completed_${workItem.workItemId}_${result.workItem.idempotencyKey}`,
|
|
7801
|
+
metadata: { tool: toolName, durationMs, sliceCount: refreshResult.slices.length, entityCount: refreshResult.entities.length, mapId: result.map?.projectContextMapId ?? "", verificationSummary: refreshResult.verificationPlan.join(" | ") }
|
|
7802
|
+
});
|
|
7803
|
+
await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
|
|
7804
|
+
console.log("Project context refresh returned for review.");
|
|
7805
|
+
return { status: "completed", exitCode: 0 };
|
|
7806
|
+
}
|
|
7807
|
+
const failedResult = await apiClient.submitProjectContextRefreshResult(projectId, workItem.workItemId, {
|
|
7808
|
+
status: "failed",
|
|
7809
|
+
runnerId,
|
|
7810
|
+
idempotencyKey: `project_context_${workItem.workItemId}_${randomUUID()}`,
|
|
7811
|
+
tool: toolName,
|
|
7812
|
+
durationMs,
|
|
7813
|
+
...sessionTelemetry,
|
|
7814
|
+
message: `${toolName} did not produce a valid project context refresh.`,
|
|
7815
|
+
...refreshError ? { error: refreshError } : {}
|
|
7816
|
+
});
|
|
7817
|
+
await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
|
|
7818
|
+
status: "failed",
|
|
7819
|
+
summary: refreshError ?? `${toolName} did not produce a valid project context refresh.`,
|
|
7820
|
+
idempotencyKey: `runner_milestone_project_context_failed_${workItem.workItemId}_${failedResult.workItem.idempotencyKey}`,
|
|
7821
|
+
metadata: { tool: toolName, durationMs, verificationSummary: "Project context output did not include valid structured JSON." }
|
|
7822
|
+
});
|
|
7823
|
+
await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
|
|
7824
|
+
console.error(refreshError ?? "Local runner project context refresh failed.");
|
|
7825
|
+
return { status: "failed", exitCode: toolResult.exitCode || 1 };
|
|
7826
|
+
}
|
|
6329
7827
|
async function createRunnerWorkPrompt(apiClient, projectId, workItem) {
|
|
6330
7828
|
if (workItem.workKind === "assistantQuestion") {
|
|
6331
|
-
const
|
|
7829
|
+
const emptyProjectContext = { maps: [], refreshes: [], misses: [] };
|
|
7830
|
+
const [{ documents: documents2 }, { messages: messages2 }, context] = await Promise.all([
|
|
6332
7831
|
apiClient.listBrainDocuments(projectId),
|
|
6333
|
-
apiClient.listAssistantMessages(projectId)
|
|
7832
|
+
apiClient.listAssistantMessages(projectId),
|
|
7833
|
+
apiClient.listProjectContext(projectId).catch(() => emptyProjectContext)
|
|
6334
7834
|
]);
|
|
6335
7835
|
const question = messages2.find((message) => message.messageId === workItem.assistantMessageId || message.id === workItem.assistantMessageId) ?? messages2.filter((message) => message.workItemId === workItem.workItemId).sort((first, second) => Date.parse(first.createdAt) - Date.parse(second.createdAt))[0];
|
|
7836
|
+
const contextPack = await apiClient.buildProjectContextPack(projectId, question?.content ?? workItem.sourceWish ?? workItem.title, workItem.workItemId).catch(() => void 0);
|
|
6336
7837
|
return createWorkExecutionPrompt(workItem, {
|
|
6337
|
-
...question ? { assistantQuestion: { question, messages: messages2, documents: documents2 } } : {}
|
|
7838
|
+
...question ? { assistantQuestion: { question, messages: messages2, documents: documents2, ...context.activeMap ? { activeMap: context.activeMap } : {}, ...contextPack ? { contextPack } : {} } } : {}
|
|
6338
7839
|
});
|
|
6339
7840
|
}
|
|
6340
7841
|
if (workItem.workKind === "impactPreview") {
|
|
6341
|
-
const {
|
|
7842
|
+
const emptyProjectContext = { maps: [], refreshes: [], misses: [] };
|
|
7843
|
+
const [{ documents: documents2 }, context] = await Promise.all([
|
|
7844
|
+
apiClient.listBrainDocuments(projectId),
|
|
7845
|
+
apiClient.listProjectContext(projectId).catch(() => emptyProjectContext)
|
|
7846
|
+
]);
|
|
6342
7847
|
const implementationPrompt = workItem.impactDocumentId ? documents2.find((document) => document.documentId === workItem.impactDocumentId || document.id === workItem.impactDocumentId) : documents2.find((document) => document.frontmatter.generatedDraftId === workItem.generatedDraftId && (document.frontmatter.createsWorkItem === true || document.documentType === "prompt"));
|
|
7848
|
+
const contextPack = await apiClient.buildProjectContextPack(projectId, implementationPrompt?.content ?? workItem.sourceWish ?? workItem.title, workItem.workItemId).catch(() => void 0);
|
|
6343
7849
|
return createWorkExecutionPrompt(workItem, {
|
|
6344
7850
|
impactPreview: {
|
|
6345
7851
|
documents: documents2,
|
|
7852
|
+
...context.activeMap ? { activeMap: context.activeMap } : {},
|
|
7853
|
+
...contextPack ? { contextPack } : {},
|
|
6346
7854
|
...implementationPrompt ? { implementationPrompt } : {}
|
|
6347
7855
|
}
|
|
6348
7856
|
});
|
|
@@ -6360,6 +7868,31 @@ async function createRunnerWorkPrompt(apiClient, projectId, workItem) {
|
|
|
6360
7868
|
}
|
|
6361
7869
|
});
|
|
6362
7870
|
}
|
|
7871
|
+
if (workItem.workKind === "securityPostureScan") {
|
|
7872
|
+
const { documents: documents2 } = await apiClient.listBrainDocuments(projectId);
|
|
7873
|
+
return createWorkExecutionPrompt(workItem, {
|
|
7874
|
+
securityPostureScan: { documents: documents2 }
|
|
7875
|
+
});
|
|
7876
|
+
}
|
|
7877
|
+
if (workItem.workKind === "appEvaluationScan") {
|
|
7878
|
+
const { documents: documents2 } = await apiClient.listBrainDocuments(projectId);
|
|
7879
|
+
return createWorkExecutionPrompt(workItem, {
|
|
7880
|
+
appEvaluationScan: { documents: documents2 }
|
|
7881
|
+
});
|
|
7882
|
+
}
|
|
7883
|
+
if (workItem.workKind === "projectContextRefresh") {
|
|
7884
|
+
const emptyProjectContext = { maps: [], refreshes: [], misses: [] };
|
|
7885
|
+
const [{ documents: documents2 }, context] = await Promise.all([
|
|
7886
|
+
apiClient.listBrainDocuments(projectId),
|
|
7887
|
+
apiClient.listProjectContext(projectId).catch(() => emptyProjectContext)
|
|
7888
|
+
]);
|
|
7889
|
+
return createWorkExecutionPrompt(workItem, {
|
|
7890
|
+
projectContextRefresh: {
|
|
7891
|
+
documents: documents2,
|
|
7892
|
+
...context.activeMap ? { activeMap: context.activeMap } : {}
|
|
7893
|
+
}
|
|
7894
|
+
});
|
|
7895
|
+
}
|
|
6363
7896
|
if (workItem.workKind !== "planRevision" || !workItem.reviewDocumentId) {
|
|
6364
7897
|
return createWorkExecutionPrompt(workItem);
|
|
6365
7898
|
}
|
|
@@ -6587,7 +8120,7 @@ function parseReasoningEffort(value) {
|
|
|
6587
8120
|
throw new Error(`Expected reasoning effort auto, low, medium, high, or xhigh; received ${value}.`);
|
|
6588
8121
|
}
|
|
6589
8122
|
function inferRepoName(root) {
|
|
6590
|
-
return
|
|
8123
|
+
return path14.basename(path14.resolve(root)) || "repository";
|
|
6591
8124
|
}
|
|
6592
8125
|
function createRepoFingerprint(accountId, projectId, repositoryLinkId) {
|
|
6593
8126
|
return createHash7("sha256").update(`${accountId}:${projectId}:${repositoryLinkId}`).digest("hex");
|