@amistio/cli 0.1.12 → 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 +1280 -65
- 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
|
|
@@ -24,6 +24,12 @@ var itemTypeSchema = z.enum([
|
|
|
24
24
|
"securityScan",
|
|
25
25
|
"securityFinding",
|
|
26
26
|
"securityPostureSnapshot",
|
|
27
|
+
"appEvaluationScan",
|
|
28
|
+
"appEvaluationFinding",
|
|
29
|
+
"projectContextMap",
|
|
30
|
+
"projectContextRefresh",
|
|
31
|
+
"projectSystemDiagram",
|
|
32
|
+
"contextMiss",
|
|
27
33
|
"syncCursor",
|
|
28
34
|
"syncConflict",
|
|
29
35
|
"workItem",
|
|
@@ -48,6 +54,8 @@ var documentTypeSchema = z.enum([
|
|
|
48
54
|
"prompt",
|
|
49
55
|
"workflow"
|
|
50
56
|
]);
|
|
57
|
+
var documentContentFormatSchema = z.enum(["markdown", "html"]);
|
|
58
|
+
var artifactFormatPreferenceSchema = z.enum(["markdown", "html", "both", "auto"]);
|
|
51
59
|
var syncStateSchema = z.enum([
|
|
52
60
|
"draft",
|
|
53
61
|
"approved",
|
|
@@ -70,7 +78,23 @@ var workStatusSchema = z.enum([
|
|
|
70
78
|
]);
|
|
71
79
|
var sourceSchema = z.enum(["web", "repo", "generated", "runner"]);
|
|
72
80
|
var executionModeSchema = z.enum(["localRunner", "cloudSandbox"]);
|
|
73
|
-
var workKindSchema = z.enum(["brainGeneration", "implementation", "planRevision", "assistantQuestion", "impactPreview", "issueDiagnosis", "securityPostureScan"]);
|
|
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();
|
|
74
98
|
var generatedDraftStatusSchema = z.enum(["queued", "generating", "reviewing", "approved", "rejected", "changesRequested", "failed"]);
|
|
75
99
|
var assistantQuestionModeSchema = z.enum(["brainOnly", "sourceAware"]);
|
|
76
100
|
var impactRiskLevelSchema = z.enum(["low", "medium", "high", "critical"]);
|
|
@@ -80,12 +104,19 @@ var issueSeveritySchema = z.enum(["low", "medium", "high", "critical"]);
|
|
|
80
104
|
var issueStatusSchema = z.enum(["queued", "diagnosing", "analysisReady", "approved", "changesRequested", "rejected", "implementationQueued", "implemented", "failed"]);
|
|
81
105
|
var issueApprovalStateSchema = z.enum(["proposed", "approved", "changesRequested", "rejected", "implemented", "blocked"]);
|
|
82
106
|
var securityScanStatusSchema = z.enum(["queued", "running", "completed", "failed", "stale", "skipped"]);
|
|
83
|
-
var securityScanSourceSchema = z.enum(["nightly", "manual"]);
|
|
107
|
+
var securityScanSourceSchema = z.enum(["nightly", "manual", "runner"]);
|
|
84
108
|
var securityFindingCategorySchema = z.enum(["owasp", "dependency", "supplyChain", "secretHygiene", "authentication", "authorization", "tenantIsolation", "headers", "redirects", "csrf", "cors", "ciCd", "logging", "runnerBoundary", "other"]);
|
|
85
109
|
var securityFindingSeveritySchema = z.enum(["info", "low", "medium", "high", "critical"]);
|
|
86
110
|
var securityFindingConfidenceSchema = z.enum(["low", "medium", "high"]);
|
|
87
111
|
var securityFindingStatusSchema = z.enum(["open", "planReady", "approved", "changesRequested", "dismissed", "acceptedRisk", "remediationQueued", "resolved", "failed"]);
|
|
88
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"]);
|
|
89
120
|
var activityEventTypeSchema = z.enum([
|
|
90
121
|
"commandSubmitted",
|
|
91
122
|
"generationQueued",
|
|
@@ -117,6 +148,22 @@ var activityEventTypeSchema = z.enum([
|
|
|
117
148
|
"securityFindingReviewed",
|
|
118
149
|
"securityRemediationPlanCreated",
|
|
119
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",
|
|
120
167
|
"handoffExported"
|
|
121
168
|
]);
|
|
122
169
|
var activityEventActorSchema = z.enum(["webUser", "runner", "cli", "system"]);
|
|
@@ -131,6 +178,7 @@ var activityMetadataValueSchema = z.union([
|
|
|
131
178
|
var activityMetadataSchema = z.record(z.string().min(1).max(80), activityMetadataValueSchema).default({});
|
|
132
179
|
var generatedBrainArtifactSchema = z.object({
|
|
133
180
|
documentType: documentTypeSchema,
|
|
181
|
+
contentFormat: documentContentFormatSchema.default("markdown"),
|
|
134
182
|
title: z.string().trim().min(1),
|
|
135
183
|
repoPath: z.string().trim().min(1),
|
|
136
184
|
content: z.string().min(1)
|
|
@@ -243,6 +291,7 @@ var organizationWorkspaceScopeSchema = z.object({
|
|
|
243
291
|
kind: z.literal("organization"),
|
|
244
292
|
accountId: z.string().min(1),
|
|
245
293
|
organizationId: z.string().min(1),
|
|
294
|
+
userId: z.string().min(1).optional(),
|
|
246
295
|
organizationSlug: z.string().trim().min(1).optional(),
|
|
247
296
|
organizationRole: z.string().trim().min(1).optional(),
|
|
248
297
|
label: z.string().trim().min(1).default("Organization workspace")
|
|
@@ -282,6 +331,7 @@ var projectItemSchema = baseItemSchema.extend({
|
|
|
282
331
|
name: z.string().min(1),
|
|
283
332
|
slug: z.string().min(1),
|
|
284
333
|
description: z.string().optional(),
|
|
334
|
+
artifactFormatPreference: artifactFormatPreferenceSchema.optional(),
|
|
285
335
|
status: projectStatusSchema.default("active"),
|
|
286
336
|
archivedAt: isoDateTimeSchema.optional(),
|
|
287
337
|
archivedByUserId: z.string().min(1).optional(),
|
|
@@ -302,6 +352,7 @@ var repositoryLinkItemSchema = baseItemSchema.extend({
|
|
|
302
352
|
linkSource: repositoryLinkSourceSchema.optional(),
|
|
303
353
|
cloneStatus: repositoryCloneStatusSchema.optional(),
|
|
304
354
|
autoSyncEnabled: z.boolean().optional(),
|
|
355
|
+
appEvaluationEnabled: z.boolean().optional(),
|
|
305
356
|
lastValidatedAt: isoDateTimeSchema.optional(),
|
|
306
357
|
status: z.enum(["active", "revoked"]).default("active")
|
|
307
358
|
});
|
|
@@ -310,6 +361,7 @@ var brainDocumentItemSchema = baseItemSchema.extend({
|
|
|
310
361
|
projectId: z.string().min(1),
|
|
311
362
|
documentId: z.string().min(1),
|
|
312
363
|
documentType: documentTypeSchema,
|
|
364
|
+
contentFormat: documentContentFormatSchema.default("markdown"),
|
|
313
365
|
title: z.string().min(1),
|
|
314
366
|
status: z.string().min(1),
|
|
315
367
|
repoPath: z.string().min(1),
|
|
@@ -369,6 +421,10 @@ var workItemSchema = baseItemSchema.extend({
|
|
|
369
421
|
issueId: z.string().min(1).optional(),
|
|
370
422
|
securityScanId: z.string().min(1).optional(),
|
|
371
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(),
|
|
372
428
|
repositoryLinkId: z.string().min(1).optional(),
|
|
373
429
|
reviewThreadId: z.string().min(1).optional(),
|
|
374
430
|
reviewDocumentId: z.string().min(1).optional(),
|
|
@@ -376,6 +432,7 @@ var workItemSchema = baseItemSchema.extend({
|
|
|
376
432
|
reviewMessageId: z.string().min(1).optional(),
|
|
377
433
|
assistantMessageId: z.string().min(1).optional(),
|
|
378
434
|
assistantQuestionMode: assistantQuestionModeSchema.optional(),
|
|
435
|
+
artifactFormatPreference: artifactFormatPreferenceSchema.optional(),
|
|
379
436
|
impactReportId: z.string().min(1).optional(),
|
|
380
437
|
impactDocumentId: z.string().min(1).optional(),
|
|
381
438
|
impactDocumentRevision: z.number().int().nonnegative().optional(),
|
|
@@ -401,6 +458,7 @@ var workItemSchema = baseItemSchema.extend({
|
|
|
401
458
|
toolSessionId: z.string().min(1).optional(),
|
|
402
459
|
sessionDecision: sessionDecisionSchema.optional(),
|
|
403
460
|
sessionDecisionReason: z.string().min(1).optional(),
|
|
461
|
+
implementationHandoff: implementationHandoffSchema.optional(),
|
|
404
462
|
lastStatusMessage: z.string().optional(),
|
|
405
463
|
lastStatusAt: isoDateTimeSchema
|
|
406
464
|
});
|
|
@@ -495,6 +553,7 @@ var runnerExecutionLogItemSchema = baseItemSchema.extend({
|
|
|
495
553
|
toolSessionId: z.string().min(1).optional(),
|
|
496
554
|
sessionDecision: sessionDecisionSchema.optional(),
|
|
497
555
|
sessionDecisionReason: z.string().min(1).optional(),
|
|
556
|
+
implementationHandoff: implementationHandoffSchema.optional(),
|
|
498
557
|
message: z.string().optional(),
|
|
499
558
|
error: z.string().optional()
|
|
500
559
|
});
|
|
@@ -591,7 +650,7 @@ var assistantAnswerResultSchema = z.object({
|
|
|
591
650
|
summary: z.string().optional()
|
|
592
651
|
});
|
|
593
652
|
var knowledgeSearchScopeSchema = z.enum(["project", "organization"]);
|
|
594
|
-
var knowledgeSourceTypeSchema = z.enum(["brainDocument", "generatedArtifact", "runnerSummary", "knowledgeEntity", "knowledgeRelation"]);
|
|
653
|
+
var knowledgeSourceTypeSchema = z.enum(["brainDocument", "generatedArtifact", "runnerSummary", "knowledgeEntity", "knowledgeRelation", "projectContextMap", "projectContextSlice", "projectSystemDiagram", "projectSystemDiagramNode", "projectSystemDiagramEdge"]);
|
|
595
654
|
var knowledgeChunkStatusSchema = z.enum(["approved", "synced", "stale"]);
|
|
596
655
|
var knowledgeEntityTypeSchema = z.enum(["project", "system", "component", "domain", "tool", "decision", "feature", "risk", "team", "workflow", "unknown"]);
|
|
597
656
|
var knowledgeRelationTypeSchema = z.enum(["uses", "depends_on", "decides", "supersedes", "touches", "blocks", "implements", "mentions"]);
|
|
@@ -703,6 +762,221 @@ var knowledgeDiagramResultSchema = z.object({
|
|
|
703
762
|
freshness: knowledgeIndexFreshnessSchema,
|
|
704
763
|
warnings: z.array(z.string().trim().min(1).max(600)).default([])
|
|
705
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
|
+
});
|
|
706
980
|
var impactAffectedAreaSchema = z.object({
|
|
707
981
|
name: z.string().trim().min(1),
|
|
708
982
|
description: z.string().trim().min(1).optional()
|
|
@@ -843,6 +1117,7 @@ var securityScanItemSchema = baseItemSchema.extend({
|
|
|
843
1117
|
status: securityScanStatusSchema,
|
|
844
1118
|
baselineVersion: z.string().trim().min(1).max(80).default("amistio-security-baseline-v1"),
|
|
845
1119
|
workItemId: z.string().min(1).optional(),
|
|
1120
|
+
repositoryLinkId: z.string().min(1).optional(),
|
|
846
1121
|
runnerId: z.string().min(1).optional(),
|
|
847
1122
|
findingIds: z.array(z.string().min(1)).default([]),
|
|
848
1123
|
summary: z.string().trim().min(1).max(2e3).optional(),
|
|
@@ -894,6 +1169,75 @@ var securityPostureSnapshotItemSchema = baseItemSchema.extend({
|
|
|
894
1169
|
nextScheduledAt: isoDateTimeSchema.optional(),
|
|
895
1170
|
summary: z.string().trim().min(1).max(2e3).optional()
|
|
896
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
|
+
});
|
|
897
1241
|
var activityEventItemSchema = baseItemSchema.extend({
|
|
898
1242
|
type: z.literal("activityEvent"),
|
|
899
1243
|
projectId: z.string().min(1),
|
|
@@ -911,6 +1255,11 @@ var activityEventItemSchema = baseItemSchema.extend({
|
|
|
911
1255
|
relatedIssueId: z.string().min(1).optional(),
|
|
912
1256
|
relatedSecurityScanId: z.string().min(1).optional(),
|
|
913
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(),
|
|
914
1263
|
generatedDraftId: z.string().min(1).optional(),
|
|
915
1264
|
runnerId: z.string().min(1).optional(),
|
|
916
1265
|
repositoryLinkId: z.string().min(1).optional(),
|
|
@@ -971,6 +1320,12 @@ var projectItemUnionSchema = z.discriminatedUnion("type", [
|
|
|
971
1320
|
securityScanItemSchema,
|
|
972
1321
|
securityFindingItemSchema,
|
|
973
1322
|
securityPostureSnapshotItemSchema,
|
|
1323
|
+
appEvaluationScanItemSchema,
|
|
1324
|
+
appEvaluationFindingItemSchema,
|
|
1325
|
+
projectContextMapItemSchema,
|
|
1326
|
+
projectContextRefreshItemSchema,
|
|
1327
|
+
projectSystemDiagramItemSchema,
|
|
1328
|
+
contextMissItemSchema,
|
|
974
1329
|
syncCursorItemSchema,
|
|
975
1330
|
syncConflictItemSchema,
|
|
976
1331
|
workItemSchema,
|
|
@@ -1010,16 +1365,43 @@ function isOfficialAmistioApiUrl(value) {
|
|
|
1010
1365
|
|
|
1011
1366
|
// ../shared/src/brain-documents.ts
|
|
1012
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]));
|
|
1013
1379
|
function isControlPlaneTemplateRepoPath(repoPath) {
|
|
1014
1380
|
const normalized = normalizeRepoPath(repoPath);
|
|
1015
1381
|
const segments = normalized.split("/").filter(Boolean);
|
|
1016
|
-
const root = segments
|
|
1382
|
+
const root = controlPlaneRootFromSegments(segments);
|
|
1017
1383
|
const basename = segments[segments.length - 1]?.toLowerCase();
|
|
1018
1384
|
return Boolean(root && controlPlaneRoots.has(root) && (basename === "_template.md" || basename === "_template.mdx"));
|
|
1019
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
|
+
}
|
|
1020
1397
|
function normalizeRepoPath(repoPath) {
|
|
1021
1398
|
return repoPath.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "");
|
|
1022
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
|
+
}
|
|
1023
1405
|
|
|
1024
1406
|
// ../shared/src/repo-metadata.ts
|
|
1025
1407
|
import { z as z2 } from "zod";
|
|
@@ -1033,6 +1415,7 @@ var repoLinkMetadataSchema = z2.object({
|
|
|
1033
1415
|
var syncedDocumentFrontmatterSchema = z2.object({
|
|
1034
1416
|
amistioDocumentId: z2.string().min(1),
|
|
1035
1417
|
amistioDocumentType: z2.string().min(1),
|
|
1418
|
+
amistioContentFormat: z2.enum(["markdown", "html"]).default("markdown"),
|
|
1036
1419
|
amistioRevision: z2.coerce.number().int().nonnegative(),
|
|
1037
1420
|
amistioContentHash: z2.string().min(1),
|
|
1038
1421
|
status: z2.string().optional()
|
|
@@ -1183,6 +1566,7 @@ function decideSyncState(state) {
|
|
|
1183
1566
|
|
|
1184
1567
|
// ../shared/src/next-action.ts
|
|
1185
1568
|
var nextActionRunnerHeartbeatFreshMs = 15 * 60 * 1e3;
|
|
1569
|
+
var nextActionCompletedWorkFreshMs = 60 * 60 * 1e3;
|
|
1186
1570
|
function computeProjectNextAction(input) {
|
|
1187
1571
|
const nowMs = input.nowMs ?? Date.now();
|
|
1188
1572
|
if (!input.projectId) {
|
|
@@ -1280,7 +1664,7 @@ function computeProjectNextAction(input) {
|
|
|
1280
1664
|
if (!readiness.ready) {
|
|
1281
1665
|
return runnerWaitAction(readiness, "Runner setup needed");
|
|
1282
1666
|
}
|
|
1283
|
-
const completedWork = latestWorkItem(input.workItems.filter((item) => item.status === "completed"));
|
|
1667
|
+
const completedWork = latestWorkItem(input.workItems.filter((item) => item.status === "completed" && isFreshCompletedWork(item, nowMs)));
|
|
1284
1668
|
if (completedWork) {
|
|
1285
1669
|
return workAction(completedWork, "workCompleted", "none", "success", "Work completed", completedWork.lastStatusMessage ?? "The latest runner work is complete.");
|
|
1286
1670
|
}
|
|
@@ -1374,6 +1758,10 @@ function workAction(workItem, kind, actor, tone, title, message) {
|
|
|
1374
1758
|
function latestWorkItem(workItems) {
|
|
1375
1759
|
return [...workItems].sort((first, second) => Date.parse(second.updatedAt) - Date.parse(first.updatedAt))[0];
|
|
1376
1760
|
}
|
|
1761
|
+
function isFreshCompletedWork(workItem, nowMs) {
|
|
1762
|
+
const timestamp = Date.parse(workItem.lastStatusAt ?? workItem.updatedAt);
|
|
1763
|
+
return timestamp > 0 && nowMs - timestamp <= nextActionCompletedWorkFreshMs;
|
|
1764
|
+
}
|
|
1377
1765
|
function latestTimestamp(values) {
|
|
1378
1766
|
return values.sort((first, second) => Date.parse(second) - Date.parse(first))[0];
|
|
1379
1767
|
}
|
|
@@ -1715,6 +2103,27 @@ var ApiClient = class {
|
|
|
1715
2103
|
{ method: "GET" }
|
|
1716
2104
|
);
|
|
1717
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
|
+
}
|
|
1718
2127
|
async pushBrainDocuments(projectId, documents) {
|
|
1719
2128
|
return this.request(
|
|
1720
2129
|
`/projects/${projectId}/brain-documents`,
|
|
@@ -1892,6 +2301,26 @@ var ApiClient = class {
|
|
|
1892
2301
|
}
|
|
1893
2302
|
);
|
|
1894
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
|
+
}
|
|
1895
2324
|
async request(urlPath, schema, init) {
|
|
1896
2325
|
const response = await fetch(resolveApiUrl(this.options.apiUrl, urlPath), {
|
|
1897
2326
|
...init,
|
|
@@ -1938,8 +2367,8 @@ var toolSessionMutationSchema = z3.object({
|
|
|
1938
2367
|
});
|
|
1939
2368
|
function resolveApiUrl(apiUrl, urlPath) {
|
|
1940
2369
|
const base = apiUrl.endsWith("/") ? apiUrl.slice(0, -1) : apiUrl;
|
|
1941
|
-
const
|
|
1942
|
-
return new URL(`${base}${
|
|
2370
|
+
const path15 = urlPath.startsWith("/") ? urlPath : `/${urlPath}`;
|
|
2371
|
+
return new URL(`${base}${path15}`);
|
|
1943
2372
|
}
|
|
1944
2373
|
|
|
1945
2374
|
// src/orchestrator.ts
|
|
@@ -2831,6 +3260,15 @@ function currentRunnerMode() {
|
|
|
2831
3260
|
function defaultRunnerMetadataDir() {
|
|
2832
3261
|
return path6.join(os3.homedir(), ".config", "amistio", "runners");
|
|
2833
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
|
+
}
|
|
2834
3272
|
async function startRunnerDaemon(input) {
|
|
2835
3273
|
const metadataDir = input.metadataDir ?? defaultRunnerMetadataDir();
|
|
2836
3274
|
const existing = await readRunnerDaemonMetadata(input, metadataDir);
|
|
@@ -2840,7 +3278,12 @@ async function startRunnerDaemon(input) {
|
|
|
2840
3278
|
await mkdir5(metadataDir, { recursive: true });
|
|
2841
3279
|
const logPath = path6.join(metadataDir, `${runnerDaemonKey(input)}.log`);
|
|
2842
3280
|
const logFd = openSync(logPath, "a");
|
|
2843
|
-
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, {
|
|
2844
3287
|
cwd: input.rootDir,
|
|
2845
3288
|
detached: true,
|
|
2846
3289
|
env: {
|
|
@@ -2877,7 +3320,13 @@ async function restartRunnerDaemonProcess(metadata, args, input = {}) {
|
|
|
2877
3320
|
await mkdir5(metadataDir, { recursive: true });
|
|
2878
3321
|
const logPath = metadata.logPath ?? path6.join(metadataDir, `${runnerDaemonKey(metadata)}.log`);
|
|
2879
3322
|
const logFd = openSync(logPath, "a");
|
|
2880
|
-
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, {
|
|
2881
3330
|
cwd: metadata.rootDir,
|
|
2882
3331
|
detached: true,
|
|
2883
3332
|
env: {
|
|
@@ -3356,6 +3805,7 @@ import { promisify as promisify3 } from "node:util";
|
|
|
3356
3805
|
var execFileAsync3 = promisify3(execFile3);
|
|
3357
3806
|
var legacySyncRoots = ["architecture", "context", "decisions", "features", "memory", "plans", "prompts", "workflows"];
|
|
3358
3807
|
var syncRoots = legacySyncRoots.map((syncRoot) => `docs/${syncRoot}`);
|
|
3808
|
+
var htmlSyncRoot = "docs/html";
|
|
3359
3809
|
var documentTypeByRoot = {
|
|
3360
3810
|
architecture: "architecture",
|
|
3361
3811
|
context: "context",
|
|
@@ -3443,15 +3893,15 @@ async function collectSyncStatus(rootDir, webDocuments = []) {
|
|
|
3443
3893
|
return { status, items, counts };
|
|
3444
3894
|
}
|
|
3445
3895
|
async function readLocalSyncedDocuments(rootDir) {
|
|
3446
|
-
const
|
|
3896
|
+
const documentFiles = await findBrainDocumentFiles(rootDir);
|
|
3447
3897
|
const documents = [];
|
|
3448
|
-
for (const fullPath of
|
|
3898
|
+
for (const fullPath of documentFiles) {
|
|
3449
3899
|
const raw = await readFile6(fullPath, "utf8");
|
|
3450
|
-
const
|
|
3900
|
+
const repoPath = toRepoPath(rootDir, fullPath);
|
|
3901
|
+
const parsed = parseSyncedDocument(raw, repoPath);
|
|
3451
3902
|
if (!parsed) {
|
|
3452
3903
|
continue;
|
|
3453
3904
|
}
|
|
3454
|
-
const repoPath = toRepoPath(rootDir, fullPath);
|
|
3455
3905
|
if (isControlPlaneTemplateRepoPath(repoPath)) {
|
|
3456
3906
|
continue;
|
|
3457
3907
|
}
|
|
@@ -3497,7 +3947,7 @@ async function materializeBrainDocuments(rootDir, documents, options = {}) {
|
|
|
3497
3947
|
continue;
|
|
3498
3948
|
}
|
|
3499
3949
|
await mkdir7(path8.dirname(fullPath), { recursive: true });
|
|
3500
|
-
await writeFile7(fullPath,
|
|
3950
|
+
await writeFile7(fullPath, createSyncedDocumentContent(document), "utf8");
|
|
3501
3951
|
result.written.push(document.repoPath);
|
|
3502
3952
|
}
|
|
3503
3953
|
return result;
|
|
@@ -3515,6 +3965,7 @@ async function collectDirtyDocumentsForPush(rootDir, metadata) {
|
|
|
3515
3965
|
projectId: metadata.amistioProjectId,
|
|
3516
3966
|
documentId: document.frontmatter.amistioDocumentId,
|
|
3517
3967
|
documentType,
|
|
3968
|
+
contentFormat: document.frontmatter.amistioContentFormat,
|
|
3518
3969
|
title: inferTitle(document.content, document.repoPath),
|
|
3519
3970
|
status: "reviewing",
|
|
3520
3971
|
repoPath: document.repoPath,
|
|
@@ -3552,11 +4003,15 @@ function autoSyncSkipCounts(skipped) {
|
|
|
3552
4003
|
unreadable: skipped.filter((item) => item.reason === "unreadable").length
|
|
3553
4004
|
};
|
|
3554
4005
|
}
|
|
3555
|
-
function
|
|
4006
|
+
function createSyncedDocumentContent(document) {
|
|
4007
|
+
return (document.contentFormat ?? inferContentFormatFromRepoPath(document.repoPath) ?? "markdown") === "html" ? createSyncedDocumentHtml(document) : createSyncedDocumentMarkdownContent(document);
|
|
4008
|
+
}
|
|
4009
|
+
function createSyncedDocumentMarkdownContent(document) {
|
|
3556
4010
|
return [
|
|
3557
4011
|
"---",
|
|
3558
4012
|
`amistioDocumentId: ${document.documentId}`,
|
|
3559
4013
|
`amistioDocumentType: ${document.documentType}`,
|
|
4014
|
+
`amistioContentFormat: ${document.contentFormat ?? "markdown"}`,
|
|
3560
4015
|
`amistioRevision: ${document.revision}`,
|
|
3561
4016
|
`amistioContentHash: ${document.contentHash}`,
|
|
3562
4017
|
`status: ${document.status}`,
|
|
@@ -3565,6 +4020,24 @@ function createSyncedDocumentMarkdown(document) {
|
|
|
3565
4020
|
""
|
|
3566
4021
|
].join("\n");
|
|
3567
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
|
+
}
|
|
3568
4041
|
function parseSyncedMarkdown(content) {
|
|
3569
4042
|
const rawFrontmatter = parseFrontmatter(content);
|
|
3570
4043
|
const frontmatter = syncedDocumentFrontmatterSchema.safeParse(rawFrontmatter);
|
|
@@ -3579,10 +4052,32 @@ function parseSyncedMarkdown(content) {
|
|
|
3579
4052
|
const bodyStart = closingLineEnd === -1 ? content.length : closingLineEnd + 1;
|
|
3580
4053
|
return { frontmatter: frontmatter.data, content: content.slice(bodyStart).replace(/\n$/, "") };
|
|
3581
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
|
+
}
|
|
3582
4077
|
async function readExistingSyncedDocument(fullPath) {
|
|
3583
4078
|
try {
|
|
3584
4079
|
const raw = await readFile6(fullPath, "utf8");
|
|
3585
|
-
const parsed =
|
|
4080
|
+
const parsed = parseSyncedDocument(raw, fullPath);
|
|
3586
4081
|
if (!parsed) {
|
|
3587
4082
|
return { exists: true };
|
|
3588
4083
|
}
|
|
@@ -3603,23 +4098,23 @@ async function readExistingSyncedDocument(fullPath) {
|
|
|
3603
4098
|
throw error;
|
|
3604
4099
|
}
|
|
3605
4100
|
}
|
|
3606
|
-
async function
|
|
4101
|
+
async function findBrainDocumentFiles(rootDir) {
|
|
3607
4102
|
const files = [];
|
|
3608
|
-
for (const syncRoot of syncRoots) {
|
|
4103
|
+
for (const syncRoot of [...syncRoots, htmlSyncRoot]) {
|
|
3609
4104
|
const fullRoot = path8.join(rootDir, syncRoot);
|
|
3610
4105
|
if (!await exists2(fullRoot)) {
|
|
3611
4106
|
continue;
|
|
3612
4107
|
}
|
|
3613
|
-
await
|
|
4108
|
+
await walkBrainDocumentFiles(fullRoot, files);
|
|
3614
4109
|
}
|
|
3615
4110
|
return files;
|
|
3616
4111
|
}
|
|
3617
|
-
async function
|
|
4112
|
+
async function walkBrainDocumentFiles(directory, files) {
|
|
3618
4113
|
for (const entry of await readdir3(directory, { withFileTypes: true })) {
|
|
3619
4114
|
const fullPath = path8.join(directory, entry.name);
|
|
3620
4115
|
if (entry.isDirectory()) {
|
|
3621
|
-
await
|
|
3622
|
-
} else if (entry.isFile() && entry.name
|
|
4116
|
+
await walkBrainDocumentFiles(fullPath, files);
|
|
4117
|
+
} else if (entry.isFile() && /\.(md|mdx|html?)$/i.test(entry.name)) {
|
|
3623
4118
|
files.push(fullPath);
|
|
3624
4119
|
}
|
|
3625
4120
|
}
|
|
@@ -3641,7 +4136,7 @@ function safeRepoPath(rootDir, repoPath) {
|
|
|
3641
4136
|
}
|
|
3642
4137
|
function isControlPlanePath(repoPath) {
|
|
3643
4138
|
const normalized = path8.normalize(repoPath);
|
|
3644
|
-
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}`);
|
|
3645
4140
|
}
|
|
3646
4141
|
function canonicalControlPlaneRepoPath(repoPath) {
|
|
3647
4142
|
const normalized = repoPath.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "");
|
|
@@ -3656,7 +4151,9 @@ function toRepoPath(rootDir, fullPath) {
|
|
|
3656
4151
|
}
|
|
3657
4152
|
function inferTitle(content, repoPath) {
|
|
3658
4153
|
const heading = content.split("\n").find((line) => line.startsWith("# "))?.replace(/^#\s+/, "").trim();
|
|
3659
|
-
|
|
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));
|
|
3660
4157
|
}
|
|
3661
4158
|
async function collectExternalBrainDocumentsForPush(rootDir, metadata, existingDocuments, options) {
|
|
3662
4159
|
const root = path8.resolve(rootDir);
|
|
@@ -3689,7 +4186,7 @@ async function collectExternalBrainDocumentsForPush(rootDir, metadata, existingD
|
|
|
3689
4186
|
skipped.push({ repoPath: normalizedRepoPath, reason: "unreadable" });
|
|
3690
4187
|
continue;
|
|
3691
4188
|
}
|
|
3692
|
-
if (
|
|
4189
|
+
if (parseSyncedDocument(content, normalizedRepoPath)) {
|
|
3693
4190
|
skipped.push({ repoPath: normalizedRepoPath, reason: "alreadyManaged" });
|
|
3694
4191
|
continue;
|
|
3695
4192
|
}
|
|
@@ -3719,6 +4216,7 @@ async function collectExternalBrainDocumentsForPush(rootDir, metadata, existingD
|
|
|
3719
4216
|
projectId: metadata.amistioProjectId,
|
|
3720
4217
|
documentId,
|
|
3721
4218
|
documentType,
|
|
4219
|
+
contentFormat: inferContentFormatFromRepoPath(canonicalRepoPath) ?? "markdown",
|
|
3722
4220
|
title: inferTitle(content, canonicalRepoPath),
|
|
3723
4221
|
status: "reviewing",
|
|
3724
4222
|
repoPath: canonicalRepoPath,
|
|
@@ -3747,7 +4245,7 @@ async function listAutoSyncCandidatePaths(rootDir) {
|
|
|
3747
4245
|
return uniqueSortedRepoPaths(gitFiles.split("\n").map(normalizeRepoPath3).filter((repoPath) => repoPath && isRecognizedBrainRepoPath(repoPath)));
|
|
3748
4246
|
}
|
|
3749
4247
|
const files = [];
|
|
3750
|
-
for (const syncRoot of [...syncRoots, ...legacySyncRoots]) {
|
|
4248
|
+
for (const syncRoot of [...syncRoots, htmlSyncRoot, ...legacySyncRoots]) {
|
|
3751
4249
|
const fullRoot = path8.join(rootDir, syncRoot);
|
|
3752
4250
|
if (await exists2(fullRoot)) {
|
|
3753
4251
|
await walkAutoSyncFiles(rootDir, fullRoot, files);
|
|
@@ -3771,7 +4269,7 @@ async function walkAutoSyncFiles(rootDir, directory, files) {
|
|
|
3771
4269
|
function autoSyncPathSkipReason(repoPath) {
|
|
3772
4270
|
if (autoSyncMetadataPaths.has(repoPath)) return "metadata";
|
|
3773
4271
|
if (isControlPlaneTemplateRepoPath(repoPath)) return "template";
|
|
3774
|
-
if (!/\.(md|mdx)$/i.test(repoPath)) return "unsupported";
|
|
4272
|
+
if (!/\.(md|mdx|html?)$/i.test(repoPath)) return "unsupported";
|
|
3775
4273
|
const basename = repoPath.split("/").at(-1)?.toLowerCase() ?? "";
|
|
3776
4274
|
if (basename.startsWith(".") || basename.endsWith(".lock") || basename.includes(".env") || basename.includes("secret") || basename.includes("credential")) return "excluded";
|
|
3777
4275
|
if (repoPath.split("/").some((segment) => autoSyncExcludedDirectoryNames.has(segment) || autoSyncGeneratedPathSegments.has(segment))) return "excluded";
|
|
@@ -3779,10 +4277,13 @@ function autoSyncPathSkipReason(repoPath) {
|
|
|
3779
4277
|
}
|
|
3780
4278
|
function isRecognizedBrainRepoPath(repoPath) {
|
|
3781
4279
|
const normalized = normalizeRepoPath3(repoPath);
|
|
3782
|
-
const [firstSegment, secondSegment] = normalized.split("/");
|
|
3783
|
-
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));
|
|
3784
4282
|
}
|
|
3785
4283
|
function documentTypeForRepoPath(repoPath) {
|
|
4284
|
+
return documentTypeForBrainRepoPath(repoPath) ?? legacyDocumentTypeForRepoPath(repoPath);
|
|
4285
|
+
}
|
|
4286
|
+
function legacyDocumentTypeForRepoPath(repoPath) {
|
|
3786
4287
|
const normalized = normalizeRepoPath3(repoPath);
|
|
3787
4288
|
const segments = normalized.split("/");
|
|
3788
4289
|
const root = segments[0] === "docs" ? segments[1] : segments[0];
|
|
@@ -3801,6 +4302,7 @@ function parseFrontmatterFromSyncedDocument(frontmatter) {
|
|
|
3801
4302
|
return {
|
|
3802
4303
|
amistioDocumentId: frontmatter.amistioDocumentId,
|
|
3803
4304
|
amistioDocumentType: frontmatter.amistioDocumentType,
|
|
4305
|
+
amistioContentFormat: frontmatter.amistioContentFormat,
|
|
3804
4306
|
amistioRevision: frontmatter.amistioRevision,
|
|
3805
4307
|
amistioContentHash: frontmatter.amistioContentHash,
|
|
3806
4308
|
...frontmatter.status ? { status: frontmatter.status } : {}
|
|
@@ -3864,6 +4366,10 @@ var issueDiagnosisStart = "AMISTIO_ISSUE_DIAGNOSIS_START";
|
|
|
3864
4366
|
var issueDiagnosisEnd = "AMISTIO_ISSUE_DIAGNOSIS_END";
|
|
3865
4367
|
var securityPostureStart = "AMISTIO_SECURITY_POSTURE_START";
|
|
3866
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";
|
|
3867
4373
|
function createWorkExecutionPrompt(workItem, context) {
|
|
3868
4374
|
if (workItem.workKind === "brainGeneration") {
|
|
3869
4375
|
return createBrainGenerationPrompt(workItem);
|
|
@@ -3883,6 +4389,12 @@ function createWorkExecutionPrompt(workItem, context) {
|
|
|
3883
4389
|
if (workItem.workKind === "securityPostureScan") {
|
|
3884
4390
|
return createSecurityPostureScanPrompt(workItem, context?.securityPostureScan);
|
|
3885
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
|
+
}
|
|
3886
4398
|
return [
|
|
3887
4399
|
"# Amistio Work Execution",
|
|
3888
4400
|
"",
|
|
@@ -3911,6 +4423,77 @@ function createWorkExecutionPrompt(workItem, context) {
|
|
|
3911
4423
|
"- Run relevant verification commands when feasible and summarize results."
|
|
3912
4424
|
].join("\n");
|
|
3913
4425
|
}
|
|
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) => [
|
|
4428
|
+
`### ${document.title}`,
|
|
4429
|
+
`documentId: ${document.documentId}`,
|
|
4430
|
+
`documentType: ${document.documentType}`,
|
|
4431
|
+
`repoPath: ${document.repoPath}`,
|
|
4432
|
+
`revision: ${document.revision}`,
|
|
4433
|
+
document.content.slice(0, 3e3)
|
|
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.";
|
|
4445
|
+
return [
|
|
4446
|
+
"# Amistio Living Project Context Refresh",
|
|
4447
|
+
"",
|
|
4448
|
+
"You are running locally through the Amistio CLI inside the user's repository.",
|
|
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.",
|
|
4452
|
+
"",
|
|
4453
|
+
"## Work Item",
|
|
4454
|
+
"",
|
|
4455
|
+
`Title: ${workItem.title}`,
|
|
4456
|
+
`Work item ID: ${workItem.workItemId}`,
|
|
4457
|
+
`Project ID: ${workItem.projectId}`,
|
|
4458
|
+
`Project context refresh ID: ${workItem.projectContextRefreshId ?? "unknown"}`,
|
|
4459
|
+
"",
|
|
4460
|
+
"## Refresh Request",
|
|
4461
|
+
"",
|
|
4462
|
+
workItem.sourceWish ?? "Refresh the living project context map.",
|
|
4463
|
+
"",
|
|
4464
|
+
"## Approved Project Brain Context",
|
|
4465
|
+
"",
|
|
4466
|
+
approvedContext || "No approved project-brain records were loaded. Inspect local repository files as needed and explain the gap in the coverage warnings.",
|
|
4467
|
+
"",
|
|
4468
|
+
"## Previous Context Map",
|
|
4469
|
+
"",
|
|
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.",
|
|
4485
|
+
"",
|
|
4486
|
+
"## Output Contract",
|
|
4487
|
+
"",
|
|
4488
|
+
"Print exactly one JSON object between the markers below. The CLI will submit only this structured refresh result back to Amistio.",
|
|
4489
|
+
"",
|
|
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,
|
|
4493
|
+
"",
|
|
4494
|
+
"Do not put Markdown fences around the markers. Do not implement any changes."
|
|
4495
|
+
].join("\n");
|
|
4496
|
+
}
|
|
3914
4497
|
function createSecurityPostureScanPrompt(workItem, context) {
|
|
3915
4498
|
const approvedContext = (context?.documents ?? []).filter((document) => document.status === "approved" || document.syncState === "approved" || document.syncState === "synced").slice(0, 20).map((document) => [
|
|
3916
4499
|
`### ${document.title}`,
|
|
@@ -3968,6 +4551,63 @@ function createSecurityPostureScanPrompt(workItem, context) {
|
|
|
3968
4551
|
"Do not put Markdown fences around the markers. Do not implement remediation."
|
|
3969
4552
|
].join("\n");
|
|
3970
4553
|
}
|
|
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) => [
|
|
4556
|
+
`### ${document.title}`,
|
|
4557
|
+
`documentId: ${document.documentId}`,
|
|
4558
|
+
`documentType: ${document.documentType}`,
|
|
4559
|
+
`repoPath: ${document.repoPath}`,
|
|
4560
|
+
`revision: ${document.revision}`,
|
|
4561
|
+
document.content.slice(0, 3e3)
|
|
4562
|
+
].join("\n")).join("\n\n");
|
|
4563
|
+
return [
|
|
4564
|
+
"# Amistio App Evaluation Scan",
|
|
4565
|
+
"",
|
|
4566
|
+
"You are running locally through the Amistio CLI inside the user's repository.",
|
|
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.",
|
|
4570
|
+
"",
|
|
4571
|
+
"## Work Item",
|
|
4572
|
+
"",
|
|
4573
|
+
`Title: ${workItem.title}`,
|
|
4574
|
+
`Work item ID: ${workItem.workItemId}`,
|
|
4575
|
+
`Project ID: ${workItem.projectId}`,
|
|
4576
|
+
`App evaluation scan ID: ${workItem.appEvaluationScanId ?? "unknown"}`,
|
|
4577
|
+
"",
|
|
4578
|
+
"## Scan Request",
|
|
4579
|
+
"",
|
|
4580
|
+
workItem.sourceWish ?? "Run the Amistio app evaluation baseline.",
|
|
4581
|
+
"",
|
|
4582
|
+
"## Approved Project Brain Context",
|
|
4583
|
+
"",
|
|
4584
|
+
approvedContext || "No approved project-brain records were loaded. Inspect local repository files as needed and explain the gap in the summary.",
|
|
4585
|
+
"",
|
|
4586
|
+
"## Baseline Requirements",
|
|
4587
|
+
"",
|
|
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.",
|
|
4593
|
+
"",
|
|
4594
|
+
"## Data Safety",
|
|
4595
|
+
"",
|
|
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
|
+
}
|
|
3971
4611
|
function createIssueDiagnosisPrompt(workItem, context) {
|
|
3972
4612
|
const issue = context?.issue;
|
|
3973
4613
|
const approvedContext = (context?.documents ?? []).filter((document) => document.status === "approved" || document.syncState === "approved" || document.syncState === "synced").slice(0, 16).map((document) => [
|
|
@@ -4018,8 +4658,66 @@ function createIssueDiagnosisPrompt(workItem, context) {
|
|
|
4018
4658
|
"Do not put Markdown fences around the markers. Do not implement the fix."
|
|
4019
4659
|
].join("\n");
|
|
4020
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
|
+
}
|
|
4021
4718
|
function createImpactPreviewPrompt(workItem, context) {
|
|
4022
4719
|
const implementationPrompt = context?.implementationPrompt;
|
|
4720
|
+
const livingContext = formatProjectContextMap(context?.activeMap, workItem.sourceWish ?? workItem.title, context?.contextPack);
|
|
4023
4721
|
const approvedContext = (context?.documents ?? []).filter((document) => document.status === "approved" || document.syncState === "approved" || document.syncState === "synced").slice(0, 16).map((document) => [
|
|
4024
4722
|
`### ${document.title}`,
|
|
4025
4723
|
`documentId: ${document.documentId}`,
|
|
@@ -4048,6 +4746,10 @@ function createImpactPreviewPrompt(workItem, context) {
|
|
|
4048
4746
|
"",
|
|
4049
4747
|
implementationPrompt ? implementationPrompt.content : workItem.sourceWish ?? workItem.title,
|
|
4050
4748
|
"",
|
|
4749
|
+
"## Approved Living Project Context Map",
|
|
4750
|
+
"",
|
|
4751
|
+
livingContext,
|
|
4752
|
+
"",
|
|
4051
4753
|
"## Approved Project Brain Context",
|
|
4052
4754
|
"",
|
|
4053
4755
|
approvedContext || "No approved project-brain records were loaded. Inspect the local repository and explain the gap in the report.",
|
|
@@ -4073,6 +4775,7 @@ function createImpactPreviewPrompt(workItem, context) {
|
|
|
4073
4775
|
}
|
|
4074
4776
|
function createAssistantQuestionPrompt(workItem, context) {
|
|
4075
4777
|
const question = context?.question.content ?? workItem.sourceWish ?? workItem.title;
|
|
4778
|
+
const livingContext = formatProjectContextMap(context?.activeMap, question, context?.contextPack);
|
|
4076
4779
|
const priorMessages = (context?.messages ?? []).filter((message) => message.messageId !== context?.question.messageId).slice(-12).map((message) => `- ${message.role} / ${message.status}: ${message.content}`).join("\n");
|
|
4077
4780
|
const brainContext = (context?.documents ?? []).filter((document) => document.status === "approved" || document.syncState === "approved" || document.syncState === "synced").slice(0, 12).map((document) => [
|
|
4078
4781
|
`### ${document.title}`,
|
|
@@ -4099,6 +4802,10 @@ function createAssistantQuestionPrompt(workItem, context) {
|
|
|
4099
4802
|
`Project ID: ${workItem.projectId}`,
|
|
4100
4803
|
`Assistant message ID: ${workItem.assistantMessageId ?? context?.question.messageId ?? "unknown"}`,
|
|
4101
4804
|
"",
|
|
4805
|
+
"## Approved Living Project Context Map",
|
|
4806
|
+
"",
|
|
4807
|
+
livingContext,
|
|
4808
|
+
"",
|
|
4102
4809
|
"## Approved Project Brain Context",
|
|
4103
4810
|
"",
|
|
4104
4811
|
brainContext || "No approved project-brain records were loaded. Say what you inspected locally and avoid pretending citations exist.",
|
|
@@ -4112,6 +4819,7 @@ function createAssistantQuestionPrompt(workItem, context) {
|
|
|
4112
4819
|
"- Keep repository source, secrets, tokens, local credential paths, and provider session references local.",
|
|
4113
4820
|
"- Summarize findings instead of dumping source code. Tiny identifiers, filenames, and short excerpts are acceptable when necessary.",
|
|
4114
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.",
|
|
4115
4823
|
'- Use sourceBoundary "localSource" when you inspected local repository files.',
|
|
4116
4824
|
'- Use sourceBoundary "mixed" when both project-brain records and local files shaped the answer.',
|
|
4117
4825
|
"- Include citations for project-brain records with documentId/title/repoPath where relevant.",
|
|
@@ -4214,8 +4922,29 @@ function parseSecurityPostureScanResult(output) {
|
|
|
4214
4922
|
const parsed = JSON.parse(stripJsonFence(payload));
|
|
4215
4923
|
return securityPostureScanResultSchema.parse(parsed);
|
|
4216
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
|
+
}
|
|
4217
4945
|
function createBrainGenerationPrompt(workItem) {
|
|
4218
4946
|
const wish = workItem.sourceWish ?? workItem.title;
|
|
4947
|
+
const artifactFormatPreference = workItem.artifactFormatPreference ?? "markdown";
|
|
4219
4948
|
return [
|
|
4220
4949
|
"# Amistio Brain Generation",
|
|
4221
4950
|
"",
|
|
@@ -4232,6 +4961,7 @@ function createBrainGenerationPrompt(workItem) {
|
|
|
4232
4961
|
`Work item ID: ${workItem.workItemId}`,
|
|
4233
4962
|
`Project ID: ${workItem.projectId}`,
|
|
4234
4963
|
`Generated draft ID: ${workItem.generatedDraftId ?? "unknown"}`,
|
|
4964
|
+
`Artifact format preference: ${artifactFormatPreference}`,
|
|
4235
4965
|
"",
|
|
4236
4966
|
"## Read First",
|
|
4237
4967
|
"",
|
|
@@ -4241,7 +4971,7 @@ function createBrainGenerationPrompt(workItem) {
|
|
|
4241
4971
|
"",
|
|
4242
4972
|
"## Generate Artifacts",
|
|
4243
4973
|
"",
|
|
4244
|
-
"Return
|
|
4974
|
+
"Return artifacts that should enter human review before implementation. Use only these document types and matching repo roots:",
|
|
4245
4975
|
"",
|
|
4246
4976
|
"- architecture -> docs/architecture/",
|
|
4247
4977
|
"- context -> docs/context/",
|
|
@@ -4251,20 +4981,35 @@ function createBrainGenerationPrompt(workItem) {
|
|
|
4251
4981
|
"- plan -> docs/plans/",
|
|
4252
4982
|
"- prompt -> docs/prompts/",
|
|
4253
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),
|
|
4254
4987
|
"",
|
|
4255
|
-
"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.",
|
|
4256
4989
|
"",
|
|
4257
4990
|
"## Output Contract",
|
|
4258
4991
|
"",
|
|
4259
4992
|
"Print exactly one JSON object between the markers below. The CLI will submit only this structured result back to Amistio.",
|
|
4260
4993
|
"",
|
|
4261
4994
|
generationResultStart,
|
|
4262
|
-
'{"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..."}]}',
|
|
4263
4996
|
generationResultEnd,
|
|
4264
4997
|
"",
|
|
4265
4998
|
"Do not put Markdown fences around the markers. Do not claim implementation is complete."
|
|
4266
4999
|
].join("\n");
|
|
4267
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
|
+
}
|
|
4268
5013
|
function stripJsonFence(value) {
|
|
4269
5014
|
const trimmed = value.trim();
|
|
4270
5015
|
if (!trimmed.startsWith("```")) {
|
|
@@ -4289,7 +5034,13 @@ function formatWatchIdleLine(action, intervalSeconds) {
|
|
|
4289
5034
|
}
|
|
4290
5035
|
function shouldPrintWatchState(action, previous, nowMs, reminderMs = watchStateReminderMs) {
|
|
4291
5036
|
const key = watchStateKey(action);
|
|
4292
|
-
|
|
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;
|
|
4293
5044
|
}
|
|
4294
5045
|
function watchStateKey(action) {
|
|
4295
5046
|
return [action.kind, action.message, action.workItemId, action.documentId, action.runnerId].filter(Boolean).join(":");
|
|
@@ -4469,7 +5220,8 @@ async function scanLegacyDocuments(options) {
|
|
|
4469
5220
|
skipped.push({ repoPath, reason: "excluded" });
|
|
4470
5221
|
continue;
|
|
4471
5222
|
}
|
|
4472
|
-
|
|
5223
|
+
const contentFormat = inferContentFormatFromRepoPath(repoPath);
|
|
5224
|
+
if (!contentFormat) {
|
|
4473
5225
|
skipped.push({ repoPath, reason: "notMarkdown" });
|
|
4474
5226
|
continue;
|
|
4475
5227
|
}
|
|
@@ -4492,16 +5244,17 @@ async function scanLegacyDocuments(options) {
|
|
|
4492
5244
|
skipped.push({ repoPath, reason: "unreadable" });
|
|
4493
5245
|
continue;
|
|
4494
5246
|
}
|
|
4495
|
-
if (
|
|
5247
|
+
if (isAmistioManagedDocument(content)) {
|
|
4496
5248
|
skipped.push({ repoPath, reason: "alreadyManaged" });
|
|
4497
5249
|
continue;
|
|
4498
5250
|
}
|
|
4499
5251
|
const documentType = classifyLegacyDocument(repoPath, content);
|
|
4500
|
-
const destinationPath = uniqueDestinationPath(canonicalImportPath(repoPath, documentType), repoPath, usedDestinationPaths);
|
|
5252
|
+
const destinationPath = uniqueDestinationPath(canonicalImportPath(repoPath, documentType, contentFormat), repoPath, usedDestinationPaths);
|
|
4501
5253
|
candidates.push({
|
|
4502
5254
|
sourcePath: repoPath,
|
|
4503
5255
|
repoPath: destinationPath,
|
|
4504
5256
|
documentType,
|
|
5257
|
+
contentFormat,
|
|
4505
5258
|
title: inferTitle2(content, repoPath),
|
|
4506
5259
|
content,
|
|
4507
5260
|
contentHash: sha256ContentHash(content)
|
|
@@ -4524,6 +5277,7 @@ function buildImportedBrainDocuments(options) {
|
|
|
4524
5277
|
projectId: options.projectId,
|
|
4525
5278
|
documentId,
|
|
4526
5279
|
documentType: candidate.documentType,
|
|
5280
|
+
contentFormat: candidate.contentFormat,
|
|
4527
5281
|
title: candidate.title,
|
|
4528
5282
|
status: "approved",
|
|
4529
5283
|
repoPath: candidate.repoPath,
|
|
@@ -4602,9 +5356,6 @@ function wildcardMatch(pattern, repoPath) {
|
|
|
4602
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, ".*");
|
|
4603
5357
|
return new RegExp(`^${escaped}$`).test(repoPath);
|
|
4604
5358
|
}
|
|
4605
|
-
function isMarkdownDocument(repoPath) {
|
|
4606
|
-
return /\.(md|mdx)$/i.test(repoPath);
|
|
4607
|
-
}
|
|
4608
5359
|
function isExcludedRepoPath(repoPath) {
|
|
4609
5360
|
if (isControlPlaneTemplateRepoPath(repoPath)) return true;
|
|
4610
5361
|
if (excludedFileNames.has(repoPath)) return true;
|
|
@@ -4613,9 +5364,12 @@ function isExcludedRepoPath(repoPath) {
|
|
|
4613
5364
|
const basename = segments[segments.length - 1]?.toLowerCase() ?? "";
|
|
4614
5365
|
return basename.startsWith(".env") || basename.includes("secret") || basename.includes("token") || basename.includes("credential") || basename.endsWith(".lock");
|
|
4615
5366
|
}
|
|
4616
|
-
function
|
|
5367
|
+
function isAmistioManagedDocument(content) {
|
|
4617
5368
|
const frontmatter = parseFrontmatter(content);
|
|
4618
|
-
|
|
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);
|
|
4619
5373
|
}
|
|
4620
5374
|
function classifyLegacyDocument(repoPath, content) {
|
|
4621
5375
|
const haystack = `${repoPath} ${inferTitle2(content, repoPath)}`.toLowerCase();
|
|
@@ -4628,7 +5382,7 @@ function classifyLegacyDocument(repoPath, content) {
|
|
|
4628
5382
|
if (/\b(workflow|workflows|runbook|playbook|process|procedure|ops)\b/.test(haystack)) return "workflow";
|
|
4629
5383
|
return "context";
|
|
4630
5384
|
}
|
|
4631
|
-
function canonicalImportPath(sourcePath, documentType) {
|
|
5385
|
+
function canonicalImportPath(sourcePath, documentType, contentFormat) {
|
|
4632
5386
|
if (isCanonicalControlPlanePath(sourcePath)) {
|
|
4633
5387
|
return sourcePath;
|
|
4634
5388
|
}
|
|
@@ -4636,7 +5390,8 @@ function canonicalImportPath(sourcePath, documentType) {
|
|
|
4636
5390
|
return `docs/${sourcePath}`;
|
|
4637
5391
|
}
|
|
4638
5392
|
const baseSlug = slugFromPath(sourcePath);
|
|
4639
|
-
|
|
5393
|
+
const extension = contentFormat === "html" ? "html" : "md";
|
|
5394
|
+
return `${documentFolderByType[documentType]}/imported/${baseSlug}.${extension}`;
|
|
4640
5395
|
}
|
|
4641
5396
|
function uniqueDestinationPath(basePath, sourcePath, usedPaths) {
|
|
4642
5397
|
if (!usedPaths.has(basePath)) {
|
|
@@ -4651,8 +5406,8 @@ function uniqueDestinationPath(basePath, sourcePath, usedPaths) {
|
|
|
4651
5406
|
return uniquePath;
|
|
4652
5407
|
}
|
|
4653
5408
|
function isCanonicalControlPlanePath(repoPath) {
|
|
4654
|
-
const [firstSegment, secondSegment] = normalizeRepoPath4(repoPath).split("/");
|
|
4655
|
-
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));
|
|
4656
5411
|
}
|
|
4657
5412
|
function isLegacyControlPlanePath(repoPath) {
|
|
4658
5413
|
const [firstSegment] = repoPath.split("/");
|
|
@@ -4662,6 +5417,8 @@ function inferTitle2(content, repoPath) {
|
|
|
4662
5417
|
const body = stripFrontmatter(content);
|
|
4663
5418
|
const heading = body.split("\n").find((line) => /^#\s+/.test(line))?.replace(/^#\s+/, "").trim();
|
|
4664
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;
|
|
4665
5422
|
const basename = path10.posix.basename(repoPath, path10.posix.extname(repoPath)).replace(/[-_]+/g, " ").trim();
|
|
4666
5423
|
return titleCase(basename || "Imported Document");
|
|
4667
5424
|
}
|
|
@@ -4673,7 +5430,7 @@ function stripFrontmatter(content) {
|
|
|
4673
5430
|
return closingLineEnd === -1 ? "" : content.slice(closingLineEnd + 1);
|
|
4674
5431
|
}
|
|
4675
5432
|
function slugFromPath(repoPath) {
|
|
4676
|
-
const withoutExtension = repoPath.replace(/\.(md|mdx)$/i, "");
|
|
5433
|
+
const withoutExtension = repoPath.replace(/\.(md|mdx|html?)$/i, "");
|
|
4677
5434
|
const slug = withoutExtension.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 90);
|
|
4678
5435
|
return slug || "imported-document";
|
|
4679
5436
|
}
|
|
@@ -4772,6 +5529,7 @@ async function runOfficialCliUpdateWithRuntimeRefresh(options) {
|
|
|
4772
5529
|
if (!options.restartBackgroundRunner) {
|
|
4773
5530
|
return {
|
|
4774
5531
|
succeeded: false,
|
|
5532
|
+
stopRunner: true,
|
|
4775
5533
|
message: `${updateResult.message} Background runner restart was not available after update. Restart the runner manually to load the updated CLI version.`,
|
|
4776
5534
|
error: "Background runner restart hook was not provided."
|
|
4777
5535
|
};
|
|
@@ -4786,6 +5544,7 @@ async function runOfficialCliUpdateWithRuntimeRefresh(options) {
|
|
|
4786
5544
|
}
|
|
4787
5545
|
return {
|
|
4788
5546
|
succeeded: false,
|
|
5547
|
+
stopRunner: true,
|
|
4789
5548
|
message: `${updateResult.message} Background runner restart failed after update. Restart the runner manually to load the updated CLI version.`,
|
|
4790
5549
|
error: restartResult.error ?? restartResult.message
|
|
4791
5550
|
};
|
|
@@ -4907,6 +5666,186 @@ function errorMessage2(error) {
|
|
|
4907
5666
|
return error instanceof Error ? error.message : String(error);
|
|
4908
5667
|
}
|
|
4909
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
|
+
|
|
4910
5849
|
// src/version.ts
|
|
4911
5850
|
import { readFileSync } from "node:fs";
|
|
4912
5851
|
function readCliPackageVersion() {
|
|
@@ -4926,7 +5865,7 @@ var DEFAULT_MAX_PREFLIGHT_ATTEMPTS = 3;
|
|
|
4926
5865
|
var DEFAULT_TOOL_TIMEOUT_SECONDS = 30 * 60;
|
|
4927
5866
|
var RUNNER_WORK_LEASE_SECONDS = 300;
|
|
4928
5867
|
var RUNNER_WORK_LEASE_RENEWAL_MS = 12e4;
|
|
4929
|
-
var runnerSupportedWorkKinds = ["brainGeneration", "implementation", "planRevision", "assistantQuestion", "impactPreview", "issueDiagnosis", "securityPostureScan"];
|
|
5868
|
+
var runnerSupportedWorkKinds = ["brainGeneration", "implementation", "planRevision", "assistantQuestion", "impactPreview", "issueDiagnosis", "securityPostureScan", "appEvaluationScan", "projectContextRefresh"];
|
|
4930
5869
|
program.name("amistio").description("Amistio project brain CLI").version(CLI_VERSION);
|
|
4931
5870
|
program.command("init").description("Create Amistio control-plane folders for a new project").option("--root <path>", "Repository root", defaultRoot).action(async (options) => {
|
|
4932
5871
|
const created = await initControlPlane(options.root);
|
|
@@ -5304,7 +6243,7 @@ program.command("run").description("Claim and run approved Amistio work locally"
|
|
|
5304
6243
|
projectId: context.metadata.amistioProjectId,
|
|
5305
6244
|
repositoryLinkId: context.metadata.repositoryLinkId,
|
|
5306
6245
|
runnerId,
|
|
5307
|
-
rootDir:
|
|
6246
|
+
rootDir: path14.resolve(options.root),
|
|
5308
6247
|
apiUrl: options.apiUrl,
|
|
5309
6248
|
args: buildBackgroundRunnerArgs(resolvedOptions)
|
|
5310
6249
|
});
|
|
@@ -5476,7 +6415,7 @@ runnerService.command("install").description("Install a user-level startup servi
|
|
|
5476
6415
|
projectId: context.metadata.amistioProjectId,
|
|
5477
6416
|
repositoryLinkId: context.metadata.repositoryLinkId,
|
|
5478
6417
|
runnerId,
|
|
5479
|
-
rootDir:
|
|
6418
|
+
rootDir: path14.resolve(options.root),
|
|
5480
6419
|
apiUrl: options.apiUrl,
|
|
5481
6420
|
args,
|
|
5482
6421
|
platform
|
|
@@ -5925,13 +6864,72 @@ async function runNextWorkItem({
|
|
|
5925
6864
|
return recordFinalizationFailure({ apiClient, error, isolationTelemetry, projectId, repositoryLinkId, runnerId, sessionContext, toolConfig, toolName: preview.toolName, workItem: result.workItem, durationMs: Date.now() - startedAt });
|
|
5926
6865
|
}
|
|
5927
6866
|
}
|
|
5928
|
-
|
|
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";
|
|
5929
6904
|
const durationMs = Date.now() - startedAt;
|
|
5930
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
|
+
}
|
|
5931
6929
|
const updatedToolSession = await finalizeToolSession({
|
|
5932
6930
|
apiClient,
|
|
5933
6931
|
projectId,
|
|
5934
|
-
status:
|
|
6932
|
+
status: toolResult.exitCode === 0 ? "completed" : "failed",
|
|
5935
6933
|
runnerId,
|
|
5936
6934
|
workItemId: result.workItem.workItemId,
|
|
5937
6935
|
stdout: toolResult.stdout,
|
|
@@ -5951,8 +6949,9 @@ async function runNextWorkItem({
|
|
|
5951
6949
|
tool: preview.toolName,
|
|
5952
6950
|
...toolResult.model ? { model: toolResult.model } : {},
|
|
5953
6951
|
durationMs,
|
|
5954
|
-
message:
|
|
6952
|
+
message: finalMessage,
|
|
5955
6953
|
...isolationTelemetry,
|
|
6954
|
+
...finalStatus === "blocked" ? { blockerReason: finalMessage } : {},
|
|
5956
6955
|
sessionPolicy: sessionContext.policy,
|
|
5957
6956
|
sessionDecision: sessionContext.decision,
|
|
5958
6957
|
sessionDecisionReason: sessionContext.reason,
|
|
@@ -5961,19 +6960,34 @@ async function runNextWorkItem({
|
|
|
5961
6960
|
...toolResult.tokensIn !== void 0 ? { tokensIn: toolResult.tokensIn } : {},
|
|
5962
6961
|
...toolResult.tokensOut !== void 0 ? { tokensOut: toolResult.tokensOut } : {},
|
|
5963
6962
|
...toolResult.costUsd !== void 0 ? { costUsd: toolResult.costUsd } : {},
|
|
5964
|
-
...
|
|
6963
|
+
...implementationHandoff ? { implementationHandoff } : {},
|
|
6964
|
+
...finalError ? { error: finalError } : {}
|
|
5965
6965
|
}
|
|
5966
6966
|
);
|
|
5967
6967
|
await recordRunnerMilestone(apiClient, projectId, result.workItem, runnerId, repositoryLinkId, {
|
|
5968
6968
|
status: finalStatus,
|
|
5969
|
-
summary:
|
|
6969
|
+
summary: finalMessage,
|
|
5970
6970
|
idempotencyKey: `runner_milestone_finished_${result.workItem.workItemId}_${statusResult.workItem.idempotencyKey}`,
|
|
5971
|
-
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
|
+
}
|
|
5972
6982
|
});
|
|
5973
6983
|
await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
|
|
5974
6984
|
const durationSeconds = Math.round(durationMs / 1e3);
|
|
5975
6985
|
console.log(`Marked ${statusResult.workItem.workItemId} ${statusResult.workItem.status} after ${durationSeconds}s.`);
|
|
5976
|
-
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");
|
|
5977
6991
|
}
|
|
5978
6992
|
async function prepareWorktreeForClaimedItem({ apiClient, maxPreflightAttempts, projectId, repositoryLinkId, root, runnerId, toolConfig, workItem }) {
|
|
5979
6993
|
if (!needsGitWorktreeIsolation(workItem)) {
|
|
@@ -6177,10 +7191,10 @@ async function executeRunnerCommand(command, context) {
|
|
|
6177
7191
|
}
|
|
6178
7192
|
return runOfficialCliUpdateWithRuntimeRefresh({
|
|
6179
7193
|
mode: currentRunnerMode(),
|
|
6180
|
-
restartBackgroundRunner: () => restartCurrentRunner(context)
|
|
7194
|
+
restartBackgroundRunner: () => restartCurrentRunner(context, { useUpdatedCliExecutable: true })
|
|
6181
7195
|
});
|
|
6182
7196
|
}
|
|
6183
|
-
async function restartCurrentRunner(context) {
|
|
7197
|
+
async function restartCurrentRunner(context, options = {}) {
|
|
6184
7198
|
if (currentRunnerMode() !== "background") {
|
|
6185
7199
|
return { succeeded: false, message: "Foreground runners cannot be restarted remotely. Stop and start the local command manually." };
|
|
6186
7200
|
}
|
|
@@ -6189,7 +7203,7 @@ async function restartCurrentRunner(context) {
|
|
|
6189
7203
|
return { succeeded: false, message: "Background runner metadata was not found, so restart needs manual action." };
|
|
6190
7204
|
}
|
|
6191
7205
|
try {
|
|
6192
|
-
const replacement = await restartRunnerDaemonProcess(metadata, context.backgroundArgs);
|
|
7206
|
+
const replacement = await restartRunnerDaemonProcess(metadata, context.backgroundArgs, options.useUpdatedCliExecutable ? updatedCliRunnerLaunchOptions() : {});
|
|
6193
7207
|
return { succeeded: true, stopRunner: true, message: `Replacement background runner started with PID ${replacement.pid}.` };
|
|
6194
7208
|
} catch (error) {
|
|
6195
7209
|
return { succeeded: false, message: "Background restart failed.", error: errorMessage3(error) };
|
|
@@ -6638,23 +7652,205 @@ ${toolResult.stderr}`);
|
|
|
6638
7652
|
console.error(scanError ?? "Local runner security posture scan failed.");
|
|
6639
7653
|
return { status: "failed", exitCode: toolResult.exitCode || 1 };
|
|
6640
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
|
+
}
|
|
6641
7827
|
async function createRunnerWorkPrompt(apiClient, projectId, workItem) {
|
|
6642
7828
|
if (workItem.workKind === "assistantQuestion") {
|
|
6643
|
-
const
|
|
7829
|
+
const emptyProjectContext = { maps: [], refreshes: [], misses: [] };
|
|
7830
|
+
const [{ documents: documents2 }, { messages: messages2 }, context] = await Promise.all([
|
|
6644
7831
|
apiClient.listBrainDocuments(projectId),
|
|
6645
|
-
apiClient.listAssistantMessages(projectId)
|
|
7832
|
+
apiClient.listAssistantMessages(projectId),
|
|
7833
|
+
apiClient.listProjectContext(projectId).catch(() => emptyProjectContext)
|
|
6646
7834
|
]);
|
|
6647
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);
|
|
6648
7837
|
return createWorkExecutionPrompt(workItem, {
|
|
6649
|
-
...question ? { assistantQuestion: { question, messages: messages2, documents: documents2 } } : {}
|
|
7838
|
+
...question ? { assistantQuestion: { question, messages: messages2, documents: documents2, ...context.activeMap ? { activeMap: context.activeMap } : {}, ...contextPack ? { contextPack } : {} } } : {}
|
|
6650
7839
|
});
|
|
6651
7840
|
}
|
|
6652
7841
|
if (workItem.workKind === "impactPreview") {
|
|
6653
|
-
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
|
+
]);
|
|
6654
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);
|
|
6655
7849
|
return createWorkExecutionPrompt(workItem, {
|
|
6656
7850
|
impactPreview: {
|
|
6657
7851
|
documents: documents2,
|
|
7852
|
+
...context.activeMap ? { activeMap: context.activeMap } : {},
|
|
7853
|
+
...contextPack ? { contextPack } : {},
|
|
6658
7854
|
...implementationPrompt ? { implementationPrompt } : {}
|
|
6659
7855
|
}
|
|
6660
7856
|
});
|
|
@@ -6678,6 +7874,25 @@ async function createRunnerWorkPrompt(apiClient, projectId, workItem) {
|
|
|
6678
7874
|
securityPostureScan: { documents: documents2 }
|
|
6679
7875
|
});
|
|
6680
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
|
+
}
|
|
6681
7896
|
if (workItem.workKind !== "planRevision" || !workItem.reviewDocumentId) {
|
|
6682
7897
|
return createWorkExecutionPrompt(workItem);
|
|
6683
7898
|
}
|
|
@@ -6905,7 +8120,7 @@ function parseReasoningEffort(value) {
|
|
|
6905
8120
|
throw new Error(`Expected reasoning effort auto, low, medium, high, or xhigh; received ${value}.`);
|
|
6906
8121
|
}
|
|
6907
8122
|
function inferRepoName(root) {
|
|
6908
|
-
return
|
|
8123
|
+
return path14.basename(path14.resolve(root)) || "repository";
|
|
6909
8124
|
}
|
|
6910
8125
|
function createRepoFingerprint(accountId, projectId, repositoryLinkId) {
|
|
6911
8126
|
return createHash7("sha256").update(`${accountId}:${projectId}:${repositoryLinkId}`).digest("hex");
|