@amistio/cli 0.1.44 → 0.1.46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -88,7 +88,7 @@ Approved implementation work uses Git as the handoff boundary. During worktree p
88
88
 
89
89
  Failed or stale work can be requeued from the web Tasks panel. Requeue creates a new linked work attempt and preserves the original terminal attempt for audit history; Requeue all sends one backend batch that recomputes safe candidates, reports already-active and skipped rows, and still uses linked attempts. Requeue is blocked while equivalent work is already active or when the paired runner does not advertise the needed work kind. Completed implementation status is separate from proof: queue `implementationVerification` from Tasks when a plan needs source-aware evidence before cleanup or implementation status decisions.
90
90
 
91
- Runner setup and local-tool execution use bounded failure controls. `amistio run --watch` retries Git worktree preflight failures by releasing the claim for another attempt, then fails the work item after `--max-preflight-attempts` attempts, defaulting to 3. Active local-tool runs renew the work lease, and `--tool-timeout-seconds` caps tool execution, defaulting to 1800 seconds.
91
+ Runner setup and local-tool execution use bounded failure controls. During Git worktree preflight, `amistio run --watch` repairs safe stale Git registrations when the target worktree directory is missing and Git marks the registration prunable; dirty, present, or ambiguous worktrees are preserved. Other Git worktree preflight failures are retried by releasing the claim for another attempt, then fail the work item after `--max-preflight-attempts` attempts, defaulting to 3. Active local-tool runs renew the work lease, and `--tool-timeout-seconds` caps tool execution, defaulting to 1800 seconds.
92
92
 
93
93
  The environment doctor blocks work before local AI/tool execution when `git`, `node`, Corepack, the package manager, required package scripts, dependencies, setup allowlist, Docker, or the requested execution profile is not ready. Foreground, background, and startup-service runners accept `--execution-profile`; use `--setup-package-manager-install` with `hostWorktreeWithSetup` when the runner may run the fixed package-manager install step in the execution worktree. Work and Runner surfaces receive sanitized profile/readiness metadata only; raw host paths, command lines, environment values, and secrets are not uploaded. Watch mode also performs a Git PATH preflight before auto-sync or work claiming. If the runner reports that Git is not available to the runner PATH, install Git or restart the foreground, background, or startup-service runner from an environment where `git --version` works. On macOS, service and GUI-launched runner environments may not inherit the same PATH as an interactive shell, so restart or reinstall the service after changing PATH.
94
94
 
package/dist/index.js CHANGED
@@ -63,6 +63,14 @@ var documentTypeSchema = z.enum([
63
63
  ]);
64
64
  var documentContentFormatSchema = z.enum(["markdown", "html"]);
65
65
  var artifactFormatPreferenceSchema = z.enum(["markdown", "html", "both", "auto"]);
66
+ var githubPullRequestUrlSchema = z.string().trim().url().max(500).refine((value) => {
67
+ try {
68
+ const url = new URL(value);
69
+ return url.protocol === "https:" && url.hostname === "github.com" && /^\/[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+\/pull\/[1-9]\d*\/?$/.test(url.pathname);
70
+ } catch {
71
+ return false;
72
+ }
73
+ }, "Pull request URL must be an HTTPS github.com pull request URL.");
66
74
  var syncStateSchema = z.enum([
67
75
  "draft",
68
76
  "approved",
@@ -223,7 +231,7 @@ var implementationHandoffSchema = z.object({
223
231
  remoteName: z.string().trim().min(1).max(80).optional(),
224
232
  commitSha: z.string().trim().min(7).max(64).optional(),
225
233
  prNumber: z.number().int().positive().optional(),
226
- prUrl: z.string().trim().url().max(500).optional(),
234
+ prUrl: githubPullRequestUrlSchema.optional(),
227
235
  cleanupStatus: implementationHandoffCleanupStatusSchema.optional(),
228
236
  cleanupMessage: z.string().trim().min(1).max(600).optional(),
229
237
  artifacts: implementationHandoffArtifactsSchema.optional(),
@@ -282,7 +290,7 @@ var autopilotCandidateLinksSchema = z.object({
282
290
  runnerId: z.string().min(1).optional(),
283
291
  branchName: z.string().trim().min(1).max(200).optional(),
284
292
  worktreeKey: z.string().trim().min(1).max(300).optional(),
285
- pullRequestUrl: z.string().trim().url().max(500).optional()
293
+ pullRequestUrl: githubPullRequestUrlSchema.optional()
286
294
  }).strict();
287
295
  var autopilotCandidateActionSchema = z.object({
288
296
  candidateId: z.string().trim().min(1).max(160),
@@ -1470,7 +1478,7 @@ var implementationVerificationResultSchema = z.object({
1470
1478
  checks: z.array(implementationVerificationCheckSchema).default([]),
1471
1479
  gaps: z.array(z.string().trim().min(1).max(600)).default([]),
1472
1480
  branch: z.string().trim().min(1).max(200).optional(),
1473
- pullRequestUrl: z.string().trim().url().max(500).optional(),
1481
+ pullRequestUrl: githubPullRequestUrlSchema.optional(),
1474
1482
  worktreeKey: z.string().trim().min(1).max(300).optional(),
1475
1483
  recommendation: implementationVerificationRecommendationSchema.default("none"),
1476
1484
  verificationPlan: z.array(z.string().trim().min(1).max(300)).default([]),
@@ -9505,6 +9513,7 @@ async function prepareGitWorktreeIsolation(rootDir, workItem) {
9505
9513
  const preparedLocalEnvironmentFileCount2 = await prepareLocalWorktreeEnvironment(repoRoot, worktreePath);
9506
9514
  return { ...identity, baseRevision, worktreePath, ...preparedLocalEnvironmentFileCount2 ? { preparedLocalEnvironmentFileCount: preparedLocalEnvironmentFileCount2 } : {} };
9507
9515
  }
9516
+ await repairMissingRegisteredWorktree(repoRoot, worktreePath, identity.branch, identity.worktreeKey);
9508
9517
  await mkdir12(path17.dirname(worktreePath), { recursive: true });
9509
9518
  const branchExists = await gitCommandSucceeds(repoRoot, ["show-ref", "--verify", "--quiet", `refs/heads/${identity.branch}`]);
9510
9519
  const worktreeArgs = branchExists ? ["worktree", "add", worktreePath, identity.branch] : ["worktree", "add", "-b", identity.branch, worktreePath, baseRevision];
@@ -9534,6 +9543,63 @@ async function assertExistingWorktree(worktreePath, branch) {
9534
9543
  throw new Error(`Existing worktree is on ${currentBranch}; expected ${branch}.`);
9535
9544
  }
9536
9545
  }
9546
+ async function repairMissingRegisteredWorktree(repoRoot, worktreePath, branch, worktreeKey) {
9547
+ const registeredWorktree = await findRegisteredWorktree(repoRoot, worktreePath);
9548
+ if (!registeredWorktree) {
9549
+ return;
9550
+ }
9551
+ const expectedBranchRef = `refs/heads/${branch}`;
9552
+ if (registeredWorktree.branch !== expectedBranchRef) {
9553
+ throw new Error(`Registered Git worktree ${worktreeKey} is missing on disk but belongs to ${registeredWorktree.branch ?? "detached HEAD"}; expected ${expectedBranchRef}. Repair local Git worktrees before retrying.`);
9554
+ }
9555
+ if (registeredWorktree.locked || !registeredWorktree.prunable) {
9556
+ throw new Error(`Registered Git worktree ${worktreeKey} is missing on disk but is not safely prunable. Repair local Git worktrees before retrying.`);
9557
+ }
9558
+ await gitOutput(repoRoot, ["worktree", "prune", "--expire", "now"]);
9559
+ const remainingWorktree = await findRegisteredWorktree(repoRoot, worktreePath);
9560
+ if (remainingWorktree) {
9561
+ throw new Error(`Registered Git worktree ${worktreeKey} remained after pruning. Repair local Git worktrees before retrying.`);
9562
+ }
9563
+ }
9564
+ async function findRegisteredWorktree(repoRoot, worktreePath) {
9565
+ const worktrees = parseRegisteredGitWorktrees(await gitOutput(repoRoot, ["worktree", "list", "--porcelain"]));
9566
+ const targetPath = normalizeGitWorktreePath(worktreePath);
9567
+ return worktrees.find((worktree) => normalizeGitWorktreePath(worktree.path) === targetPath);
9568
+ }
9569
+ function parseRegisteredGitWorktrees(output) {
9570
+ const worktrees = [];
9571
+ let current;
9572
+ for (const line of output.split(/\r?\n/)) {
9573
+ if (!line) {
9574
+ current = void 0;
9575
+ continue;
9576
+ }
9577
+ if (line.startsWith("worktree ")) {
9578
+ current = { locked: false, path: line.slice("worktree ".length), prunable: false };
9579
+ worktrees.push(current);
9580
+ continue;
9581
+ }
9582
+ if (!current) {
9583
+ continue;
9584
+ }
9585
+ if (line.startsWith("branch ")) {
9586
+ current.branch = line.slice("branch ".length);
9587
+ continue;
9588
+ }
9589
+ if (line.startsWith("locked")) {
9590
+ current.locked = true;
9591
+ continue;
9592
+ }
9593
+ if (line.startsWith("prunable")) {
9594
+ current.prunable = true;
9595
+ }
9596
+ }
9597
+ return worktrees;
9598
+ }
9599
+ function normalizeGitWorktreePath(value) {
9600
+ const resolved = path17.resolve(value);
9601
+ return resolved.startsWith("/private/var/") ? resolved.replace("/private/var/", "/var/") : resolved;
9602
+ }
9537
9603
  async function assertBaseRevision(repoRoot, baseRevision, currentHead) {
9538
9604
  if (!baseRevision || baseRevision === currentHead) {
9539
9605
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amistio/cli",
3
- "version": "0.1.44",
3
+ "version": "0.1.46",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",