@amistio/cli 0.1.5 → 0.1.6

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
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { createHash as createHash4, randomUUID } from "node:crypto";
4
+ import { createHash as createHash5, randomUUID } from "node:crypto";
5
5
  import { writeFile as writeFile8 } from "node:fs/promises";
6
6
  import os5 from "node:os";
7
- import path11 from "node:path";
7
+ import path12 from "node:path";
8
8
  import { Command } from "commander";
9
9
 
10
10
  // ../shared/src/schemas.ts
@@ -28,6 +28,8 @@ var itemTypeSchema = z.enum([
28
28
  "runnerCredential",
29
29
  "runnerCommand",
30
30
  "planReviewMessage",
31
+ "assistantMessage",
32
+ "impactReport",
31
33
  "toolSession",
32
34
  "activityEvent",
33
35
  "pairingSession"
@@ -64,8 +66,41 @@ var workStatusSchema = z.enum([
64
66
  ]);
65
67
  var sourceSchema = z.enum(["web", "repo", "generated", "runner"]);
66
68
  var executionModeSchema = z.enum(["localRunner", "cloudSandbox"]);
67
- var workKindSchema = z.enum(["brainGeneration", "implementation", "planRevision"]);
69
+ var workKindSchema = z.enum(["brainGeneration", "implementation", "planRevision", "assistantQuestion", "impactPreview"]);
68
70
  var generatedDraftStatusSchema = z.enum(["queued", "generating", "reviewing", "approved", "rejected", "changesRequested", "failed"]);
71
+ var assistantQuestionModeSchema = z.enum(["brainOnly", "sourceAware"]);
72
+ var impactRiskLevelSchema = z.enum(["low", "medium", "high", "critical"]);
73
+ var impactReportStatusSchema = z.enum(["queued", "running", "completed", "failed", "stale"]);
74
+ var activityEventTypeSchema = z.enum([
75
+ "commandSubmitted",
76
+ "generationQueued",
77
+ "generationCompleted",
78
+ "generationFailed",
79
+ "documentGenerated",
80
+ "documentReview",
81
+ "documentSync",
82
+ "workQueued",
83
+ "workClaimed",
84
+ "workStatusChanged",
85
+ "runnerMilestone",
86
+ "assistantAnswered",
87
+ "assistantFailed",
88
+ "impactPreviewQueued",
89
+ "impactPreviewCompleted",
90
+ "impactPreviewFailed",
91
+ "impactPreviewStale",
92
+ "handoffExported"
93
+ ]);
94
+ var activityEventActorSchema = z.enum(["webUser", "runner", "cli", "system"]);
95
+ var activityEventStatusSchema = z.enum(["info", "queued", "running", "completed", "failed", "blocked", "warning"]);
96
+ var activityMetadataValueSchema = z.union([
97
+ z.string().max(600),
98
+ z.number(),
99
+ z.boolean(),
100
+ z.null(),
101
+ z.array(z.string().max(200)).max(20)
102
+ ]);
103
+ var activityMetadataSchema = z.record(z.string().min(1).max(80), activityMetadataValueSchema).default({});
69
104
  var generatedBrainArtifactSchema = z.object({
70
105
  documentType: documentTypeSchema,
71
106
  title: z.string().trim().min(1),
@@ -102,11 +137,37 @@ var runnerToolCapabilitySchema = z.object({
102
137
  execution: z.enum(["sdk", "command", "unavailable"]),
103
138
  supportsSessionReuse: z.boolean(),
104
139
  resumabilityScope: sessionResumabilityScopeSchema,
105
- supportsModelSelection: z.boolean()
140
+ supportsModelSelection: z.boolean(),
141
+ supportsBranchIsolation: z.boolean().optional(),
142
+ supportsGitWorktreeIsolation: z.boolean().optional()
106
143
  });
144
+ var workIsolationModeSchema = z.enum(["none", "primaryCheckout", "branch", "gitWorktree"]);
107
145
  var repositoryLinkSourceSchema = z.enum(["web", "cli"]);
108
146
  var repositoryCloneStatusSchema = z.enum(["notCloned", "cloned", "validated", "failed"]);
109
147
  var projectStatusSchema = z.enum(["active", "archived"]);
148
+ var workspaceScopeKindSchema = z.enum(["personal", "organization"]);
149
+ var personalWorkspaceScopeSchema = z.object({
150
+ kind: z.literal("personal"),
151
+ accountId: z.string().min(1),
152
+ userId: z.string().min(1),
153
+ label: z.string().trim().min(1).default("Personal workspace")
154
+ });
155
+ var organizationWorkspaceScopeSchema = z.object({
156
+ kind: z.literal("organization"),
157
+ accountId: z.string().min(1),
158
+ organizationId: z.string().min(1),
159
+ organizationSlug: z.string().trim().min(1).optional(),
160
+ organizationRole: z.string().trim().min(1).optional(),
161
+ label: z.string().trim().min(1).default("Organization workspace")
162
+ });
163
+ var activeWorkspaceScopeSchema = z.discriminatedUnion("kind", [personalWorkspaceScopeSchema, organizationWorkspaceScopeSchema]);
164
+ var organizationWorkspaceOnboardingStatusSchema = z.enum(["available", "upgradeRequired", "authRequired", "unconfigured"]);
165
+ var organizationWorkspaceOnboardingSchema = z.object({
166
+ enabled: z.boolean(),
167
+ canCreate: z.boolean(),
168
+ status: organizationWorkspaceOnboardingStatusSchema,
169
+ reason: z.string().trim().min(1).optional()
170
+ });
110
171
  var baseItemSchema = z.object({
111
172
  id: z.string().min(1),
112
173
  type: itemTypeSchema,
@@ -212,15 +273,36 @@ var workItemSchema = baseItemSchema.extend({
212
273
  executionMode: executionModeSchema.optional(),
213
274
  title: z.string().min(1),
214
275
  requestedBy: z.string().min(1),
276
+ requestedByUserId: z.string().min(1).optional(),
215
277
  approvedBy: z.string().min(1).optional(),
278
+ approvedByUserId: z.string().min(1).optional(),
216
279
  sourceWish: z.string().min(1).optional(),
217
280
  generatedDraftId: z.string().min(1).optional(),
281
+ repositoryLinkId: z.string().min(1).optional(),
218
282
  reviewThreadId: z.string().min(1).optional(),
219
283
  reviewDocumentId: z.string().min(1).optional(),
220
284
  reviewDocumentRevision: z.number().int().nonnegative().optional(),
221
285
  reviewMessageId: z.string().min(1).optional(),
286
+ assistantMessageId: z.string().min(1).optional(),
287
+ assistantQuestionMode: assistantQuestionModeSchema.optional(),
288
+ impactReportId: z.string().min(1).optional(),
289
+ impactDocumentId: z.string().min(1).optional(),
290
+ impactDocumentRevision: z.number().int().nonnegative().optional(),
222
291
  claimedByRunnerId: z.string().optional(),
292
+ pairedByUserId: z.string().min(1).optional(),
293
+ machineId: z.string().min(1).optional(),
294
+ claimLeaseId: z.string().min(1).optional(),
295
+ claimAttempt: z.number().int().nonnegative().optional(),
223
296
  leaseExpiresAt: isoDateTimeSchema.optional(),
297
+ controllingAdrId: z.string().min(1).optional(),
298
+ implementationScopeId: z.string().min(1).optional(),
299
+ executionBranch: z.string().min(1).optional(),
300
+ executionWorktreeKey: z.string().min(1).optional(),
301
+ isolationMode: workIsolationModeSchema.optional(),
302
+ repositoryLockId: z.string().min(1).optional(),
303
+ baseRevision: z.string().min(1).optional(),
304
+ baseContentHash: z.string().min(1).optional(),
305
+ blockerReason: z.string().min(1).optional(),
224
306
  attempt: z.number().int().nonnegative().default(0),
225
307
  idempotencyKey: z.string().min(1),
226
308
  sessionPolicy: sessionPolicySchema.optional(),
@@ -237,10 +319,19 @@ var runnerHeartbeatItemSchema = baseItemSchema.extend({
237
319
  runnerId: z.string().min(1),
238
320
  repositoryLinkId: z.string().min(1),
239
321
  status: z.enum(["online", "offline", "running", "blocked", "removed"]),
322
+ workspaceId: z.string().min(1).optional(),
323
+ pairedByUserId: z.string().min(1).optional(),
324
+ machineId: z.string().min(1).optional(),
240
325
  version: z.string().optional(),
241
326
  mode: z.enum(["foreground", "background"]).optional(),
242
327
  hostname: z.string().min(1).optional(),
243
328
  runnerName: z.string().min(1).optional(),
329
+ supportsBranchIsolation: z.boolean().optional(),
330
+ supportsGitWorktreeIsolation: z.boolean().optional(),
331
+ currentWorkItemId: z.string().min(1).optional(),
332
+ currentImplementationScopeId: z.string().min(1).optional(),
333
+ currentWorktreeKey: z.string().min(1).optional(),
334
+ currentBranch: z.string().min(1).optional(),
244
335
  capabilities: z.array(runnerToolCapabilitySchema).optional(),
245
336
  requestedTool: runnerToolSelectionSchema.optional(),
246
337
  requestedInvocationChannel: runnerInvocationChannelSchema.optional(),
@@ -269,6 +360,16 @@ var runnerExecutionLogItemSchema = baseItemSchema.extend({
269
360
  workTitle: z.string().min(1).optional(),
270
361
  initiatedBy: z.string().min(1).optional(),
271
362
  approvedBy: z.string().min(1).optional(),
363
+ repositoryLinkId: z.string().min(1).optional(),
364
+ pairedByUserId: z.string().min(1).optional(),
365
+ machineId: z.string().min(1).optional(),
366
+ controllingAdrId: z.string().min(1).optional(),
367
+ implementationScopeId: z.string().min(1).optional(),
368
+ executionBranch: z.string().min(1).optional(),
369
+ executionWorktreeKey: z.string().min(1).optional(),
370
+ isolationMode: workIsolationModeSchema.optional(),
371
+ repositoryLockId: z.string().min(1).optional(),
372
+ claimLeaseId: z.string().min(1).optional(),
272
373
  status: z.enum(["claimed", "running", "completed", "failed", "blocked", "idle"]),
273
374
  executionMode: executionModeSchema.optional(),
274
375
  tool: z.string().min(1).optional(),
@@ -293,6 +394,8 @@ var runnerCredentialItemSchema = baseItemSchema.extend({
293
394
  projectId: z.string().min(1),
294
395
  runnerCredentialId: z.string().min(1),
295
396
  repositoryLinkId: z.string().min(1),
397
+ pairedByUserId: z.string().min(1).optional(),
398
+ machineId: z.string().min(1).optional(),
296
399
  tokenHash: z.string().min(32),
297
400
  issuedAt: isoDateTimeSchema,
298
401
  lastUsedAt: isoDateTimeSchema.optional(),
@@ -339,6 +442,231 @@ var planReviewMessageItemSchema = baseItemSchema.extend({
339
442
  createdByUserId: z.string().min(1).optional(),
340
443
  runnerId: z.string().min(1).optional()
341
444
  });
445
+ var assistantMessageRoleSchema = z.enum(["user", "assistant", "system"]);
446
+ var assistantMessageStatusSchema = z.enum(["posted", "queued", "running", "completed", "failed"]);
447
+ var assistantSourceBoundarySchema = z.enum(["projectBrain", "localSource", "runnerState", "mixed"]);
448
+ var assistantCitationSchema = z.object({
449
+ source: assistantSourceBoundarySchema,
450
+ documentId: z.string().min(1).optional(),
451
+ title: z.string().min(1).optional(),
452
+ repoPath: z.string().min(1).optional(),
453
+ documentRevision: z.number().int().nonnegative().optional(),
454
+ excerpt: z.string().max(600).optional()
455
+ });
456
+ var assistantMessageItemSchema = baseItemSchema.extend({
457
+ type: z.literal("assistantMessage"),
458
+ projectId: z.string().min(1),
459
+ messageId: z.string().min(1),
460
+ threadId: z.string().min(1),
461
+ role: assistantMessageRoleSchema,
462
+ status: assistantMessageStatusSchema,
463
+ content: z.string().min(1),
464
+ questionMode: assistantQuestionModeSchema.optional(),
465
+ sourceBoundary: assistantSourceBoundarySchema.optional(),
466
+ citations: z.array(assistantCitationSchema).default([]),
467
+ relatedDocumentIds: z.array(z.string().min(1)).default([]),
468
+ workItemId: z.string().min(1).optional(),
469
+ responseToMessageId: z.string().min(1).optional(),
470
+ createdByUserId: z.string().min(1).optional(),
471
+ runnerId: z.string().min(1).optional(),
472
+ repositoryLinkId: z.string().min(1).optional(),
473
+ repoFingerprint: z.string().min(1).optional(),
474
+ answeredAt: isoDateTimeSchema.optional(),
475
+ error: z.string().optional()
476
+ });
477
+ var assistantAnswerResultSchema = z.object({
478
+ answer: z.string().trim().min(1),
479
+ sourceBoundary: assistantSourceBoundarySchema.default("localSource"),
480
+ citations: z.array(assistantCitationSchema).default([]),
481
+ summary: z.string().optional()
482
+ });
483
+ var knowledgeSearchScopeSchema = z.enum(["project", "organization"]);
484
+ var knowledgeSourceTypeSchema = z.enum(["brainDocument", "generatedArtifact", "runnerSummary", "knowledgeEntity", "knowledgeRelation"]);
485
+ var knowledgeChunkStatusSchema = z.enum(["approved", "synced", "stale"]);
486
+ var knowledgeEntityTypeSchema = z.enum(["project", "system", "component", "domain", "tool", "decision", "feature", "risk", "team", "workflow", "unknown"]);
487
+ var knowledgeRelationTypeSchema = z.enum(["uses", "depends_on", "decides", "supersedes", "touches", "blocks", "implements", "mentions"]);
488
+ var knowledgeIndexFreshnessSchema = z.object({
489
+ status: z.enum(["disabled", "fresh", "stale", "indexing", "degraded"]),
490
+ indexedAt: isoDateTimeSchema.optional(),
491
+ staleReason: z.string().trim().min(1).optional(),
492
+ failedChunkCount: z.number().int().nonnegative().default(0),
493
+ indexedChunkCount: z.number().int().nonnegative().default(0)
494
+ });
495
+ var knowledgeSearchChunkSchema = z.object({
496
+ chunkId: z.string().min(1),
497
+ accountId: z.string().min(1),
498
+ projectId: z.string().min(1),
499
+ sourceType: knowledgeSourceTypeSchema.default("brainDocument"),
500
+ documentId: z.string().min(1),
501
+ documentType: documentTypeSchema,
502
+ title: z.string().min(1),
503
+ repoPath: z.string().min(1),
504
+ revision: z.number().int().nonnegative(),
505
+ sectionHeading: z.string().trim().min(1).optional(),
506
+ chunkText: z.string().trim().min(1).max(12e3),
507
+ chunkHash: z.string().min(1),
508
+ contentHash: z.string().min(1),
509
+ sourceBoundary: assistantSourceBoundarySchema.default("projectBrain"),
510
+ status: knowledgeChunkStatusSchema,
511
+ embeddingModel: z.string().trim().min(1).optional(),
512
+ embedding: z.array(z.number()).max(8192).optional(),
513
+ indexedAt: isoDateTimeSchema.optional(),
514
+ createdAt: isoDateTimeSchema,
515
+ updatedAt: isoDateTimeSchema
516
+ });
517
+ var knowledgeEntitySchema = z.object({
518
+ entityId: z.string().min(1),
519
+ accountId: z.string().min(1),
520
+ projectIds: z.array(z.string().min(1)).min(1),
521
+ name: z.string().trim().min(1).max(200),
522
+ entityType: knowledgeEntityTypeSchema,
523
+ aliases: z.array(z.string().trim().min(1).max(200)).default([]),
524
+ description: z.string().trim().min(1).max(1200).optional(),
525
+ sourceDocumentIds: z.array(z.string().min(1)).default([]),
526
+ confidence: z.number().min(0).max(1).default(0.6),
527
+ createdAt: isoDateTimeSchema,
528
+ updatedAt: isoDateTimeSchema
529
+ });
530
+ var knowledgeRelationEndpointSchema = z.object({
531
+ entityId: z.string().min(1),
532
+ name: z.string().trim().min(1).max(200),
533
+ entityType: knowledgeEntityTypeSchema.default("unknown")
534
+ });
535
+ var knowledgeRelationSchema = z.object({
536
+ relationId: z.string().min(1),
537
+ accountId: z.string().min(1),
538
+ projectIds: z.array(z.string().min(1)).min(1),
539
+ relationType: knowledgeRelationTypeSchema,
540
+ from: knowledgeRelationEndpointSchema,
541
+ to: knowledgeRelationEndpointSchema,
542
+ summary: z.string().trim().min(1).max(1200),
543
+ citations: z.array(assistantCitationSchema).default([]),
544
+ confidence: z.number().min(0).max(1).default(0.6),
545
+ createdAt: isoDateTimeSchema,
546
+ updatedAt: isoDateTimeSchema
547
+ });
548
+ var knowledgeSearchRequestSchema = z.object({
549
+ query: z.string().trim().min(1).max(2e3),
550
+ scope: knowledgeSearchScopeSchema.default("project"),
551
+ projectIds: z.array(z.string().min(1)).max(100).optional(),
552
+ topK: z.number().int().min(1).max(20).default(6),
553
+ includeRelations: z.boolean().default(true),
554
+ useSemanticRanking: z.boolean().default(false)
555
+ });
556
+ var knowledgeSearchResultSchema = z.object({
557
+ chunkId: z.string().min(1),
558
+ accountId: z.string().min(1),
559
+ projectId: z.string().min(1),
560
+ documentId: z.string().min(1),
561
+ documentType: documentTypeSchema,
562
+ title: z.string().min(1),
563
+ repoPath: z.string().min(1),
564
+ revision: z.number().int().nonnegative(),
565
+ sectionHeading: z.string().trim().min(1).optional(),
566
+ excerpt: z.string().trim().min(1).max(1200),
567
+ sourceBoundary: assistantSourceBoundarySchema.default("projectBrain"),
568
+ score: z.number().optional(),
569
+ citation: assistantCitationSchema
570
+ });
571
+ var knowledgeSearchResponseSchema = z.object({
572
+ query: z.string().trim().min(1),
573
+ scope: knowledgeSearchScopeSchema,
574
+ results: z.array(knowledgeSearchResultSchema).default([]),
575
+ relations: z.array(knowledgeRelationSchema).default([]),
576
+ freshness: knowledgeIndexFreshnessSchema,
577
+ degradedReason: z.string().trim().min(1).optional()
578
+ });
579
+ var knowledgeDiagramFormatSchema = z.enum(["mermaid"]);
580
+ var knowledgeDiagramRequestSchema = knowledgeSearchRequestSchema.extend({
581
+ format: knowledgeDiagramFormatSchema.default("mermaid"),
582
+ saveAsDocument: z.boolean().default(false),
583
+ title: z.string().trim().min(1).max(160).optional()
584
+ });
585
+ var knowledgeDiagramResultSchema = z.object({
586
+ diagramId: z.string().min(1),
587
+ title: z.string().trim().min(1).max(160),
588
+ format: knowledgeDiagramFormatSchema,
589
+ content: z.string().trim().min(1),
590
+ citations: z.array(assistantCitationSchema).default([]),
591
+ sourceResultIds: z.array(z.string().min(1)).default([]),
592
+ documentId: z.string().min(1).optional(),
593
+ freshness: knowledgeIndexFreshnessSchema,
594
+ warnings: z.array(z.string().trim().min(1).max(600)).default([])
595
+ });
596
+ var impactAffectedAreaSchema = z.object({
597
+ name: z.string().trim().min(1),
598
+ description: z.string().trim().min(1).optional()
599
+ });
600
+ var impactPathSchema = z.object({
601
+ repoPath: z.string().trim().min(1),
602
+ reason: z.string().trim().min(1).optional()
603
+ });
604
+ var impactReportItemSchema = baseItemSchema.extend({
605
+ type: z.literal("impactReport"),
606
+ projectId: z.string().min(1),
607
+ impactReportId: z.string().min(1),
608
+ status: impactReportStatusSchema,
609
+ riskLevel: impactRiskLevelSchema.optional(),
610
+ summary: z.string().trim().min(1).optional(),
611
+ affectedAreas: z.array(impactAffectedAreaSchema).default([]),
612
+ likelyPaths: z.array(impactPathSchema).default([]),
613
+ dependencies: z.array(z.string().trim().min(1)).default([]),
614
+ dataSchemaImpact: z.string().trim().min(1).optional(),
615
+ securityPrivacyImpact: z.string().trim().min(1).optional(),
616
+ verificationPlan: z.array(z.string().trim().min(1)).default([]),
617
+ rollbackPlan: z.string().trim().min(1).optional(),
618
+ generatedDraftId: z.string().min(1).optional(),
619
+ sourceWorkItemId: z.string().min(1).optional(),
620
+ documentId: z.string().min(1).optional(),
621
+ documentRevision: z.number().int().nonnegative().optional(),
622
+ repositoryLinkId: z.string().min(1).optional(),
623
+ repoFingerprint: z.string().min(1).optional(),
624
+ analyzedRepoRevision: z.number().int().nonnegative().optional(),
625
+ workItemId: z.string().min(1).optional(),
626
+ runnerId: z.string().min(1).optional(),
627
+ createdByUserId: z.string().min(1).optional(),
628
+ completedAt: isoDateTimeSchema.optional(),
629
+ staleReason: z.string().optional(),
630
+ error: z.string().optional()
631
+ });
632
+ var impactPreviewResultSchema = z.object({
633
+ riskLevel: impactRiskLevelSchema,
634
+ summary: z.string().trim().min(1),
635
+ affectedAreas: z.array(impactAffectedAreaSchema).min(1),
636
+ likelyPaths: z.array(impactPathSchema).default([]),
637
+ dependencies: z.array(z.string().trim().min(1)).default([]),
638
+ dataSchemaImpact: z.string().trim().min(1),
639
+ securityPrivacyImpact: z.string().trim().min(1),
640
+ verificationPlan: z.array(z.string().trim().min(1)).min(1),
641
+ rollbackPlan: z.string().trim().min(1),
642
+ analyzedRepoRevision: z.number().int().nonnegative().optional(),
643
+ repoFingerprint: z.string().min(1).optional()
644
+ });
645
+ var activityEventItemSchema = baseItemSchema.extend({
646
+ type: z.literal("activityEvent"),
647
+ projectId: z.string().min(1),
648
+ activityEventId: z.string().min(1),
649
+ eventType: activityEventTypeSchema,
650
+ actorType: activityEventActorSchema,
651
+ actorId: z.string().min(1).optional(),
652
+ status: activityEventStatusSchema,
653
+ summary: z.string().trim().min(1).max(600),
654
+ metadata: activityMetadataSchema,
655
+ idempotencyKey: z.string().min(1),
656
+ occurredAt: isoDateTimeSchema,
657
+ relatedWorkItemId: z.string().min(1).optional(),
658
+ relatedDocumentId: z.string().min(1).optional(),
659
+ generatedDraftId: z.string().min(1).optional(),
660
+ runnerId: z.string().min(1).optional(),
661
+ repositoryLinkId: z.string().min(1).optional(),
662
+ runnerLogId: z.string().min(1).optional(),
663
+ commandId: z.string().min(1).optional()
664
+ });
665
+ var activityHandoffExportSchema = z.object({
666
+ markdown: z.string().min(1),
667
+ generatedAt: isoDateTimeSchema,
668
+ eventCount: z.number().int().nonnegative()
669
+ });
342
670
  var toolSessionItemSchema = baseItemSchema.extend({
343
671
  type: z.literal("toolSession"),
344
672
  projectId: z.string().min(1),
@@ -355,6 +683,10 @@ var toolSessionItemSchema = baseItemSchema.extend({
355
683
  status: toolSessionStatusSchema,
356
684
  createdByUserId: z.string().min(1).optional(),
357
685
  runnerId: z.string().min(1).optional(),
686
+ machineId: z.string().min(1).optional(),
687
+ implementationScopeId: z.string().min(1).optional(),
688
+ executionWorktreeKey: z.string().min(1).optional(),
689
+ isolationMode: workIsolationModeSchema.optional(),
358
690
  lastWorkItemId: z.string().min(1).optional(),
359
691
  lastActivityAt: isoDateTimeSchema,
360
692
  messageCount: z.number().int().nonnegative().optional(),
@@ -387,6 +719,9 @@ var projectItemUnionSchema = z.discriminatedUnion("type", [
387
719
  runnerCredentialItemSchema,
388
720
  runnerCommandItemSchema,
389
721
  planReviewMessageItemSchema,
722
+ assistantMessageItemSchema,
723
+ impactReportItemSchema,
724
+ activityEventItemSchema,
390
725
  toolSessionItemSchema
391
726
  ]);
392
727
 
@@ -412,6 +747,19 @@ function isOfficialAmistioApiUrl(value) {
412
747
  }
413
748
  }
414
749
 
750
+ // ../shared/src/brain-documents.ts
751
+ var controlPlaneRoots = /* @__PURE__ */ new Set(["architecture", "context", "decisions", "features", "memory", "plans", "prompts", "workflows"]);
752
+ function isControlPlaneTemplateRepoPath(repoPath) {
753
+ const normalized = normalizeRepoPath(repoPath);
754
+ const segments = normalized.split("/").filter(Boolean);
755
+ const root = segments[0] === "docs" ? segments[1] : segments[0];
756
+ const basename = segments[segments.length - 1]?.toLowerCase();
757
+ return Boolean(root && controlPlaneRoots.has(root) && (basename === "_template.md" || basename === "_template.mdx"));
758
+ }
759
+ function normalizeRepoPath(repoPath) {
760
+ return repoPath.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "");
761
+ }
762
+
415
763
  // ../shared/src/repo-metadata.ts
416
764
  import { z as z2 } from "zod";
417
765
  var repoLinkMetadataSchema = z2.object({
@@ -504,7 +852,7 @@ function buildParsedRepositoryCloneUrl({ cloneUrl, host, protocol, rawPath }) {
504
852
  if (!normalizedHost) {
505
853
  throw new Error("Repository URL must include a host.");
506
854
  }
507
- const pathWithoutSlash = normalizeRepoPath(rawPath);
855
+ const pathWithoutSlash = normalizeRepoPath2(rawPath);
508
856
  const segments = pathWithoutSlash.split("/").filter(Boolean);
509
857
  if (segments.length < 2) {
510
858
  throw new Error("Repository URL must include an owner and repository name.");
@@ -531,7 +879,7 @@ function buildParsedRepositoryCloneUrl({ cloneUrl, host, protocol, rawPath }) {
531
879
  repoFullName
532
880
  };
533
881
  }
534
- function normalizeRepoPath(rawPath) {
882
+ function normalizeRepoPath2(rawPath) {
535
883
  const withoutLeadingSlash = rawPath.trim().replace(/^\/+/, "").replace(/\/+$/, "");
536
884
  if (!withoutLeadingSlash || withoutLeadingSlash.includes("..")) {
537
885
  throw new Error("Repository URL path is not supported.");
@@ -886,14 +1234,14 @@ function credentialKey(accountId, projectId, repositoryLinkId) {
886
1234
  import { mkdir as mkdir3, readFile as readFile2, stat as stat2, writeFile as writeFile2 } from "node:fs/promises";
887
1235
  import path3 from "node:path";
888
1236
  var controlPlaneFolders = [
889
- "architecture",
890
- "context",
891
- "decisions",
892
- "features",
893
- "memory",
894
- "plans",
895
- path3.join("prompts", "shared"),
896
- "workflows"
1237
+ path3.join("docs", "architecture"),
1238
+ path3.join("docs", "context"),
1239
+ path3.join("docs", "decisions"),
1240
+ path3.join("docs", "features"),
1241
+ path3.join("docs", "memory"),
1242
+ path3.join("docs", "plans"),
1243
+ path3.join("docs", "prompts", "shared"),
1244
+ path3.join("docs", "workflows")
897
1245
  ];
898
1246
  async function initControlPlane(rootDir) {
899
1247
  const created = [];
@@ -906,10 +1254,10 @@ async function initControlPlane(rootDir) {
906
1254
  }
907
1255
  await writeIfMissing(
908
1256
  path3.join(rootDir, "AGENTS.md"),
909
- "# Agents\n\nRoot control-plane folders are the project brain. Keep architecture, context, decisions, features, memory, plans, prompts, and workflows in sync with product intent.\n"
1257
+ "# Agents\n\nThe docs/ control-plane folders are the project brain. Keep docs/architecture, docs/context, docs/decisions, docs/features, docs/memory, docs/plans, docs/prompts, and docs/workflows in sync with product intent.\n"
910
1258
  );
911
- await writeIfMissing(path3.join(rootDir, "context", "product.md"), "# Product Context\n\nDescribe the product direction here.\n");
912
- await writeIfMissing(path3.join(rootDir, "architecture", "overview.md"), "# Architecture Overview\n\nDescribe the system shape here.\n");
1259
+ await writeIfMissing(path3.join(rootDir, "docs", "context", "product.md"), "# Product Context\n\nDescribe the product direction here.\n");
1260
+ await writeIfMissing(path3.join(rootDir, "docs", "architecture", "overview.md"), "# Architecture Overview\n\nDescribe the system shape here.\n");
913
1261
  return created;
914
1262
  }
915
1263
  async function inspectControlPlane(rootDir) {
@@ -925,20 +1273,26 @@ async function inspectControlPlane(rootDir) {
925
1273
  return { present, missing };
926
1274
  }
927
1275
  async function writeProjectLink(rootDir, metadata) {
928
- const contextDir = path3.join(rootDir, "context");
1276
+ const contextDir = path3.join(rootDir, "docs", "context");
929
1277
  await mkdir3(contextDir, { recursive: true });
930
1278
  const filePath = path3.join(contextDir, "amistio-project.md");
931
1279
  await writeFile2(filePath, createProjectLinkMarkdown(metadata), "utf8");
932
1280
  return filePath;
933
1281
  }
934
1282
  async function readProjectLink(rootDir) {
935
- const filePath = path3.join(rootDir, "context", "amistio-project.md");
936
- if (!await exists(filePath)) {
937
- return void 0;
1283
+ const candidatePaths = [
1284
+ path3.join(rootDir, "docs", "context", "amistio-project.md"),
1285
+ path3.join(rootDir, "context", "amistio-project.md")
1286
+ ];
1287
+ for (const filePath of candidatePaths) {
1288
+ if (!await exists(filePath)) {
1289
+ continue;
1290
+ }
1291
+ const content = await readFile2(filePath, "utf8");
1292
+ const frontmatter = parseFrontmatter(content);
1293
+ return repoLinkMetadataSchema.parse(frontmatter);
938
1294
  }
939
- const content = await readFile2(filePath, "utf8");
940
- const frontmatter = parseFrontmatter(content);
941
- return repoLinkMetadataSchema.parse(frontmatter);
1295
+ return void 0;
942
1296
  }
943
1297
  function parseFrontmatter(content) {
944
1298
  if (!content.startsWith("---\n")) {
@@ -1017,13 +1371,13 @@ var ApiClient = class {
1017
1371
  }
1018
1372
  );
1019
1373
  }
1020
- async claimWork(projectId, runnerId, repositoryLinkId, leaseSeconds = 300) {
1374
+ async claimWork(projectId, runnerId, repositoryLinkId, leaseSeconds = 300, metadata = {}) {
1021
1375
  return this.request(
1022
1376
  `/projects/${projectId}/work-items/claim`,
1023
1377
  z3.object({ workItem: workItemSchema.optional() }).transform(({ workItem }) => ({ workItem })),
1024
1378
  {
1025
1379
  method: "POST",
1026
- body: JSON.stringify({ runnerId, repositoryLinkId, leaseSeconds })
1380
+ body: JSON.stringify({ runnerId, repositoryLinkId, leaseSeconds, ...metadata })
1027
1381
  }
1028
1382
  );
1029
1383
  }
@@ -1049,6 +1403,21 @@ var ApiClient = class {
1049
1403
  { method: "GET" }
1050
1404
  );
1051
1405
  }
1406
+ async listAssistantMessages(projectId) {
1407
+ return this.request(
1408
+ `/projects/${projectId}/assistant-messages`,
1409
+ z3.object({ messages: z3.array(assistantMessageItemSchema) }),
1410
+ { method: "GET" }
1411
+ );
1412
+ }
1413
+ async listImpactReports(projectId, generatedDraftId) {
1414
+ const query = generatedDraftId ? `?generatedDraftId=${encodeURIComponent(generatedDraftId)}` : "";
1415
+ return this.request(
1416
+ `/projects/${projectId}/impact-reports${query}`,
1417
+ z3.object({ reports: z3.array(impactReportItemSchema) }),
1418
+ { method: "GET" }
1419
+ );
1420
+ }
1052
1421
  async pushBrainDocuments(projectId, documents) {
1053
1422
  return this.request(
1054
1423
  `/projects/${projectId}/brain-documents`,
@@ -1115,6 +1484,16 @@ var ApiClient = class {
1115
1484
  }
1116
1485
  );
1117
1486
  }
1487
+ async recordRunnerLog(projectId, input) {
1488
+ return this.request(
1489
+ `/projects/${projectId}/runner-logs`,
1490
+ z3.object({ runnerLog: runnerExecutionLogItemSchema }),
1491
+ {
1492
+ method: "POST",
1493
+ body: JSON.stringify(input)
1494
+ }
1495
+ );
1496
+ }
1118
1497
  async listToolSessions(projectId) {
1119
1498
  return this.request(
1120
1499
  `/projects/${projectId}/tool-sessions`,
@@ -1142,6 +1521,16 @@ var ApiClient = class {
1142
1521
  }
1143
1522
  );
1144
1523
  }
1524
+ async recordActivityEvent(projectId, event) {
1525
+ return this.request(
1526
+ `/projects/${projectId}/activity-events`,
1527
+ z3.object({ event: activityEventItemSchema }),
1528
+ {
1529
+ method: "POST",
1530
+ body: JSON.stringify(event)
1531
+ }
1532
+ );
1533
+ }
1145
1534
  async updateWorkStatus(projectId, workItemId, status, idempotencyKey, runnerId, telemetry = {}) {
1146
1535
  return this.request(
1147
1536
  `/projects/${projectId}/work-items/${workItemId}/status`,
@@ -1162,6 +1551,26 @@ var ApiClient = class {
1162
1551
  }
1163
1552
  );
1164
1553
  }
1554
+ async submitAssistantResult(projectId, workItemId, result) {
1555
+ return this.request(
1556
+ `/projects/${projectId}/work-items/${workItemId}/assistant-result`,
1557
+ z3.object({ message: assistantMessageItemSchema, answer: assistantMessageItemSchema.optional(), workItem: workItemSchema }),
1558
+ {
1559
+ method: "POST",
1560
+ body: JSON.stringify(result)
1561
+ }
1562
+ );
1563
+ }
1564
+ async submitImpactPreviewResult(projectId, workItemId, result) {
1565
+ return this.request(
1566
+ `/projects/${projectId}/work-items/${workItemId}/impact-result`,
1567
+ z3.object({ report: impactReportItemSchema, workItem: workItemSchema, implementationWorkItem: workItemSchema.optional() }),
1568
+ {
1569
+ method: "POST",
1570
+ body: JSON.stringify(result)
1571
+ }
1572
+ );
1573
+ }
1165
1574
  async request(urlPath, schema, init) {
1166
1575
  const response = await fetch(resolveApiUrl(this.options.apiUrl, urlPath), {
1167
1576
  ...init,
@@ -1200,12 +1609,16 @@ var toolSessionMutationSchema = z3.object({
1200
1609
  sessionGroupKey: z3.string().min(1).optional(),
1201
1610
  reusePolicy: sessionPolicySchema.optional(),
1202
1611
  sessionDecision: sessionDecisionSchema.optional(),
1203
- closedReason: z3.string().optional()
1612
+ closedReason: z3.string().optional(),
1613
+ machineId: z3.string().min(1).optional(),
1614
+ implementationScopeId: z3.string().min(1).optional(),
1615
+ executionWorktreeKey: z3.string().min(1).optional(),
1616
+ isolationMode: workIsolationModeSchema.optional()
1204
1617
  });
1205
1618
  function resolveApiUrl(apiUrl, urlPath) {
1206
1619
  const base = apiUrl.endsWith("/") ? apiUrl.slice(0, -1) : apiUrl;
1207
- const path12 = urlPath.startsWith("/") ? urlPath : `/${urlPath}`;
1208
- return new URL(`${base}${path12}`);
1620
+ const path13 = urlPath.startsWith("/") ? urlPath : `/${urlPath}`;
1621
+ return new URL(`${base}${path13}`);
1209
1622
  }
1210
1623
 
1211
1624
  // src/orchestrator.ts
@@ -1225,12 +1638,12 @@ async function createOrchestrationPrompt(options) {
1225
1638
  "",
1226
1639
  "## Orchestrator Principle",
1227
1640
  "",
1228
- "The root control-plane files are the project brain. They exist so agentic coding sessions can continue from established context, decisions, memory, plans, workflows, and prompts instead of drifting into a fresh interpretation each time.",
1641
+ "The docs/ control-plane files are the project brain. They exist so agentic coding sessions can continue from established context, decisions, memory, plans, workflows, and prompts instead of drifting into a fresh interpretation each time.",
1229
1642
  "",
1230
1643
  "## Repository Rules",
1231
1644
  "",
1232
1645
  "- Read AGENTS.md first when it exists.",
1233
- "- Read existing architecture, context, decisions, features, memory, plans, prompts, and workflows before proposing changes.",
1646
+ "- Read existing docs/architecture, docs/context, docs/decisions, docs/features, docs/memory, docs/plans, docs/prompts, and docs/workflows files before proposing changes.",
1234
1647
  "- Preserve existing intent. Add the next useful orchestration layer instead of replacing old content wholesale.",
1235
1648
  "- Prefer small additive edits, dated notes, explicit decisions, focused plans, and executable prompts.",
1236
1649
  "- Do not modify product source code unless the user's goal explicitly asks for implementation.",
@@ -1241,14 +1654,14 @@ async function createOrchestrationPrompt(options) {
1241
1654
  "## Control-Plane Areas",
1242
1655
  "",
1243
1656
  "- AGENTS.md for agent operating rules.",
1244
- "- architecture/**/*.md for system shape and boundaries.",
1245
- "- context/**/*.md for product, stack, and implementation context.",
1246
- "- decisions/**/*.md for ADRs and durable choices.",
1247
- "- features/**/*.md for behavior specs and acceptance criteria.",
1248
- "- memory/**/*.md for lessons, patterns, and known pitfalls.",
1249
- "- plans/**/*.md for near-term execution plans.",
1250
- "- prompts/**/*.md for model-agnostic implementation prompts.",
1251
- "- workflows/**/*.md for repeatable procedures.",
1657
+ "- docs/architecture/**/*.md for system shape and boundaries.",
1658
+ "- docs/context/**/*.md for product, stack, and implementation context.",
1659
+ "- docs/decisions/**/*.md for ADRs and durable choices.",
1660
+ "- docs/features/**/*.md for behavior specs and acceptance criteria.",
1661
+ "- docs/memory/**/*.md for lessons, patterns, and known pitfalls.",
1662
+ "- docs/plans/**/*.md for near-term execution plans.",
1663
+ "- docs/prompts/**/*.md for model-agnostic implementation prompts.",
1664
+ "- docs/workflows/**/*.md for repeatable procedures.",
1252
1665
  "",
1253
1666
  "## Current Control-Plane Folder Status",
1254
1667
  "",
@@ -2020,9 +2433,22 @@ function sessionIneligibleReason(session, input, now) {
2020
2433
  if (session.repositoryLinkId && session.repositoryLinkId !== input.repositoryLinkId) {
2021
2434
  return "repository link mismatch";
2022
2435
  }
2436
+ if (session.machineId && session.machineId !== input.machineId) {
2437
+ return "session is scoped to another machine";
2438
+ }
2023
2439
  if (session.resumabilityScope === "localMachine" && session.runnerId && session.runnerId !== input.runnerId) {
2024
2440
  return "session is scoped to another runner machine";
2025
2441
  }
2442
+ const requestedByUserId = input.workItem.requestedByUserId ?? input.workItem.requestedBy;
2443
+ if (session.createdByUserId && session.createdByUserId !== requestedByUserId) {
2444
+ return "session belongs to another requester";
2445
+ }
2446
+ if (input.workItem.implementationScopeId && session.implementationScopeId !== input.workItem.implementationScopeId) {
2447
+ return "implementation scope mismatch";
2448
+ }
2449
+ if (input.workItem.executionWorktreeKey && session.executionWorktreeKey !== input.workItem.executionWorktreeKey) {
2450
+ return "worktree scope mismatch";
2451
+ }
2026
2452
  if (session.status === "closed" || session.status === "archived" || session.status === "blocked" || session.status === "unavailable") {
2027
2453
  return `session is ${session.status}`;
2028
2454
  }
@@ -2074,11 +2500,13 @@ function tokens(value) {
2074
2500
  // src/sync.ts
2075
2501
  import { mkdir as mkdir6, readdir as readdir3, readFile as readFile4, stat as stat3, writeFile as writeFile6 } from "node:fs/promises";
2076
2502
  import path7 from "node:path";
2077
- var syncRoots = ["architecture", "context", "decisions", "features", "memory", "plans", "prompts", "workflows"];
2503
+ var legacySyncRoots = ["architecture", "context", "decisions", "features", "memory", "plans", "prompts", "workflows"];
2504
+ var syncRoots = legacySyncRoots.map((syncRoot) => `docs/${syncRoot}`);
2078
2505
  async function collectSyncStatus(rootDir, webDocuments = []) {
2079
2506
  const localDocuments = await readLocalSyncedDocuments(rootDir);
2080
- const webByDocumentId = new Map(webDocuments.map((document) => [document.documentId, document]));
2081
- const pullableWebDocuments = webDocuments.filter((document) => document.status === "approved" || document.syncState === "approved" || document.syncState === "synced");
2507
+ const normalizedWebDocuments = webDocuments.map((document) => ({ ...document, repoPath: canonicalControlPlaneRepoPath(document.repoPath) }));
2508
+ const webByDocumentId = new Map(normalizedWebDocuments.map((document) => [document.documentId, document]));
2509
+ const pullableWebDocuments = normalizedWebDocuments.filter((document) => !isControlPlaneTemplateRepoPath(document.repoPath) && (document.status === "approved" || document.syncState === "approved" || document.syncState === "synced"));
2082
2510
  const localByDocumentId = new Map(localDocuments.map((document) => [document.frontmatter.amistioDocumentId, document]));
2083
2511
  const items = [];
2084
2512
  for (const localDocument of localDocuments) {
@@ -2155,9 +2583,13 @@ async function readLocalSyncedDocuments(rootDir) {
2155
2583
  if (!parsed) {
2156
2584
  continue;
2157
2585
  }
2586
+ const repoPath = toRepoPath(rootDir, fullPath);
2587
+ if (isControlPlaneTemplateRepoPath(repoPath)) {
2588
+ continue;
2589
+ }
2158
2590
  documents.push({
2159
2591
  fullPath,
2160
- repoPath: toRepoPath(rootDir, fullPath),
2592
+ repoPath,
2161
2593
  frontmatter: parsed.frontmatter,
2162
2594
  content: parsed.content,
2163
2595
  contentHash: sha256ContentHash(parsed.content)
@@ -2168,10 +2600,15 @@ async function readLocalSyncedDocuments(rootDir) {
2168
2600
  async function materializeBrainDocuments(rootDir, documents, options = {}) {
2169
2601
  const result = { written: [], skipped: [], conflicts: [] };
2170
2602
  for (const inputDocument of documents) {
2171
- const document = brainDocumentItemSchema.parse(inputDocument);
2603
+ const parsedDocument = brainDocumentItemSchema.parse(inputDocument);
2604
+ const document = { ...parsedDocument, repoPath: canonicalControlPlaneRepoPath(parsedDocument.repoPath) };
2172
2605
  const fullPath = safeRepoPath(rootDir, document.repoPath);
2606
+ if (isControlPlaneTemplateRepoPath(document.repoPath)) {
2607
+ result.skipped.push(document.repoPath);
2608
+ continue;
2609
+ }
2173
2610
  if (!isControlPlanePath(document.repoPath)) {
2174
- result.conflicts.push(`${document.repoPath}: refusing to write outside root control-plane folders`);
2611
+ result.conflicts.push(`${document.repoPath}: refusing to write outside docs control-plane folders`);
2175
2612
  continue;
2176
2613
  }
2177
2614
  const existing = await readExistingSyncedDocument(fullPath);
@@ -2315,6 +2752,14 @@ function isControlPlanePath(repoPath) {
2315
2752
  const normalized = path7.normalize(repoPath);
2316
2753
  return syncRoots.some((syncRoot) => normalized === syncRoot || normalized.startsWith(`${syncRoot}${path7.sep}`));
2317
2754
  }
2755
+ function canonicalControlPlaneRepoPath(repoPath) {
2756
+ const normalized = repoPath.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "");
2757
+ const [firstSegment] = normalized.split("/");
2758
+ if (firstSegment && legacySyncRoots.includes(firstSegment)) {
2759
+ return `docs/${normalized}`;
2760
+ }
2761
+ return normalized;
2762
+ }
2318
2763
  function toRepoPath(rootDir, fullPath) {
2319
2764
  return path7.relative(rootDir, fullPath).split(path7.sep).join("/");
2320
2765
  }
@@ -2381,6 +2826,10 @@ function defaultSessionStorePath() {
2381
2826
  // src/work-runner.ts
2382
2827
  var generationResultStart = "AMISTIO_BRAIN_GENERATION_RESULT_START";
2383
2828
  var generationResultEnd = "AMISTIO_BRAIN_GENERATION_RESULT_END";
2829
+ var assistantAnswerStart = "AMISTIO_ASSISTANT_ANSWER_START";
2830
+ var assistantAnswerEnd = "AMISTIO_ASSISTANT_ANSWER_END";
2831
+ var impactPreviewStart = "AMISTIO_IMPACT_PREVIEW_START";
2832
+ var impactPreviewEnd = "AMISTIO_IMPACT_PREVIEW_END";
2384
2833
  function createWorkExecutionPrompt(workItem, context) {
2385
2834
  if (workItem.workKind === "brainGeneration") {
2386
2835
  return createBrainGenerationPrompt(workItem);
@@ -2388,6 +2837,12 @@ function createWorkExecutionPrompt(workItem, context) {
2388
2837
  if (workItem.workKind === "planRevision") {
2389
2838
  return createPlanRevisionPrompt(workItem, context?.planRevision);
2390
2839
  }
2840
+ if (workItem.workKind === "assistantQuestion") {
2841
+ return createAssistantQuestionPrompt(workItem, context?.assistantQuestion);
2842
+ }
2843
+ if (workItem.workKind === "impactPreview") {
2844
+ return createImpactPreviewPrompt(workItem, context?.impactPreview);
2845
+ }
2391
2846
  return [
2392
2847
  "# Amistio Work Execution",
2393
2848
  "",
@@ -2399,11 +2854,15 @@ function createWorkExecutionPrompt(workItem, context) {
2399
2854
  `Title: ${workItem.title}`,
2400
2855
  `Work item ID: ${workItem.workItemId}`,
2401
2856
  `Project ID: ${workItem.projectId}`,
2857
+ `Implementation scope: ${workItem.implementationScopeId ?? workItem.controllingAdrId ?? "work-item"}`,
2858
+ `Execution branch: ${workItem.executionBranch ?? "managed by Amistio CLI"}`,
2859
+ `Execution worktree key: ${workItem.executionWorktreeKey ?? "managed by Amistio CLI"}`,
2402
2860
  "",
2403
2861
  "## Rules",
2404
2862
  "",
2405
2863
  "- Read AGENTS.md first when it exists.",
2406
- "- Read the relevant root control-plane files before implementation so the work stays aligned with existing direction.",
2864
+ "- Read AGENTS.md and the relevant docs/ control-plane files before implementation so the work stays aligned with existing direction.",
2865
+ "- Treat the current working directory as the Amistio-managed implementation worktree; do not switch back to the paired primary checkout for mutating work.",
2407
2866
  "- Keep changes focused on this work item.",
2408
2867
  "- Preserve old decisions, plans, memory, and prompts unless the work item explicitly supersedes them.",
2409
2868
  "- Do not commit changes.",
@@ -2412,6 +2871,116 @@ function createWorkExecutionPrompt(workItem, context) {
2412
2871
  "- Run relevant verification commands when feasible and summarize results."
2413
2872
  ].join("\n");
2414
2873
  }
2874
+ function createImpactPreviewPrompt(workItem, context) {
2875
+ const implementationPrompt = context?.implementationPrompt;
2876
+ const approvedContext = (context?.documents ?? []).filter((document) => document.status === "approved" || document.syncState === "approved" || document.syncState === "synced").slice(0, 16).map((document) => [
2877
+ `### ${document.title}`,
2878
+ `documentId: ${document.documentId}`,
2879
+ `documentType: ${document.documentType}`,
2880
+ `repoPath: ${document.repoPath}`,
2881
+ `revision: ${document.revision}`,
2882
+ document.content.slice(0, 3e3)
2883
+ ].join("\n")).join("\n\n");
2884
+ return [
2885
+ "# Amistio Implementation Impact Preview",
2886
+ "",
2887
+ "You are running locally through the Amistio CLI inside the user's repository.",
2888
+ "Analyze the likely impact of the approved implementation prompt. This is a preview-only task.",
2889
+ "Do not modify files, do not create branches, do not run implementation commands, and do not commit changes.",
2890
+ "",
2891
+ "## Work Item",
2892
+ "",
2893
+ `Title: ${workItem.title}`,
2894
+ `Work item ID: ${workItem.workItemId}`,
2895
+ `Project ID: ${workItem.projectId}`,
2896
+ `Generated draft ID: ${workItem.generatedDraftId ?? "unknown"}`,
2897
+ `Impact report ID: ${workItem.impactReportId ?? "unknown"}`,
2898
+ `Analyzed repo revision: ${context?.analyzedRepoRevision ?? "unknown"}`,
2899
+ "",
2900
+ "## Approved Implementation Prompt",
2901
+ "",
2902
+ implementationPrompt ? implementationPrompt.content : workItem.sourceWish ?? workItem.title,
2903
+ "",
2904
+ "## Approved Project Brain Context",
2905
+ "",
2906
+ approvedContext || "No approved project-brain records were loaded. Inspect the local repository and explain the gap in the report.",
2907
+ "",
2908
+ "## Report Requirements",
2909
+ "",
2910
+ "- Identify affected product areas and likely modules/files using safe path summaries.",
2911
+ "- Assign riskLevel as low, medium, high, or critical with operational reasoning in summary.",
2912
+ "- Include data/schema/migration impact, security/privacy/access-control considerations, verification plan, and rollback plan.",
2913
+ "- Keep repository source and secrets local. Do not include raw source dumps, credentials, local secret paths, or provider session references.",
2914
+ "- If you inspect files, cite paths only through likelyPaths and concise reasons.",
2915
+ "",
2916
+ "## Output Contract",
2917
+ "",
2918
+ "Print exactly one JSON object between the markers below. The CLI will submit only this structured report back to Amistio.",
2919
+ "",
2920
+ impactPreviewStart,
2921
+ '{"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."}',
2922
+ impactPreviewEnd,
2923
+ "",
2924
+ "Do not put Markdown fences around the markers. Do not implement the work."
2925
+ ].join("\n");
2926
+ }
2927
+ function createAssistantQuestionPrompt(workItem, context) {
2928
+ const question = context?.question.content ?? workItem.sourceWish ?? workItem.title;
2929
+ const priorMessages = (context?.messages ?? []).filter((message) => message.messageId !== context?.question.messageId).slice(-12).map((message) => `- ${message.role} / ${message.status}: ${message.content}`).join("\n");
2930
+ const brainContext = (context?.documents ?? []).filter((document) => document.status === "approved" || document.syncState === "approved" || document.syncState === "synced").slice(0, 12).map((document) => [
2931
+ `### ${document.title}`,
2932
+ `documentId: ${document.documentId}`,
2933
+ `repoPath: ${document.repoPath}`,
2934
+ `revision: ${document.revision}`,
2935
+ document.content.slice(0, 2400)
2936
+ ].join("\n")).join("\n\n");
2937
+ return [
2938
+ "# Amistio Project Knowledge Assistant",
2939
+ "",
2940
+ "You are running locally through the Amistio CLI inside the user's repository.",
2941
+ "Answer the user's project question using approved project-brain context and local repository inspection when useful.",
2942
+ "This is an answer-only task. Do not modify files, do not create branches, and do not commit changes.",
2943
+ "",
2944
+ "## User Question",
2945
+ "",
2946
+ question,
2947
+ "",
2948
+ "## Work Item",
2949
+ "",
2950
+ `Title: ${workItem.title}`,
2951
+ `Work item ID: ${workItem.workItemId}`,
2952
+ `Project ID: ${workItem.projectId}`,
2953
+ `Assistant message ID: ${workItem.assistantMessageId ?? context?.question.messageId ?? "unknown"}`,
2954
+ "",
2955
+ "## Approved Project Brain Context",
2956
+ "",
2957
+ brainContext || "No approved project-brain records were loaded. Say what you inspected locally and avoid pretending citations exist.",
2958
+ "",
2959
+ "## Recent Assistant Conversation",
2960
+ "",
2961
+ priorMessages || "No prior assistant messages were loaded.",
2962
+ "",
2963
+ "## Source Boundary Rules",
2964
+ "",
2965
+ "- Keep repository source, secrets, tokens, local credential paths, and provider session references local.",
2966
+ "- Summarize findings instead of dumping source code. Tiny identifiers, filenames, and short excerpts are acceptable when necessary.",
2967
+ '- Use sourceBoundary "projectBrain" when the answer only uses project-brain records.',
2968
+ '- Use sourceBoundary "localSource" when you inspected local repository files.',
2969
+ '- Use sourceBoundary "mixed" when both project-brain records and local files shaped the answer.',
2970
+ "- Include citations for project-brain records with documentId/title/repoPath where relevant.",
2971
+ "- Include local file citations with repoPath only; do not include raw source dumps.",
2972
+ "",
2973
+ "## Output Contract",
2974
+ "",
2975
+ "Print exactly one JSON object between the markers below. The CLI will submit only this structured answer back to Amistio.",
2976
+ "",
2977
+ assistantAnswerStart,
2978
+ '{"answer":"Concise answer with concrete next steps.","sourceBoundary":"mixed","citations":[{"source":"projectBrain","documentId":"doc_123","title":"ADR-001","repoPath":"docs/decisions/ADR-001.md","excerpt":"Short supporting excerpt"},{"source":"localSource","repoPath":"src/apps/web/example.ts","excerpt":"Short local finding"}]}',
2979
+ assistantAnswerEnd,
2980
+ "",
2981
+ "Do not put Markdown fences around the markers. Do not include implementation diffs."
2982
+ ].join("\n");
2983
+ }
2415
2984
  function createPlanRevisionPrompt(workItem, context) {
2416
2985
  const messages = context?.messages ?? [];
2417
2986
  return [
@@ -2439,10 +3008,10 @@ function createPlanRevisionPrompt(workItem, context) {
2439
3008
  "## Output Contract",
2440
3009
  "",
2441
3010
  "Print exactly one JSON object between the markers below with an artifacts array containing one revised plan artifact.",
2442
- 'The artifact must use documentType "plan", keep the plan under plans/, and include the complete revised plan content.',
3011
+ 'The artifact must use documentType "plan", keep the plan under docs/plans/, and include the complete revised plan content.',
2443
3012
  "",
2444
3013
  generationResultStart,
2445
- '{"artifacts":[{"documentType":"plan","title":"Revised Plan","repoPath":"plans/PLAN-revised.md","content":"# Revised Plan\\n\\n## Goal\\n..."}]}',
3014
+ '{"artifacts":[{"documentType":"plan","title":"Revised Plan","repoPath":"docs/plans/PLAN-revised.md","content":"# Revised Plan\\n\\n## Goal\\n..."}]}',
2446
3015
  generationResultEnd,
2447
3016
  "",
2448
3017
  "Do not put Markdown fences around the markers. Do not claim implementation is complete."
@@ -2458,6 +3027,26 @@ function parseBrainGenerationArtifacts(output) {
2458
3027
  const parsed = JSON.parse(stripJsonFence(payload));
2459
3028
  return brainGenerationResultSchema.parse(parsed).artifacts;
2460
3029
  }
3030
+ function parseAssistantAnswerResult(output) {
3031
+ const start = output.indexOf(assistantAnswerStart);
3032
+ const end = output.indexOf(assistantAnswerEnd, start + assistantAnswerStart.length);
3033
+ if (start === -1 || end === -1 || end <= start) {
3034
+ throw new Error("Local AI answer did not return an Amistio assistant answer block.");
3035
+ }
3036
+ const payload = output.slice(start + assistantAnswerStart.length, end).trim();
3037
+ const parsed = JSON.parse(stripJsonFence(payload));
3038
+ return assistantAnswerResultSchema.parse(parsed);
3039
+ }
3040
+ function parseImpactPreviewResult(output) {
3041
+ const start = output.indexOf(impactPreviewStart);
3042
+ const end = output.indexOf(impactPreviewEnd, start + impactPreviewStart.length);
3043
+ if (start === -1 || end === -1 || end <= start) {
3044
+ throw new Error("Local AI preview did not return an Amistio impact preview block.");
3045
+ }
3046
+ const payload = output.slice(start + impactPreviewStart.length, end).trim();
3047
+ const parsed = JSON.parse(stripJsonFence(payload));
3048
+ return impactPreviewResultSchema.parse(parsed);
3049
+ }
2461
3050
  function createBrainGenerationPrompt(workItem) {
2462
3051
  const wish = workItem.sourceWish ?? workItem.title;
2463
3052
  return [
@@ -2480,21 +3069,21 @@ function createBrainGenerationPrompt(workItem) {
2480
3069
  "## Read First",
2481
3070
  "",
2482
3071
  "- Read AGENTS.md first when it exists.",
2483
- "- Read relevant files under architecture/, context/, decisions/, features/, memory/, plans/, prompts/, and workflows/ before drafting.",
3072
+ "- Read relevant files under docs/architecture/, docs/context/, docs/decisions/, docs/features/, docs/memory/, docs/plans/, docs/prompts/, and docs/workflows/ before drafting.",
2484
3073
  "- Keep source code and secrets local. Do not include source-code payloads, access tokens, API keys, local credential paths, or provider session references in the result.",
2485
3074
  "",
2486
3075
  "## Generate Artifacts",
2487
3076
  "",
2488
3077
  "Return Markdown artifacts that should enter human review before implementation. Use only these document types and matching repo roots:",
2489
3078
  "",
2490
- "- architecture -> architecture/",
2491
- "- context -> context/",
2492
- "- decision -> decisions/",
2493
- "- feature -> features/",
2494
- "- memory -> memory/",
2495
- "- plan -> plans/",
2496
- "- prompt -> prompts/",
2497
- "- workflow -> workflows/",
3079
+ "- architecture -> docs/architecture/",
3080
+ "- context -> docs/context/",
3081
+ "- decision -> docs/decisions/",
3082
+ "- feature -> docs/features/",
3083
+ "- memory -> docs/memory/",
3084
+ "- plan -> docs/plans/",
3085
+ "- prompt -> docs/prompts/",
3086
+ "- workflow -> docs/workflows/",
2498
3087
  "",
2499
3088
  "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.",
2500
3089
  "",
@@ -2503,7 +3092,7 @@ function createBrainGenerationPrompt(workItem) {
2503
3092
  "Print exactly one JSON object between the markers below. The CLI will submit only this structured result back to Amistio.",
2504
3093
  "",
2505
3094
  generationResultStart,
2506
- '{"artifacts":[{"documentType":"plan","title":"Plan: Example","repoPath":"plans/PLAN-example.md","content":"# Plan: Example\\n\\n## Goal\\n..."}]}',
3095
+ '{"artifacts":[{"documentType":"plan","title":"Plan: Example","repoPath":"docs/plans/PLAN-example.md","content":"# Plan: Example\\n\\n## Goal\\n..."}]}',
2507
3096
  generationResultEnd,
2508
3097
  "",
2509
3098
  "Do not put Markdown fences around the markers. Do not claim implementation is complete."
@@ -2518,6 +3107,7 @@ function stripJsonFence(value) {
2518
3107
  }
2519
3108
 
2520
3109
  // src/runner-status.ts
3110
+ import { createHash as createHash3 } from "node:crypto";
2521
3111
  var watchStateReminderMs = 60 * 1e3;
2522
3112
  function formatWatchStartupContext(input) {
2523
3113
  return [
@@ -2537,28 +3127,32 @@ function shouldPrintWatchState(action, previous, nowMs, reminderMs = watchStateR
2537
3127
  function watchStateKey(action) {
2538
3128
  return [action.kind, action.message, action.workItemId, action.documentId, action.runnerId].filter(Boolean).join(":");
2539
3129
  }
3130
+ function stableRunnerId(input) {
3131
+ const digest = createHash3("sha256").update(`${input.accountId}:${input.projectId}:${input.repositoryLinkId}:${input.machineId}`).digest("hex").slice(0, 20);
3132
+ return `runner_${digest}`;
3133
+ }
2540
3134
 
2541
3135
  // src/importer.ts
2542
3136
  import { execFile as execFile2 } from "node:child_process";
2543
- import { createHash as createHash3 } from "node:crypto";
3137
+ import { createHash as createHash4 } from "node:crypto";
2544
3138
  import { readdir as readdir4, readFile as readFile6, stat as stat4 } from "node:fs/promises";
2545
3139
  import path9 from "node:path";
2546
3140
  import { promisify as promisify2 } from "node:util";
2547
3141
  var execFileAsync2 = promisify2(execFile2);
2548
3142
  var defaultMaxFileKb = 256;
2549
- var controlPlaneRoots = ["architecture", "context", "decisions", "features", "memory", "plans", "prompts", "workflows"];
3143
+ var controlPlaneRoots2 = ["architecture", "context", "decisions", "features", "memory", "plans", "prompts", "workflows"];
2550
3144
  var excludedDirectoryNames = /* @__PURE__ */ new Set([".git", "node_modules", ".pnpm-store", ".next", "dist", "build", "coverage", ".cache", "cache", "tmp", "temp", "vendor"]);
2551
- var excludedFileNames = /* @__PURE__ */ new Set(["context/amistio-project.md"]);
3145
+ var excludedFileNames = /* @__PURE__ */ new Set(["docs/context/amistio-project.md", "context/amistio-project.md"]);
2552
3146
  var generatedPathSegments = /* @__PURE__ */ new Set(["generated", "__generated__", "vendor", "vendors"]);
2553
3147
  var documentFolderByType = {
2554
- architecture: "architecture",
2555
- context: "context",
2556
- decision: "decisions",
2557
- feature: "features",
2558
- memory: "memory",
2559
- plan: "plans",
2560
- prompt: "prompts/shared",
2561
- workflow: "workflows"
3148
+ architecture: "docs/architecture",
3149
+ context: "docs/context",
3150
+ decision: "docs/decisions",
3151
+ feature: "docs/features",
3152
+ memory: "docs/memory",
3153
+ plan: "docs/plans",
3154
+ prompt: "docs/prompts/shared",
3155
+ workflow: "docs/workflows"
2562
3156
  };
2563
3157
  async function inspectLocalRepository(rootDir, defaultBranch) {
2564
3158
  const requestedRoot = path9.resolve(rootDir);
@@ -2688,7 +3282,7 @@ function parseOptionalOriginCloneUrl(originUrl) {
2688
3282
  async function listRepositoryPaths(rootDir) {
2689
3283
  const gitFiles = await runGit2(["-C", rootDir, "ls-files", "--cached", "--others", "--exclude-standard"]).catch(() => void 0);
2690
3284
  if (gitFiles !== void 0) {
2691
- return gitFiles.split("\n").map((line) => normalizeRepoPath2(line)).filter((line) => line.length > 0);
3285
+ return gitFiles.split("\n").map((line) => normalizeRepoPath3(line)).filter((line) => line.length > 0);
2692
3286
  }
2693
3287
  const files = [];
2694
3288
  await walkRepository(rootDir, rootDir, files);
@@ -2698,7 +3292,7 @@ async function walkRepository(rootDir, directory, files) {
2698
3292
  const entries = await readdir4(directory, { withFileTypes: true }).catch(() => []);
2699
3293
  for (const entry of entries) {
2700
3294
  const fullPath = path9.join(directory, entry.name);
2701
- const repoPath = normalizeRepoPath2(path9.relative(rootDir, fullPath));
3295
+ const repoPath = normalizeRepoPath3(path9.relative(rootDir, fullPath));
2702
3296
  if (entry.isDirectory()) {
2703
3297
  if (!excludedDirectoryNames.has(entry.name)) {
2704
3298
  await walkRepository(rootDir, fullPath, files);
@@ -2718,7 +3312,7 @@ function matchesIncludeExclude(repoPath, include, exclude) {
2718
3312
  return true;
2719
3313
  }
2720
3314
  function wildcardMatch(pattern, repoPath) {
2721
- const normalizedPattern = normalizeRepoPath2(pattern);
3315
+ const normalizedPattern = normalizeRepoPath3(pattern);
2722
3316
  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, ".*");
2723
3317
  return new RegExp(`^${escaped}$`).test(repoPath);
2724
3318
  }
@@ -2726,6 +3320,7 @@ function isMarkdownDocument(repoPath) {
2726
3320
  return /\.(md|mdx)$/i.test(repoPath);
2727
3321
  }
2728
3322
  function isExcludedRepoPath(repoPath) {
3323
+ if (isControlPlaneTemplateRepoPath(repoPath)) return true;
2729
3324
  if (excludedFileNames.has(repoPath)) return true;
2730
3325
  const segments = repoPath.split("/");
2731
3326
  if (segments.some((segment) => excludedDirectoryNames.has(segment) || generatedPathSegments.has(segment))) return true;
@@ -2748,9 +3343,12 @@ function classifyLegacyDocument(repoPath, content) {
2748
3343
  return "context";
2749
3344
  }
2750
3345
  function canonicalImportPath(sourcePath, documentType) {
2751
- if (isControlPlanePath2(sourcePath)) {
3346
+ if (isCanonicalControlPlanePath(sourcePath)) {
2752
3347
  return sourcePath;
2753
3348
  }
3349
+ if (isLegacyControlPlanePath(sourcePath)) {
3350
+ return `docs/${sourcePath}`;
3351
+ }
2754
3352
  const baseSlug = slugFromPath(sourcePath);
2755
3353
  return `${documentFolderByType[documentType]}/imported/${baseSlug}.md`;
2756
3354
  }
@@ -2766,9 +3364,13 @@ function uniqueDestinationPath(basePath, sourcePath, usedPaths) {
2766
3364
  usedPaths.add(uniquePath);
2767
3365
  return uniquePath;
2768
3366
  }
2769
- function isControlPlanePath2(repoPath) {
3367
+ function isCanonicalControlPlanePath(repoPath) {
3368
+ const [firstSegment, secondSegment] = normalizeRepoPath3(repoPath).split("/");
3369
+ return firstSegment === "docs" && Boolean(secondSegment && controlPlaneRoots2.includes(secondSegment));
3370
+ }
3371
+ function isLegacyControlPlanePath(repoPath) {
2770
3372
  const [firstSegment] = repoPath.split("/");
2771
- return Boolean(firstSegment && controlPlaneRoots.includes(firstSegment));
3373
+ return Boolean(firstSegment && controlPlaneRoots2.includes(firstSegment));
2772
3374
  }
2773
3375
  function inferTitle2(content, repoPath) {
2774
3376
  const body = stripFrontmatter(content);
@@ -2796,9 +3398,9 @@ function stableImportDocumentId(accountId, projectId, repositoryLinkId, sourcePa
2796
3398
  return `doc_import_${hashText(`${accountId}\0${projectId}\0${repositoryLinkId}\0${sourcePath}`, 24)}`;
2797
3399
  }
2798
3400
  function hashText(value, length) {
2799
- return createHash3("sha256").update(value).digest("hex").slice(0, length);
3401
+ return createHash4("sha256").update(value).digest("hex").slice(0, length);
2800
3402
  }
2801
- function normalizeRepoPath2(value) {
3403
+ function normalizeRepoPath3(value) {
2802
3404
  return value.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "");
2803
3405
  }
2804
3406
  async function runGit2(args) {
@@ -2842,6 +3444,9 @@ function buildBackgroundRunnerArgs(options) {
2842
3444
  if (!options.stream) {
2843
3445
  args.push("--no-stream");
2844
3446
  }
3447
+ if (options.verbose) {
3448
+ args.push("--verbose");
3449
+ }
2845
3450
  return args;
2846
3451
  }
2847
3452
  async function runOfficialCliUpdate() {
@@ -2880,6 +3485,94 @@ function truncateProcessOutput(value) {
2880
3485
  return trimmed.length > 1200 ? `${trimmed.slice(0, 1200)}...` : trimmed;
2881
3486
  }
2882
3487
 
3488
+ // src/git-worktree.ts
3489
+ import { execFile as execFile3 } from "node:child_process";
3490
+ import { mkdir as mkdir8, stat as stat5 } from "node:fs/promises";
3491
+ import path11 from "node:path";
3492
+ import { promisify as promisify3 } from "node:util";
3493
+ var execFileAsync3 = promisify3(execFile3);
3494
+ function needsGitWorktreeIsolation(workItem) {
3495
+ return (workItem.workKind ?? "implementation") === "implementation";
3496
+ }
3497
+ function resolveWorktreeIdentity(workItem) {
3498
+ const implementationScopeId = workItem.controllingAdrId ?? workItem.implementationScopeId ?? workItem.impactDocumentId ?? workItem.reviewDocumentId ?? workItem.generatedDraftId ?? workItem.workItemId;
3499
+ const slug = workIsolationSlug(implementationScopeId, workItem.title);
3500
+ return {
3501
+ implementationScopeId,
3502
+ branch: workItem.executionBranch ?? `amistio/work/${slug}`,
3503
+ worktreeKey: workItem.executionWorktreeKey ?? `amistio/worktrees/${slug}`,
3504
+ ...workItem.repositoryLockId ? { repositoryLockId: workItem.repositoryLockId } : {}
3505
+ };
3506
+ }
3507
+ async function prepareGitWorktreeIsolation(rootDir, workItem) {
3508
+ const identity = resolveWorktreeIdentity(workItem);
3509
+ const repoRoot = await gitOutput(rootDir, ["rev-parse", "--show-toplevel"]).catch((error) => {
3510
+ throw new Error(`Git worktree isolation requires a paired Git checkout: ${errorMessage2(error)}`);
3511
+ });
3512
+ const currentHead = await gitOutput(repoRoot, ["rev-parse", "HEAD"]);
3513
+ await assertBaseRevision(repoRoot, workItem.baseRevision, currentHead);
3514
+ const baseRevision = currentHead;
3515
+ const worktreePath = localWorktreePath(repoRoot, identity.worktreeKey);
3516
+ if (await pathExists(worktreePath)) {
3517
+ await assertExistingWorktree(worktreePath, identity.branch);
3518
+ return { ...identity, baseRevision, worktreePath };
3519
+ }
3520
+ await mkdir8(path11.dirname(worktreePath), { recursive: true });
3521
+ const branchExists = await gitCommandSucceeds(repoRoot, ["show-ref", "--verify", "--quiet", `refs/heads/${identity.branch}`]);
3522
+ const worktreeArgs = branchExists ? ["worktree", "add", worktreePath, identity.branch] : ["worktree", "add", "-b", identity.branch, worktreePath, baseRevision];
3523
+ await gitOutput(repoRoot, worktreeArgs).catch((error) => {
3524
+ throw new Error(`Could not create Git worktree ${identity.worktreeKey} on ${identity.branch}: ${errorMessage2(error)}`);
3525
+ });
3526
+ return { ...identity, baseRevision, worktreePath };
3527
+ }
3528
+ function localWorktreePath(repoRoot, worktreeKey) {
3529
+ const repoName = path11.basename(repoRoot);
3530
+ const worktreeSlug = worktreeKey.split("/").filter(Boolean).pop() ?? "work";
3531
+ return path11.join(path11.dirname(repoRoot), `${repoName}.worktrees`, worktreeSlug);
3532
+ }
3533
+ async function assertExistingWorktree(worktreePath, branch) {
3534
+ await gitOutput(worktreePath, ["rev-parse", "--is-inside-work-tree"]);
3535
+ const currentBranch = await gitOutput(worktreePath, ["branch", "--show-current"]);
3536
+ if (currentBranch && currentBranch !== branch) {
3537
+ throw new Error(`Existing worktree is on ${currentBranch}; expected ${branch}.`);
3538
+ }
3539
+ }
3540
+ async function assertBaseRevision(repoRoot, baseRevision, currentHead) {
3541
+ if (!baseRevision || baseRevision === currentHead) {
3542
+ return;
3543
+ }
3544
+ const revisionExists = await gitCommandSucceeds(repoRoot, ["cat-file", "-e", `${baseRevision}^{commit}`]);
3545
+ if (!revisionExists) {
3546
+ throw new Error(`Work item base revision ${baseRevision} is not available in this checkout; sync the repository before running implementation work.`);
3547
+ }
3548
+ const isAncestor = await gitCommandSucceeds(repoRoot, ["merge-base", "--is-ancestor", baseRevision, currentHead]);
3549
+ if (!isAncestor) {
3550
+ throw new Error(`Work item base revision ${baseRevision} is not an ancestor of ${currentHead}; refresh the work item before implementation.`);
3551
+ }
3552
+ }
3553
+ async function gitOutput(cwd, args) {
3554
+ const { stdout } = await execFileAsync3("git", args, { cwd, maxBuffer: 1024 * 1024 });
3555
+ return stdout.trim();
3556
+ }
3557
+ async function gitCommandSucceeds(cwd, args) {
3558
+ return execFileAsync3("git", args, { cwd }).then(() => true, () => false);
3559
+ }
3560
+ async function pathExists(value) {
3561
+ return stat5(value).then(() => true, () => false);
3562
+ }
3563
+ function workIsolationSlug(scopeId, title) {
3564
+ const scopeSlug = slugify(scopeId);
3565
+ const titleSlug = slugify(title).slice(0, 32);
3566
+ const combined = titleSlug && !scopeSlug.includes(titleSlug) ? `${scopeSlug}-${titleSlug}` : scopeSlug;
3567
+ return combined.slice(0, 96).replace(/-+$/g, "") || "work";
3568
+ }
3569
+ function slugify(value) {
3570
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "work";
3571
+ }
3572
+ function errorMessage2(error) {
3573
+ return error instanceof Error ? error.message : String(error);
3574
+ }
3575
+
2883
3576
  // src/version.ts
2884
3577
  import { readFileSync } from "node:fs";
2885
3578
  function readCliPackageVersion() {
@@ -3149,7 +3842,7 @@ work.command("prompt").description("Print or write an approved work prompt witho
3149
3842
  console.log(workItemId ? `No work item found for ${workItemId}.` : "No approved work item is ready for prompt export.");
3150
3843
  return;
3151
3844
  }
3152
- const prompt = createWorkExecutionPrompt(workItem);
3845
+ const prompt = await createRunnerWorkPrompt(context.client, context.metadata.amistioProjectId, workItem);
3153
3846
  if (options.out) {
3154
3847
  await writeFile8(options.out, prompt, "utf8");
3155
3848
  console.log(`Wrote work prompt to ${options.out}.`);
@@ -3199,7 +3892,7 @@ program.command("orchestrate").description("Update the Amistio control plane thr
3199
3892
  process.exitCode = result.exitCode;
3200
3893
  }
3201
3894
  });
3202
- program.command("run").description("Claim and run approved Amistio work locally").option("--api-url <url>", apiUrlOptionDescription, defaultApiUrl()).option("--runner-id <runnerId>", "Stable runner ID", `runner_${randomUUID()}`).option("--root <path>", "Repository root", defaultRoot).option("--tool <name>", "Local tool to use: auto, none, opencode, claude, codex, copilot, gemini, aider, cursor-agent").option("--invocation-channel <channel>", "Local invocation channel: auto, sdk, or command", parseInvocationChannel).option("--model <model>", "Model to request when the selected local tool supports model selection").option("--tool-command <command>", "Custom local command. Use {promptFile} and {root} placeholders when supported").option("--session <policy>", "Tool session policy: auto, new, continue:<toolSessionId>, or none", "auto").option("--dry-run", "Claim work and print the generated execution prompt without running a tool").option("--watch", "Keep polling for approved work until stopped").option("--background", "Start a detached background runner that watches for approved work").option("--interval-seconds <seconds>", "Polling interval for --watch", parsePositiveInteger, 10).option("--max-iterations <count>", "Stop watch mode after this many polling attempts", parsePositiveInteger).option("--no-stream", "Capture local tool output instead of streaming it").action(async (options, command) => {
3895
+ program.command("run").description("Claim and run approved Amistio work locally").option("--api-url <url>", apiUrlOptionDescription, defaultApiUrl()).option("--runner-id <runnerId>", "Stable runner ID").option("--root <path>", "Repository root", defaultRoot).option("--tool <name>", "Local tool to use: auto, none, opencode, claude, codex, copilot, gemini, aider, cursor-agent").option("--invocation-channel <channel>", "Local invocation channel: auto, sdk, or command", parseInvocationChannel).option("--model <model>", "Model to request when the selected local tool supports model selection").option("--tool-command <command>", "Custom local command. Use {promptFile} and {root} placeholders when supported").option("--session <policy>", "Tool session policy: auto, new, continue:<toolSessionId>, or none", "auto").option("--dry-run", "Claim work and print the generated execution prompt without running a tool").option("--watch", "Keep polling for approved work until stopped").option("--background", "Start a detached background runner that watches for approved work").option("--interval-seconds <seconds>", "Polling interval for --watch", parsePositiveInteger, 10).option("--max-iterations <count>", "Stop watch mode after this many polling attempts", parsePositiveInteger).option("--no-stream", "Capture local tool output instead of streaming it").option("--verbose", "Print detailed runner errors while watching").action(async (options, command) => {
3203
3896
  const context = await loadPairedApiContext(options.root, options.apiUrl);
3204
3897
  if (!context) {
3205
3898
  console.log("Repository is not paired. Run `amistio pair` first.");
@@ -3210,6 +3903,13 @@ program.command("run").description("Claim and run approved Amistio work locally"
3210
3903
  process.exitCode = 1;
3211
3904
  return;
3212
3905
  }
3906
+ const runnerId = options.runnerId ?? stableRunnerId({
3907
+ accountId: context.metadata.amistioAccountId,
3908
+ projectId: context.metadata.amistioProjectId,
3909
+ repositoryLinkId: context.metadata.repositoryLinkId,
3910
+ machineId: runnerMachineId()
3911
+ });
3912
+ const resolvedOptions = { ...options, runnerId };
3213
3913
  if (options.background) {
3214
3914
  if (options.dryRun) {
3215
3915
  console.log("Background runners cannot be started in dry-run mode.");
@@ -3220,10 +3920,10 @@ program.command("run").description("Claim and run approved Amistio work locally"
3220
3920
  accountId: context.metadata.amistioAccountId,
3221
3921
  projectId: context.metadata.amistioProjectId,
3222
3922
  repositoryLinkId: context.metadata.repositoryLinkId,
3223
- runnerId: options.runnerId,
3224
- rootDir: path11.resolve(options.root),
3923
+ runnerId,
3924
+ rootDir: path12.resolve(options.root),
3225
3925
  apiUrl: options.apiUrl,
3226
- args: buildBackgroundRunnerArgs(options)
3926
+ args: buildBackgroundRunnerArgs(resolvedOptions)
3227
3927
  });
3228
3928
  console.log(`Started background runner ${metadata.runnerId} with PID ${metadata.pid}.`);
3229
3929
  if (metadata.logPath) {
@@ -3232,59 +3932,60 @@ program.command("run").description("Claim and run approved Amistio work locally"
3232
3932
  return;
3233
3933
  }
3234
3934
  if (options.watch) {
3235
- for (const line of formatWatchStartupContext({ runnerId: options.runnerId, projectId: context.metadata.amistioProjectId, repositoryLinkId: context.metadata.repositoryLinkId, apiUrl: options.apiUrl, intervalSeconds: options.intervalSeconds })) {
3935
+ for (const line of formatWatchStartupContext({ runnerId, projectId: context.metadata.amistioProjectId, repositoryLinkId: context.metadata.repositoryLinkId, apiUrl: options.apiUrl, intervalSeconds: options.intervalSeconds })) {
3236
3936
  console.log(line);
3237
3937
  }
3238
3938
  }
3939
+ let offlineSent = false;
3940
+ const sendOfflineHeartbeat = async (message) => {
3941
+ if (offlineSent || options.dryRun) return;
3942
+ offlineSent = true;
3943
+ await context.client.sendRunnerHeartbeat(context.metadata.amistioProjectId, runnerId, context.metadata.repositoryLinkId, "offline", { ...runnerHeartbeatMetadata(), ...message ? { preferenceMessage: message } : {} }).catch(() => void 0);
3944
+ };
3945
+ const handleShutdownSignal = (signal) => {
3946
+ void sendOfflineHeartbeat(`Runner stopped by ${signal}.`).finally(() => {
3947
+ process.exit(signal === "SIGINT" ? 130 : 143);
3948
+ });
3949
+ };
3950
+ process.once("SIGINT", handleShutdownSignal);
3951
+ process.once("SIGTERM", handleShutdownSignal);
3239
3952
  let iterations = 0;
3240
3953
  let lastWatchStateLog;
3241
- while (true) {
3242
- iterations += 1;
3243
- const result = await runNextWorkItem({
3244
- apiClient: context.client,
3245
- projectId: context.metadata.amistioProjectId,
3246
- repositoryLinkId: context.metadata.repositoryLinkId,
3247
- runnerId: options.runnerId,
3248
- root: options.root,
3249
- sessionPolicy: normalizeSessionPolicy(options.session),
3250
- ...command.getOptionValueSource("tool") === "cli" && options.tool ? { explicitTool: options.tool } : {},
3251
- ...command.getOptionValueSource("invocationChannel") === "cli" && options.invocationChannel ? { explicitInvocationChannel: options.invocationChannel } : {},
3252
- ...command.getOptionValueSource("model") === "cli" && options.model ? { explicitModel: options.model } : {},
3253
- ...options.toolCommand ? { toolCommand: options.toolCommand } : {},
3254
- dryRun: Boolean(options.dryRun),
3255
- stream: options.stream,
3256
- commandContext: {
3257
- accountId: context.metadata.amistioAccountId,
3258
- apiUrl: options.apiUrl,
3259
- backgroundArgs: buildBackgroundRunnerArgs(options),
3260
- projectId: context.metadata.amistioProjectId,
3261
- repositoryLinkId: context.metadata.repositoryLinkId,
3262
- root: options.root,
3263
- runnerId: options.runnerId
3264
- },
3265
- suppressIdleOutput: Boolean(options.watch)
3266
- });
3267
- if (!options.watch || options.dryRun) {
3268
- if (result.exitCode !== 0) {
3269
- process.exitCode = result.exitCode;
3954
+ try {
3955
+ while (true) {
3956
+ iterations += 1;
3957
+ const result = await runWatchIteration({
3958
+ command,
3959
+ context,
3960
+ options: resolvedOptions,
3961
+ runnerId
3962
+ });
3963
+ if (!options.watch || options.dryRun) {
3964
+ if (result.exitCode !== 0) {
3965
+ process.exitCode = result.exitCode;
3966
+ }
3967
+ return;
3270
3968
  }
3271
- return;
3272
- }
3273
- if (result.status === "idle" && result.nextAction) {
3274
- const nowMs = Date.now();
3275
- if (shouldPrintWatchState(result.nextAction, lastWatchStateLog, nowMs)) {
3276
- console.log(formatWatchIdleLine(result.nextAction, options.intervalSeconds));
3277
- lastWatchStateLog = { key: watchStateKey(result.nextAction), printedAtMs: nowMs };
3969
+ if (result.status === "idle" && result.nextAction) {
3970
+ const nowMs = Date.now();
3971
+ if (shouldPrintWatchState(result.nextAction, lastWatchStateLog, nowMs)) {
3972
+ console.log(formatWatchIdleLine(result.nextAction, options.intervalSeconds));
3973
+ lastWatchStateLog = { key: watchStateKey(result.nextAction), printedAtMs: nowMs };
3974
+ }
3278
3975
  }
3976
+ if (result.stopRunner) {
3977
+ return;
3978
+ }
3979
+ if (options.maxIterations !== void 0 && iterations >= options.maxIterations) {
3980
+ console.log(`Runner stopped after ${iterations} polling attempt${iterations === 1 ? "" : "s"}.`);
3981
+ return;
3982
+ }
3983
+ await delay(options.intervalSeconds * 1e3);
3279
3984
  }
3280
- if (result.stopRunner) {
3281
- return;
3282
- }
3283
- if (options.maxIterations !== void 0 && iterations >= options.maxIterations) {
3284
- console.log(`Runner stopped after ${iterations} polling attempt${iterations === 1 ? "" : "s"}.`);
3285
- return;
3286
- }
3287
- await delay(options.intervalSeconds * 1e3);
3985
+ } finally {
3986
+ process.off("SIGINT", handleShutdownSignal);
3987
+ process.off("SIGTERM", handleShutdownSignal);
3988
+ await sendOfflineHeartbeat("Runner stopped.");
3288
3989
  }
3289
3990
  });
3290
3991
  var runner = program.command("runner").description("Manage local Amistio runner processes");
@@ -3358,6 +4059,59 @@ runner.command("stop").description("Stop a background runner for the paired repo
3358
4059
  }).catch(() => void 0);
3359
4060
  console.log(stopResult === "stopped" ? `Stopped background runner ${record.runnerId}.` : `Marked background runner ${record.runnerId} stopped; process was not running.`);
3360
4061
  });
4062
+ async function runWatchIteration({ command, context, options, runnerId }) {
4063
+ try {
4064
+ return await runNextWorkItem({
4065
+ apiClient: context.client,
4066
+ projectId: context.metadata.amistioProjectId,
4067
+ repositoryLinkId: context.metadata.repositoryLinkId,
4068
+ runnerId,
4069
+ root: options.root,
4070
+ sessionPolicy: normalizeSessionPolicy(options.session),
4071
+ ...command.getOptionValueSource("tool") === "cli" && options.tool ? { explicitTool: options.tool } : {},
4072
+ ...command.getOptionValueSource("invocationChannel") === "cli" && options.invocationChannel ? { explicitInvocationChannel: options.invocationChannel } : {},
4073
+ ...command.getOptionValueSource("model") === "cli" && options.model ? { explicitModel: options.model } : {},
4074
+ ...options.toolCommand ? { toolCommand: options.toolCommand } : {},
4075
+ dryRun: Boolean(options.dryRun),
4076
+ stream: options.stream,
4077
+ commandContext: {
4078
+ accountId: context.metadata.amistioAccountId,
4079
+ apiUrl: options.apiUrl,
4080
+ backgroundArgs: buildBackgroundRunnerArgs(options),
4081
+ projectId: context.metadata.amistioProjectId,
4082
+ repositoryLinkId: context.metadata.repositoryLinkId,
4083
+ root: options.root,
4084
+ runnerId
4085
+ },
4086
+ suppressIdleOutput: Boolean(options.watch),
4087
+ verbose: Boolean(options.verbose)
4088
+ });
4089
+ } catch (error) {
4090
+ if (!options.watch) {
4091
+ throw error;
4092
+ }
4093
+ const detail = truncateLogExcerpt(errorDetail(error));
4094
+ const message = "Runner hit an error while watching and will keep listening.";
4095
+ if (options.verbose) {
4096
+ console.error(`${message}
4097
+ ${detail}`);
4098
+ } else {
4099
+ console.error(`${message} Run with --verbose for details.`);
4100
+ }
4101
+ await Promise.allSettled([
4102
+ context.client.sendRunnerHeartbeat(context.metadata.amistioProjectId, runnerId, context.metadata.repositoryLinkId, "blocked", { ...runnerHeartbeatMetadata(), preferenceMessage: message }),
4103
+ context.client.recordRunnerLog(context.metadata.amistioProjectId, {
4104
+ runnerId,
4105
+ repositoryLinkId: context.metadata.repositoryLinkId,
4106
+ status: "failed",
4107
+ message,
4108
+ error: detail,
4109
+ machineId: runnerMachineId()
4110
+ })
4111
+ ]);
4112
+ return { status: "failed", exitCode: 1, message };
4113
+ }
4114
+ }
3361
4115
  async function runNextWorkItem({
3362
4116
  apiClient,
3363
4117
  dryRun,
@@ -3372,7 +4126,8 @@ async function runNextWorkItem({
3372
4126
  explicitTool,
3373
4127
  toolCommand,
3374
4128
  commandContext,
3375
- suppressIdleOutput
4129
+ suppressIdleOutput,
4130
+ verbose
3376
4131
  }) {
3377
4132
  const toolConfig = await resolveRunnerToolConfig({
3378
4133
  apiClient,
@@ -3394,7 +4149,7 @@ async function runNextWorkItem({
3394
4149
  console.log(toolConfig.message);
3395
4150
  return { status: "blocked", exitCode: 1 };
3396
4151
  }
3397
- const result = await apiClient.claimWork(projectId, runnerId, repositoryLinkId);
4152
+ const result = await apiClient.claimWork(projectId, runnerId, repositoryLinkId, 300, runnerIsolationCapabilityMetadata());
3398
4153
  if (!result.workItem) {
3399
4154
  const nextAction = await loadProjectNextAction(apiClient, projectId, repositoryLinkId, root);
3400
4155
  const message = formatProjectNextAction(nextAction);
@@ -3403,51 +4158,100 @@ async function runNextWorkItem({
3403
4158
  }
3404
4159
  return { status: "idle", exitCode: 0, nextAction, message };
3405
4160
  }
3406
- await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "running", runnerHeartbeatMetadata(toolConfig));
3407
4161
  const prompt = await createRunnerWorkPrompt(apiClient, projectId, result.workItem);
3408
4162
  if (dryRun || toolConfig.tool === "none") {
3409
4163
  console.log(prompt);
3410
4164
  await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
3411
4165
  return { status: "preview", exitCode: 0 };
3412
4166
  }
3413
- const preview = await createToolRunPreview({ rootDir: root, prompt, tool: toolConfig.tool, invocationChannel: toolConfig.requestedInvocationChannel ?? "auto", ...toolCommand ? { toolCommand } : {}, ...toolConfig.model ? { model: toolConfig.model } : {} });
4167
+ const worktreeIsolation = await prepareWorktreeForClaimedItem({ apiClient, projectId, repositoryLinkId, root, runnerId, toolConfig, workItem: result.workItem });
4168
+ if (worktreeIsolation.status === "blocked") {
4169
+ return { status: "blocked", exitCode: 1, message: worktreeIsolation.message };
4170
+ }
4171
+ const executionRoot = worktreeIsolation.isolation?.worktreePath ?? root;
4172
+ const isolationTelemetry = workItemIsolationTelemetry(result.workItem, worktreeIsolation.isolation);
4173
+ await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "running", {
4174
+ ...runnerHeartbeatMetadata(toolConfig),
4175
+ currentWorkItemId: result.workItem.workItemId,
4176
+ ...isolationTelemetry.implementationScopeId ? { currentImplementationScopeId: isolationTelemetry.implementationScopeId } : {},
4177
+ ...isolationTelemetry.executionWorktreeKey ? { currentWorktreeKey: isolationTelemetry.executionWorktreeKey } : {},
4178
+ ...isolationTelemetry.executionBranch ? { currentBranch: isolationTelemetry.executionBranch } : {}
4179
+ });
4180
+ const preview = await createToolRunPreview({ rootDir: executionRoot, prompt, tool: toolConfig.tool, invocationChannel: toolConfig.requestedInvocationChannel ?? "auto", ...toolCommand ? { toolCommand } : {}, ...toolConfig.model ? { model: toolConfig.model } : {} });
3414
4181
  const sessionContext = await prepareToolSession({
3415
4182
  apiClient,
3416
4183
  projectId,
3417
4184
  repositoryLinkId,
3418
4185
  runnerId,
4186
+ machineId: runnerMachineId(),
3419
4187
  sessionPolicy: result.workItem.sessionPolicy ?? sessionPolicy,
3420
4188
  toolName: preview.toolName,
3421
4189
  ...toolConfig.model ? { model: toolConfig.model } : {},
3422
4190
  supportsSessionReuse: preview.supportsSessionReuse,
3423
4191
  resumabilityScope: preview.resumabilityScope,
3424
- workItem: result.workItem
4192
+ workItem: result.workItem,
4193
+ isolationTelemetry
3425
4194
  });
3426
4195
  console.log(`Claimed ${result.workItem.workItemId}. Running ${preview.toolName}: ${preview.displayCommand}`);
4196
+ await recordRunnerMilestone(apiClient, projectId, result.workItem, runnerId, repositoryLinkId, {
4197
+ status: "running",
4198
+ summary: `Local runner started ${preview.toolName} execution.`,
4199
+ idempotencyKey: `runner_milestone_started_${result.workItem.workItemId}_${result.workItem.attempt}`,
4200
+ metadata: { tool: preview.toolName, invocationChannel: toolConfig.requestedInvocationChannel ?? "auto" }
4201
+ });
3427
4202
  const startedAt = Date.now();
3428
4203
  const providerSessionStore = new LocalToolSessionStore();
3429
4204
  const providerSessionId = sessionContext.toolSession ? await providerSessionStore.getProviderSessionId(sessionContext.toolSession.toolSessionId, preview.toolName) : void 0;
3430
- const toolResult = await runLocalTool({
3431
- rootDir: root,
3432
- prompt,
3433
- tool: toolConfig.tool,
3434
- invocationChannel: toolConfig.requestedInvocationChannel ?? "auto",
3435
- ...toolCommand ? { toolCommand } : {},
3436
- ...toolConfig.model ? { model: toolConfig.model } : {},
3437
- streamOutput: stream,
3438
- ...sessionContext.toolSession ? {
3439
- session: {
3440
- toolSessionId: sessionContext.toolSession.toolSessionId,
3441
- policy: sessionContext.policy,
3442
- decision: sessionContext.decision,
3443
- ...providerSessionId ? { providerSessionId } : {}
3444
- }
3445
- } : {}
3446
- }).catch(async (error) => {
3447
- await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "blocked", runnerHeartbeatMetadata(toolConfig));
3448
- await markToolSessionBlocked(apiClient, projectId, sessionContext.toolSession, errorMessage2(error));
3449
- throw error;
3450
- });
4205
+ let toolResult;
4206
+ try {
4207
+ toolResult = await runLocalTool({
4208
+ rootDir: executionRoot,
4209
+ prompt,
4210
+ tool: toolConfig.tool,
4211
+ invocationChannel: toolConfig.requestedInvocationChannel ?? "auto",
4212
+ ...toolCommand ? { toolCommand } : {},
4213
+ ...toolConfig.model ? { model: toolConfig.model } : {},
4214
+ streamOutput: stream,
4215
+ ...sessionContext.toolSession ? {
4216
+ session: {
4217
+ toolSessionId: sessionContext.toolSession.toolSessionId,
4218
+ policy: sessionContext.policy,
4219
+ decision: sessionContext.decision,
4220
+ ...providerSessionId ? { providerSessionId } : {}
4221
+ }
4222
+ } : {}
4223
+ });
4224
+ } catch (error) {
4225
+ const detail = truncateLogExcerpt(errorDetail(error));
4226
+ const durationMs2 = Date.now() - startedAt;
4227
+ const message = `${preview.toolName} failed before returning a result.`;
4228
+ await Promise.allSettled([
4229
+ apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig)),
4230
+ markToolSessionBlocked(apiClient, projectId, sessionContext.toolSession, errorMessage3(error)),
4231
+ apiClient.updateWorkStatus(projectId, result.workItem.workItemId, "failed", `run_failed_${result.workItem.workItemId}_${result.workItem.attempt}_${runnerId}`, runnerId, {
4232
+ ...isolationTelemetry,
4233
+ tool: preview.toolName,
4234
+ ...toolConfig.model ? { model: toolConfig.model } : {},
4235
+ durationMs: durationMs2,
4236
+ message,
4237
+ error: detail,
4238
+ ...sessionContext.toolSession ? { toolSessionId: sessionContext.toolSession.toolSessionId } : {},
4239
+ sessionPolicy: sessionContext.policy,
4240
+ sessionDecision: sessionContext.decision,
4241
+ sessionDecisionReason: sessionContext.reason
4242
+ }),
4243
+ recordRunnerMilestone(apiClient, projectId, result.workItem, runnerId, repositoryLinkId, {
4244
+ status: "failed",
4245
+ summary: message,
4246
+ idempotencyKey: `runner_milestone_tool_throw_${result.workItem.workItemId}_${result.workItem.attempt}`,
4247
+ metadata: { tool: preview.toolName, error: detail }
4248
+ })
4249
+ ]);
4250
+ if (verbose || !stream) {
4251
+ console.error(detail);
4252
+ }
4253
+ return { status: "failed", exitCode: 1, message };
4254
+ }
3451
4255
  if (sessionContext.toolSession && toolResult.providerSessionId) {
3452
4256
  await providerSessionStore.setProviderSessionId(sessionContext.toolSession.toolSessionId, preview.toolName, toolResult.providerSessionId);
3453
4257
  }
@@ -3471,6 +4275,35 @@ async function runNextWorkItem({
3471
4275
  workItem: result.workItem
3472
4276
  });
3473
4277
  }
4278
+ if (result.workItem.workKind === "assistantQuestion") {
4279
+ return finalizeAssistantQuestionWork({
4280
+ apiClient,
4281
+ durationMs: Date.now() - startedAt,
4282
+ projectId,
4283
+ repositoryLinkId,
4284
+ runnerId,
4285
+ sessionContext,
4286
+ toolConfig,
4287
+ toolName: preview.toolName,
4288
+ toolResult,
4289
+ workItem: result.workItem
4290
+ });
4291
+ }
4292
+ if (result.workItem.workKind === "impactPreview") {
4293
+ return finalizeImpactPreviewWork({
4294
+ apiClient,
4295
+ durationMs: Date.now() - startedAt,
4296
+ projectId,
4297
+ repositoryLinkId,
4298
+ root,
4299
+ runnerId,
4300
+ sessionContext,
4301
+ toolConfig,
4302
+ toolName: preview.toolName,
4303
+ toolResult,
4304
+ workItem: result.workItem
4305
+ });
4306
+ }
3474
4307
  const finalStatus = toolResult.exitCode === 0 ? "completed" : "failed";
3475
4308
  const durationMs = Date.now() - startedAt;
3476
4309
  const failureExcerpt = toolResult.exitCode === 0 ? void 0 : truncateLogExcerpt(toolResult.stderr || toolResult.stdout);
@@ -3498,6 +4331,7 @@ async function runNextWorkItem({
3498
4331
  ...toolResult.model ? { model: toolResult.model } : {},
3499
4332
  durationMs,
3500
4333
  message: `${preview.toolName} exited with code ${toolResult.exitCode}.`,
4334
+ ...isolationTelemetry,
3501
4335
  sessionPolicy: sessionContext.policy,
3502
4336
  sessionDecision: sessionContext.decision,
3503
4337
  sessionDecisionReason: sessionContext.reason,
@@ -3509,11 +4343,85 @@ async function runNextWorkItem({
3509
4343
  ...failureExcerpt ? { error: failureExcerpt } : {}
3510
4344
  }
3511
4345
  );
4346
+ await recordRunnerMilestone(apiClient, projectId, result.workItem, runnerId, repositoryLinkId, {
4347
+ status: finalStatus,
4348
+ summary: `${preview.toolName} exited with code ${toolResult.exitCode}.`,
4349
+ idempotencyKey: `runner_milestone_finished_${result.workItem.workItemId}_${statusResult.workItem.idempotencyKey}`,
4350
+ 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 ?? "" }
4351
+ });
3512
4352
  await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
3513
4353
  const durationSeconds = Math.round(durationMs / 1e3);
3514
4354
  console.log(`Marked ${statusResult.workItem.workItemId} ${statusResult.workItem.status} after ${durationSeconds}s.`);
3515
4355
  return { status: finalStatus, exitCode: toolResult.exitCode };
3516
4356
  }
4357
+ async function prepareWorktreeForClaimedItem({ apiClient, projectId, repositoryLinkId, root, runnerId, toolConfig, workItem }) {
4358
+ if (!needsGitWorktreeIsolation(workItem)) {
4359
+ return { status: "ready" };
4360
+ }
4361
+ const identity = resolveWorktreeIdentity(workItem);
4362
+ try {
4363
+ const isolation = await prepareGitWorktreeIsolation(root, workItem);
4364
+ await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
4365
+ status: "running",
4366
+ summary: `Prepared Git worktree ${isolation.worktreeKey} on ${isolation.branch}.`,
4367
+ idempotencyKey: `runner_milestone_worktree_${workItem.workItemId}_${workItem.attempt}`,
4368
+ metadata: { executionWorktreeKey: isolation.worktreeKey, executionBranch: isolation.branch, implementationScopeId: isolation.implementationScopeId }
4369
+ });
4370
+ return { status: "ready", isolation };
4371
+ } catch (error) {
4372
+ const message = errorMessage3(error);
4373
+ const telemetry = workItemIsolationTelemetry(workItem, { ...identity, baseRevision: workItem.baseRevision ?? "unknown", worktreePath: "" });
4374
+ const statusResult = await apiClient.updateWorkStatus(projectId, workItem.workItemId, "blocked", `worktree_${workItem.workItemId}_${randomUUID()}`, runnerId, {
4375
+ ...telemetry,
4376
+ message,
4377
+ blockerReason: message,
4378
+ error: message
4379
+ });
4380
+ await recordRunnerMilestone(apiClient, projectId, statusResult.workItem, runnerId, repositoryLinkId, {
4381
+ status: "blocked",
4382
+ summary: message,
4383
+ idempotencyKey: `runner_milestone_worktree_blocked_${workItem.workItemId}_${statusResult.workItem.idempotencyKey}`,
4384
+ metadata: { executionWorktreeKey: telemetry.executionWorktreeKey ?? "", executionBranch: telemetry.executionBranch ?? "", implementationScopeId: telemetry.implementationScopeId ?? "" }
4385
+ });
4386
+ await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "blocked", {
4387
+ ...runnerHeartbeatMetadata(toolConfig),
4388
+ currentWorkItemId: workItem.workItemId,
4389
+ ...telemetry.implementationScopeId ? { currentImplementationScopeId: telemetry.implementationScopeId } : {},
4390
+ ...telemetry.executionWorktreeKey ? { currentWorktreeKey: telemetry.executionWorktreeKey } : {},
4391
+ ...telemetry.executionBranch ? { currentBranch: telemetry.executionBranch } : {}
4392
+ });
4393
+ console.error(message);
4394
+ return { status: "blocked", message };
4395
+ }
4396
+ }
4397
+ function workItemIsolationTelemetry(workItem, isolation) {
4398
+ const implementationScopeId = isolation?.implementationScopeId ?? workItem.implementationScopeId;
4399
+ const executionBranch = isolation?.branch ?? workItem.executionBranch;
4400
+ const executionWorktreeKey = isolation?.worktreeKey ?? workItem.executionWorktreeKey;
4401
+ const repositoryLockId = isolation?.repositoryLockId ?? workItem.repositoryLockId;
4402
+ return {
4403
+ ...needsGitWorktreeIsolation(workItem) ? { isolationMode: "gitWorktree" } : workItem.isolationMode ? { isolationMode: workItem.isolationMode } : {},
4404
+ ...workItem.claimLeaseId ? { claimLeaseId: workItem.claimLeaseId } : {},
4405
+ ...workItem.controllingAdrId ? { controllingAdrId: workItem.controllingAdrId } : {},
4406
+ ...implementationScopeId ? { implementationScopeId } : {},
4407
+ ...executionBranch ? { executionBranch } : {},
4408
+ ...executionWorktreeKey ? { executionWorktreeKey } : {},
4409
+ ...repositoryLockId ? { repositoryLockId } : {},
4410
+ ...isolation?.baseRevision && isolation.baseRevision !== "unknown" ? { baseRevision: isolation.baseRevision } : {},
4411
+ machineId: runnerMachineId()
4412
+ };
4413
+ }
4414
+ async function recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, input) {
4415
+ await apiClient.recordActivityEvent(projectId, {
4416
+ eventType: "runnerMilestone",
4417
+ runnerId,
4418
+ repositoryLinkId,
4419
+ relatedWorkItemId: workItem.workItemId,
4420
+ ...workItem.reviewDocumentId ? { relatedDocumentId: workItem.reviewDocumentId } : workItem.impactDocumentId ? { relatedDocumentId: workItem.impactDocumentId } : {},
4421
+ ...workItem.generatedDraftId ? { generatedDraftId: workItem.generatedDraftId } : {},
4422
+ ...input
4423
+ }).catch(() => void 0);
4424
+ }
3517
4425
  async function runPendingRunnerCommand(apiClient, context, heartbeatMetadata) {
3518
4426
  const { commands } = await apiClient.listRunnerCommands(context.projectId, context.runnerId, context.repositoryLinkId).catch(() => ({ commands: [] }));
3519
4427
  const command = commands.filter((item) => item.status === "pending" || item.status === "acknowledged" || item.status === "running").sort((first, second) => Date.parse(first.createdAt) - Date.parse(second.createdAt))[0];
@@ -3564,7 +4472,7 @@ async function restartCurrentRunner(context) {
3564
4472
  const replacement = await restartRunnerDaemonProcess(metadata, context.backgroundArgs);
3565
4473
  return { succeeded: true, stopRunner: true, message: `Replacement background runner started with PID ${replacement.pid}.` };
3566
4474
  } catch (error) {
3567
- return { succeeded: false, message: "Background restart failed.", error: errorMessage2(error) };
4475
+ return { succeeded: false, message: "Background restart failed.", error: errorMessage3(error) };
3568
4476
  }
3569
4477
  }
3570
4478
  function runnerCommandLabel(commandKind) {
@@ -3591,7 +4499,7 @@ async function finalizeBrainGenerationWork({
3591
4499
  artifacts = parseBrainGenerationArtifacts(`${toolResult.stdout}
3592
4500
  ${toolResult.stderr}`);
3593
4501
  } catch (error) {
3594
- generationError = errorMessage2(error);
4502
+ generationError = errorMessage3(error);
3595
4503
  }
3596
4504
  } else {
3597
4505
  generationError = truncateLogExcerpt(toolResult.stderr || toolResult.stdout) || `${toolName} exited with code ${toolResult.exitCode}.`;
@@ -3629,11 +4537,17 @@ ${toolResult.stderr}`);
3629
4537
  ...sessionTelemetry,
3630
4538
  message: completionMessage
3631
4539
  });
4540
+ await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
4541
+ status: "completed",
4542
+ summary: completionMessage,
4543
+ idempotencyKey: `runner_milestone_generation_completed_${workItem.workItemId}_${result.workItem.idempotencyKey}`,
4544
+ metadata: { tool: toolName, durationMs, artifactCount: result.documents.length, verificationSummary: "Generated artifacts were accepted by the Amistio API for review." }
4545
+ });
3632
4546
  await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
3633
4547
  console.log(workItem.workKind === "planRevision" ? "Revised plan returned for review." : `Generated ${result.documents.length} brain artifact${result.documents.length === 1 ? "" : "s"} for review.`);
3634
4548
  return { status: "completed", exitCode: 0 };
3635
4549
  }
3636
- await apiClient.submitBrainGenerationResult(projectId, workItem.workItemId, {
4550
+ const failedResult = await apiClient.submitBrainGenerationResult(projectId, workItem.workItemId, {
3637
4551
  status: "failed",
3638
4552
  runnerId,
3639
4553
  idempotencyKey: `generation_${workItem.workItemId}_${randomUUID()}`,
@@ -3643,11 +4557,216 @@ ${toolResult.stderr}`);
3643
4557
  message: `${toolName} did not produce valid brain artifacts.`,
3644
4558
  ...generationError ? { error: generationError } : {}
3645
4559
  });
4560
+ await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
4561
+ status: "failed",
4562
+ summary: generationError ?? `${toolName} did not produce valid brain artifacts.`,
4563
+ idempotencyKey: `runner_milestone_generation_failed_${workItem.workItemId}_${failedResult.workItem.idempotencyKey}`,
4564
+ metadata: { tool: toolName, durationMs, verificationSummary: "Generation output could not be parsed into approved artifact JSON." }
4565
+ });
3646
4566
  await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
3647
4567
  console.error(generationError ?? "Local runner generation failed.");
3648
4568
  return { status: "failed", exitCode: toolResult.exitCode || 1 };
3649
4569
  }
4570
+ async function finalizeAssistantQuestionWork({
4571
+ apiClient,
4572
+ durationMs,
4573
+ projectId,
4574
+ repositoryLinkId,
4575
+ runnerId,
4576
+ sessionContext,
4577
+ toolConfig,
4578
+ toolName,
4579
+ toolResult,
4580
+ workItem
4581
+ }) {
4582
+ let answerResult;
4583
+ let answerError;
4584
+ if (toolResult.exitCode === 0) {
4585
+ try {
4586
+ answerResult = parseAssistantAnswerResult(`${toolResult.stdout}
4587
+ ${toolResult.stderr}`);
4588
+ } catch (error) {
4589
+ answerError = errorMessage3(error);
4590
+ }
4591
+ } else {
4592
+ answerError = truncateLogExcerpt(toolResult.stderr || toolResult.stdout) || `${toolName} exited with code ${toolResult.exitCode}.`;
4593
+ }
4594
+ const finalStatus = answerResult ? "completed" : "failed";
4595
+ const updatedToolSession = await finalizeToolSession({
4596
+ apiClient,
4597
+ projectId,
4598
+ status: finalStatus,
4599
+ runnerId,
4600
+ workItemId: workItem.workItemId,
4601
+ stdout: toolResult.stdout,
4602
+ ...sessionContext.toolSession ? { session: sessionContext.toolSession } : {},
4603
+ ...toolResult.messageCount !== void 0 ? { messageCount: toolResult.messageCount } : {},
4604
+ ...toolResult.tokensIn !== void 0 ? { tokensIn: toolResult.tokensIn } : {},
4605
+ ...toolResult.tokensOut !== void 0 ? { tokensOut: toolResult.tokensOut } : {},
4606
+ ...toolResult.costUsd !== void 0 ? { costUsd: toolResult.costUsd } : {}
4607
+ });
4608
+ const sessionTelemetry = {
4609
+ sessionPolicy: sessionContext.policy,
4610
+ sessionDecision: sessionContext.decision,
4611
+ sessionDecisionReason: sessionContext.reason,
4612
+ ...updatedToolSession ? { toolSessionId: updatedToolSession.toolSessionId } : {},
4613
+ ...updatedToolSession?.sessionGroupKey ? { sessionGroupKey: updatedToolSession.sessionGroupKey } : {}
4614
+ };
4615
+ if (answerResult) {
4616
+ const result = await apiClient.submitAssistantResult(projectId, workItem.workItemId, {
4617
+ status: "completed",
4618
+ runnerId,
4619
+ idempotencyKey: `assistant_${workItem.workItemId}_${randomUUID()}`,
4620
+ answer: answerResult.answer,
4621
+ sourceBoundary: answerResult.sourceBoundary,
4622
+ citations: answerResult.citations,
4623
+ tool: toolName,
4624
+ durationMs,
4625
+ ...sessionTelemetry,
4626
+ message: `${toolName} returned a project knowledge answer.`
4627
+ });
4628
+ await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
4629
+ status: "completed",
4630
+ summary: `${toolName} returned a project knowledge answer.`,
4631
+ idempotencyKey: `runner_milestone_assistant_completed_${workItem.workItemId}_${result.workItem.idempotencyKey}`,
4632
+ metadata: { tool: toolName, durationMs, sourceBoundary: answerResult.sourceBoundary, citationCount: answerResult.citations.length }
4633
+ });
4634
+ await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
4635
+ console.log("Project knowledge answer returned.");
4636
+ return { status: "completed", exitCode: 0 };
4637
+ }
4638
+ const failedResult = await apiClient.submitAssistantResult(projectId, workItem.workItemId, {
4639
+ status: "failed",
4640
+ runnerId,
4641
+ idempotencyKey: `assistant_${workItem.workItemId}_${randomUUID()}`,
4642
+ tool: toolName,
4643
+ durationMs,
4644
+ ...sessionTelemetry,
4645
+ message: `${toolName} did not produce a valid project knowledge answer.`,
4646
+ ...answerError ? { error: answerError } : {}
4647
+ });
4648
+ await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
4649
+ status: "failed",
4650
+ summary: answerError ?? `${toolName} did not produce a valid project knowledge answer.`,
4651
+ idempotencyKey: `runner_milestone_assistant_failed_${workItem.workItemId}_${failedResult.workItem.idempotencyKey}`,
4652
+ metadata: { tool: toolName, durationMs, verificationSummary: "Assistant output did not include a valid structured answer." }
4653
+ });
4654
+ await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
4655
+ console.error(answerError ?? "Local runner assistant answer failed.");
4656
+ return { status: "failed", exitCode: toolResult.exitCode || 1 };
4657
+ }
4658
+ async function finalizeImpactPreviewWork({
4659
+ apiClient,
4660
+ durationMs,
4661
+ projectId,
4662
+ repositoryLinkId,
4663
+ root,
4664
+ runnerId,
4665
+ sessionContext,
4666
+ toolConfig,
4667
+ toolName,
4668
+ toolResult,
4669
+ workItem
4670
+ }) {
4671
+ let report = void 0;
4672
+ let previewError;
4673
+ if (toolResult.exitCode === 0) {
4674
+ try {
4675
+ report = parseImpactPreviewResult(`${toolResult.stdout}
4676
+ ${toolResult.stderr}`);
4677
+ } catch (error) {
4678
+ previewError = errorMessage3(error);
4679
+ }
4680
+ } else {
4681
+ previewError = truncateLogExcerpt(toolResult.stderr || toolResult.stdout) || `${toolName} exited with code ${toolResult.exitCode}.`;
4682
+ }
4683
+ const finalStatus = report ? "completed" : "failed";
4684
+ const updatedToolSession = await finalizeToolSession({
4685
+ apiClient,
4686
+ projectId,
4687
+ status: finalStatus,
4688
+ runnerId,
4689
+ workItemId: workItem.workItemId,
4690
+ stdout: toolResult.stdout,
4691
+ ...sessionContext.toolSession ? { session: sessionContext.toolSession } : {},
4692
+ ...toolResult.messageCount !== void 0 ? { messageCount: toolResult.messageCount } : {},
4693
+ ...toolResult.tokensIn !== void 0 ? { tokensIn: toolResult.tokensIn } : {},
4694
+ ...toolResult.tokensOut !== void 0 ? { tokensOut: toolResult.tokensOut } : {},
4695
+ ...toolResult.costUsd !== void 0 ? { costUsd: toolResult.costUsd } : {}
4696
+ });
4697
+ const sessionTelemetry = {
4698
+ sessionPolicy: sessionContext.policy,
4699
+ sessionDecision: sessionContext.decision,
4700
+ sessionDecisionReason: sessionContext.reason,
4701
+ ...updatedToolSession ? { toolSessionId: updatedToolSession.toolSessionId } : {},
4702
+ ...updatedToolSession?.sessionGroupKey ? { sessionGroupKey: updatedToolSession.sessionGroupKey } : {}
4703
+ };
4704
+ if (report) {
4705
+ const metadata = await readProjectLink(root).catch(() => void 0);
4706
+ const result = await apiClient.submitImpactPreviewResult(projectId, workItem.workItemId, {
4707
+ status: "completed",
4708
+ runnerId,
4709
+ idempotencyKey: `impact_${workItem.workItemId}_${randomUUID()}`,
4710
+ report: {
4711
+ ...report,
4712
+ analyzedRepoRevision: report.analyzedRepoRevision ?? metadata?.lastSyncedRevision
4713
+ },
4714
+ tool: toolName,
4715
+ durationMs,
4716
+ ...sessionTelemetry,
4717
+ message: `${toolName} returned an implementation impact preview.`
4718
+ });
4719
+ await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
4720
+ status: "completed",
4721
+ summary: `${toolName} returned an implementation impact preview.`,
4722
+ idempotencyKey: `runner_milestone_impact_completed_${workItem.workItemId}_${result.workItem.idempotencyKey}`,
4723
+ metadata: { tool: toolName, durationMs, riskLevel: report.riskLevel, likelyPathCount: report.likelyPaths.length, verificationSummary: report.verificationPlan }
4724
+ });
4725
+ await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
4726
+ console.log(result.implementationWorkItem ? "Impact preview returned; implementation work is now queued." : "Impact preview returned.");
4727
+ return { status: "completed", exitCode: 0 };
4728
+ }
4729
+ const failedResult = await apiClient.submitImpactPreviewResult(projectId, workItem.workItemId, {
4730
+ status: "failed",
4731
+ runnerId,
4732
+ idempotencyKey: `impact_${workItem.workItemId}_${randomUUID()}`,
4733
+ tool: toolName,
4734
+ durationMs,
4735
+ ...sessionTelemetry,
4736
+ message: `${toolName} did not produce a valid impact preview.`,
4737
+ ...previewError ? { error: previewError } : {}
4738
+ });
4739
+ await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
4740
+ status: "failed",
4741
+ summary: previewError ?? `${toolName} did not produce a valid impact preview.`,
4742
+ idempotencyKey: `runner_milestone_impact_failed_${workItem.workItemId}_${failedResult.workItem.idempotencyKey}`,
4743
+ metadata: { tool: toolName, durationMs, verificationSummary: "Impact preview output did not include valid structured JSON." }
4744
+ });
4745
+ await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
4746
+ console.error(previewError ?? "Local runner impact preview failed.");
4747
+ return { status: "failed", exitCode: toolResult.exitCode || 1 };
4748
+ }
3650
4749
  async function createRunnerWorkPrompt(apiClient, projectId, workItem) {
4750
+ if (workItem.workKind === "assistantQuestion") {
4751
+ const [{ documents: documents2 }, { messages: messages2 }] = await Promise.all([
4752
+ apiClient.listBrainDocuments(projectId),
4753
+ apiClient.listAssistantMessages(projectId)
4754
+ ]);
4755
+ 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];
4756
+ return createWorkExecutionPrompt(workItem, {
4757
+ ...question ? { assistantQuestion: { question, messages: messages2, documents: documents2 } } : {}
4758
+ });
4759
+ }
4760
+ if (workItem.workKind === "impactPreview") {
4761
+ const { documents: documents2 } = await apiClient.listBrainDocuments(projectId);
4762
+ 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"));
4763
+ return createWorkExecutionPrompt(workItem, {
4764
+ impactPreview: {
4765
+ documents: documents2,
4766
+ ...implementationPrompt ? { implementationPrompt } : {}
4767
+ }
4768
+ });
4769
+ }
3651
4770
  if (workItem.workKind !== "planRevision" || !workItem.reviewDocumentId) {
3652
4771
  return createWorkExecutionPrompt(workItem);
3653
4772
  }
@@ -3720,6 +4839,8 @@ function isPromptReadyStatus(status) {
3720
4839
  }
3721
4840
  async function prepareToolSession({
3722
4841
  apiClient,
4842
+ isolationTelemetry,
4843
+ machineId,
3723
4844
  projectId,
3724
4845
  repositoryLinkId,
3725
4846
  runnerId,
@@ -3738,6 +4859,7 @@ async function prepareToolSession({
3738
4859
  toolName,
3739
4860
  runnerId,
3740
4861
  repositoryLinkId,
4862
+ machineId,
3741
4863
  supportsSessionReuse
3742
4864
  });
3743
4865
  if (selection.decision === "skipped") {
@@ -3747,8 +4869,12 @@ async function prepareToolSession({
3747
4869
  const { toolSession: toolSession2 } = await apiClient.updateToolSession(projectId, selection.toolSession.toolSessionId, {
3748
4870
  status: "active",
3749
4871
  runnerId,
4872
+ machineId,
3750
4873
  lastWorkItemId: workItem.workItemId,
3751
- reusePolicy: sessionPolicy
4874
+ reusePolicy: sessionPolicy,
4875
+ ...isolationTelemetry.implementationScopeId ? { implementationScopeId: isolationTelemetry.implementationScopeId } : {},
4876
+ ...isolationTelemetry.executionWorktreeKey ? { executionWorktreeKey: isolationTelemetry.executionWorktreeKey } : {},
4877
+ ...isolationTelemetry.isolationMode ? { isolationMode: isolationTelemetry.isolationMode } : {}
3752
4878
  });
3753
4879
  return { ...selection, toolSession: toolSession2 };
3754
4880
  }
@@ -3764,9 +4890,14 @@ async function prepareToolSession({
3764
4890
  summary: selection.reason,
3765
4891
  status: "active",
3766
4892
  runnerId,
4893
+ machineId,
3767
4894
  lastWorkItemId: workItem.workItemId,
3768
4895
  messageCount: 0,
3769
4896
  reusePolicy: sessionPolicy,
4897
+ ...workItem.requestedByUserId ?? workItem.requestedBy ? { createdByUserId: workItem.requestedByUserId ?? workItem.requestedBy } : {},
4898
+ ...isolationTelemetry.implementationScopeId ? { implementationScopeId: isolationTelemetry.implementationScopeId } : {},
4899
+ ...isolationTelemetry.executionWorktreeKey ? { executionWorktreeKey: isolationTelemetry.executionWorktreeKey } : {},
4900
+ ...isolationTelemetry.isolationMode ? { isolationMode: isolationTelemetry.isolationMode } : {},
3770
4901
  ...workItem.sessionGroupKey ? { sessionGroupKey: workItem.sessionGroupKey } : {}
3771
4902
  });
3772
4903
  return { ...selection, toolSession };
@@ -3827,9 +4958,12 @@ function summarizeToolOutput(value) {
3827
4958
  }
3828
4959
  return trimmed.length > 300 ? `${trimmed.slice(0, 300)}...` : trimmed;
3829
4960
  }
3830
- function errorMessage2(error) {
4961
+ function errorMessage3(error) {
3831
4962
  return error instanceof Error ? error.message : String(error);
3832
4963
  }
4964
+ function errorDetail(error) {
4965
+ return error instanceof Error ? error.stack ?? error.message : String(error);
4966
+ }
3833
4967
  function truncateLogExcerpt(value) {
3834
4968
  const trimmed = value.trim();
3835
4969
  return trimmed.length > 1200 ? `${trimmed.slice(0, 1200)}...` : trimmed;
@@ -3854,10 +4988,10 @@ function parseInvocationChannel(value) {
3854
4988
  throw new Error(`Expected invocation channel auto, sdk, or command; received ${value}.`);
3855
4989
  }
3856
4990
  function inferRepoName(root) {
3857
- return path11.basename(path11.resolve(root)) || "repository";
4991
+ return path12.basename(path12.resolve(root)) || "repository";
3858
4992
  }
3859
4993
  function createRepoFingerprint(accountId, projectId, repositoryLinkId) {
3860
- return createHash4("sha256").update(`${accountId}:${projectId}:${repositoryLinkId}`).digest("hex");
4994
+ return createHash5("sha256").update(`${accountId}:${projectId}:${repositoryLinkId}`).digest("hex");
3861
4995
  }
3862
4996
  function defaultApiUrl() {
3863
4997
  const envApiUrl = process.env[AMISTIO_API_URL_ENV]?.trim();
@@ -3971,14 +5105,24 @@ function toRunnerToolCapabilities(tools) {
3971
5105
  execution: tool.execution,
3972
5106
  supportsSessionReuse: tool.supportsSessionReuse,
3973
5107
  resumabilityScope: tool.resumabilityScope,
3974
- supportsModelSelection: tool.supportsModelSelection
5108
+ supportsModelSelection: tool.supportsModelSelection,
5109
+ supportsBranchIsolation: true,
5110
+ supportsGitWorktreeIsolation: true
3975
5111
  }));
3976
5112
  }
5113
+ function runnerIsolationCapabilityMetadata() {
5114
+ return {
5115
+ machineId: runnerMachineId(),
5116
+ supportsBranchIsolation: true,
5117
+ supportsGitWorktreeIsolation: true
5118
+ };
5119
+ }
3977
5120
  function runnerHeartbeatMetadata(toolConfig, mode = currentRunnerMode()) {
3978
5121
  return {
3979
5122
  version: CLI_VERSION,
3980
5123
  mode,
3981
5124
  hostname: os5.hostname(),
5125
+ ...runnerIsolationCapabilityMetadata(),
3982
5126
  ...toolConfig?.capabilities ? { capabilities: toolConfig.capabilities } : {},
3983
5127
  ...toolConfig?.requestedTool ? { requestedTool: toolConfig.requestedTool } : {},
3984
5128
  ...toolConfig?.requestedInvocationChannel ? { requestedInvocationChannel: toolConfig.requestedInvocationChannel } : {},
@@ -3990,6 +5134,9 @@ function runnerHeartbeatMetadata(toolConfig, mode = currentRunnerMode()) {
3990
5134
  ...toolConfig?.message ? { preferenceMessage: toolConfig.message } : {}
3991
5135
  };
3992
5136
  }
5137
+ function runnerMachineId() {
5138
+ return createHash5("sha256").update(`${os5.hostname()}:${os5.platform()}:${os5.arch()}`).digest("hex").slice(0, 20);
5139
+ }
3993
5140
  async function delay(milliseconds) {
3994
5141
  await new Promise((resolve) => setTimeout(resolve, milliseconds));
3995
5142
  }