@amistio/cli 0.1.11 → 0.1.13

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