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