@amistio/cli 0.1.12 → 0.1.14
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 +1320 -72
- 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()
|
|
@@ -825,11 +1099,26 @@ var securityFindingResultSchema = z.object({
|
|
|
825
1099
|
safePaths: z.array(z.string().trim().min(1).max(300)).default([]),
|
|
826
1100
|
status: securityFindingStatusSchema.default("open")
|
|
827
1101
|
});
|
|
1102
|
+
var securityPostureGradeSchema = z.enum(["A", "B", "C", "D", "F", "Unknown"]);
|
|
1103
|
+
var securityPostureScanResultGradeSchema = z.preprocess((value) => {
|
|
1104
|
+
if (typeof value !== "string") {
|
|
1105
|
+
return value;
|
|
1106
|
+
}
|
|
1107
|
+
const trimmed = value.trim();
|
|
1108
|
+
if (trimmed.toUpperCase() === "UNKNOWN") {
|
|
1109
|
+
return "Unknown";
|
|
1110
|
+
}
|
|
1111
|
+
const gradeMatch = /^(?:GRADE\s*)?([ABCDF])(?:\s*[+-])?(?:\b|$)/i.exec(trimmed);
|
|
1112
|
+
if (gradeMatch?.[1]) {
|
|
1113
|
+
return gradeMatch[1].toUpperCase();
|
|
1114
|
+
}
|
|
1115
|
+
return "Unknown";
|
|
1116
|
+
}, securityPostureGradeSchema);
|
|
828
1117
|
var securityPostureScanResultSchema = z.object({
|
|
829
1118
|
summary: z.string().trim().min(1).max(2e3),
|
|
830
1119
|
baselineVersion: z.string().trim().min(1).max(80),
|
|
831
1120
|
postureScore: z.number().min(0).max(100).optional(),
|
|
832
|
-
postureGrade:
|
|
1121
|
+
postureGrade: securityPostureScanResultGradeSchema.default("Unknown"),
|
|
833
1122
|
categorySummaries: z.array(securityCategorySummarySchema).default([]),
|
|
834
1123
|
findings: z.array(securityFindingResultSchema).default([]),
|
|
835
1124
|
verificationPlan: z.array(z.string().trim().min(1).max(300)).min(1)
|
|
@@ -843,6 +1132,7 @@ var securityScanItemSchema = baseItemSchema.extend({
|
|
|
843
1132
|
status: securityScanStatusSchema,
|
|
844
1133
|
baselineVersion: z.string().trim().min(1).max(80).default("amistio-security-baseline-v1"),
|
|
845
1134
|
workItemId: z.string().min(1).optional(),
|
|
1135
|
+
repositoryLinkId: z.string().min(1).optional(),
|
|
846
1136
|
runnerId: z.string().min(1).optional(),
|
|
847
1137
|
findingIds: z.array(z.string().min(1)).default([]),
|
|
848
1138
|
summary: z.string().trim().min(1).max(2e3).optional(),
|
|
@@ -887,13 +1177,82 @@ var securityPostureSnapshotItemSchema = baseItemSchema.extend({
|
|
|
887
1177
|
status: z.enum(["unknown", "fresh", "stale", "running", "failed"]),
|
|
888
1178
|
baselineVersion: z.string().trim().min(1).max(80).default("amistio-security-baseline-v1"),
|
|
889
1179
|
postureScore: z.number().min(0).max(100).optional(),
|
|
890
|
-
postureGrade:
|
|
1180
|
+
postureGrade: securityPostureGradeSchema.default("Unknown"),
|
|
891
1181
|
severityCounts: securitySeverityCountsSchema.default(defaultSecuritySeverityCounts),
|
|
892
1182
|
categorySummaries: z.array(securityCategorySummarySchema).default([]),
|
|
893
1183
|
lastScannedAt: isoDateTimeSchema.optional(),
|
|
894
1184
|
nextScheduledAt: isoDateTimeSchema.optional(),
|
|
895
1185
|
summary: z.string().trim().min(1).max(2e3).optional()
|
|
896
1186
|
});
|
|
1187
|
+
var appEvaluationFindingResultSchema = z.object({
|
|
1188
|
+
title: z.string().trim().min(1).max(200),
|
|
1189
|
+
category: appEvaluationFindingCategorySchema,
|
|
1190
|
+
severity: appEvaluationFindingSeveritySchema,
|
|
1191
|
+
confidence: appEvaluationFindingConfidenceSchema.default("medium"),
|
|
1192
|
+
summary: z.string().trim().min(1).max(2e3),
|
|
1193
|
+
affectedSurfaces: z.array(z.string().trim().min(1).max(300)).default([]),
|
|
1194
|
+
evidence: z.array(z.string().trim().min(1).max(600)).default([]),
|
|
1195
|
+
suggestedAction: z.string().trim().min(1).max(4e3),
|
|
1196
|
+
verificationPlan: z.array(z.string().trim().min(1).max(300)).min(1),
|
|
1197
|
+
safePaths: z.array(z.string().trim().min(1).max(300)).default([]),
|
|
1198
|
+
proposedLifecycleAction: appEvaluationPlanLifecycleActionSchema.default("none"),
|
|
1199
|
+
relatedArtifactIds: z.array(z.string().trim().min(1).max(160)).default([]),
|
|
1200
|
+
proposedPlanTitle: z.string().trim().min(1).max(200).optional(),
|
|
1201
|
+
proposedPlanRepoPath: z.string().trim().min(1).max(300).optional(),
|
|
1202
|
+
proposedPlanContent: z.string().trim().min(1).max(3e4).optional(),
|
|
1203
|
+
status: appEvaluationFindingStatusSchema.default("open")
|
|
1204
|
+
});
|
|
1205
|
+
var appEvaluationScanResultSchema = z.object({
|
|
1206
|
+
summary: z.string().trim().min(1).max(2e3),
|
|
1207
|
+
baselineVersion: z.string().trim().min(1).max(80),
|
|
1208
|
+
findings: z.array(appEvaluationFindingResultSchema).default([]),
|
|
1209
|
+
verificationPlan: z.array(z.string().trim().min(1).max(300)).min(1)
|
|
1210
|
+
});
|
|
1211
|
+
var appEvaluationScanItemSchema = baseItemSchema.extend({
|
|
1212
|
+
type: z.literal("appEvaluationScan"),
|
|
1213
|
+
projectId: z.string().min(1),
|
|
1214
|
+
appEvaluationScanId: z.string().min(1),
|
|
1215
|
+
hourBucket: z.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:00:00\.000Z$/),
|
|
1216
|
+
source: z.enum(["runner", "manual"]),
|
|
1217
|
+
status: appEvaluationScanStatusSchema,
|
|
1218
|
+
baselineVersion: z.string().trim().min(1).max(80).default("amistio-app-evaluation-v1"),
|
|
1219
|
+
workItemId: z.string().min(1).optional(),
|
|
1220
|
+
repositoryLinkId: z.string().min(1).optional(),
|
|
1221
|
+
runnerId: z.string().min(1).optional(),
|
|
1222
|
+
findingIds: z.array(z.string().min(1)).default([]),
|
|
1223
|
+
summary: z.string().trim().min(1).max(2e3).optional(),
|
|
1224
|
+
startedAt: isoDateTimeSchema.optional(),
|
|
1225
|
+
completedAt: isoDateTimeSchema.optional(),
|
|
1226
|
+
failedAt: isoDateTimeSchema.optional(),
|
|
1227
|
+
error: z.string().max(1e3).optional()
|
|
1228
|
+
});
|
|
1229
|
+
var appEvaluationFindingItemSchema = baseItemSchema.extend({
|
|
1230
|
+
type: z.literal("appEvaluationFinding"),
|
|
1231
|
+
projectId: z.string().min(1),
|
|
1232
|
+
appEvaluationFindingId: z.string().min(1),
|
|
1233
|
+
appEvaluationScanId: z.string().min(1),
|
|
1234
|
+
title: z.string().trim().min(1).max(200),
|
|
1235
|
+
category: appEvaluationFindingCategorySchema,
|
|
1236
|
+
severity: appEvaluationFindingSeveritySchema,
|
|
1237
|
+
confidence: appEvaluationFindingConfidenceSchema.default("medium"),
|
|
1238
|
+
status: appEvaluationFindingStatusSchema,
|
|
1239
|
+
approvalState: appEvaluationApprovalStateSchema.default("proposed"),
|
|
1240
|
+
summary: z.string().trim().min(1).max(2e3),
|
|
1241
|
+
affectedSurfaces: z.array(z.string().trim().min(1).max(300)).default([]),
|
|
1242
|
+
evidence: z.array(z.string().trim().min(1).max(600)).default([]),
|
|
1243
|
+
safePaths: z.array(z.string().trim().min(1).max(300)).default([]),
|
|
1244
|
+
suggestedAction: z.string().trim().min(1).max(4e3),
|
|
1245
|
+
verificationPlan: z.array(z.string().trim().min(1).max(300)).min(1),
|
|
1246
|
+
proposedLifecycleAction: appEvaluationPlanLifecycleActionSchema.default("none"),
|
|
1247
|
+
relatedArtifactIds: z.array(z.string().trim().min(1).max(160)).default([]),
|
|
1248
|
+
proposedPlanDocumentId: z.string().min(1).optional(),
|
|
1249
|
+
implementationWorkItemId: z.string().min(1).optional(),
|
|
1250
|
+
reviewNotes: z.string().trim().min(1).optional(),
|
|
1251
|
+
reviewedByUserId: z.string().min(1).optional(),
|
|
1252
|
+
reviewedAt: isoDateTimeSchema.optional(),
|
|
1253
|
+
resolvedAt: isoDateTimeSchema.optional(),
|
|
1254
|
+
lastReviewAction: z.enum(["approve", "requestChanges", "dismiss", "acceptRisk", "reopen"]).optional()
|
|
1255
|
+
});
|
|
897
1256
|
var activityEventItemSchema = baseItemSchema.extend({
|
|
898
1257
|
type: z.literal("activityEvent"),
|
|
899
1258
|
projectId: z.string().min(1),
|
|
@@ -911,6 +1270,11 @@ var activityEventItemSchema = baseItemSchema.extend({
|
|
|
911
1270
|
relatedIssueId: z.string().min(1).optional(),
|
|
912
1271
|
relatedSecurityScanId: z.string().min(1).optional(),
|
|
913
1272
|
relatedSecurityFindingId: z.string().min(1).optional(),
|
|
1273
|
+
relatedAppEvaluationScanId: z.string().min(1).optional(),
|
|
1274
|
+
relatedAppEvaluationFindingId: z.string().min(1).optional(),
|
|
1275
|
+
relatedProjectContextRefreshId: z.string().min(1).optional(),
|
|
1276
|
+
relatedProjectSystemDiagramId: z.string().min(1).optional(),
|
|
1277
|
+
relatedContextMissId: z.string().min(1).optional(),
|
|
914
1278
|
generatedDraftId: z.string().min(1).optional(),
|
|
915
1279
|
runnerId: z.string().min(1).optional(),
|
|
916
1280
|
repositoryLinkId: z.string().min(1).optional(),
|
|
@@ -971,6 +1335,12 @@ var projectItemUnionSchema = z.discriminatedUnion("type", [
|
|
|
971
1335
|
securityScanItemSchema,
|
|
972
1336
|
securityFindingItemSchema,
|
|
973
1337
|
securityPostureSnapshotItemSchema,
|
|
1338
|
+
appEvaluationScanItemSchema,
|
|
1339
|
+
appEvaluationFindingItemSchema,
|
|
1340
|
+
projectContextMapItemSchema,
|
|
1341
|
+
projectContextRefreshItemSchema,
|
|
1342
|
+
projectSystemDiagramItemSchema,
|
|
1343
|
+
contextMissItemSchema,
|
|
974
1344
|
syncCursorItemSchema,
|
|
975
1345
|
syncConflictItemSchema,
|
|
976
1346
|
workItemSchema,
|
|
@@ -1010,16 +1380,43 @@ function isOfficialAmistioApiUrl(value) {
|
|
|
1010
1380
|
|
|
1011
1381
|
// ../shared/src/brain-documents.ts
|
|
1012
1382
|
var controlPlaneRoots = /* @__PURE__ */ new Set(["architecture", "context", "decisions", "features", "memory", "plans", "prompts", "workflows"]);
|
|
1383
|
+
var documentTypeFolders = {
|
|
1384
|
+
architecture: "architecture",
|
|
1385
|
+
context: "context",
|
|
1386
|
+
decision: "decisions",
|
|
1387
|
+
feature: "features",
|
|
1388
|
+
memory: "memory",
|
|
1389
|
+
plan: "plans",
|
|
1390
|
+
prompt: "prompts",
|
|
1391
|
+
workflow: "workflows"
|
|
1392
|
+
};
|
|
1393
|
+
var documentTypeByFolder = Object.fromEntries(Object.entries(documentTypeFolders).map(([documentType, folder]) => [folder, documentType]));
|
|
1013
1394
|
function isControlPlaneTemplateRepoPath(repoPath) {
|
|
1014
1395
|
const normalized = normalizeRepoPath(repoPath);
|
|
1015
1396
|
const segments = normalized.split("/").filter(Boolean);
|
|
1016
|
-
const root = segments
|
|
1397
|
+
const root = controlPlaneRootFromSegments(segments);
|
|
1017
1398
|
const basename = segments[segments.length - 1]?.toLowerCase();
|
|
1018
1399
|
return Boolean(root && controlPlaneRoots.has(root) && (basename === "_template.md" || basename === "_template.mdx"));
|
|
1019
1400
|
}
|
|
1401
|
+
function documentTypeForBrainRepoPath(repoPath) {
|
|
1402
|
+
const normalized = normalizeRepoPath(repoPath);
|
|
1403
|
+
const segments = normalized.split("/").filter(Boolean);
|
|
1404
|
+
const root = controlPlaneRootFromSegments(segments);
|
|
1405
|
+
return root ? documentTypeByFolder[root] : void 0;
|
|
1406
|
+
}
|
|
1407
|
+
function inferContentFormatFromRepoPath(repoPath) {
|
|
1408
|
+
if (/\.html?$/i.test(repoPath)) return "html";
|
|
1409
|
+
if (/\.mdx?$/i.test(repoPath)) return "markdown";
|
|
1410
|
+
return void 0;
|
|
1411
|
+
}
|
|
1020
1412
|
function normalizeRepoPath(repoPath) {
|
|
1021
1413
|
return repoPath.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "");
|
|
1022
1414
|
}
|
|
1415
|
+
function controlPlaneRootFromSegments(segments) {
|
|
1416
|
+
if (segments[0] === "docs" && segments[1] === "html") return segments[2];
|
|
1417
|
+
if (segments[0] === "docs") return segments[1];
|
|
1418
|
+
return segments[0];
|
|
1419
|
+
}
|
|
1023
1420
|
|
|
1024
1421
|
// ../shared/src/repo-metadata.ts
|
|
1025
1422
|
import { z as z2 } from "zod";
|
|
@@ -1033,6 +1430,7 @@ var repoLinkMetadataSchema = z2.object({
|
|
|
1033
1430
|
var syncedDocumentFrontmatterSchema = z2.object({
|
|
1034
1431
|
amistioDocumentId: z2.string().min(1),
|
|
1035
1432
|
amistioDocumentType: z2.string().min(1),
|
|
1433
|
+
amistioContentFormat: z2.enum(["markdown", "html"]).default("markdown"),
|
|
1036
1434
|
amistioRevision: z2.coerce.number().int().nonnegative(),
|
|
1037
1435
|
amistioContentHash: z2.string().min(1),
|
|
1038
1436
|
status: z2.string().optional()
|
|
@@ -1183,6 +1581,7 @@ function decideSyncState(state) {
|
|
|
1183
1581
|
|
|
1184
1582
|
// ../shared/src/next-action.ts
|
|
1185
1583
|
var nextActionRunnerHeartbeatFreshMs = 15 * 60 * 1e3;
|
|
1584
|
+
var nextActionCompletedWorkFreshMs = 60 * 60 * 1e3;
|
|
1186
1585
|
function computeProjectNextAction(input) {
|
|
1187
1586
|
const nowMs = input.nowMs ?? Date.now();
|
|
1188
1587
|
if (!input.projectId) {
|
|
@@ -1280,7 +1679,7 @@ function computeProjectNextAction(input) {
|
|
|
1280
1679
|
if (!readiness.ready) {
|
|
1281
1680
|
return runnerWaitAction(readiness, "Runner setup needed");
|
|
1282
1681
|
}
|
|
1283
|
-
const completedWork = latestWorkItem(input.workItems.filter((item) => item.status === "completed"));
|
|
1682
|
+
const completedWork = latestWorkItem(input.workItems.filter((item) => item.status === "completed" && isFreshCompletedWork(item, nowMs)));
|
|
1284
1683
|
if (completedWork) {
|
|
1285
1684
|
return workAction(completedWork, "workCompleted", "none", "success", "Work completed", completedWork.lastStatusMessage ?? "The latest runner work is complete.");
|
|
1286
1685
|
}
|
|
@@ -1374,6 +1773,10 @@ function workAction(workItem, kind, actor, tone, title, message) {
|
|
|
1374
1773
|
function latestWorkItem(workItems) {
|
|
1375
1774
|
return [...workItems].sort((first, second) => Date.parse(second.updatedAt) - Date.parse(first.updatedAt))[0];
|
|
1376
1775
|
}
|
|
1776
|
+
function isFreshCompletedWork(workItem, nowMs) {
|
|
1777
|
+
const timestamp = Date.parse(workItem.lastStatusAt ?? workItem.updatedAt);
|
|
1778
|
+
return timestamp > 0 && nowMs - timestamp <= nextActionCompletedWorkFreshMs;
|
|
1779
|
+
}
|
|
1377
1780
|
function latestTimestamp(values) {
|
|
1378
1781
|
return values.sort((first, second) => Date.parse(second) - Date.parse(first))[0];
|
|
1379
1782
|
}
|
|
@@ -1715,6 +2118,27 @@ var ApiClient = class {
|
|
|
1715
2118
|
{ method: "GET" }
|
|
1716
2119
|
);
|
|
1717
2120
|
}
|
|
2121
|
+
async listProjectContext(projectId) {
|
|
2122
|
+
return this.request(
|
|
2123
|
+
`/projects/${projectId}/project-context`,
|
|
2124
|
+
z3.object({
|
|
2125
|
+
maps: z3.array(projectContextMapItemSchema),
|
|
2126
|
+
refreshes: z3.array(projectContextRefreshItemSchema),
|
|
2127
|
+
misses: z3.array(contextMissItemSchema),
|
|
2128
|
+
activeMap: projectContextMapItemSchema.optional()
|
|
2129
|
+
}),
|
|
2130
|
+
{ method: "GET" }
|
|
2131
|
+
);
|
|
2132
|
+
}
|
|
2133
|
+
async buildProjectContextPack(projectId, query, workItemId) {
|
|
2134
|
+
const params = new URLSearchParams({ packQuery: query });
|
|
2135
|
+
if (workItemId) params.set("workItemId", workItemId);
|
|
2136
|
+
return this.request(
|
|
2137
|
+
`/projects/${projectId}/project-context?${params.toString()}`,
|
|
2138
|
+
projectContextPackSchema,
|
|
2139
|
+
{ method: "GET" }
|
|
2140
|
+
);
|
|
2141
|
+
}
|
|
1718
2142
|
async pushBrainDocuments(projectId, documents) {
|
|
1719
2143
|
return this.request(
|
|
1720
2144
|
`/projects/${projectId}/brain-documents`,
|
|
@@ -1892,6 +2316,26 @@ var ApiClient = class {
|
|
|
1892
2316
|
}
|
|
1893
2317
|
);
|
|
1894
2318
|
}
|
|
2319
|
+
async submitAppEvaluationScanResult(projectId, workItemId, result) {
|
|
2320
|
+
return this.request(
|
|
2321
|
+
`/projects/${projectId}/work-items/${workItemId}/app-evaluation-result`,
|
|
2322
|
+
z3.object({ scan: appEvaluationScanItemSchema, findings: z3.array(appEvaluationFindingItemSchema), workItem: workItemSchema }),
|
|
2323
|
+
{
|
|
2324
|
+
method: "POST",
|
|
2325
|
+
body: JSON.stringify(result)
|
|
2326
|
+
}
|
|
2327
|
+
);
|
|
2328
|
+
}
|
|
2329
|
+
async submitProjectContextRefreshResult(projectId, workItemId, result) {
|
|
2330
|
+
return this.request(
|
|
2331
|
+
`/projects/${projectId}/work-items/${workItemId}/project-context-result`,
|
|
2332
|
+
z3.object({ refresh: projectContextRefreshItemSchema, map: projectContextMapItemSchema.optional(), workItem: workItemSchema }),
|
|
2333
|
+
{
|
|
2334
|
+
method: "POST",
|
|
2335
|
+
body: JSON.stringify(result)
|
|
2336
|
+
}
|
|
2337
|
+
);
|
|
2338
|
+
}
|
|
1895
2339
|
async request(urlPath, schema, init) {
|
|
1896
2340
|
const response = await fetch(resolveApiUrl(this.options.apiUrl, urlPath), {
|
|
1897
2341
|
...init,
|
|
@@ -1938,8 +2382,8 @@ var toolSessionMutationSchema = z3.object({
|
|
|
1938
2382
|
});
|
|
1939
2383
|
function resolveApiUrl(apiUrl, urlPath) {
|
|
1940
2384
|
const base = apiUrl.endsWith("/") ? apiUrl.slice(0, -1) : apiUrl;
|
|
1941
|
-
const
|
|
1942
|
-
return new URL(`${base}${
|
|
2385
|
+
const path15 = urlPath.startsWith("/") ? urlPath : `/${urlPath}`;
|
|
2386
|
+
return new URL(`${base}${path15}`);
|
|
1943
2387
|
}
|
|
1944
2388
|
|
|
1945
2389
|
// src/orchestrator.ts
|
|
@@ -2831,6 +3275,15 @@ function currentRunnerMode() {
|
|
|
2831
3275
|
function defaultRunnerMetadataDir() {
|
|
2832
3276
|
return path6.join(os3.homedir(), ".config", "amistio", "runners");
|
|
2833
3277
|
}
|
|
3278
|
+
function updatedCliRunnerLaunchOptions() {
|
|
3279
|
+
return { executablePath: "amistio", directExecutable: true };
|
|
3280
|
+
}
|
|
3281
|
+
function resolveRunnerDaemonLaunch(input) {
|
|
3282
|
+
if (input.directExecutable) {
|
|
3283
|
+
return { executablePath: input.executablePath ?? "amistio", args: input.args };
|
|
3284
|
+
}
|
|
3285
|
+
return { executablePath: input.executablePath ?? process.execPath, args: [input.scriptPath ?? process.argv[1], ...input.args] };
|
|
3286
|
+
}
|
|
2834
3287
|
async function startRunnerDaemon(input) {
|
|
2835
3288
|
const metadataDir = input.metadataDir ?? defaultRunnerMetadataDir();
|
|
2836
3289
|
const existing = await readRunnerDaemonMetadata(input, metadataDir);
|
|
@@ -2840,7 +3293,12 @@ async function startRunnerDaemon(input) {
|
|
|
2840
3293
|
await mkdir5(metadataDir, { recursive: true });
|
|
2841
3294
|
const logPath = path6.join(metadataDir, `${runnerDaemonKey(input)}.log`);
|
|
2842
3295
|
const logFd = openSync(logPath, "a");
|
|
2843
|
-
const
|
|
3296
|
+
const launch = resolveRunnerDaemonLaunch({
|
|
3297
|
+
args: input.args,
|
|
3298
|
+
...input.executablePath ? { executablePath: input.executablePath } : {},
|
|
3299
|
+
...input.scriptPath ? { scriptPath: input.scriptPath } : {}
|
|
3300
|
+
});
|
|
3301
|
+
const child = spawn2(launch.executablePath, launch.args, {
|
|
2844
3302
|
cwd: input.rootDir,
|
|
2845
3303
|
detached: true,
|
|
2846
3304
|
env: {
|
|
@@ -2877,7 +3335,13 @@ async function restartRunnerDaemonProcess(metadata, args, input = {}) {
|
|
|
2877
3335
|
await mkdir5(metadataDir, { recursive: true });
|
|
2878
3336
|
const logPath = metadata.logPath ?? path6.join(metadataDir, `${runnerDaemonKey(metadata)}.log`);
|
|
2879
3337
|
const logFd = openSync(logPath, "a");
|
|
2880
|
-
const
|
|
3338
|
+
const launch = resolveRunnerDaemonLaunch({
|
|
3339
|
+
args,
|
|
3340
|
+
...input.executablePath ? { executablePath: input.executablePath } : {},
|
|
3341
|
+
...input.scriptPath ? { scriptPath: input.scriptPath } : {},
|
|
3342
|
+
...input.directExecutable ? { directExecutable: true } : {}
|
|
3343
|
+
});
|
|
3344
|
+
const child = spawn2(launch.executablePath, launch.args, {
|
|
2881
3345
|
cwd: metadata.rootDir,
|
|
2882
3346
|
detached: true,
|
|
2883
3347
|
env: {
|
|
@@ -3356,6 +3820,7 @@ import { promisify as promisify3 } from "node:util";
|
|
|
3356
3820
|
var execFileAsync3 = promisify3(execFile3);
|
|
3357
3821
|
var legacySyncRoots = ["architecture", "context", "decisions", "features", "memory", "plans", "prompts", "workflows"];
|
|
3358
3822
|
var syncRoots = legacySyncRoots.map((syncRoot) => `docs/${syncRoot}`);
|
|
3823
|
+
var htmlSyncRoot = "docs/html";
|
|
3359
3824
|
var documentTypeByRoot = {
|
|
3360
3825
|
architecture: "architecture",
|
|
3361
3826
|
context: "context",
|
|
@@ -3443,15 +3908,15 @@ async function collectSyncStatus(rootDir, webDocuments = []) {
|
|
|
3443
3908
|
return { status, items, counts };
|
|
3444
3909
|
}
|
|
3445
3910
|
async function readLocalSyncedDocuments(rootDir) {
|
|
3446
|
-
const
|
|
3911
|
+
const documentFiles = await findBrainDocumentFiles(rootDir);
|
|
3447
3912
|
const documents = [];
|
|
3448
|
-
for (const fullPath of
|
|
3913
|
+
for (const fullPath of documentFiles) {
|
|
3449
3914
|
const raw = await readFile6(fullPath, "utf8");
|
|
3450
|
-
const
|
|
3915
|
+
const repoPath = toRepoPath(rootDir, fullPath);
|
|
3916
|
+
const parsed = parseSyncedDocument(raw, repoPath);
|
|
3451
3917
|
if (!parsed) {
|
|
3452
3918
|
continue;
|
|
3453
3919
|
}
|
|
3454
|
-
const repoPath = toRepoPath(rootDir, fullPath);
|
|
3455
3920
|
if (isControlPlaneTemplateRepoPath(repoPath)) {
|
|
3456
3921
|
continue;
|
|
3457
3922
|
}
|
|
@@ -3497,7 +3962,7 @@ async function materializeBrainDocuments(rootDir, documents, options = {}) {
|
|
|
3497
3962
|
continue;
|
|
3498
3963
|
}
|
|
3499
3964
|
await mkdir7(path8.dirname(fullPath), { recursive: true });
|
|
3500
|
-
await writeFile7(fullPath,
|
|
3965
|
+
await writeFile7(fullPath, createSyncedDocumentContent(document), "utf8");
|
|
3501
3966
|
result.written.push(document.repoPath);
|
|
3502
3967
|
}
|
|
3503
3968
|
return result;
|
|
@@ -3515,6 +3980,7 @@ async function collectDirtyDocumentsForPush(rootDir, metadata) {
|
|
|
3515
3980
|
projectId: metadata.amistioProjectId,
|
|
3516
3981
|
documentId: document.frontmatter.amistioDocumentId,
|
|
3517
3982
|
documentType,
|
|
3983
|
+
contentFormat: document.frontmatter.amistioContentFormat,
|
|
3518
3984
|
title: inferTitle(document.content, document.repoPath),
|
|
3519
3985
|
status: "reviewing",
|
|
3520
3986
|
repoPath: document.repoPath,
|
|
@@ -3552,11 +4018,15 @@ function autoSyncSkipCounts(skipped) {
|
|
|
3552
4018
|
unreadable: skipped.filter((item) => item.reason === "unreadable").length
|
|
3553
4019
|
};
|
|
3554
4020
|
}
|
|
3555
|
-
function
|
|
4021
|
+
function createSyncedDocumentContent(document) {
|
|
4022
|
+
return (document.contentFormat ?? inferContentFormatFromRepoPath(document.repoPath) ?? "markdown") === "html" ? createSyncedDocumentHtml(document) : createSyncedDocumentMarkdownContent(document);
|
|
4023
|
+
}
|
|
4024
|
+
function createSyncedDocumentMarkdownContent(document) {
|
|
3556
4025
|
return [
|
|
3557
4026
|
"---",
|
|
3558
4027
|
`amistioDocumentId: ${document.documentId}`,
|
|
3559
4028
|
`amistioDocumentType: ${document.documentType}`,
|
|
4029
|
+
`amistioContentFormat: ${document.contentFormat ?? "markdown"}`,
|
|
3560
4030
|
`amistioRevision: ${document.revision}`,
|
|
3561
4031
|
`amistioContentHash: ${document.contentHash}`,
|
|
3562
4032
|
`status: ${document.status}`,
|
|
@@ -3565,6 +4035,24 @@ function createSyncedDocumentMarkdown(document) {
|
|
|
3565
4035
|
""
|
|
3566
4036
|
].join("\n");
|
|
3567
4037
|
}
|
|
4038
|
+
function createSyncedDocumentHtml(document) {
|
|
4039
|
+
return [
|
|
4040
|
+
"<!--",
|
|
4041
|
+
`amistioDocumentId: ${document.documentId}`,
|
|
4042
|
+
`amistioDocumentType: ${document.documentType}`,
|
|
4043
|
+
"amistioContentFormat: html",
|
|
4044
|
+
`amistioRevision: ${document.revision}`,
|
|
4045
|
+
`amistioContentHash: ${document.contentHash}`,
|
|
4046
|
+
`status: ${document.status}`,
|
|
4047
|
+
"-->",
|
|
4048
|
+
document.content,
|
|
4049
|
+
""
|
|
4050
|
+
].join("\n");
|
|
4051
|
+
}
|
|
4052
|
+
function parseSyncedDocument(content, repoPath) {
|
|
4053
|
+
const format = inferContentFormatFromRepoPath(repoPath);
|
|
4054
|
+
return format === "html" ? parseSyncedHtml(content) : parseSyncedMarkdown(content);
|
|
4055
|
+
}
|
|
3568
4056
|
function parseSyncedMarkdown(content) {
|
|
3569
4057
|
const rawFrontmatter = parseFrontmatter(content);
|
|
3570
4058
|
const frontmatter = syncedDocumentFrontmatterSchema.safeParse(rawFrontmatter);
|
|
@@ -3579,10 +4067,32 @@ function parseSyncedMarkdown(content) {
|
|
|
3579
4067
|
const bodyStart = closingLineEnd === -1 ? content.length : closingLineEnd + 1;
|
|
3580
4068
|
return { frontmatter: frontmatter.data, content: content.slice(bodyStart).replace(/\n$/, "") };
|
|
3581
4069
|
}
|
|
4070
|
+
function parseSyncedHtml(content) {
|
|
4071
|
+
const trimmedStart = content.trimStart();
|
|
4072
|
+
if (!trimmedStart.startsWith("<!--")) {
|
|
4073
|
+
return void 0;
|
|
4074
|
+
}
|
|
4075
|
+
const startOffset = content.indexOf("<!--");
|
|
4076
|
+
const closingMarker = content.indexOf("-->", startOffset + 4);
|
|
4077
|
+
if (closingMarker === -1) {
|
|
4078
|
+
return void 0;
|
|
4079
|
+
}
|
|
4080
|
+
const metadataLines = content.slice(startOffset + 4, closingMarker).split(/\r?\n/);
|
|
4081
|
+
const metadata = Object.fromEntries(metadataLines.map((line) => {
|
|
4082
|
+
const separator = line.indexOf(":");
|
|
4083
|
+
return separator === -1 ? void 0 : [line.slice(0, separator).trim(), line.slice(separator + 1).trim()];
|
|
4084
|
+
}).filter((entry) => Boolean(entry?.[0])));
|
|
4085
|
+
const frontmatter = syncedDocumentFrontmatterSchema.safeParse({ ...metadata, amistioContentFormat: "html" });
|
|
4086
|
+
if (!frontmatter.success) {
|
|
4087
|
+
return void 0;
|
|
4088
|
+
}
|
|
4089
|
+
const bodyStart = content.indexOf("\n", closingMarker + 3);
|
|
4090
|
+
return { frontmatter: frontmatter.data, content: content.slice(bodyStart === -1 ? closingMarker + 3 : bodyStart + 1).replace(/\n$/, "") };
|
|
4091
|
+
}
|
|
3582
4092
|
async function readExistingSyncedDocument(fullPath) {
|
|
3583
4093
|
try {
|
|
3584
4094
|
const raw = await readFile6(fullPath, "utf8");
|
|
3585
|
-
const parsed =
|
|
4095
|
+
const parsed = parseSyncedDocument(raw, fullPath);
|
|
3586
4096
|
if (!parsed) {
|
|
3587
4097
|
return { exists: true };
|
|
3588
4098
|
}
|
|
@@ -3603,23 +4113,23 @@ async function readExistingSyncedDocument(fullPath) {
|
|
|
3603
4113
|
throw error;
|
|
3604
4114
|
}
|
|
3605
4115
|
}
|
|
3606
|
-
async function
|
|
4116
|
+
async function findBrainDocumentFiles(rootDir) {
|
|
3607
4117
|
const files = [];
|
|
3608
|
-
for (const syncRoot of syncRoots) {
|
|
4118
|
+
for (const syncRoot of [...syncRoots, htmlSyncRoot]) {
|
|
3609
4119
|
const fullRoot = path8.join(rootDir, syncRoot);
|
|
3610
4120
|
if (!await exists2(fullRoot)) {
|
|
3611
4121
|
continue;
|
|
3612
4122
|
}
|
|
3613
|
-
await
|
|
4123
|
+
await walkBrainDocumentFiles(fullRoot, files);
|
|
3614
4124
|
}
|
|
3615
4125
|
return files;
|
|
3616
4126
|
}
|
|
3617
|
-
async function
|
|
4127
|
+
async function walkBrainDocumentFiles(directory, files) {
|
|
3618
4128
|
for (const entry of await readdir3(directory, { withFileTypes: true })) {
|
|
3619
4129
|
const fullPath = path8.join(directory, entry.name);
|
|
3620
4130
|
if (entry.isDirectory()) {
|
|
3621
|
-
await
|
|
3622
|
-
} else if (entry.isFile() && entry.name
|
|
4131
|
+
await walkBrainDocumentFiles(fullPath, files);
|
|
4132
|
+
} else if (entry.isFile() && /\.(md|mdx|html?)$/i.test(entry.name)) {
|
|
3623
4133
|
files.push(fullPath);
|
|
3624
4134
|
}
|
|
3625
4135
|
}
|
|
@@ -3641,7 +4151,7 @@ function safeRepoPath(rootDir, repoPath) {
|
|
|
3641
4151
|
}
|
|
3642
4152
|
function isControlPlanePath(repoPath) {
|
|
3643
4153
|
const normalized = path8.normalize(repoPath);
|
|
3644
|
-
return syncRoots.some((syncRoot) => normalized === syncRoot || normalized.startsWith(`${syncRoot}${path8.sep}`));
|
|
4154
|
+
return syncRoots.some((syncRoot) => normalized === syncRoot || normalized.startsWith(`${syncRoot}${path8.sep}`)) || normalized === htmlSyncRoot || normalized.startsWith(`${htmlSyncRoot}${path8.sep}`);
|
|
3645
4155
|
}
|
|
3646
4156
|
function canonicalControlPlaneRepoPath(repoPath) {
|
|
3647
4157
|
const normalized = repoPath.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "");
|
|
@@ -3656,7 +4166,9 @@ function toRepoPath(rootDir, fullPath) {
|
|
|
3656
4166
|
}
|
|
3657
4167
|
function inferTitle(content, repoPath) {
|
|
3658
4168
|
const heading = content.split("\n").find((line) => line.startsWith("# "))?.replace(/^#\s+/, "").trim();
|
|
3659
|
-
|
|
4169
|
+
if (heading) return heading;
|
|
4170
|
+
const htmlHeading = content.match(/<h1\b[^>]*>([\s\S]*?)<\/h1>/i)?.[1]?.replace(/<[^>]+>/g, "").trim();
|
|
4171
|
+
return htmlHeading || path8.basename(repoPath, path8.extname(repoPath));
|
|
3660
4172
|
}
|
|
3661
4173
|
async function collectExternalBrainDocumentsForPush(rootDir, metadata, existingDocuments, options) {
|
|
3662
4174
|
const root = path8.resolve(rootDir);
|
|
@@ -3689,7 +4201,7 @@ async function collectExternalBrainDocumentsForPush(rootDir, metadata, existingD
|
|
|
3689
4201
|
skipped.push({ repoPath: normalizedRepoPath, reason: "unreadable" });
|
|
3690
4202
|
continue;
|
|
3691
4203
|
}
|
|
3692
|
-
if (
|
|
4204
|
+
if (parseSyncedDocument(content, normalizedRepoPath)) {
|
|
3693
4205
|
skipped.push({ repoPath: normalizedRepoPath, reason: "alreadyManaged" });
|
|
3694
4206
|
continue;
|
|
3695
4207
|
}
|
|
@@ -3719,6 +4231,7 @@ async function collectExternalBrainDocumentsForPush(rootDir, metadata, existingD
|
|
|
3719
4231
|
projectId: metadata.amistioProjectId,
|
|
3720
4232
|
documentId,
|
|
3721
4233
|
documentType,
|
|
4234
|
+
contentFormat: inferContentFormatFromRepoPath(canonicalRepoPath) ?? "markdown",
|
|
3722
4235
|
title: inferTitle(content, canonicalRepoPath),
|
|
3723
4236
|
status: "reviewing",
|
|
3724
4237
|
repoPath: canonicalRepoPath,
|
|
@@ -3747,7 +4260,7 @@ async function listAutoSyncCandidatePaths(rootDir) {
|
|
|
3747
4260
|
return uniqueSortedRepoPaths(gitFiles.split("\n").map(normalizeRepoPath3).filter((repoPath) => repoPath && isRecognizedBrainRepoPath(repoPath)));
|
|
3748
4261
|
}
|
|
3749
4262
|
const files = [];
|
|
3750
|
-
for (const syncRoot of [...syncRoots, ...legacySyncRoots]) {
|
|
4263
|
+
for (const syncRoot of [...syncRoots, htmlSyncRoot, ...legacySyncRoots]) {
|
|
3751
4264
|
const fullRoot = path8.join(rootDir, syncRoot);
|
|
3752
4265
|
if (await exists2(fullRoot)) {
|
|
3753
4266
|
await walkAutoSyncFiles(rootDir, fullRoot, files);
|
|
@@ -3771,7 +4284,7 @@ async function walkAutoSyncFiles(rootDir, directory, files) {
|
|
|
3771
4284
|
function autoSyncPathSkipReason(repoPath) {
|
|
3772
4285
|
if (autoSyncMetadataPaths.has(repoPath)) return "metadata";
|
|
3773
4286
|
if (isControlPlaneTemplateRepoPath(repoPath)) return "template";
|
|
3774
|
-
if (!/\.(md|mdx)$/i.test(repoPath)) return "unsupported";
|
|
4287
|
+
if (!/\.(md|mdx|html?)$/i.test(repoPath)) return "unsupported";
|
|
3775
4288
|
const basename = repoPath.split("/").at(-1)?.toLowerCase() ?? "";
|
|
3776
4289
|
if (basename.startsWith(".") || basename.endsWith(".lock") || basename.includes(".env") || basename.includes("secret") || basename.includes("credential")) return "excluded";
|
|
3777
4290
|
if (repoPath.split("/").some((segment) => autoSyncExcludedDirectoryNames.has(segment) || autoSyncGeneratedPathSegments.has(segment))) return "excluded";
|
|
@@ -3779,10 +4292,13 @@ function autoSyncPathSkipReason(repoPath) {
|
|
|
3779
4292
|
}
|
|
3780
4293
|
function isRecognizedBrainRepoPath(repoPath) {
|
|
3781
4294
|
const normalized = normalizeRepoPath3(repoPath);
|
|
3782
|
-
const [firstSegment, secondSegment] = normalized.split("/");
|
|
3783
|
-
return Boolean(firstSegment === "docs" && secondSegment && legacySyncRoots.includes(secondSegment) || firstSegment && legacySyncRoots.includes(firstSegment));
|
|
4295
|
+
const [firstSegment, secondSegment, thirdSegment] = normalized.split("/");
|
|
4296
|
+
return Boolean(firstSegment === "docs" && secondSegment === "html" && thirdSegment && legacySyncRoots.includes(thirdSegment) || firstSegment === "docs" && secondSegment && legacySyncRoots.includes(secondSegment) || firstSegment && legacySyncRoots.includes(firstSegment));
|
|
3784
4297
|
}
|
|
3785
4298
|
function documentTypeForRepoPath(repoPath) {
|
|
4299
|
+
return documentTypeForBrainRepoPath(repoPath) ?? legacyDocumentTypeForRepoPath(repoPath);
|
|
4300
|
+
}
|
|
4301
|
+
function legacyDocumentTypeForRepoPath(repoPath) {
|
|
3786
4302
|
const normalized = normalizeRepoPath3(repoPath);
|
|
3787
4303
|
const segments = normalized.split("/");
|
|
3788
4304
|
const root = segments[0] === "docs" ? segments[1] : segments[0];
|
|
@@ -3801,6 +4317,7 @@ function parseFrontmatterFromSyncedDocument(frontmatter) {
|
|
|
3801
4317
|
return {
|
|
3802
4318
|
amistioDocumentId: frontmatter.amistioDocumentId,
|
|
3803
4319
|
amistioDocumentType: frontmatter.amistioDocumentType,
|
|
4320
|
+
amistioContentFormat: frontmatter.amistioContentFormat,
|
|
3804
4321
|
amistioRevision: frontmatter.amistioRevision,
|
|
3805
4322
|
amistioContentHash: frontmatter.amistioContentHash,
|
|
3806
4323
|
...frontmatter.status ? { status: frontmatter.status } : {}
|
|
@@ -3864,6 +4381,10 @@ var issueDiagnosisStart = "AMISTIO_ISSUE_DIAGNOSIS_START";
|
|
|
3864
4381
|
var issueDiagnosisEnd = "AMISTIO_ISSUE_DIAGNOSIS_END";
|
|
3865
4382
|
var securityPostureStart = "AMISTIO_SECURITY_POSTURE_START";
|
|
3866
4383
|
var securityPostureEnd = "AMISTIO_SECURITY_POSTURE_END";
|
|
4384
|
+
var appEvaluationStart = "AMISTIO_APP_EVALUATION_START";
|
|
4385
|
+
var appEvaluationEnd = "AMISTIO_APP_EVALUATION_END";
|
|
4386
|
+
var projectContextRefreshStart = "AMISTIO_PROJECT_CONTEXT_REFRESH_START";
|
|
4387
|
+
var projectContextRefreshEnd = "AMISTIO_PROJECT_CONTEXT_REFRESH_END";
|
|
3867
4388
|
function createWorkExecutionPrompt(workItem, context) {
|
|
3868
4389
|
if (workItem.workKind === "brainGeneration") {
|
|
3869
4390
|
return createBrainGenerationPrompt(workItem);
|
|
@@ -3883,6 +4404,12 @@ function createWorkExecutionPrompt(workItem, context) {
|
|
|
3883
4404
|
if (workItem.workKind === "securityPostureScan") {
|
|
3884
4405
|
return createSecurityPostureScanPrompt(workItem, context?.securityPostureScan);
|
|
3885
4406
|
}
|
|
4407
|
+
if (workItem.workKind === "appEvaluationScan") {
|
|
4408
|
+
return createAppEvaluationScanPrompt(workItem, context?.appEvaluationScan);
|
|
4409
|
+
}
|
|
4410
|
+
if (workItem.workKind === "projectContextRefresh") {
|
|
4411
|
+
return createProjectContextRefreshPrompt(workItem, context?.projectContextRefresh);
|
|
4412
|
+
}
|
|
3886
4413
|
return [
|
|
3887
4414
|
"# Amistio Work Execution",
|
|
3888
4415
|
"",
|
|
@@ -3911,6 +4438,77 @@ function createWorkExecutionPrompt(workItem, context) {
|
|
|
3911
4438
|
"- Run relevant verification commands when feasible and summarize results."
|
|
3912
4439
|
].join("\n");
|
|
3913
4440
|
}
|
|
4441
|
+
function createProjectContextRefreshPrompt(workItem, context) {
|
|
4442
|
+
const approvedContext = (context?.documents ?? []).filter((document) => document.status === "approved" || document.syncState === "approved" || document.syncState === "synced").slice(0, 24).map((document) => [
|
|
4443
|
+
`### ${document.title}`,
|
|
4444
|
+
`documentId: ${document.documentId}`,
|
|
4445
|
+
`documentType: ${document.documentType}`,
|
|
4446
|
+
`repoPath: ${document.repoPath}`,
|
|
4447
|
+
`revision: ${document.revision}`,
|
|
4448
|
+
document.content.slice(0, 3e3)
|
|
4449
|
+
].join("\n")).join("\n\n");
|
|
4450
|
+
const activeMap = context?.activeMap;
|
|
4451
|
+
const previousMap = activeMap ? [
|
|
4452
|
+
`Map ID: ${activeMap.projectContextMapId}`,
|
|
4453
|
+
`Version: ${activeMap.version}`,
|
|
4454
|
+
`Status: ${activeMap.status}`,
|
|
4455
|
+
`Coverage: ${activeMap.coverage.status}`,
|
|
4456
|
+
`Summary: ${activeMap.summary}`,
|
|
4457
|
+
"Slices:",
|
|
4458
|
+
...activeMap.slices.slice(0, 20).map((slice) => `- ${slice.sliceId} / ${slice.kind} / ${slice.freshness}: ${slice.title} - ${slice.summary.slice(0, 500)}`)
|
|
4459
|
+
].join("\n") : "No approved project context map was loaded. Build the initial map.";
|
|
4460
|
+
return [
|
|
4461
|
+
"# Amistio Living Project Context Refresh",
|
|
4462
|
+
"",
|
|
4463
|
+
"You are running locally through the Amistio CLI inside the user's repository.",
|
|
4464
|
+
"Run a read-only project context refresh that captures the whole app as bounded, reviewable knowledge.",
|
|
4465
|
+
"Do not modify files, create branches, commit, install packages, run implementation prompts, call external services, or make unaudited network calls.",
|
|
4466
|
+
"Use only safe local inspection and focused diagnostic commands that do not expose secrets.",
|
|
4467
|
+
"",
|
|
4468
|
+
"## Work Item",
|
|
4469
|
+
"",
|
|
4470
|
+
`Title: ${workItem.title}`,
|
|
4471
|
+
`Work item ID: ${workItem.workItemId}`,
|
|
4472
|
+
`Project ID: ${workItem.projectId}`,
|
|
4473
|
+
`Project context refresh ID: ${workItem.projectContextRefreshId ?? "unknown"}`,
|
|
4474
|
+
"",
|
|
4475
|
+
"## Refresh Request",
|
|
4476
|
+
"",
|
|
4477
|
+
workItem.sourceWish ?? "Refresh the living project context map.",
|
|
4478
|
+
"",
|
|
4479
|
+
"## Approved Project Brain Context",
|
|
4480
|
+
"",
|
|
4481
|
+
approvedContext || "No approved project-brain records were loaded. Inspect local repository files as needed and explain the gap in the coverage warnings.",
|
|
4482
|
+
"",
|
|
4483
|
+
"## Previous Context Map",
|
|
4484
|
+
"",
|
|
4485
|
+
previousMap,
|
|
4486
|
+
"",
|
|
4487
|
+
"## Mapping Requirements",
|
|
4488
|
+
"",
|
|
4489
|
+
"- Create slices for architecture, domain, data, API, frontend, backend, CLI, workflows, operations, security, and testing when those surfaces exist.",
|
|
4490
|
+
"- Capture entities and relations that explain how the app is put together and where future work should look first.",
|
|
4491
|
+
"- Prefer summaries, repository-relative paths, short citations, tags, and freshness status over raw source excerpts.",
|
|
4492
|
+
"- Mark stale or missing areas explicitly instead of guessing.",
|
|
4493
|
+
"- Keep repoPaths repository-relative; never include /absolute paths or ../ traversal.",
|
|
4494
|
+
"",
|
|
4495
|
+
"## Data Safety",
|
|
4496
|
+
"",
|
|
4497
|
+
"- Persist summaries, entities, relations, citations, and repository-relative paths only.",
|
|
4498
|
+
"- Do not include raw source dumps, secrets, env vars, process lists, absolute local paths, credential values, tokens, provider sessions, or destructive shell output.",
|
|
4499
|
+
"- Citation excerpts must be short evidence snippets, not code payloads.",
|
|
4500
|
+
"",
|
|
4501
|
+
"## Output Contract",
|
|
4502
|
+
"",
|
|
4503
|
+
"Print exactly one JSON object between the markers below. The CLI will submit only this structured refresh result back to Amistio.",
|
|
4504
|
+
"",
|
|
4505
|
+
projectContextRefreshStart,
|
|
4506
|
+
'{"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"]}',
|
|
4507
|
+
projectContextRefreshEnd,
|
|
4508
|
+
"",
|
|
4509
|
+
"Do not put Markdown fences around the markers. Do not implement any changes."
|
|
4510
|
+
].join("\n");
|
|
4511
|
+
}
|
|
3914
4512
|
function createSecurityPostureScanPrompt(workItem, context) {
|
|
3915
4513
|
const approvedContext = (context?.documents ?? []).filter((document) => document.status === "approved" || document.syncState === "approved" || document.syncState === "synced").slice(0, 20).map((document) => [
|
|
3916
4514
|
`### ${document.title}`,
|
|
@@ -3968,6 +4566,63 @@ function createSecurityPostureScanPrompt(workItem, context) {
|
|
|
3968
4566
|
"Do not put Markdown fences around the markers. Do not implement remediation."
|
|
3969
4567
|
].join("\n");
|
|
3970
4568
|
}
|
|
4569
|
+
function createAppEvaluationScanPrompt(workItem, context) {
|
|
4570
|
+
const approvedContext = (context?.documents ?? []).filter((document) => document.status === "approved" || document.syncState === "approved" || document.syncState === "synced").slice(0, 24).map((document) => [
|
|
4571
|
+
`### ${document.title}`,
|
|
4572
|
+
`documentId: ${document.documentId}`,
|
|
4573
|
+
`documentType: ${document.documentType}`,
|
|
4574
|
+
`repoPath: ${document.repoPath}`,
|
|
4575
|
+
`revision: ${document.revision}`,
|
|
4576
|
+
document.content.slice(0, 3e3)
|
|
4577
|
+
].join("\n")).join("\n\n");
|
|
4578
|
+
return [
|
|
4579
|
+
"# Amistio App Evaluation Scan",
|
|
4580
|
+
"",
|
|
4581
|
+
"You are running locally through the Amistio CLI inside the user's repository.",
|
|
4582
|
+
"Run a read-only app evaluation scan and produce approval-gated improvement and cleanup findings.",
|
|
4583
|
+
"Do not modify files, create branches, commit, install packages, run implementation prompts, delete or archive plans, call external services, or make unaudited network calls.",
|
|
4584
|
+
"Use only safe local inspection and focused diagnostic commands that do not expose secrets.",
|
|
4585
|
+
"",
|
|
4586
|
+
"## Work Item",
|
|
4587
|
+
"",
|
|
4588
|
+
`Title: ${workItem.title}`,
|
|
4589
|
+
`Work item ID: ${workItem.workItemId}`,
|
|
4590
|
+
`Project ID: ${workItem.projectId}`,
|
|
4591
|
+
`App evaluation scan ID: ${workItem.appEvaluationScanId ?? "unknown"}`,
|
|
4592
|
+
"",
|
|
4593
|
+
"## Scan Request",
|
|
4594
|
+
"",
|
|
4595
|
+
workItem.sourceWish ?? "Run the Amistio app evaluation baseline.",
|
|
4596
|
+
"",
|
|
4597
|
+
"## Approved Project Brain Context",
|
|
4598
|
+
"",
|
|
4599
|
+
approvedContext || "No approved project-brain records were loaded. Inspect local repository files as needed and explain the gap in the summary.",
|
|
4600
|
+
"",
|
|
4601
|
+
"## Baseline Requirements",
|
|
4602
|
+
"",
|
|
4603
|
+
"- Check app verification health: lint, typecheck, test, build, CI references, flaky or missing gates.",
|
|
4604
|
+
"- Check product and docs drift between approved context, plans, prompts, and current implementation.",
|
|
4605
|
+
"- Check old plans and prompts for cleanup candidates, but only propose lifecycle actions such as markCompleted, markSuperseded, archive, keepActive, or none.",
|
|
4606
|
+
"- Check missing memory or workflow updates when repeated lessons or operational rules are visible.",
|
|
4607
|
+
"- Check release readiness, UX, accessibility, performance, reliability, and security-posture follow-through at a summary level.",
|
|
4608
|
+
"",
|
|
4609
|
+
"## Data Safety",
|
|
4610
|
+
"",
|
|
4611
|
+
"- Persist summaries, recommended actions, repository-relative paths, and redacted evidence only.",
|
|
4612
|
+
"- Do not include raw source dumps, secrets, env vars, process lists, absolute local paths, credential values, tokens, provider sessions, or destructive shell output.",
|
|
4613
|
+
"- Use safePaths for repo-relative paths only; never include /absolute paths or ../ traversal.",
|
|
4614
|
+
"",
|
|
4615
|
+
"## Output Contract",
|
|
4616
|
+
"",
|
|
4617
|
+
"Print exactly one JSON object between the markers below. The CLI will submit only this structured scan result back to Amistio.",
|
|
4618
|
+
"",
|
|
4619
|
+
appEvaluationStart,
|
|
4620
|
+
'{"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"]}',
|
|
4621
|
+
appEvaluationEnd,
|
|
4622
|
+
"",
|
|
4623
|
+
"Do not put Markdown fences around the markers. Do not implement improvements or cleanup."
|
|
4624
|
+
].join("\n");
|
|
4625
|
+
}
|
|
3971
4626
|
function createIssueDiagnosisPrompt(workItem, context) {
|
|
3972
4627
|
const issue = context?.issue;
|
|
3973
4628
|
const approvedContext = (context?.documents ?? []).filter((document) => document.status === "approved" || document.syncState === "approved" || document.syncState === "synced").slice(0, 16).map((document) => [
|
|
@@ -4018,11 +4673,69 @@ function createIssueDiagnosisPrompt(workItem, context) {
|
|
|
4018
4673
|
"Do not put Markdown fences around the markers. Do not implement the fix."
|
|
4019
4674
|
].join("\n");
|
|
4020
4675
|
}
|
|
4021
|
-
function
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4676
|
+
function formatProjectContextMap(map, query, contextPack) {
|
|
4677
|
+
if ((!map || map.status !== "approved") && !contextPack) {
|
|
4678
|
+
return "No approved living context map was loaded. Use approved brain records and inspect local source only where needed.";
|
|
4679
|
+
}
|
|
4680
|
+
if (!map || map.status !== "approved") {
|
|
4681
|
+
return [
|
|
4682
|
+
`Context pack ID: ${contextPack?.packId ?? "unknown"}`,
|
|
4683
|
+
contextPack?.freshnessWarnings.length ? `Freshness warnings: ${contextPack.freshnessWarnings.join(" | ")}` : "Freshness warnings: none recorded",
|
|
4684
|
+
"No approved living context map was loaded. Use approved brain records and inspect local source only where needed."
|
|
4685
|
+
].join("\n");
|
|
4686
|
+
}
|
|
4687
|
+
const tokens2 = tokenizeForContext(query);
|
|
4688
|
+
const candidateSlices = contextPack?.slices.length ? contextPack.slices : map.slices;
|
|
4689
|
+
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));
|
|
4690
|
+
const selectedSlices = rankedSlices.filter((entry) => entry.score > 0).map((entry) => entry.slice).slice(0, 8);
|
|
4691
|
+
const slices = (selectedSlices.length ? selectedSlices : candidateSlices.slice(0, 8)).map((slice) => [
|
|
4692
|
+
`### ${slice.title}`,
|
|
4693
|
+
`sliceId: ${slice.sliceId}`,
|
|
4694
|
+
`kind: ${slice.kind}`,
|
|
4695
|
+
`freshness: ${slice.freshness}`,
|
|
4696
|
+
`confidence: ${Math.round(slice.confidence * 100)}%`,
|
|
4697
|
+
slice.repoPaths.length ? `repoPaths: ${slice.repoPaths.slice(0, 12).join(", ")}` : "repoPaths: none recorded",
|
|
4698
|
+
slice.summary
|
|
4699
|
+
].join("\n")).join("\n\n");
|
|
4700
|
+
const entities = (contextPack?.entities.length ? contextPack.entities : map.entities).slice(0, 12).map((entity) => `- ${entity.name} (${entity.entityType}): ${entity.summary ?? entity.entityId}`).join("\n");
|
|
4701
|
+
const relations = (contextPack?.relations.length ? contextPack.relations : map.relations).slice(0, 12).map((relation) => `- ${relation.fromId} ${relation.relationType} ${relation.toId}: ${relation.summary}`).join("\n");
|
|
4702
|
+
return [
|
|
4703
|
+
contextPack ? `Context pack ID: ${contextPack.packId}` : "Context pack ID: not recorded",
|
|
4704
|
+
`Map ID: ${map.projectContextMapId}`,
|
|
4705
|
+
`Version: ${map.version}`,
|
|
4706
|
+
contextPack?.tokenEstimate !== void 0 ? `Pack token estimate: ${contextPack.tokenEstimate}` : "Pack token estimate: not recorded",
|
|
4707
|
+
`Coverage: ${map.coverage.status}; slices ${map.coverage.sliceCount}; stale ${map.coverage.staleSliceCount}`,
|
|
4708
|
+
contextPack?.freshnessWarnings.length ? `Freshness warnings: ${contextPack.freshnessWarnings.join(" | ")}` : "Freshness warnings: none recorded",
|
|
4709
|
+
map.coverage.missingAreas.length ? `Missing areas: ${map.coverage.missingAreas.join(", ")}` : "Missing areas: none recorded",
|
|
4710
|
+
map.coverage.warnings.length ? `Warnings: ${map.coverage.warnings.join(" | ")}` : "Warnings: none recorded",
|
|
4711
|
+
"",
|
|
4712
|
+
map.summary,
|
|
4713
|
+
"",
|
|
4714
|
+
"### Relevant Context Slices",
|
|
4715
|
+
slices || "No slices recorded.",
|
|
4716
|
+
"",
|
|
4717
|
+
"### Entities",
|
|
4718
|
+
entities || "No entities recorded.",
|
|
4719
|
+
"",
|
|
4720
|
+
"### Relations",
|
|
4721
|
+
relations || "No relations recorded."
|
|
4722
|
+
].join("\n");
|
|
4723
|
+
}
|
|
4724
|
+
function tokenizeForContext(value) {
|
|
4725
|
+
return [...new Set(value.toLowerCase().match(/[a-z0-9][a-z0-9_-]{2,}/g) ?? [])].slice(0, 24);
|
|
4726
|
+
}
|
|
4727
|
+
function scoreContextSlice(tokens2, title, summary, metadata) {
|
|
4728
|
+
const haystackTitle = title.toLowerCase();
|
|
4729
|
+
const haystackSummary = summary.toLowerCase();
|
|
4730
|
+
const haystackMetadata = metadata.toLowerCase();
|
|
4731
|
+
return tokens2.reduce((score, token) => score + (haystackTitle.includes(token) ? 5 : 0) + (haystackMetadata.includes(token) ? 3 : 0) + (haystackSummary.includes(token) ? 1 : 0), 0);
|
|
4732
|
+
}
|
|
4733
|
+
function createImpactPreviewPrompt(workItem, context) {
|
|
4734
|
+
const implementationPrompt = context?.implementationPrompt;
|
|
4735
|
+
const livingContext = formatProjectContextMap(context?.activeMap, workItem.sourceWish ?? workItem.title, context?.contextPack);
|
|
4736
|
+
const approvedContext = (context?.documents ?? []).filter((document) => document.status === "approved" || document.syncState === "approved" || document.syncState === "synced").slice(0, 16).map((document) => [
|
|
4737
|
+
`### ${document.title}`,
|
|
4738
|
+
`documentId: ${document.documentId}`,
|
|
4026
4739
|
`documentType: ${document.documentType}`,
|
|
4027
4740
|
`repoPath: ${document.repoPath}`,
|
|
4028
4741
|
`revision: ${document.revision}`,
|
|
@@ -4048,6 +4761,10 @@ function createImpactPreviewPrompt(workItem, context) {
|
|
|
4048
4761
|
"",
|
|
4049
4762
|
implementationPrompt ? implementationPrompt.content : workItem.sourceWish ?? workItem.title,
|
|
4050
4763
|
"",
|
|
4764
|
+
"## Approved Living Project Context Map",
|
|
4765
|
+
"",
|
|
4766
|
+
livingContext,
|
|
4767
|
+
"",
|
|
4051
4768
|
"## Approved Project Brain Context",
|
|
4052
4769
|
"",
|
|
4053
4770
|
approvedContext || "No approved project-brain records were loaded. Inspect the local repository and explain the gap in the report.",
|
|
@@ -4073,6 +4790,7 @@ function createImpactPreviewPrompt(workItem, context) {
|
|
|
4073
4790
|
}
|
|
4074
4791
|
function createAssistantQuestionPrompt(workItem, context) {
|
|
4075
4792
|
const question = context?.question.content ?? workItem.sourceWish ?? workItem.title;
|
|
4793
|
+
const livingContext = formatProjectContextMap(context?.activeMap, question, context?.contextPack);
|
|
4076
4794
|
const priorMessages = (context?.messages ?? []).filter((message) => message.messageId !== context?.question.messageId).slice(-12).map((message) => `- ${message.role} / ${message.status}: ${message.content}`).join("\n");
|
|
4077
4795
|
const brainContext = (context?.documents ?? []).filter((document) => document.status === "approved" || document.syncState === "approved" || document.syncState === "synced").slice(0, 12).map((document) => [
|
|
4078
4796
|
`### ${document.title}`,
|
|
@@ -4099,6 +4817,10 @@ function createAssistantQuestionPrompt(workItem, context) {
|
|
|
4099
4817
|
`Project ID: ${workItem.projectId}`,
|
|
4100
4818
|
`Assistant message ID: ${workItem.assistantMessageId ?? context?.question.messageId ?? "unknown"}`,
|
|
4101
4819
|
"",
|
|
4820
|
+
"## Approved Living Project Context Map",
|
|
4821
|
+
"",
|
|
4822
|
+
livingContext,
|
|
4823
|
+
"",
|
|
4102
4824
|
"## Approved Project Brain Context",
|
|
4103
4825
|
"",
|
|
4104
4826
|
brainContext || "No approved project-brain records were loaded. Say what you inspected locally and avoid pretending citations exist.",
|
|
@@ -4112,6 +4834,7 @@ function createAssistantQuestionPrompt(workItem, context) {
|
|
|
4112
4834
|
"- Keep repository source, secrets, tokens, local credential paths, and provider session references local.",
|
|
4113
4835
|
"- Summarize findings instead of dumping source code. Tiny identifiers, filenames, and short excerpts are acceptable when necessary.",
|
|
4114
4836
|
'- Use sourceBoundary "projectBrain" when the answer only uses project-brain records.',
|
|
4837
|
+
"- Treat the approved living context map as project-brain context; use it to avoid broad repository rereads when it is fresh and relevant.",
|
|
4115
4838
|
'- Use sourceBoundary "localSource" when you inspected local repository files.',
|
|
4116
4839
|
'- Use sourceBoundary "mixed" when both project-brain records and local files shaped the answer.',
|
|
4117
4840
|
"- Include citations for project-brain records with documentId/title/repoPath where relevant.",
|
|
@@ -4214,8 +4937,35 @@ function parseSecurityPostureScanResult(output) {
|
|
|
4214
4937
|
const parsed = JSON.parse(stripJsonFence(payload));
|
|
4215
4938
|
return securityPostureScanResultSchema.parse(parsed);
|
|
4216
4939
|
}
|
|
4940
|
+
function parseAppEvaluationScanResult(output) {
|
|
4941
|
+
const start = output.indexOf(appEvaluationStart);
|
|
4942
|
+
const end = output.indexOf(appEvaluationEnd, start + appEvaluationStart.length);
|
|
4943
|
+
if (start === -1 || end === -1 || end <= start) {
|
|
4944
|
+
throw new Error("Local AI scan did not return an Amistio app evaluation block.");
|
|
4945
|
+
}
|
|
4946
|
+
const payload = output.slice(start + appEvaluationStart.length, end).trim();
|
|
4947
|
+
const parsed = JSON.parse(stripJsonFence(payload));
|
|
4948
|
+
return appEvaluationScanResultSchema.parse(parsed);
|
|
4949
|
+
}
|
|
4950
|
+
function parseProjectContextRefreshResult(output) {
|
|
4951
|
+
const start = output.indexOf(projectContextRefreshStart);
|
|
4952
|
+
const end = output.indexOf(projectContextRefreshEnd, start + projectContextRefreshStart.length);
|
|
4953
|
+
if (start === -1 || end === -1 || end <= start) {
|
|
4954
|
+
throw new Error("Local AI refresh did not return an Amistio project context block.");
|
|
4955
|
+
}
|
|
4956
|
+
const payload = output.slice(start + projectContextRefreshStart.length, end).trim();
|
|
4957
|
+
const parsed = JSON.parse(stripJsonFence(payload));
|
|
4958
|
+
return projectContextRefreshResultSchema.parse(parsed);
|
|
4959
|
+
}
|
|
4960
|
+
function projectContextRefreshSubmissionFailureSummary(result) {
|
|
4961
|
+
if (result.refresh.status !== "failed" && result.workItem.status !== "failed") {
|
|
4962
|
+
return void 0;
|
|
4963
|
+
}
|
|
4964
|
+
return result.refresh.error ?? result.workItem.lastStatusMessage ?? "Server rejected the project context refresh result.";
|
|
4965
|
+
}
|
|
4217
4966
|
function createBrainGenerationPrompt(workItem) {
|
|
4218
4967
|
const wish = workItem.sourceWish ?? workItem.title;
|
|
4968
|
+
const artifactFormatPreference = workItem.artifactFormatPreference ?? "markdown";
|
|
4219
4969
|
return [
|
|
4220
4970
|
"# Amistio Brain Generation",
|
|
4221
4971
|
"",
|
|
@@ -4232,6 +4982,7 @@ function createBrainGenerationPrompt(workItem) {
|
|
|
4232
4982
|
`Work item ID: ${workItem.workItemId}`,
|
|
4233
4983
|
`Project ID: ${workItem.projectId}`,
|
|
4234
4984
|
`Generated draft ID: ${workItem.generatedDraftId ?? "unknown"}`,
|
|
4985
|
+
`Artifact format preference: ${artifactFormatPreference}`,
|
|
4235
4986
|
"",
|
|
4236
4987
|
"## Read First",
|
|
4237
4988
|
"",
|
|
@@ -4241,7 +4992,7 @@ function createBrainGenerationPrompt(workItem) {
|
|
|
4241
4992
|
"",
|
|
4242
4993
|
"## Generate Artifacts",
|
|
4243
4994
|
"",
|
|
4244
|
-
"Return
|
|
4995
|
+
"Return artifacts that should enter human review before implementation. Use only these document types and matching repo roots:",
|
|
4245
4996
|
"",
|
|
4246
4997
|
"- architecture -> docs/architecture/",
|
|
4247
4998
|
"- context -> docs/context/",
|
|
@@ -4251,20 +5002,35 @@ function createBrainGenerationPrompt(workItem) {
|
|
|
4251
5002
|
"- plan -> docs/plans/",
|
|
4252
5003
|
"- prompt -> docs/prompts/",
|
|
4253
5004
|
"- workflow -> docs/workflows/",
|
|
5005
|
+
"- HTML companions -> docs/html/<same-folder>/ using .html, for example docs/html/plans/PLAN-example.html",
|
|
5006
|
+
"",
|
|
5007
|
+
artifactFormatPreferenceInstructions(artifactFormatPreference),
|
|
4254
5008
|
"",
|
|
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.",
|
|
5009
|
+
"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
5010
|
"",
|
|
4257
5011
|
"## Output Contract",
|
|
4258
5012
|
"",
|
|
4259
5013
|
"Print exactly one JSON object between the markers below. The CLI will submit only this structured result back to Amistio.",
|
|
4260
5014
|
"",
|
|
4261
5015
|
generationResultStart,
|
|
4262
|
-
'{"artifacts":[{"documentType":"plan","title":"Plan: Example","repoPath":"docs/plans/PLAN-example.md","content":"# Plan: Example\\n\\n## Goal\\n..."}]}',
|
|
5016
|
+
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
5017
|
generationResultEnd,
|
|
4264
5018
|
"",
|
|
4265
5019
|
"Do not put Markdown fences around the markers. Do not claim implementation is complete."
|
|
4266
5020
|
].join("\n");
|
|
4267
5021
|
}
|
|
5022
|
+
function artifactFormatPreferenceInstructions(preference) {
|
|
5023
|
+
if (preference === "html") {
|
|
5024
|
+
return "The user chose HTML. Generate .html artifacts under docs/html/<folder>/ and set contentFormat to html.";
|
|
5025
|
+
}
|
|
5026
|
+
if (preference === "both") {
|
|
5027
|
+
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.";
|
|
5028
|
+
}
|
|
5029
|
+
if (preference === "auto") {
|
|
5030
|
+
return "Choose Markdown or HTML based on the artifact content. Use Markdown for text-first docs and HTML for visual or layout-sensitive artifacts.";
|
|
5031
|
+
}
|
|
5032
|
+
return "The user chose Markdown. Generate Markdown artifacts under docs/<folder>/ and set contentFormat to markdown or omit it.";
|
|
5033
|
+
}
|
|
4268
5034
|
function stripJsonFence(value) {
|
|
4269
5035
|
const trimmed = value.trim();
|
|
4270
5036
|
if (!trimmed.startsWith("```")) {
|
|
@@ -4289,7 +5055,13 @@ function formatWatchIdleLine(action, intervalSeconds) {
|
|
|
4289
5055
|
}
|
|
4290
5056
|
function shouldPrintWatchState(action, previous, nowMs, reminderMs = watchStateReminderMs) {
|
|
4291
5057
|
const key = watchStateKey(action);
|
|
4292
|
-
|
|
5058
|
+
if (!previous || previous.key !== key) {
|
|
5059
|
+
return true;
|
|
5060
|
+
}
|
|
5061
|
+
if (action.kind === "workCompleted") {
|
|
5062
|
+
return false;
|
|
5063
|
+
}
|
|
5064
|
+
return nowMs - previous.printedAtMs >= reminderMs;
|
|
4293
5065
|
}
|
|
4294
5066
|
function watchStateKey(action) {
|
|
4295
5067
|
return [action.kind, action.message, action.workItemId, action.documentId, action.runnerId].filter(Boolean).join(":");
|
|
@@ -4469,7 +5241,8 @@ async function scanLegacyDocuments(options) {
|
|
|
4469
5241
|
skipped.push({ repoPath, reason: "excluded" });
|
|
4470
5242
|
continue;
|
|
4471
5243
|
}
|
|
4472
|
-
|
|
5244
|
+
const contentFormat = inferContentFormatFromRepoPath(repoPath);
|
|
5245
|
+
if (!contentFormat) {
|
|
4473
5246
|
skipped.push({ repoPath, reason: "notMarkdown" });
|
|
4474
5247
|
continue;
|
|
4475
5248
|
}
|
|
@@ -4492,16 +5265,17 @@ async function scanLegacyDocuments(options) {
|
|
|
4492
5265
|
skipped.push({ repoPath, reason: "unreadable" });
|
|
4493
5266
|
continue;
|
|
4494
5267
|
}
|
|
4495
|
-
if (
|
|
5268
|
+
if (isAmistioManagedDocument(content)) {
|
|
4496
5269
|
skipped.push({ repoPath, reason: "alreadyManaged" });
|
|
4497
5270
|
continue;
|
|
4498
5271
|
}
|
|
4499
5272
|
const documentType = classifyLegacyDocument(repoPath, content);
|
|
4500
|
-
const destinationPath = uniqueDestinationPath(canonicalImportPath(repoPath, documentType), repoPath, usedDestinationPaths);
|
|
5273
|
+
const destinationPath = uniqueDestinationPath(canonicalImportPath(repoPath, documentType, contentFormat), repoPath, usedDestinationPaths);
|
|
4501
5274
|
candidates.push({
|
|
4502
5275
|
sourcePath: repoPath,
|
|
4503
5276
|
repoPath: destinationPath,
|
|
4504
5277
|
documentType,
|
|
5278
|
+
contentFormat,
|
|
4505
5279
|
title: inferTitle2(content, repoPath),
|
|
4506
5280
|
content,
|
|
4507
5281
|
contentHash: sha256ContentHash(content)
|
|
@@ -4524,6 +5298,7 @@ function buildImportedBrainDocuments(options) {
|
|
|
4524
5298
|
projectId: options.projectId,
|
|
4525
5299
|
documentId,
|
|
4526
5300
|
documentType: candidate.documentType,
|
|
5301
|
+
contentFormat: candidate.contentFormat,
|
|
4527
5302
|
title: candidate.title,
|
|
4528
5303
|
status: "approved",
|
|
4529
5304
|
repoPath: candidate.repoPath,
|
|
@@ -4602,9 +5377,6 @@ function wildcardMatch(pattern, repoPath) {
|
|
|
4602
5377
|
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
5378
|
return new RegExp(`^${escaped}$`).test(repoPath);
|
|
4604
5379
|
}
|
|
4605
|
-
function isMarkdownDocument(repoPath) {
|
|
4606
|
-
return /\.(md|mdx)$/i.test(repoPath);
|
|
4607
|
-
}
|
|
4608
5380
|
function isExcludedRepoPath(repoPath) {
|
|
4609
5381
|
if (isControlPlaneTemplateRepoPath(repoPath)) return true;
|
|
4610
5382
|
if (excludedFileNames.has(repoPath)) return true;
|
|
@@ -4613,9 +5385,12 @@ function isExcludedRepoPath(repoPath) {
|
|
|
4613
5385
|
const basename = segments[segments.length - 1]?.toLowerCase() ?? "";
|
|
4614
5386
|
return basename.startsWith(".env") || basename.includes("secret") || basename.includes("token") || basename.includes("credential") || basename.endsWith(".lock");
|
|
4615
5387
|
}
|
|
4616
|
-
function
|
|
5388
|
+
function isAmistioManagedDocument(content) {
|
|
4617
5389
|
const frontmatter = parseFrontmatter(content);
|
|
4618
|
-
|
|
5390
|
+
if (typeof frontmatter.amistioDocumentId === "string" && frontmatter.amistioDocumentId.trim().length > 0) {
|
|
5391
|
+
return true;
|
|
5392
|
+
}
|
|
5393
|
+
return /^\s*<!--[\s\S]*?amistioDocumentId\s*:/i.test(content);
|
|
4619
5394
|
}
|
|
4620
5395
|
function classifyLegacyDocument(repoPath, content) {
|
|
4621
5396
|
const haystack = `${repoPath} ${inferTitle2(content, repoPath)}`.toLowerCase();
|
|
@@ -4628,7 +5403,7 @@ function classifyLegacyDocument(repoPath, content) {
|
|
|
4628
5403
|
if (/\b(workflow|workflows|runbook|playbook|process|procedure|ops)\b/.test(haystack)) return "workflow";
|
|
4629
5404
|
return "context";
|
|
4630
5405
|
}
|
|
4631
|
-
function canonicalImportPath(sourcePath, documentType) {
|
|
5406
|
+
function canonicalImportPath(sourcePath, documentType, contentFormat) {
|
|
4632
5407
|
if (isCanonicalControlPlanePath(sourcePath)) {
|
|
4633
5408
|
return sourcePath;
|
|
4634
5409
|
}
|
|
@@ -4636,7 +5411,8 @@ function canonicalImportPath(sourcePath, documentType) {
|
|
|
4636
5411
|
return `docs/${sourcePath}`;
|
|
4637
5412
|
}
|
|
4638
5413
|
const baseSlug = slugFromPath(sourcePath);
|
|
4639
|
-
|
|
5414
|
+
const extension = contentFormat === "html" ? "html" : "md";
|
|
5415
|
+
return `${documentFolderByType[documentType]}/imported/${baseSlug}.${extension}`;
|
|
4640
5416
|
}
|
|
4641
5417
|
function uniqueDestinationPath(basePath, sourcePath, usedPaths) {
|
|
4642
5418
|
if (!usedPaths.has(basePath)) {
|
|
@@ -4651,8 +5427,8 @@ function uniqueDestinationPath(basePath, sourcePath, usedPaths) {
|
|
|
4651
5427
|
return uniquePath;
|
|
4652
5428
|
}
|
|
4653
5429
|
function isCanonicalControlPlanePath(repoPath) {
|
|
4654
|
-
const [firstSegment, secondSegment] = normalizeRepoPath4(repoPath).split("/");
|
|
4655
|
-
return firstSegment === "docs" && Boolean(secondSegment && controlPlaneRoots2.includes(secondSegment));
|
|
5430
|
+
const [firstSegment, secondSegment, thirdSegment] = normalizeRepoPath4(repoPath).split("/");
|
|
5431
|
+
return firstSegment === "docs" && Boolean(secondSegment === "html" && thirdSegment && controlPlaneRoots2.includes(thirdSegment) || secondSegment && controlPlaneRoots2.includes(secondSegment));
|
|
4656
5432
|
}
|
|
4657
5433
|
function isLegacyControlPlanePath(repoPath) {
|
|
4658
5434
|
const [firstSegment] = repoPath.split("/");
|
|
@@ -4662,6 +5438,8 @@ function inferTitle2(content, repoPath) {
|
|
|
4662
5438
|
const body = stripFrontmatter(content);
|
|
4663
5439
|
const heading = body.split("\n").find((line) => /^#\s+/.test(line))?.replace(/^#\s+/, "").trim();
|
|
4664
5440
|
if (heading) return heading;
|
|
5441
|
+
const htmlHeading = body.match(/<h1\b[^>]*>([\s\S]*?)<\/h1>/i)?.[1]?.replace(/<[^>]+>/g, "").trim();
|
|
5442
|
+
if (htmlHeading) return htmlHeading;
|
|
4665
5443
|
const basename = path10.posix.basename(repoPath, path10.posix.extname(repoPath)).replace(/[-_]+/g, " ").trim();
|
|
4666
5444
|
return titleCase(basename || "Imported Document");
|
|
4667
5445
|
}
|
|
@@ -4673,7 +5451,7 @@ function stripFrontmatter(content) {
|
|
|
4673
5451
|
return closingLineEnd === -1 ? "" : content.slice(closingLineEnd + 1);
|
|
4674
5452
|
}
|
|
4675
5453
|
function slugFromPath(repoPath) {
|
|
4676
|
-
const withoutExtension = repoPath.replace(/\.(md|mdx)$/i, "");
|
|
5454
|
+
const withoutExtension = repoPath.replace(/\.(md|mdx|html?)$/i, "");
|
|
4677
5455
|
const slug = withoutExtension.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 90);
|
|
4678
5456
|
return slug || "imported-document";
|
|
4679
5457
|
}
|
|
@@ -4772,6 +5550,7 @@ async function runOfficialCliUpdateWithRuntimeRefresh(options) {
|
|
|
4772
5550
|
if (!options.restartBackgroundRunner) {
|
|
4773
5551
|
return {
|
|
4774
5552
|
succeeded: false,
|
|
5553
|
+
stopRunner: true,
|
|
4775
5554
|
message: `${updateResult.message} Background runner restart was not available after update. Restart the runner manually to load the updated CLI version.`,
|
|
4776
5555
|
error: "Background runner restart hook was not provided."
|
|
4777
5556
|
};
|
|
@@ -4786,6 +5565,7 @@ async function runOfficialCliUpdateWithRuntimeRefresh(options) {
|
|
|
4786
5565
|
}
|
|
4787
5566
|
return {
|
|
4788
5567
|
succeeded: false,
|
|
5568
|
+
stopRunner: true,
|
|
4789
5569
|
message: `${updateResult.message} Background runner restart failed after update. Restart the runner manually to load the updated CLI version.`,
|
|
4790
5570
|
error: restartResult.error ?? restartResult.message
|
|
4791
5571
|
};
|
|
@@ -4907,6 +5687,186 @@ function errorMessage2(error) {
|
|
|
4907
5687
|
return error instanceof Error ? error.message : String(error);
|
|
4908
5688
|
}
|
|
4909
5689
|
|
|
5690
|
+
// src/implementation-handoff.ts
|
|
5691
|
+
import { execFile as execFile6 } from "node:child_process";
|
|
5692
|
+
import path13 from "node:path";
|
|
5693
|
+
import { promisify as promisify6 } from "node:util";
|
|
5694
|
+
var execFileAsync6 = promisify6(execFile6);
|
|
5695
|
+
async function completeImplementationHandoff(input) {
|
|
5696
|
+
const run = input.commandRunner ?? defaultCommandRunner;
|
|
5697
|
+
const headBranch = input.worktreeIsolation?.branch ?? input.workItem.executionBranch;
|
|
5698
|
+
const baseBranch = input.baseBranch ?? input.repositoryLink?.defaultBranch ?? "main";
|
|
5699
|
+
if (!headBranch) {
|
|
5700
|
+
return blockedHandoff({ baseBranch, message: "Implementation handoff needs an execution branch before it can create a pull request." });
|
|
5701
|
+
}
|
|
5702
|
+
const common = {
|
|
5703
|
+
provider: input.repositoryLink?.provider ?? "github",
|
|
5704
|
+
baseBranch,
|
|
5705
|
+
headBranch
|
|
5706
|
+
};
|
|
5707
|
+
try {
|
|
5708
|
+
const unmergedFiles = await gitOutput2(run, input.worktreePath, ["diff", "--name-only", "--diff-filter=U"]);
|
|
5709
|
+
if (unmergedFiles.trim()) {
|
|
5710
|
+
return blockedHandoff({ ...common, message: "Implementation handoff is blocked because the worktree has unresolved merge conflicts." });
|
|
5711
|
+
}
|
|
5712
|
+
const status = await gitOutput2(run, input.worktreePath, ["status", "--porcelain=v1", "-z", "--untracked-files=all"]);
|
|
5713
|
+
if (!status) {
|
|
5714
|
+
return {
|
|
5715
|
+
...common,
|
|
5716
|
+
status: "noChanges",
|
|
5717
|
+
cleanupStatus: "notApplicable",
|
|
5718
|
+
message: "Local execution completed with no repository changes to hand off."
|
|
5719
|
+
};
|
|
5720
|
+
}
|
|
5721
|
+
const remoteName = await resolveRemoteName(run, input.worktreePath);
|
|
5722
|
+
const remoteUrl = await gitOutput2(run, input.worktreePath, ["remote", "get-url", remoteName]);
|
|
5723
|
+
const provider = input.repositoryLink?.provider ?? inferProvider(remoteUrl);
|
|
5724
|
+
if (provider !== "github") {
|
|
5725
|
+
return blockedHandoff({
|
|
5726
|
+
...common,
|
|
5727
|
+
provider,
|
|
5728
|
+
remoteName,
|
|
5729
|
+
message: "Automated pull request handoff currently requires a GitHub remote. Commit and push manually, or link a GitHub repository."
|
|
5730
|
+
});
|
|
5731
|
+
}
|
|
5732
|
+
await gitOutput2(run, input.worktreePath, ["add", "-A"]);
|
|
5733
|
+
await gitOutput2(run, input.worktreePath, ["commit", "-m", commitSubject(input.workItem), "-m", commitBody(input)]);
|
|
5734
|
+
const commitSha = await gitOutput2(run, input.worktreePath, ["rev-parse", "HEAD"]);
|
|
5735
|
+
await gitOutput2(run, input.worktreePath, ["push", "--set-upstream", remoteName, headBranch]);
|
|
5736
|
+
const pullRequest = await ensureGithubPullRequest(run, input.worktreePath, { baseBranch, headBranch, workItem: input.workItem, ...input.verificationSummary ? { verificationSummary: input.verificationSummary } : {} });
|
|
5737
|
+
const cleanup = await cleanupWorktree(run, input);
|
|
5738
|
+
return {
|
|
5739
|
+
provider: "github",
|
|
5740
|
+
status: "prReady",
|
|
5741
|
+
baseBranch,
|
|
5742
|
+
headBranch,
|
|
5743
|
+
remoteName,
|
|
5744
|
+
commitSha,
|
|
5745
|
+
prNumber: pullRequest.number,
|
|
5746
|
+
prUrl: pullRequest.url,
|
|
5747
|
+
cleanupStatus: cleanup.status,
|
|
5748
|
+
...cleanup.message ? { cleanupMessage: cleanup.message } : {},
|
|
5749
|
+
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."
|
|
5750
|
+
};
|
|
5751
|
+
} catch (error) {
|
|
5752
|
+
return blockedHandoff({ ...common, message: "Implementation handoff is blocked and the local worktree was preserved for recovery.", error: safeErrorMessage(error) });
|
|
5753
|
+
}
|
|
5754
|
+
}
|
|
5755
|
+
async function ensureGithubPullRequest(run, cwd, input) {
|
|
5756
|
+
const existing = await run("gh", ["pr", "list", "--head", input.headBranch, "--base", input.baseBranch, "--state", "open", "--json", "number,url", "--limit", "1"], { cwd });
|
|
5757
|
+
const parsed = parsePullRequestList(existing.stdout);
|
|
5758
|
+
if (parsed) {
|
|
5759
|
+
return parsed;
|
|
5760
|
+
}
|
|
5761
|
+
const created = await run("gh", ["pr", "create", "--base", input.baseBranch, "--head", input.headBranch, "--title", pullRequestTitle(input.workItem), "--body", pullRequestBody(input)], { cwd });
|
|
5762
|
+
const url = extractPullRequestUrl(created.stdout);
|
|
5763
|
+
if (!url) {
|
|
5764
|
+
throw new Error("GitHub CLI did not return a pull request URL.");
|
|
5765
|
+
}
|
|
5766
|
+
const number = pullRequestNumber(url);
|
|
5767
|
+
if (!number) {
|
|
5768
|
+
throw new Error("GitHub CLI returned a pull request URL without a parseable number.");
|
|
5769
|
+
}
|
|
5770
|
+
return { number, url };
|
|
5771
|
+
}
|
|
5772
|
+
async function cleanupWorktree(run, input) {
|
|
5773
|
+
const status = await gitOutput2(run, input.worktreePath, ["status", "--porcelain=v1", "-z", "--untracked-files=all"]);
|
|
5774
|
+
if (status) {
|
|
5775
|
+
return { status: "failed", message: "Cleanup skipped because the worktree is not clean after PR handoff." };
|
|
5776
|
+
}
|
|
5777
|
+
try {
|
|
5778
|
+
await gitOutput2(run, input.primaryRepoRoot || path13.dirname(input.worktreePath), ["worktree", "remove", input.worktreePath]);
|
|
5779
|
+
return { status: "completed" };
|
|
5780
|
+
} catch (error) {
|
|
5781
|
+
return { status: "failed", message: `Cleanup failed: ${safeErrorMessage(error)}` };
|
|
5782
|
+
}
|
|
5783
|
+
}
|
|
5784
|
+
async function resolveRemoteName(run, cwd) {
|
|
5785
|
+
const remotes = (await gitOutput2(run, cwd, ["remote"])).split(/\r?\n/g).map((remote) => remote.trim()).filter(Boolean);
|
|
5786
|
+
if (!remotes.length) {
|
|
5787
|
+
throw new Error("No Git remote is configured for PR handoff.");
|
|
5788
|
+
}
|
|
5789
|
+
return remotes.includes("origin") ? "origin" : remotes[0];
|
|
5790
|
+
}
|
|
5791
|
+
async function gitOutput2(run, cwd, args) {
|
|
5792
|
+
const result = await run("git", args, { cwd });
|
|
5793
|
+
return result.stdout.trim();
|
|
5794
|
+
}
|
|
5795
|
+
function blockedHandoff(input) {
|
|
5796
|
+
return {
|
|
5797
|
+
status: "blocked",
|
|
5798
|
+
cleanupStatus: "pending",
|
|
5799
|
+
...input
|
|
5800
|
+
};
|
|
5801
|
+
}
|
|
5802
|
+
function inferProvider(remoteUrl) {
|
|
5803
|
+
try {
|
|
5804
|
+
return parseRepositoryCloneUrl(remoteUrl).provider ?? "other";
|
|
5805
|
+
} catch {
|
|
5806
|
+
return "other";
|
|
5807
|
+
}
|
|
5808
|
+
}
|
|
5809
|
+
function parsePullRequestList(stdout) {
|
|
5810
|
+
const parsed = JSON.parse(stdout || "[]");
|
|
5811
|
+
const first = parsed[0];
|
|
5812
|
+
if (typeof first?.number === "number" && typeof first.url === "string" && first.url) {
|
|
5813
|
+
return { number: first.number, url: first.url };
|
|
5814
|
+
}
|
|
5815
|
+
return void 0;
|
|
5816
|
+
}
|
|
5817
|
+
function extractPullRequestUrl(stdout) {
|
|
5818
|
+
return stdout.split(/\s+/g).find((value) => /^https:\/\/github\.com\/[^\s]+\/pull\/\d+$/i.test(value));
|
|
5819
|
+
}
|
|
5820
|
+
function pullRequestNumber(url) {
|
|
5821
|
+
const match = /\/pull\/(\d+)(?:$|[?#])/.exec(url);
|
|
5822
|
+
return match ? Number.parseInt(match[1], 10) : void 0;
|
|
5823
|
+
}
|
|
5824
|
+
function commitSubject(workItem) {
|
|
5825
|
+
return truncate(`Amistio: ${workItem.title}`, 72);
|
|
5826
|
+
}
|
|
5827
|
+
function commitBody(input) {
|
|
5828
|
+
return [
|
|
5829
|
+
`Work item: ${input.workItem.workItemId}`,
|
|
5830
|
+
`Implementation scope: ${input.workItem.controllingAdrId ?? input.workItem.implementationScopeId ?? "unspecified"}`,
|
|
5831
|
+
"",
|
|
5832
|
+
"Generated by Amistio local runner.",
|
|
5833
|
+
"Do not include secrets or source patches in Amistio SaaS metadata."
|
|
5834
|
+
].join("\n");
|
|
5835
|
+
}
|
|
5836
|
+
function pullRequestTitle(workItem) {
|
|
5837
|
+
return truncate(workItem.title, 120);
|
|
5838
|
+
}
|
|
5839
|
+
function pullRequestBody(input) {
|
|
5840
|
+
return [
|
|
5841
|
+
`Amistio work item: ${input.workItem.workItemId}`,
|
|
5842
|
+
`Implementation scope: ${input.workItem.controllingAdrId ?? input.workItem.implementationScopeId ?? "unspecified"}`,
|
|
5843
|
+
`Base branch: ${input.baseBranch}`,
|
|
5844
|
+
`Head branch: ${input.headBranch}`,
|
|
5845
|
+
"",
|
|
5846
|
+
input.verificationSummary ?? "Verification summary was not reported by the local runner."
|
|
5847
|
+
].join("\n");
|
|
5848
|
+
}
|
|
5849
|
+
function truncate(value, maxLength) {
|
|
5850
|
+
return value.length <= maxLength ? value : value.slice(0, maxLength - 1).trimEnd();
|
|
5851
|
+
}
|
|
5852
|
+
async function defaultCommandRunner(command, args, options) {
|
|
5853
|
+
const { stdout, stderr } = await execFileAsync6(command, args, { cwd: options.cwd, maxBuffer: 1024 * 1024 });
|
|
5854
|
+
return { stdout, stderr };
|
|
5855
|
+
}
|
|
5856
|
+
function safeErrorMessage(error) {
|
|
5857
|
+
const scrub = (value) => redactLocalPaths(value.replace(/^Command failed:[^\n]*(\n)?/i, "").trim() || value);
|
|
5858
|
+
if (typeof error === "object" && error && "stderr" in error && typeof error.stderr === "string" && error.stderr.trim()) {
|
|
5859
|
+
return truncate(scrub(error.stderr.trim()), 600);
|
|
5860
|
+
}
|
|
5861
|
+
if (error instanceof Error) {
|
|
5862
|
+
return truncate(scrub(error.message), 600);
|
|
5863
|
+
}
|
|
5864
|
+
return truncate(scrub(String(error)), 600);
|
|
5865
|
+
}
|
|
5866
|
+
function redactLocalPaths(value) {
|
|
5867
|
+
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>");
|
|
5868
|
+
}
|
|
5869
|
+
|
|
4910
5870
|
// src/version.ts
|
|
4911
5871
|
import { readFileSync } from "node:fs";
|
|
4912
5872
|
function readCliPackageVersion() {
|
|
@@ -4926,7 +5886,7 @@ var DEFAULT_MAX_PREFLIGHT_ATTEMPTS = 3;
|
|
|
4926
5886
|
var DEFAULT_TOOL_TIMEOUT_SECONDS = 30 * 60;
|
|
4927
5887
|
var RUNNER_WORK_LEASE_SECONDS = 300;
|
|
4928
5888
|
var RUNNER_WORK_LEASE_RENEWAL_MS = 12e4;
|
|
4929
|
-
var runnerSupportedWorkKinds = ["brainGeneration", "implementation", "planRevision", "assistantQuestion", "impactPreview", "issueDiagnosis", "securityPostureScan"];
|
|
5889
|
+
var runnerSupportedWorkKinds = ["brainGeneration", "implementation", "planRevision", "assistantQuestion", "impactPreview", "issueDiagnosis", "securityPostureScan", "appEvaluationScan", "projectContextRefresh"];
|
|
4930
5890
|
program.name("amistio").description("Amistio project brain CLI").version(CLI_VERSION);
|
|
4931
5891
|
program.command("init").description("Create Amistio control-plane folders for a new project").option("--root <path>", "Repository root", defaultRoot).action(async (options) => {
|
|
4932
5892
|
const created = await initControlPlane(options.root);
|
|
@@ -5304,7 +6264,7 @@ program.command("run").description("Claim and run approved Amistio work locally"
|
|
|
5304
6264
|
projectId: context.metadata.amistioProjectId,
|
|
5305
6265
|
repositoryLinkId: context.metadata.repositoryLinkId,
|
|
5306
6266
|
runnerId,
|
|
5307
|
-
rootDir:
|
|
6267
|
+
rootDir: path14.resolve(options.root),
|
|
5308
6268
|
apiUrl: options.apiUrl,
|
|
5309
6269
|
args: buildBackgroundRunnerArgs(resolvedOptions)
|
|
5310
6270
|
});
|
|
@@ -5476,7 +6436,7 @@ runnerService.command("install").description("Install a user-level startup servi
|
|
|
5476
6436
|
projectId: context.metadata.amistioProjectId,
|
|
5477
6437
|
repositoryLinkId: context.metadata.repositoryLinkId,
|
|
5478
6438
|
runnerId,
|
|
5479
|
-
rootDir:
|
|
6439
|
+
rootDir: path14.resolve(options.root),
|
|
5480
6440
|
apiUrl: options.apiUrl,
|
|
5481
6441
|
args,
|
|
5482
6442
|
platform
|
|
@@ -5925,13 +6885,72 @@ async function runNextWorkItem({
|
|
|
5925
6885
|
return recordFinalizationFailure({ apiClient, error, isolationTelemetry, projectId, repositoryLinkId, runnerId, sessionContext, toolConfig, toolName: preview.toolName, workItem: result.workItem, durationMs: Date.now() - startedAt });
|
|
5926
6886
|
}
|
|
5927
6887
|
}
|
|
5928
|
-
|
|
6888
|
+
if (result.workItem.workKind === "appEvaluationScan") {
|
|
6889
|
+
try {
|
|
6890
|
+
return await finalizeAppEvaluationScanWork({
|
|
6891
|
+
apiClient,
|
|
6892
|
+
durationMs: Date.now() - startedAt,
|
|
6893
|
+
projectId,
|
|
6894
|
+
repositoryLinkId,
|
|
6895
|
+
runnerId,
|
|
6896
|
+
sessionContext,
|
|
6897
|
+
toolConfig,
|
|
6898
|
+
toolName: preview.toolName,
|
|
6899
|
+
toolResult,
|
|
6900
|
+
workItem: result.workItem
|
|
6901
|
+
});
|
|
6902
|
+
} catch (error) {
|
|
6903
|
+
return recordFinalizationFailure({ apiClient, error, isolationTelemetry, projectId, repositoryLinkId, runnerId, sessionContext, toolConfig, toolName: preview.toolName, workItem: result.workItem, durationMs: Date.now() - startedAt });
|
|
6904
|
+
}
|
|
6905
|
+
}
|
|
6906
|
+
if (result.workItem.workKind === "projectContextRefresh") {
|
|
6907
|
+
try {
|
|
6908
|
+
return await finalizeProjectContextRefreshWork({
|
|
6909
|
+
apiClient,
|
|
6910
|
+
durationMs: Date.now() - startedAt,
|
|
6911
|
+
projectId,
|
|
6912
|
+
repositoryLinkId,
|
|
6913
|
+
runnerId,
|
|
6914
|
+
sessionContext,
|
|
6915
|
+
toolConfig,
|
|
6916
|
+
toolName: preview.toolName,
|
|
6917
|
+
toolResult,
|
|
6918
|
+
workItem: result.workItem
|
|
6919
|
+
});
|
|
6920
|
+
} catch (error) {
|
|
6921
|
+
return recordFinalizationFailure({ apiClient, error, isolationTelemetry, projectId, repositoryLinkId, runnerId, sessionContext, toolConfig, toolName: preview.toolName, workItem: result.workItem, durationMs: Date.now() - startedAt });
|
|
6922
|
+
}
|
|
6923
|
+
}
|
|
6924
|
+
let finalStatus = toolResult.exitCode === 0 ? "completed" : "failed";
|
|
5929
6925
|
const durationMs = Date.now() - startedAt;
|
|
5930
6926
|
const failureExcerpt = toolResult.exitCode === 0 ? void 0 : truncateLogExcerpt(toolResult.stderr || toolResult.stdout);
|
|
6927
|
+
let implementationHandoff;
|
|
6928
|
+
let finalMessage = `${preview.toolName} exited with code ${toolResult.exitCode}.`;
|
|
6929
|
+
let finalError = failureExcerpt;
|
|
6930
|
+
if (toolResult.exitCode === 0 && (result.workItem.workKind ?? "implementation") === "implementation") {
|
|
6931
|
+
await recordRunnerMilestone(apiClient, projectId, result.workItem, runnerId, repositoryLinkId, {
|
|
6932
|
+
status: "running",
|
|
6933
|
+
summary: "Preparing GitHub PR handoff for implementation work.",
|
|
6934
|
+
idempotencyKey: `runner_milestone_handoff_started_${result.workItem.workItemId}_${result.workItem.attempt}`,
|
|
6935
|
+
metadata: { executionWorktreeKey: isolationTelemetry.executionWorktreeKey ?? "", executionBranch: isolationTelemetry.executionBranch ?? "" }
|
|
6936
|
+
});
|
|
6937
|
+
const repositoryLink = await loadWorkItemRepositoryLink(apiClient, projectId, result.workItem.repositoryLinkId ?? repositoryLinkId);
|
|
6938
|
+
implementationHandoff = await completeImplementationHandoff({
|
|
6939
|
+
primaryRepoRoot: root,
|
|
6940
|
+
...repositoryLink ? { repositoryLink } : {},
|
|
6941
|
+
verificationSummary: "Local execution reported completion.",
|
|
6942
|
+
workItem: result.workItem,
|
|
6943
|
+
...worktreeIsolation.isolation ? { worktreeIsolation: worktreeIsolation.isolation } : {},
|
|
6944
|
+
worktreePath: executionRoot
|
|
6945
|
+
});
|
|
6946
|
+
finalStatus = implementationHandoff.status === "prReady" || implementationHandoff.status === "noChanges" ? "completed" : "blocked";
|
|
6947
|
+
finalMessage = implementationHandoff.message ?? "Implementation handoff finished.";
|
|
6948
|
+
finalError = implementationHandoff.error;
|
|
6949
|
+
}
|
|
5931
6950
|
const updatedToolSession = await finalizeToolSession({
|
|
5932
6951
|
apiClient,
|
|
5933
6952
|
projectId,
|
|
5934
|
-
status:
|
|
6953
|
+
status: toolResult.exitCode === 0 ? "completed" : "failed",
|
|
5935
6954
|
runnerId,
|
|
5936
6955
|
workItemId: result.workItem.workItemId,
|
|
5937
6956
|
stdout: toolResult.stdout,
|
|
@@ -5951,8 +6970,9 @@ async function runNextWorkItem({
|
|
|
5951
6970
|
tool: preview.toolName,
|
|
5952
6971
|
...toolResult.model ? { model: toolResult.model } : {},
|
|
5953
6972
|
durationMs,
|
|
5954
|
-
message:
|
|
6973
|
+
message: finalMessage,
|
|
5955
6974
|
...isolationTelemetry,
|
|
6975
|
+
...finalStatus === "blocked" ? { blockerReason: finalMessage } : {},
|
|
5956
6976
|
sessionPolicy: sessionContext.policy,
|
|
5957
6977
|
sessionDecision: sessionContext.decision,
|
|
5958
6978
|
sessionDecisionReason: sessionContext.reason,
|
|
@@ -5961,19 +6981,34 @@ async function runNextWorkItem({
|
|
|
5961
6981
|
...toolResult.tokensIn !== void 0 ? { tokensIn: toolResult.tokensIn } : {},
|
|
5962
6982
|
...toolResult.tokensOut !== void 0 ? { tokensOut: toolResult.tokensOut } : {},
|
|
5963
6983
|
...toolResult.costUsd !== void 0 ? { costUsd: toolResult.costUsd } : {},
|
|
5964
|
-
...
|
|
6984
|
+
...implementationHandoff ? { implementationHandoff } : {},
|
|
6985
|
+
...finalError ? { error: finalError } : {}
|
|
5965
6986
|
}
|
|
5966
6987
|
);
|
|
5967
6988
|
await recordRunnerMilestone(apiClient, projectId, result.workItem, runnerId, repositoryLinkId, {
|
|
5968
6989
|
status: finalStatus,
|
|
5969
|
-
summary:
|
|
6990
|
+
summary: finalMessage,
|
|
5970
6991
|
idempotencyKey: `runner_milestone_finished_${result.workItem.workItemId}_${statusResult.workItem.idempotencyKey}`,
|
|
5971
|
-
metadata: {
|
|
6992
|
+
metadata: {
|
|
6993
|
+
tool: preview.toolName,
|
|
6994
|
+
durationMs,
|
|
6995
|
+
exitCode: toolResult.exitCode,
|
|
6996
|
+
verificationSummary: finalStatus === "completed" ? "Local execution reported completion." : "Local execution reported failure.",
|
|
6997
|
+
executionWorktreeKey: isolationTelemetry.executionWorktreeKey ?? "",
|
|
6998
|
+
executionBranch: isolationTelemetry.executionBranch ?? "",
|
|
6999
|
+
...implementationHandoff?.status ? { handoffStatus: implementationHandoff.status } : {},
|
|
7000
|
+
...implementationHandoff?.prUrl ? { prUrl: implementationHandoff.prUrl } : {},
|
|
7001
|
+
...implementationHandoff?.cleanupStatus ? { cleanupStatus: implementationHandoff.cleanupStatus } : {}
|
|
7002
|
+
}
|
|
5972
7003
|
});
|
|
5973
7004
|
await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
|
|
5974
7005
|
const durationSeconds = Math.round(durationMs / 1e3);
|
|
5975
7006
|
console.log(`Marked ${statusResult.workItem.workItemId} ${statusResult.workItem.status} after ${durationSeconds}s.`);
|
|
5976
|
-
return { status: finalStatus, exitCode: toolResult.exitCode };
|
|
7007
|
+
return { status: finalStatus, exitCode: finalStatus === "completed" ? toolResult.exitCode : 1 };
|
|
7008
|
+
}
|
|
7009
|
+
async function loadWorkItemRepositoryLink(apiClient, projectId, repositoryLinkId) {
|
|
7010
|
+
const { repositoryLinks } = await apiClient.listRepositoryLinks(projectId);
|
|
7011
|
+
return repositoryLinks.find((link) => link.repositoryLinkId === repositoryLinkId && link.status !== "revoked");
|
|
5977
7012
|
}
|
|
5978
7013
|
async function prepareWorktreeForClaimedItem({ apiClient, maxPreflightAttempts, projectId, repositoryLinkId, root, runnerId, toolConfig, workItem }) {
|
|
5979
7014
|
if (!needsGitWorktreeIsolation(workItem)) {
|
|
@@ -6177,10 +7212,10 @@ async function executeRunnerCommand(command, context) {
|
|
|
6177
7212
|
}
|
|
6178
7213
|
return runOfficialCliUpdateWithRuntimeRefresh({
|
|
6179
7214
|
mode: currentRunnerMode(),
|
|
6180
|
-
restartBackgroundRunner: () => restartCurrentRunner(context)
|
|
7215
|
+
restartBackgroundRunner: () => restartCurrentRunner(context, { useUpdatedCliExecutable: true })
|
|
6181
7216
|
});
|
|
6182
7217
|
}
|
|
6183
|
-
async function restartCurrentRunner(context) {
|
|
7218
|
+
async function restartCurrentRunner(context, options = {}) {
|
|
6184
7219
|
if (currentRunnerMode() !== "background") {
|
|
6185
7220
|
return { succeeded: false, message: "Foreground runners cannot be restarted remotely. Stop and start the local command manually." };
|
|
6186
7221
|
}
|
|
@@ -6189,7 +7224,7 @@ async function restartCurrentRunner(context) {
|
|
|
6189
7224
|
return { succeeded: false, message: "Background runner metadata was not found, so restart needs manual action." };
|
|
6190
7225
|
}
|
|
6191
7226
|
try {
|
|
6192
|
-
const replacement = await restartRunnerDaemonProcess(metadata, context.backgroundArgs);
|
|
7227
|
+
const replacement = await restartRunnerDaemonProcess(metadata, context.backgroundArgs, options.useUpdatedCliExecutable ? updatedCliRunnerLaunchOptions() : {});
|
|
6193
7228
|
return { succeeded: true, stopRunner: true, message: `Replacement background runner started with PID ${replacement.pid}.` };
|
|
6194
7229
|
} catch (error) {
|
|
6195
7230
|
return { succeeded: false, message: "Background restart failed.", error: errorMessage3(error) };
|
|
@@ -6638,23 +7673,217 @@ ${toolResult.stderr}`);
|
|
|
6638
7673
|
console.error(scanError ?? "Local runner security posture scan failed.");
|
|
6639
7674
|
return { status: "failed", exitCode: toolResult.exitCode || 1 };
|
|
6640
7675
|
}
|
|
7676
|
+
async function finalizeAppEvaluationScanWork({
|
|
7677
|
+
apiClient,
|
|
7678
|
+
durationMs,
|
|
7679
|
+
projectId,
|
|
7680
|
+
repositoryLinkId,
|
|
7681
|
+
runnerId,
|
|
7682
|
+
sessionContext,
|
|
7683
|
+
toolConfig,
|
|
7684
|
+
toolName,
|
|
7685
|
+
toolResult,
|
|
7686
|
+
workItem
|
|
7687
|
+
}) {
|
|
7688
|
+
let scanResult = void 0;
|
|
7689
|
+
let scanError;
|
|
7690
|
+
if (toolResult.exitCode === 0) {
|
|
7691
|
+
try {
|
|
7692
|
+
scanResult = parseAppEvaluationScanResult(`${toolResult.stdout}
|
|
7693
|
+
${toolResult.stderr}`);
|
|
7694
|
+
} catch (error) {
|
|
7695
|
+
scanError = errorMessage3(error);
|
|
7696
|
+
}
|
|
7697
|
+
} else {
|
|
7698
|
+
scanError = truncateLogExcerpt(toolResult.stderr || toolResult.stdout) || `${toolName} exited with code ${toolResult.exitCode}.`;
|
|
7699
|
+
}
|
|
7700
|
+
const finalStatus = scanResult ? "completed" : "failed";
|
|
7701
|
+
const updatedToolSession = await finalizeToolSession({
|
|
7702
|
+
apiClient,
|
|
7703
|
+
projectId,
|
|
7704
|
+
status: finalStatus,
|
|
7705
|
+
runnerId,
|
|
7706
|
+
workItemId: workItem.workItemId,
|
|
7707
|
+
stdout: toolResult.stdout,
|
|
7708
|
+
...sessionContext.toolSession ? { session: sessionContext.toolSession } : {},
|
|
7709
|
+
...toolResult.messageCount !== void 0 ? { messageCount: toolResult.messageCount } : {},
|
|
7710
|
+
...toolResult.tokensIn !== void 0 ? { tokensIn: toolResult.tokensIn } : {},
|
|
7711
|
+
...toolResult.tokensOut !== void 0 ? { tokensOut: toolResult.tokensOut } : {},
|
|
7712
|
+
...toolResult.costUsd !== void 0 ? { costUsd: toolResult.costUsd } : {}
|
|
7713
|
+
});
|
|
7714
|
+
const sessionTelemetry = {
|
|
7715
|
+
sessionPolicy: sessionContext.policy,
|
|
7716
|
+
sessionDecision: sessionContext.decision,
|
|
7717
|
+
sessionDecisionReason: sessionContext.reason,
|
|
7718
|
+
...updatedToolSession ? { toolSessionId: updatedToolSession.toolSessionId } : {},
|
|
7719
|
+
...updatedToolSession?.sessionGroupKey ? { sessionGroupKey: updatedToolSession.sessionGroupKey } : {}
|
|
7720
|
+
};
|
|
7721
|
+
if (scanResult) {
|
|
7722
|
+
const result = await apiClient.submitAppEvaluationScanResult(projectId, workItem.workItemId, {
|
|
7723
|
+
status: "completed",
|
|
7724
|
+
runnerId,
|
|
7725
|
+
idempotencyKey: `app_evaluation_${workItem.workItemId}_${randomUUID()}`,
|
|
7726
|
+
result: scanResult,
|
|
7727
|
+
tool: toolName,
|
|
7728
|
+
durationMs,
|
|
7729
|
+
...sessionTelemetry,
|
|
7730
|
+
message: `${toolName} returned an app evaluation scan.`
|
|
7731
|
+
});
|
|
7732
|
+
await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
|
|
7733
|
+
status: "completed",
|
|
7734
|
+
summary: `${toolName} returned an app evaluation scan.`,
|
|
7735
|
+
idempotencyKey: `runner_milestone_app_evaluation_completed_${workItem.workItemId}_${result.workItem.idempotencyKey}`,
|
|
7736
|
+
metadata: { tool: toolName, durationMs, findingCount: scanResult.findings.length, verificationSummary: scanResult.verificationPlan.join(" | ") }
|
|
7737
|
+
});
|
|
7738
|
+
await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
|
|
7739
|
+
console.log("App evaluation scan returned for review.");
|
|
7740
|
+
return { status: "completed", exitCode: 0 };
|
|
7741
|
+
}
|
|
7742
|
+
const failedResult = await apiClient.submitAppEvaluationScanResult(projectId, workItem.workItemId, {
|
|
7743
|
+
status: "failed",
|
|
7744
|
+
runnerId,
|
|
7745
|
+
idempotencyKey: `app_evaluation_${workItem.workItemId}_${randomUUID()}`,
|
|
7746
|
+
tool: toolName,
|
|
7747
|
+
durationMs,
|
|
7748
|
+
...sessionTelemetry,
|
|
7749
|
+
message: `${toolName} did not produce a valid app evaluation scan.`,
|
|
7750
|
+
...scanError ? { error: scanError } : {}
|
|
7751
|
+
});
|
|
7752
|
+
await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
|
|
7753
|
+
status: "failed",
|
|
7754
|
+
summary: scanError ?? `${toolName} did not produce a valid app evaluation scan.`,
|
|
7755
|
+
idempotencyKey: `runner_milestone_app_evaluation_failed_${workItem.workItemId}_${failedResult.workItem.idempotencyKey}`,
|
|
7756
|
+
metadata: { tool: toolName, durationMs, verificationSummary: "App evaluation output did not include valid structured JSON." }
|
|
7757
|
+
});
|
|
7758
|
+
await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
|
|
7759
|
+
console.error(scanError ?? "Local runner app evaluation scan failed.");
|
|
7760
|
+
return { status: "failed", exitCode: toolResult.exitCode || 1 };
|
|
7761
|
+
}
|
|
7762
|
+
async function finalizeProjectContextRefreshWork({
|
|
7763
|
+
apiClient,
|
|
7764
|
+
durationMs,
|
|
7765
|
+
projectId,
|
|
7766
|
+
repositoryLinkId,
|
|
7767
|
+
runnerId,
|
|
7768
|
+
sessionContext,
|
|
7769
|
+
toolConfig,
|
|
7770
|
+
toolName,
|
|
7771
|
+
toolResult,
|
|
7772
|
+
workItem
|
|
7773
|
+
}) {
|
|
7774
|
+
let refreshResult = void 0;
|
|
7775
|
+
let refreshError;
|
|
7776
|
+
if (toolResult.exitCode === 0) {
|
|
7777
|
+
try {
|
|
7778
|
+
refreshResult = parseProjectContextRefreshResult(`${toolResult.stdout}
|
|
7779
|
+
${toolResult.stderr}`);
|
|
7780
|
+
} catch (error) {
|
|
7781
|
+
refreshError = errorMessage3(error);
|
|
7782
|
+
}
|
|
7783
|
+
} else {
|
|
7784
|
+
refreshError = truncateLogExcerpt(toolResult.stderr || toolResult.stdout) || `${toolName} exited with code ${toolResult.exitCode}.`;
|
|
7785
|
+
}
|
|
7786
|
+
const finalStatus = refreshResult ? "completed" : "failed";
|
|
7787
|
+
const updatedToolSession = await finalizeToolSession({
|
|
7788
|
+
apiClient,
|
|
7789
|
+
projectId,
|
|
7790
|
+
status: finalStatus,
|
|
7791
|
+
runnerId,
|
|
7792
|
+
workItemId: workItem.workItemId,
|
|
7793
|
+
stdout: toolResult.stdout,
|
|
7794
|
+
...sessionContext.toolSession ? { session: sessionContext.toolSession } : {},
|
|
7795
|
+
...toolResult.messageCount !== void 0 ? { messageCount: toolResult.messageCount } : {},
|
|
7796
|
+
...toolResult.tokensIn !== void 0 ? { tokensIn: toolResult.tokensIn } : {},
|
|
7797
|
+
...toolResult.tokensOut !== void 0 ? { tokensOut: toolResult.tokensOut } : {},
|
|
7798
|
+
...toolResult.costUsd !== void 0 ? { costUsd: toolResult.costUsd } : {}
|
|
7799
|
+
});
|
|
7800
|
+
const sessionTelemetry = {
|
|
7801
|
+
sessionPolicy: sessionContext.policy,
|
|
7802
|
+
sessionDecision: sessionContext.decision,
|
|
7803
|
+
sessionDecisionReason: sessionContext.reason,
|
|
7804
|
+
...updatedToolSession ? { toolSessionId: updatedToolSession.toolSessionId } : {},
|
|
7805
|
+
...updatedToolSession?.sessionGroupKey ? { sessionGroupKey: updatedToolSession.sessionGroupKey } : {}
|
|
7806
|
+
};
|
|
7807
|
+
if (refreshResult) {
|
|
7808
|
+
const result = await apiClient.submitProjectContextRefreshResult(projectId, workItem.workItemId, {
|
|
7809
|
+
status: "completed",
|
|
7810
|
+
runnerId,
|
|
7811
|
+
idempotencyKey: `project_context_${workItem.workItemId}_${randomUUID()}`,
|
|
7812
|
+
result: refreshResult,
|
|
7813
|
+
tool: toolName,
|
|
7814
|
+
durationMs,
|
|
7815
|
+
...sessionTelemetry,
|
|
7816
|
+
message: `${toolName} returned a project context refresh.`
|
|
7817
|
+
});
|
|
7818
|
+
const failureSummary = projectContextRefreshSubmissionFailureSummary(result);
|
|
7819
|
+
if (failureSummary) {
|
|
7820
|
+
await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
|
|
7821
|
+
status: "failed",
|
|
7822
|
+
summary: failureSummary,
|
|
7823
|
+
idempotencyKey: `runner_milestone_project_context_failed_${workItem.workItemId}_${result.workItem.idempotencyKey}`,
|
|
7824
|
+
metadata: { tool: toolName, durationMs, sliceCount: refreshResult.slices.length, entityCount: refreshResult.entities.length, verificationSummary: "Server rejected the project context refresh result." }
|
|
7825
|
+
});
|
|
7826
|
+
await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
|
|
7827
|
+
console.error(failureSummary);
|
|
7828
|
+
return { status: "failed", exitCode: 1 };
|
|
7829
|
+
}
|
|
7830
|
+
await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
|
|
7831
|
+
status: "completed",
|
|
7832
|
+
summary: `${toolName} returned a project context refresh.`,
|
|
7833
|
+
idempotencyKey: `runner_milestone_project_context_completed_${workItem.workItemId}_${result.workItem.idempotencyKey}`,
|
|
7834
|
+
metadata: { tool: toolName, durationMs, sliceCount: refreshResult.slices.length, entityCount: refreshResult.entities.length, mapId: result.map?.projectContextMapId ?? "", verificationSummary: refreshResult.verificationPlan.join(" | ") }
|
|
7835
|
+
});
|
|
7836
|
+
await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
|
|
7837
|
+
console.log("Project context refresh returned for review.");
|
|
7838
|
+
return { status: "completed", exitCode: 0 };
|
|
7839
|
+
}
|
|
7840
|
+
const failedResult = await apiClient.submitProjectContextRefreshResult(projectId, workItem.workItemId, {
|
|
7841
|
+
status: "failed",
|
|
7842
|
+
runnerId,
|
|
7843
|
+
idempotencyKey: `project_context_${workItem.workItemId}_${randomUUID()}`,
|
|
7844
|
+
tool: toolName,
|
|
7845
|
+
durationMs,
|
|
7846
|
+
...sessionTelemetry,
|
|
7847
|
+
message: `${toolName} did not produce a valid project context refresh.`,
|
|
7848
|
+
...refreshError ? { error: refreshError } : {}
|
|
7849
|
+
});
|
|
7850
|
+
await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
|
|
7851
|
+
status: "failed",
|
|
7852
|
+
summary: refreshError ?? `${toolName} did not produce a valid project context refresh.`,
|
|
7853
|
+
idempotencyKey: `runner_milestone_project_context_failed_${workItem.workItemId}_${failedResult.workItem.idempotencyKey}`,
|
|
7854
|
+
metadata: { tool: toolName, durationMs, verificationSummary: "Project context output did not include valid structured JSON." }
|
|
7855
|
+
});
|
|
7856
|
+
await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
|
|
7857
|
+
console.error(refreshError ?? "Local runner project context refresh failed.");
|
|
7858
|
+
return { status: "failed", exitCode: toolResult.exitCode || 1 };
|
|
7859
|
+
}
|
|
6641
7860
|
async function createRunnerWorkPrompt(apiClient, projectId, workItem) {
|
|
6642
7861
|
if (workItem.workKind === "assistantQuestion") {
|
|
6643
|
-
const
|
|
7862
|
+
const emptyProjectContext = { maps: [], refreshes: [], misses: [] };
|
|
7863
|
+
const [{ documents: documents2 }, { messages: messages2 }, context] = await Promise.all([
|
|
6644
7864
|
apiClient.listBrainDocuments(projectId),
|
|
6645
|
-
apiClient.listAssistantMessages(projectId)
|
|
7865
|
+
apiClient.listAssistantMessages(projectId),
|
|
7866
|
+
apiClient.listProjectContext(projectId).catch(() => emptyProjectContext)
|
|
6646
7867
|
]);
|
|
6647
7868
|
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];
|
|
7869
|
+
const contextPack = await apiClient.buildProjectContextPack(projectId, question?.content ?? workItem.sourceWish ?? workItem.title, workItem.workItemId).catch(() => void 0);
|
|
6648
7870
|
return createWorkExecutionPrompt(workItem, {
|
|
6649
|
-
...question ? { assistantQuestion: { question, messages: messages2, documents: documents2 } } : {}
|
|
7871
|
+
...question ? { assistantQuestion: { question, messages: messages2, documents: documents2, ...context.activeMap ? { activeMap: context.activeMap } : {}, ...contextPack ? { contextPack } : {} } } : {}
|
|
6650
7872
|
});
|
|
6651
7873
|
}
|
|
6652
7874
|
if (workItem.workKind === "impactPreview") {
|
|
6653
|
-
const {
|
|
7875
|
+
const emptyProjectContext = { maps: [], refreshes: [], misses: [] };
|
|
7876
|
+
const [{ documents: documents2 }, context] = await Promise.all([
|
|
7877
|
+
apiClient.listBrainDocuments(projectId),
|
|
7878
|
+
apiClient.listProjectContext(projectId).catch(() => emptyProjectContext)
|
|
7879
|
+
]);
|
|
6654
7880
|
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"));
|
|
7881
|
+
const contextPack = await apiClient.buildProjectContextPack(projectId, implementationPrompt?.content ?? workItem.sourceWish ?? workItem.title, workItem.workItemId).catch(() => void 0);
|
|
6655
7882
|
return createWorkExecutionPrompt(workItem, {
|
|
6656
7883
|
impactPreview: {
|
|
6657
7884
|
documents: documents2,
|
|
7885
|
+
...context.activeMap ? { activeMap: context.activeMap } : {},
|
|
7886
|
+
...contextPack ? { contextPack } : {},
|
|
6658
7887
|
...implementationPrompt ? { implementationPrompt } : {}
|
|
6659
7888
|
}
|
|
6660
7889
|
});
|
|
@@ -6678,6 +7907,25 @@ async function createRunnerWorkPrompt(apiClient, projectId, workItem) {
|
|
|
6678
7907
|
securityPostureScan: { documents: documents2 }
|
|
6679
7908
|
});
|
|
6680
7909
|
}
|
|
7910
|
+
if (workItem.workKind === "appEvaluationScan") {
|
|
7911
|
+
const { documents: documents2 } = await apiClient.listBrainDocuments(projectId);
|
|
7912
|
+
return createWorkExecutionPrompt(workItem, {
|
|
7913
|
+
appEvaluationScan: { documents: documents2 }
|
|
7914
|
+
});
|
|
7915
|
+
}
|
|
7916
|
+
if (workItem.workKind === "projectContextRefresh") {
|
|
7917
|
+
const emptyProjectContext = { maps: [], refreshes: [], misses: [] };
|
|
7918
|
+
const [{ documents: documents2 }, context] = await Promise.all([
|
|
7919
|
+
apiClient.listBrainDocuments(projectId),
|
|
7920
|
+
apiClient.listProjectContext(projectId).catch(() => emptyProjectContext)
|
|
7921
|
+
]);
|
|
7922
|
+
return createWorkExecutionPrompt(workItem, {
|
|
7923
|
+
projectContextRefresh: {
|
|
7924
|
+
documents: documents2,
|
|
7925
|
+
...context.activeMap ? { activeMap: context.activeMap } : {}
|
|
7926
|
+
}
|
|
7927
|
+
});
|
|
7928
|
+
}
|
|
6681
7929
|
if (workItem.workKind !== "planRevision" || !workItem.reviewDocumentId) {
|
|
6682
7930
|
return createWorkExecutionPrompt(workItem);
|
|
6683
7931
|
}
|
|
@@ -6905,7 +8153,7 @@ function parseReasoningEffort(value) {
|
|
|
6905
8153
|
throw new Error(`Expected reasoning effort auto, low, medium, high, or xhigh; received ${value}.`);
|
|
6906
8154
|
}
|
|
6907
8155
|
function inferRepoName(root) {
|
|
6908
|
-
return
|
|
8156
|
+
return path14.basename(path14.resolve(root)) || "repository";
|
|
6909
8157
|
}
|
|
6910
8158
|
function createRepoFingerprint(accountId, projectId, repositoryLinkId) {
|
|
6911
8159
|
return createHash7("sha256").update(`${accountId}:${projectId}:${repositoryLinkId}`).digest("hex");
|