@h-rig/repos-plugin 0.0.6-alpha.156 → 0.0.6-alpha.158

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.
@@ -1,19 +1,8 @@
1
1
  // @bun
2
- var __defProp = Object.defineProperty;
3
- var __returnValue = (v) => v;
4
- function __exportSetter(name, newValue) {
5
- this[name] = __returnValue.bind(null, newValue);
6
- }
7
- var __export = (target, all) => {
8
- for (var name in all)
9
- __defProp(target, name, {
10
- get: all[name],
11
- enumerable: true,
12
- configurable: true,
13
- set: __exportSetter.bind(all, name)
14
- });
15
- };
16
- var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
2
+ // packages/repos-plugin/src/plugin.ts
3
+ import { definePlugin } from "@rig/core/config";
4
+ import { defineCapability as defineCapability2 } from "@rig/core/capability";
5
+ import { MANAGED_REPO_SERVICE_CAPABILITY, REPO_OPERATIONS_CAPABILITY } from "@rig/contracts";
17
6
 
18
7
  // packages/repos-plugin/src/registry.ts
19
8
  function createRepoRegistry(entries) {
@@ -29,6 +18,7 @@ function createRepoRegistry(entries) {
29
18
  list: () => ordered
30
19
  };
31
20
  }
21
+ var MANAGED_REPOS = new Map;
32
22
  function setManagedRepos(entries) {
33
23
  const next = new Map;
34
24
  for (const e of entries) {
@@ -66,19 +56,15 @@ function repoRegistrationToManagedEntry(reg) {
66
56
  alias: reg.defaultPath ?? reg.id,
67
57
  defaultBranch: reg.defaultBranch,
68
58
  defaultRemoteUrl: reg.url,
69
- remoteEnvVar: reg.remoteEnvVar,
70
- checkoutEnvVar: reg.checkoutEnvVar
59
+ ...reg.remoteEnvVar !== undefined ? { remoteEnvVar: reg.remoteEnvVar } : {},
60
+ ...reg.checkoutEnvVar !== undefined ? { checkoutEnvVar: reg.checkoutEnvVar } : {}
71
61
  };
72
62
  }
73
- var MANAGED_REPOS;
74
- var init_registry = __esm(() => {
75
- MANAGED_REPOS = new Map;
76
- });
77
63
 
78
64
  // packages/repos-plugin/src/layout.ts
79
65
  import { existsSync } from "fs";
80
66
  import { basename, dirname, join, resolve } from "path";
81
- import { resolveMonorepoRoot } from "@rig/runtime/layout";
67
+ import { resolveMonorepoRoot } from "@rig/core/layout";
82
68
  function resolveRepoStateDir(projectRoot) {
83
69
  const normalizedProjectRoot = resolve(projectRoot);
84
70
  const projectParent = dirname(normalizedProjectRoot);
@@ -129,12 +115,18 @@ function resolveMonorepoRepoLayout(projectRoot) {
129
115
  const primary = entries[0];
130
116
  return resolveManagedRepoLayout(projectRoot, primary.id);
131
117
  }
132
- var init_layout = __esm(() => {
133
- init_registry();
134
- });
118
+
119
+ // packages/repos-plugin/src/mirror/refresh.ts
120
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, realpathSync as realpathSync2, rmSync } from "fs";
121
+ import { resolve as resolve3 } from "path";
122
+
123
+ // packages/repos-plugin/src/mirror/bootstrap.ts
124
+ import { existsSync as existsSync2, mkdirSync, realpathSync } from "fs";
125
+ import { resolve as resolve2 } from "path";
135
126
 
136
127
  // packages/repos-plugin/src/mirror/state.ts
137
- import { readAuthorityProjectStateJson, writeAuthorityProjectStateJson } from "@rig/runtime/control-plane/json-files";
128
+ import { readAuthorityProjectStateJson, writeAuthorityProjectStateJson } from "@rig/core/json-files";
129
+ var STATE_VERSION = 1;
138
130
  function defaultMirrorState(projectRoot, repoId) {
139
131
  const layout = resolveManagedRepoLayout(projectRoot, repoId);
140
132
  return {
@@ -160,14 +152,8 @@ function writeManagedRepoMirrorState(projectRoot, repoId, patch) {
160
152
  writeAuthorityProjectStateJson(projectRoot, layout.mirrorStateRelativePath, next);
161
153
  return next;
162
154
  }
163
- var STATE_VERSION = 1;
164
- var init_state = __esm(() => {
165
- init_layout();
166
- });
167
155
 
168
156
  // packages/repos-plugin/src/mirror/bootstrap.ts
169
- import { existsSync as existsSync2, mkdirSync, realpathSync } from "fs";
170
- import { resolve as resolve2 } from "path";
171
157
  function nowIso() {
172
158
  return new Date().toISOString();
173
159
  }
@@ -266,15 +252,8 @@ function ensureManagedRepoMirror(projectRoot, repoId) {
266
252
  });
267
253
  return layout;
268
254
  }
269
- var init_bootstrap = __esm(() => {
270
- init_registry();
271
- init_layout();
272
- init_state();
273
- });
274
255
 
275
256
  // packages/repos-plugin/src/mirror/refresh.ts
276
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, realpathSync as realpathSync2, rmSync } from "fs";
277
- import { resolve as resolve3 } from "path";
278
257
  function nowIso2() {
279
258
  return new Date().toISOString();
280
259
  }
@@ -398,50 +377,516 @@ function syncManagedRepo(projectRoot, repoId) {
398
377
  }
399
378
  return { layout, headCommit };
400
379
  }
401
- var init_refresh = __esm(() => {
402
- init_bootstrap();
403
- init_state();
404
- });
405
380
 
406
381
  // packages/repos-plugin/src/service.ts
407
- var exports_service = {};
408
- __export(exports_service, {
409
- svc: () => svc,
410
- managedRepoService: () => managedRepoService
411
- });
412
- var managedRepoService, svc;
413
- var init_service = __esm(() => {
414
- init_registry();
415
- init_layout();
416
- init_refresh();
417
- managedRepoService = {
418
- setManagedRepos,
419
- listManagedRepoEntries,
420
- resolveManagedRepoIdByAlias,
421
- repoRegistrationToManagedEntry,
422
- createRepoRegistry,
423
- resolveManagedRepoLayoutByAlias,
424
- resolveMonorepoRepoLayout,
425
- syncManagedRepo
382
+ var managedRepoService = {
383
+ setManagedRepos,
384
+ listManagedRepoEntries,
385
+ resolveManagedRepoIdByAlias,
386
+ repoRegistrationToManagedEntry,
387
+ createRepoRegistry,
388
+ resolveManagedRepoLayoutByAlias,
389
+ resolveMonorepoRepoLayout,
390
+ syncManagedRepo
391
+ };
392
+ var svc = managedRepoService;
393
+
394
+ // packages/repos-plugin/src/repo-operations.ts
395
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync, readdirSync, rmSync as rmSync2, writeFileSync } from "fs";
396
+ import { basename as basename2, dirname as dirname2, resolve as resolve4 } from "path";
397
+ import { CliError, TASK_DATA_SERVICE_CAPABILITY } from "@rig/contracts";
398
+ import { defineCapability } from "@rig/core/capability";
399
+ import { requireInstalledCapability } from "@rig/core/capability-loaders";
400
+ import { loadRuntimeContext } from "@rig/core/runtime-context";
401
+ import { resolveHarnessPaths } from "@rig/core/harness-paths";
402
+ import { nowIso as nowIso3, readJsonFile, runCapture } from "@rig/core/exec";
403
+ var TaskDataCap = defineCapability(TASK_DATA_SERVICE_CAPABILITY);
404
+ var taskData = () => requireInstalledCapability(TaskDataCap, "task-data capability unavailable: load @rig/task-sources-plugin (default bundle) before resolving task repos.");
405
+ function managedRepoEntries() {
406
+ return managedRepoService.listManagedRepoEntries();
407
+ }
408
+ function primaryManagedRepoId() {
409
+ const entries = managedRepoEntries();
410
+ return entries.length > 0 ? entries[0].id : null;
411
+ }
412
+ function primaryManagedRepoAlias() {
413
+ const entries = managedRepoEntries();
414
+ return entries.length > 0 ? entries[0].alias : null;
415
+ }
416
+ function ensureMonorepoReady(projectRoot) {
417
+ const id = primaryManagedRepoId();
418
+ if (!id) {
419
+ return null;
420
+ }
421
+ const synced = managedRepoService.syncManagedRepo(projectRoot, id);
422
+ const sha = synced.headCommit.slice(0, 7);
423
+ console.log(`Monorepo ready: ${synced.layout.alias}@${sha}`);
424
+ return synced;
425
+ }
426
+ function repoEnsure(projectRoot, taskId) {
427
+ const monorepo = ensureMonorepoReady(projectRoot);
428
+ const resolvedTask = taskId || taskData().currentTaskId(projectRoot);
429
+ if (!resolvedTask) {
430
+ if (monorepo) {
431
+ const alias = primaryManagedRepoAlias();
432
+ if (alias) {
433
+ persistBaselinePins(projectRoot, { [alias]: monorepo.headCommit });
434
+ }
435
+ }
436
+ console.log("No active task. Refreshed baseline repo pins.");
437
+ return;
438
+ }
439
+ const pins = resolvedPins(projectRoot, resolvedTask);
440
+ applyPins(projectRoot, pins);
441
+ verifyPins(projectRoot, pins);
442
+ }
443
+ function repoPins(projectRoot, taskId) {
444
+ const resolvedTask = taskId || taskData().currentTaskId(projectRoot);
445
+ if (!resolvedTask) {
446
+ return {};
447
+ }
448
+ return resolvedPins(projectRoot, resolvedTask);
449
+ }
450
+ function repoVerify(projectRoot, taskId) {
451
+ const resolvedTask = taskId || taskData().currentTaskId(projectRoot);
452
+ if (!resolvedTask) {
453
+ console.log("No active task. Nothing to verify.");
454
+ return true;
455
+ }
456
+ const pins = resolvedPins(projectRoot, resolvedTask);
457
+ return verifyPins(projectRoot, pins);
458
+ }
459
+ function repoDiscover(projectRoot, taskId) {
460
+ const resolvedTask = taskId || taskData().currentTaskId(projectRoot);
461
+ if (!resolvedTask) {
462
+ return {};
463
+ }
464
+ const explicit = explicitPins(projectRoot, resolvedTask);
465
+ if (Object.keys(explicit).length > 0) {
466
+ return explicit;
467
+ }
468
+ return discoverPins(projectRoot, resolvedTask);
469
+ }
470
+ function repoBaseline(projectRoot, refresh = false) {
471
+ const paths = resolveRepoDiscoveryPaths(projectRoot);
472
+ if (!refresh && existsSync4(paths.baseRepoPinsPath)) {
473
+ const baseline = readJsonFile(paths.baseRepoPinsPath, { recorded_at: nowIso3(), repos: {} });
474
+ return baseline.repos || {};
475
+ }
476
+ const id = primaryManagedRepoId();
477
+ if (!id) {
478
+ return persistBaselinePins(projectRoot, {});
479
+ }
480
+ const synced = managedRepoService.syncManagedRepo(projectRoot, id);
481
+ const alias = primaryManagedRepoAlias() ?? id;
482
+ return persistBaselinePins(projectRoot, { [alias]: synced.headCommit });
483
+ }
484
+ function resetBaseline(projectRoot, keepTaskStatus) {
485
+ const paths = resolveHarnessPaths(projectRoot);
486
+ const label = primaryManagedRepoAlias() ?? "monorepo";
487
+ resetRepoToOriginMain(projectRoot, paths.monorepoRoot, label);
488
+ deleteBranches(projectRoot, paths.monorepoRoot, label, "rig/*");
489
+ deleteBranches(projectRoot, projectRoot, "project-rig", "codex/*");
490
+ repoBaseline(projectRoot, true);
491
+ console.log("OK: refreshed baseline repo pins");
492
+ if (!keepTaskStatus) {
493
+ reopenClosedTasks(projectRoot);
494
+ } else {
495
+ console.log("INFO: task status unchanged (--keep-task-status)");
496
+ }
497
+ clearDirectory(paths.artifactsDir, ".gitkeep");
498
+ clearDirectory(paths.logsDir, ".gitkeep");
499
+ for (const stateFile of ["hook_trips.log", "task-repo-commits.json", "current-task-id.txt"]) {
500
+ rmSync2(resolve4(paths.stateDir, stateFile), { force: true });
501
+ }
502
+ console.log("OK: cleared harness artifacts/logs/runtime state");
503
+ console.log("");
504
+ console.log("Baseline reset complete.");
505
+ }
506
+ function persistBaselinePins(projectRoot, repos) {
507
+ const paths = resolveRepoDiscoveryPaths(projectRoot);
508
+ mkdirSync3(resolve4(paths.baseRepoPinsPath, ".."), { recursive: true });
509
+ writeFileSync(paths.baseRepoPinsPath, `${JSON.stringify({ recorded_at: nowIso3(), repos }, null, 2)}
510
+ `, "utf-8");
511
+ return repos;
512
+ }
513
+ function resetRepoToOriginMain(projectRoot, repoPath, label) {
514
+ if (!existsSync4(resolve4(repoPath, ".git"))) {
515
+ console.log(`WARN: ${label} repo missing at ${repoPath}, skipping reset.`);
516
+ return;
517
+ }
518
+ runGitCapture(["git", "-C", repoPath, "fetch", "origin", "main"], projectRoot);
519
+ const hasMain = runGitCapture(["git", "-C", repoPath, "show-ref", "--verify", "--quiet", "refs/heads/main"], projectRoot).exitCode === 0;
520
+ if (hasMain) {
521
+ runGitCapture(["git", "-C", repoPath, "checkout", "main"], projectRoot);
522
+ } else {
523
+ runGitCapture(["git", "-C", repoPath, "checkout", "-B", "main", "origin/main"], projectRoot);
524
+ }
525
+ const resetResult = runGitCapture(["git", "-C", repoPath, "reset", "--hard", "origin/main"], projectRoot);
526
+ if (resetResult.exitCode !== 0) {
527
+ throw new Error(`Failed to reset ${label} to origin/main:
528
+ ${resetResult.stderr || resetResult.stdout}`);
529
+ }
530
+ runGitCapture(["git", "-C", repoPath, "clean", "-fd"], projectRoot);
531
+ const head = runGitCapture(["git", "-C", repoPath, "rev-parse", "--short", "HEAD"], projectRoot).stdout.trim();
532
+ console.log(`OK: ${label} reset to origin/main (${head})`);
533
+ }
534
+ function deleteBranches(projectRoot, repoPath, label, pattern) {
535
+ if (!existsSync4(resolve4(repoPath, ".git"))) {
536
+ return;
537
+ }
538
+ const listed = runGitCapture(["git", "-C", repoPath, "for-each-ref", `refs/heads/${pattern}`, "--format=%(refname:short)"], projectRoot).stdout.split(/\r?\n/).filter(Boolean);
539
+ let deleted = 0;
540
+ for (const branch of listed) {
541
+ runGitCapture(["git", "-C", repoPath, "branch", "-D", branch], projectRoot);
542
+ deleted += 1;
543
+ }
544
+ console.log(`OK: ${label} deleted ${deleted} branch(es) matching ${pattern}`);
545
+ }
546
+ function reopenClosedTasks(projectRoot) {
547
+ const paths = resolveHarnessPaths(projectRoot);
548
+ const issuesPath = resolve4(paths.monorepoRoot, ".beads/issues.jsonl");
549
+ if (!existsSync4(issuesPath)) {
550
+ console.log("WARN: .beads/issues.jsonl not found, skipping task reopen.");
551
+ return;
552
+ }
553
+ const content = readFileSync(issuesPath, "utf-8");
554
+ const closed = [];
555
+ for (const line of content.split(/\r?\n/)) {
556
+ const trimmed = line.trim();
557
+ if (!trimmed) {
558
+ continue;
559
+ }
560
+ try {
561
+ const issue = JSON.parse(trimmed);
562
+ const normalizedStatus = taskData().normalizeTaskLifecycleStatus(issue.status);
563
+ if (issue.issue_type === "task" && issue.id && (normalizedStatus === "completed" || normalizedStatus === "cancelled" || normalizedStatus === "blocked")) {
564
+ closed.push(issue.id);
565
+ }
566
+ } catch {}
567
+ }
568
+ if (closed.length === 0) {
569
+ console.log("OK: no terminal tasks to reopen");
570
+ return;
571
+ }
572
+ for (const id of closed) {
573
+ const update = runCapture(["br", "--no-db", "update", id, "--status", "open"], paths.monorepoRoot);
574
+ if (update.exitCode === 0) {
575
+ console.log(`OK: reopened task ${id}`);
576
+ } else {
577
+ console.log(`WARN: failed to reopen task ${id}`);
578
+ }
579
+ }
580
+ }
581
+ function clearDirectory(dir, keepName) {
582
+ if (!existsSync4(dir)) {
583
+ return;
584
+ }
585
+ for (const entry of readdirSync(dir)) {
586
+ if (entry === keepName) {
587
+ continue;
588
+ }
589
+ rmSync2(resolve4(dir, entry), { recursive: true, force: true });
590
+ }
591
+ }
592
+ function resolvedPins(projectRoot, taskId) {
593
+ const explicit = explicitPins(projectRoot, taskId);
594
+ if (Object.keys(explicit).length > 0) {
595
+ return explicit;
596
+ }
597
+ return discoverPins(projectRoot, taskId);
598
+ }
599
+ function explicitPins(projectRoot, taskId) {
600
+ const repoPins2 = readRepoDiscoveryTaskConfig(projectRoot)[taskId]?.repo_pins || {};
601
+ const normalized = {};
602
+ const validAliases = new Set(managedRepoEntries().map((e) => e.alias));
603
+ for (const [key, value] of Object.entries(repoPins2)) {
604
+ if (!value) {
605
+ continue;
606
+ }
607
+ if (validAliases.size > 0 && !validAliases.has(key)) {
608
+ throw new Error(`Unsupported repo pin key for ${taskId}: ${key}. Known aliases: ${[...validAliases].join(", ") || "(none registered)"}`);
609
+ }
610
+ const existing = normalized[key];
611
+ if (existing && existing !== value) {
612
+ throw new Error(`Conflicting explicit repo pins for ${key}: ${existing} vs ${value}`);
613
+ }
614
+ normalized[key] = value;
615
+ }
616
+ return normalized;
617
+ }
618
+ function taskDependencies(projectRoot, taskId) {
619
+ return taskData().taskDependencyIds(projectRoot, taskId);
620
+ }
621
+ function discoverPins(projectRoot, taskId) {
622
+ const deps = taskDependencies(projectRoot, taskId);
623
+ if (deps.length === 0) {
624
+ return repoBaseline(projectRoot, true);
625
+ }
626
+ const paths = resolveRepoDiscoveryPaths(projectRoot);
627
+ const state = readJsonFile(paths.taskRepoCommitsPath, {});
628
+ const pinKeys = managedRepoEntries().map((e) => e.alias);
629
+ if (pinKeys.length === 0) {
630
+ return {};
631
+ }
632
+ const discovered = {};
633
+ for (const key of pinKeys) {
634
+ const commits = new Set;
635
+ for (const dep of deps) {
636
+ const fromState = state[dep]?.repos?.[key];
637
+ if (fromState) {
638
+ commits.add(fromState);
639
+ }
640
+ const fromArtifact = readPinFromArtifact(projectRoot, dep, key);
641
+ if (fromArtifact) {
642
+ commits.add(fromArtifact);
643
+ }
644
+ }
645
+ if (commits.size > 1) {
646
+ const values = [...commits].join(`
647
+ - `);
648
+ throw new Error(`Conflicting discovered pins for ${key} from dependency graph of ${taskId}:
649
+ - ${values}`);
650
+ }
651
+ if (commits.size === 1) {
652
+ discovered[key] = [...commits][0];
653
+ }
654
+ }
655
+ return discovered;
656
+ }
657
+ function readRepoDiscoveryTaskConfig(projectRoot) {
658
+ try {
659
+ return taskData().readSourceTaskConfig(projectRoot);
660
+ } catch {
661
+ return taskData().readTaskConfig(projectRoot);
662
+ }
663
+ }
664
+ function resolveRepoDiscoveryPaths(projectRoot) {
665
+ const monorepoRoot = managedRepoService.resolveMonorepoRepoLayout(projectRoot).checkoutRoot;
666
+ const explicitHostProjectRoot = (process.env.RIG_HOST_PROJECT_ROOT || "").trim();
667
+ const normalizedProjectRoot = resolve4(projectRoot);
668
+ const hostProjectRoot = explicitHostProjectRoot && shouldUseHostProjectStateRoot(normalizedProjectRoot) ? explicitHostProjectRoot : normalizedProjectRoot;
669
+ const stateDir = resolve4(hostProjectRoot, ".rig", "state");
670
+ return {
671
+ monorepoRoot,
672
+ taskRepoCommitsPath: resolve4(stateDir, "task-repo-commits.json"),
673
+ baseRepoPinsPath: resolve4(stateDir, "base-repo-pins.json")
426
674
  };
427
- svc = managedRepoService;
428
- });
675
+ }
676
+ function shouldUseHostProjectStateRoot(projectRoot) {
677
+ const runtimeWorkspace = process.env.RIG_TASK_WORKSPACE?.trim();
678
+ if (runtimeWorkspace && resolve4(runtimeWorkspace) === projectRoot) {
679
+ return true;
680
+ }
681
+ return basename2(dirname2(projectRoot)) === ".worktrees";
682
+ }
683
+ function readPinFromArtifact(projectRoot, depTask, repoKey) {
684
+ const snapshot = resolve4(taskData().artifactDirForId(projectRoot, depTask), "git-state.txt");
685
+ if (!existsSync4(snapshot)) {
686
+ return "";
687
+ }
688
+ const content = readFileSync(snapshot, "utf-8");
689
+ const chunk = content.split(/\r?\n/);
690
+ let inSection = false;
691
+ for (const line of chunk) {
692
+ if (line.startsWith("## ")) {
693
+ inSection = line.startsWith(`## ${repoKey}`);
694
+ continue;
695
+ }
696
+ if (!inSection) {
697
+ continue;
698
+ }
699
+ if (line.startsWith("head:")) {
700
+ return line.replace(/^head:\s*/, "").trim();
701
+ }
702
+ }
703
+ return "";
704
+ }
705
+ function repoPath(projectRoot, key) {
706
+ const managed = managedRepoService.resolveManagedRepoLayoutByAlias(projectRoot, key);
707
+ if (managed) {
708
+ return managed.checkoutRoot;
709
+ }
710
+ return key.startsWith("/") ? key : resolve4(projectRoot, key);
711
+ }
712
+ function applyPins(projectRoot, pins) {
713
+ for (const [key, commit] of Object.entries(pins)) {
714
+ const path = repoPath(projectRoot, key);
715
+ if (!existsSync4(resolve4(path, ".git"))) {
716
+ throw new Error(`Repo for pin not available: ${key} (${path})`);
717
+ }
718
+ let hasCommit = runGitCapture(["git", "-C", path, "cat-file", "-e", `${commit}^{commit}`], projectRoot).exitCode === 0;
719
+ if (!hasCommit) {
720
+ runGitCapture(["git", "-C", path, "fetch", "--all", "--tags", "--prune"], projectRoot);
721
+ hasCommit = runGitCapture(["git", "-C", path, "cat-file", "-e", `${commit}^{commit}`], projectRoot).exitCode === 0;
722
+ }
723
+ if (!hasCommit) {
724
+ throw new Error(`Commit not found for ${key}: ${commit}`);
725
+ }
726
+ const current = runGitCapture(["git", "-C", path, "rev-parse", "HEAD"], projectRoot).stdout.trim();
727
+ if (current === commit) {
728
+ console.log(`Repo pin: ${key} already at ${commit}`);
729
+ continue;
730
+ }
731
+ const branch = runGitCapture(["git", "-C", path, "rev-parse", "--abbrev-ref", "HEAD"], projectRoot).stdout.trim();
732
+ const checkout = branch.startsWith("rig/") ? runGitCapture(["git", "-C", path, "reset", "--hard", commit], projectRoot) : runGitCapture(["git", "-C", path, "checkout", "--detach", commit], projectRoot);
733
+ if (checkout.exitCode !== 0) {
734
+ throw new Error(`Failed to apply repo pin ${key} -> ${commit}:
735
+ ${checkout.stderr || checkout.stdout}`);
736
+ }
737
+ console.log(`Repo pin: ${key} -> ${commit}`);
738
+ }
739
+ }
740
+ function verifyPins(projectRoot, pins) {
741
+ let ok = true;
742
+ for (const [key, expected] of Object.entries(pins)) {
743
+ const path = repoPath(projectRoot, key);
744
+ if (!existsSync4(resolve4(path, ".git"))) {
745
+ console.error(`ERROR: repo missing during pin verification: ${key}`);
746
+ ok = false;
747
+ continue;
748
+ }
749
+ const current = runGitCapture(["git", "-C", path, "rev-parse", "HEAD"], projectRoot).stdout.trim();
750
+ if (current === expected) {
751
+ console.log(`Repo verify: ${key} at ${current}`);
752
+ } else {
753
+ console.error(`ERROR: repo pin mismatch for ${key}: expected ${expected}, got ${current}`);
754
+ ok = false;
755
+ }
756
+ }
757
+ return ok;
758
+ }
759
+ function runGitCapture(command, projectRoot) {
760
+ return runCapture(command, projectRoot, resolveRuntimeGitEnv());
761
+ }
762
+ function resolveRuntimeGitEnv() {
763
+ if (process.env.GIT_SSH_COMMAND?.trim()) {
764
+ return {
765
+ HOME: process.env.HOME ?? "",
766
+ GIT_SSH_COMMAND: process.env.GIT_SSH_COMMAND
767
+ };
768
+ }
769
+ const runtimeRoot = process.env.RIG_RUNTIME_HOME?.trim() || (process.env.RIG_RUNTIME_CONTEXT_FILE?.trim() ? resolve4(process.env.RIG_RUNTIME_CONTEXT_FILE, "..") : inferRuntimeRootFromWorkspace(process.cwd()));
770
+ const runtimeHome = runtimeRoot ? resolve4(runtimeRoot, "home") : process.env.HOME?.trim() || "";
771
+ if (!runtimeHome) {
772
+ return;
773
+ }
774
+ const knownHostsPath = resolve4(runtimeHome, ".ssh", "known_hosts");
775
+ if (!existsSync4(knownHostsPath)) {
776
+ return { HOME: runtimeHome };
777
+ }
778
+ const agentSshKey = resolve4(runtimeHome, ".ssh", "rig-agent-key");
779
+ const sshParts = [
780
+ "ssh",
781
+ `-o UserKnownHostsFile="${knownHostsPath}"`,
782
+ "-o StrictHostKeyChecking=yes",
783
+ "-F /dev/null"
784
+ ];
785
+ if (existsSync4(agentSshKey)) {
786
+ sshParts.splice(1, 0, `-i "${agentSshKey}"`, "-o IdentitiesOnly=yes");
787
+ }
788
+ return {
789
+ HOME: runtimeHome,
790
+ GIT_SSH_COMMAND: sshParts.join(" ")
791
+ };
792
+ }
793
+ function inferRuntimeRootFromWorkspace(cwd) {
794
+ const contextPath = findRuntimeContextFile(cwd);
795
+ if (!contextPath || !existsSync4(contextPath)) {
796
+ return "";
797
+ }
798
+ try {
799
+ loadRuntimeContext(contextPath);
800
+ return resolve4(contextPath, "..");
801
+ } catch {
802
+ return "";
803
+ }
804
+ }
805
+ function findRuntimeContextFile(startPath) {
806
+ let current = resolve4(startPath);
807
+ while (true) {
808
+ const candidate = resolve4(current, "runtime-context.json");
809
+ if (existsSync4(candidate)) {
810
+ return candidate;
811
+ }
812
+ const parent = resolve4(current, "..");
813
+ if (parent === current) {
814
+ return "";
815
+ }
816
+ current = parent;
817
+ }
818
+ }
819
+ function runGit3(projectRoot, args, failureSummary) {
820
+ const result = runCapture(["git", ...args], projectRoot);
821
+ if (result.exitCode !== 0) {
822
+ const details = result.stderr.trim() || result.stdout.trim() || "unknown git failure";
823
+ throw new CliError(`${failureSummary}: ${details}`, 2);
824
+ }
825
+ return result.stdout.trim();
826
+ }
827
+ async function ensureProjectMainFreshBeforeRun(options) {
828
+ if (options.disabled) {
829
+ return { status: "disabled" };
830
+ }
831
+ const defaultSync = (projectRoot) => {
832
+ for (const entry of managedRepoEntries()) {
833
+ managedRepoService.syncManagedRepo(projectRoot, entry.id);
834
+ }
835
+ };
836
+ await (options.syncMonorepo ?? defaultSync)(options.projectRoot);
837
+ runGit3(options.projectRoot, ["fetch", "origin", "main"], "Failed to fetch origin/main");
838
+ const branch = runGit3(options.projectRoot, ["rev-parse", "--abbrev-ref", "HEAD"], "Failed to read current branch");
839
+ if (branch !== "main") {
840
+ return { status: "skipped_not_main", branch };
841
+ }
842
+ const countsText = runGit3(options.projectRoot, ["rev-list", "--left-right", "--count", "main...origin/main"], "Failed to compare local main to origin/main");
843
+ const [localAheadRaw, remoteAheadRaw] = countsText.split(/\s+/);
844
+ const localAhead = Number.parseInt(localAheadRaw || "0", 10);
845
+ const remoteAhead = Number.parseInt(remoteAheadRaw || "0", 10);
846
+ if (!Number.isFinite(localAhead) || !Number.isFinite(remoteAhead)) {
847
+ throw new CliError(`Failed to compare local main to origin/main: unexpected rev-list output "${countsText}"`, 2);
848
+ }
849
+ if (remoteAhead === 0) {
850
+ if (localAhead > 0) {
851
+ return { status: "local_ahead", localAhead };
852
+ }
853
+ return { status: "up_to_date" };
854
+ }
855
+ if (localAhead > 0) {
856
+ throw new CliError("Local main has diverged from origin/main. Resolve the branch state manually or rerun with --skip-project-sync.", 2);
857
+ }
858
+ runGit3(options.projectRoot, ["merge", "--ff-only", "origin/main"], "Failed to fast-forward local main to origin/main");
859
+ await options.runBootstrap();
860
+ return { status: "updated", remoteAhead };
861
+ }
862
+ var repoOperationsService = {
863
+ repoEnsure,
864
+ repoPins,
865
+ repoVerify,
866
+ repoDiscover,
867
+ repoBaseline,
868
+ resetBaseline,
869
+ ensureProjectMainFreshBeforeRun
870
+ };
871
+ var repoOps = repoOperationsService;
429
872
 
430
873
  // packages/repos-plugin/src/plugin.ts
431
- import { definePlugin } from "@rig/core/config";
432
- import { MANAGED_REPO_SERVICE_CAPABILITY_ID } from "@rig/contracts";
433
874
  var REPOS_PLUGIN_NAME = "@rig/repos-plugin";
875
+ var ManagedRepoCap = defineCapability2(MANAGED_REPO_SERVICE_CAPABILITY);
876
+ var RepoOperationsCap = defineCapability2(REPO_OPERATIONS_CAPABILITY);
434
877
  var reposPlugin = definePlugin({
435
878
  name: REPOS_PLUGIN_NAME,
436
879
  version: "0.0.0-alpha.1",
437
880
  contributes: {
438
881
  capabilities: [
439
- {
440
- id: MANAGED_REPO_SERVICE_CAPABILITY_ID,
882
+ ManagedRepoCap.provide(svc, {
441
883
  title: "Managed repositories",
442
- description: "Registry, on-disk layout, and mirror/sync for plugin-contributed managed repos.",
443
- run: async () => (await Promise.resolve().then(() => (init_service(), exports_service))).svc
444
- }
884
+ description: "Registry, on-disk layout, and mirror/sync for plugin-contributed managed repos."
885
+ }),
886
+ RepoOperationsCap.provide(repoOps, {
887
+ title: "Repository operations",
888
+ description: "Task repo pinning, baseline reset, and project-main pre-run sync."
889
+ })
445
890
  ]
446
891
  }
447
892
  });
@@ -60,8 +60,8 @@ function repoRegistrationToManagedEntry(reg) {
60
60
  alias: reg.defaultPath ?? reg.id,
61
61
  defaultBranch: reg.defaultBranch,
62
62
  defaultRemoteUrl: reg.url,
63
- remoteEnvVar: reg.remoteEnvVar,
64
- checkoutEnvVar: reg.checkoutEnvVar
63
+ ...reg.remoteEnvVar !== undefined ? { remoteEnvVar: reg.remoteEnvVar } : {},
64
+ ...reg.checkoutEnvVar !== undefined ? { checkoutEnvVar: reg.checkoutEnvVar } : {}
65
65
  };
66
66
  }
67
67
  export {
@@ -0,0 +1,3 @@
1
+ import type { RepoOperationsService } from "@rig/contracts";
2
+ export declare const repoOperationsService: RepoOperationsService;
3
+ export declare const repoOps: RepoOperationsService;