@pratik7368patil/anchor-core 0.1.40 → 0.1.41

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
@@ -7496,17 +7496,849 @@ function watchCodebase(db, input) {
7496
7496
  return () => clearInterval(timer);
7497
7497
  }
7498
7498
 
7499
- // src/ci.ts
7499
+ // src/autosync.ts
7500
+ import { execFileSync as execFileSync4 } from "child_process";
7501
+ import fs10 from "fs";
7502
+ import os3 from "os";
7503
+ import path21 from "path";
7504
+
7505
+ // src/org/config.ts
7500
7506
  import fs9 from "fs";
7507
+ import os2 from "os";
7501
7508
  import path20 from "path";
7509
+ import { z as z2 } from "zod";
7510
+ var ORG_REPO_GROUPS = ["backend", "frontend", "shared", "infra", "docs", "unknown"];
7511
+ var OrgRepoSchema = z2.object({
7512
+ fullName: z2.string().regex(/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/),
7513
+ alias: z2.string().min(1),
7514
+ group: z2.enum(ORG_REPO_GROUPS),
7515
+ cloneUrl: z2.string().min(1),
7516
+ defaultBranch: z2.string().min(1),
7517
+ enabled: z2.boolean()
7518
+ });
7519
+ var OrgConfigSchema = z2.object({
7520
+ version: z2.literal(1),
7521
+ org: z2.string().regex(/^[A-Za-z0-9_.-]+$/),
7522
+ repos: z2.array(OrgRepoSchema)
7523
+ });
7524
+ function validateOrgName(org) {
7525
+ const trimmed = org.trim();
7526
+ if (!/^[A-Za-z0-9_.-]+$/.test(trimmed)) {
7527
+ throw new Error("Invalid org name. Use only letters, numbers, dot, underscore, and hyphen.");
7528
+ }
7529
+ return trimmed;
7530
+ }
7531
+ function validateOrgRepoFullName(fullName) {
7532
+ const trimmed = fullName.trim();
7533
+ if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(trimmed)) {
7534
+ throw new Error("Invalid repo name. Use owner/name.");
7535
+ }
7536
+ return trimmed;
7537
+ }
7538
+ function validateOrgRepoGroup(group) {
7539
+ if (!group) return "unknown";
7540
+ if (ORG_REPO_GROUPS.includes(group)) return group;
7541
+ throw new Error(`Invalid repo group: ${group}`);
7542
+ }
7543
+ function defaultOrgBaseDir() {
7544
+ if (process.env.ANCHOR_ORG_HOME) return process.env.ANCHOR_ORG_HOME;
7545
+ return path20.join(os2.homedir(), ".anchor", "orgs");
7546
+ }
7547
+ function orgRoot(org, baseDir = defaultOrgBaseDir()) {
7548
+ return path20.join(baseDir, validateOrgName(org));
7549
+ }
7550
+ function orgConfigPath(org, baseDir = defaultOrgBaseDir()) {
7551
+ return path20.join(orgRoot(org, baseDir), "org.json");
7552
+ }
7553
+ function orgDatabasePath(org, baseDir = defaultOrgBaseDir()) {
7554
+ return path20.join(orgRoot(org, baseDir), "org.sqlite");
7555
+ }
7556
+ function orgReposRoot(org, baseDir = defaultOrgBaseDir()) {
7557
+ return path20.join(orgRoot(org, baseDir), "repos");
7558
+ }
7559
+ function repoAliasFromFullName(fullName) {
7560
+ return validateOrgRepoFullName(fullName).split("/")[1] ?? fullName.replace(/\W+/g, "-");
7561
+ }
7562
+ function defaultOrgCloneUrl(fullName) {
7563
+ return `https://github.com/${validateOrgRepoFullName(fullName)}.git`;
7564
+ }
7565
+ function orgRepoLocalPath(org, repo, baseDir = defaultOrgBaseDir()) {
7566
+ const safeAlias = repo.alias.replace(/[^A-Za-z0-9_.-]/g, "-") || repoAliasFromFullName(repo.fullName);
7567
+ return path20.join(orgReposRoot(org, baseDir), safeAlias);
7568
+ }
7569
+ function parseOrgConfig(text) {
7570
+ const parsed = OrgConfigSchema.parse(JSON.parse(text));
7571
+ return parsed;
7572
+ }
7573
+ function atomicWriteJson(filePath, value) {
7574
+ fs9.mkdirSync(path20.dirname(filePath), { recursive: true });
7575
+ const tmp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
7576
+ fs9.writeFileSync(tmp, `${JSON.stringify(value, null, 2)}
7577
+ `, { mode: 384 });
7578
+ fs9.renameSync(tmp, filePath);
7579
+ }
7580
+ function loadOrgConfig(org, baseDir = defaultOrgBaseDir()) {
7581
+ const filePath = orgConfigPath(org, baseDir);
7582
+ if (!fs9.existsSync(filePath)) {
7583
+ throw new Error(
7584
+ `Anchor org config not found at ${filePath}. Run anchor org init --org ${org}.`
7585
+ );
7586
+ }
7587
+ return parseOrgConfig(fs9.readFileSync(filePath, "utf8"));
7588
+ }
7589
+ function maybeLoadOrgConfig(org, baseDir = defaultOrgBaseDir()) {
7590
+ const filePath = orgConfigPath(org, baseDir);
7591
+ if (!fs9.existsSync(filePath)) return void 0;
7592
+ return loadOrgConfig(org, baseDir);
7593
+ }
7594
+ function saveOrgConfig(config, baseDir = defaultOrgBaseDir()) {
7595
+ const parsed = OrgConfigSchema.parse(config);
7596
+ atomicWriteJson(orgConfigPath(parsed.org, baseDir), parsed);
7597
+ return parsed;
7598
+ }
7599
+ function initOrgConfig(org, baseDir = defaultOrgBaseDir()) {
7600
+ const normalizedOrg = validateOrgName(org);
7601
+ fs9.mkdirSync(orgReposRoot(normalizedOrg, baseDir), { recursive: true });
7602
+ const existing = maybeLoadOrgConfig(normalizedOrg, baseDir);
7603
+ if (existing) return existing;
7604
+ return saveOrgConfig({ version: 1, org: normalizedOrg, repos: [] }, baseDir);
7605
+ }
7606
+ function addOrgRepoConfig(org, repoFullName, input = {}, baseDir = defaultOrgBaseDir()) {
7607
+ const config = initOrgConfig(org, baseDir);
7608
+ const fullName = validateOrgRepoFullName(repoFullName);
7609
+ const existing = config.repos.find((repo) => repo.fullName === fullName);
7610
+ const candidate = {
7611
+ fullName,
7612
+ alias: input.alias?.trim() || existing?.alias || repoAliasFromFullName(fullName),
7613
+ group: validateOrgRepoGroup(input.group ?? existing?.group),
7614
+ cloneUrl: input.cloneUrl?.trim() || existing?.cloneUrl || defaultOrgCloneUrl(fullName),
7615
+ defaultBranch: input.defaultBranch?.trim() || existing?.defaultBranch || "main",
7616
+ enabled: true
7617
+ };
7618
+ const repos = existing ? config.repos.map((repo) => repo.fullName === fullName ? candidate : repo) : [...config.repos, candidate];
7619
+ return saveOrgConfig(
7620
+ { ...config, repos: repos.sort((a, b) => a.fullName.localeCompare(b.fullName)) },
7621
+ baseDir
7622
+ );
7623
+ }
7624
+ function removeOrgRepoConfig(org, repoFullName, baseDir = defaultOrgBaseDir()) {
7625
+ const config = loadOrgConfig(org, baseDir);
7626
+ const fullName = validateOrgRepoFullName(repoFullName);
7627
+ return saveOrgConfig(
7628
+ {
7629
+ ...config,
7630
+ repos: config.repos.map(
7631
+ (repo) => repo.fullName === fullName ? { ...repo, enabled: false } : repo
7632
+ )
7633
+ },
7634
+ baseDir
7635
+ );
7636
+ }
7637
+ function listOrgNames(baseDir = defaultOrgBaseDir()) {
7638
+ if (!fs9.existsSync(baseDir)) return [];
7639
+ return fs9.readdirSync(baseDir, { withFileTypes: true }).filter(
7640
+ (entry) => entry.isDirectory() && fs9.existsSync(path20.join(baseDir, entry.name, "org.json"))
7641
+ ).map((entry) => entry.name).sort();
7642
+ }
7643
+ function resolveOrgForTool(org, baseDir = defaultOrgBaseDir()) {
7644
+ if (org) return validateOrgName(org);
7645
+ const names = listOrgNames(baseDir);
7646
+ if (names.length === 1) return names[0] ?? "";
7647
+ if (names.length === 0) {
7648
+ throw new Error("No Anchor org configured. Run anchor org init --org <org>.");
7649
+ }
7650
+ throw new Error(`Multiple Anchor orgs configured (${names.join(", ")}). Pass org explicitly.`);
7651
+ }
7652
+
7653
+ // src/autosync.ts
7654
+ var CONFIG_VERSION = 1;
7655
+ var REPO_TIMEOUT_MS = 45 * 60 * 1e3;
7656
+ var ORG_TIMEOUT_MS = 2 * 60 * 60 * 1e3;
7657
+ var GRAPH_TIMEOUT_MS = 2 * 60 * 60 * 1e3;
7658
+ var STALE_RUN_MS = 36 * 60 * 60 * 1e3;
7659
+ function autosyncRoot(homeDir = os3.homedir()) {
7660
+ return path21.join(homeDir, ".anchor");
7661
+ }
7662
+ function autosyncConfigPath(homeDir = os3.homedir()) {
7663
+ return path21.join(autosyncRoot(homeDir), "autosync", "config.json");
7664
+ }
7665
+ function autosyncLogsRoot(homeDir = os3.homedir()) {
7666
+ return path21.join(autosyncRoot(homeDir), "logs", "autosync");
7667
+ }
7668
+ function autosyncLocksRoot(homeDir = os3.homedir()) {
7669
+ return path21.join(autosyncRoot(homeDir), "locks", "autosync");
7670
+ }
7671
+ function autosyncJobIdForRepo(repoRoot) {
7672
+ return `repo-${stableHashText(path21.resolve(repoRoot))}`;
7673
+ }
7674
+ function autosyncJobIdForOrg(org) {
7675
+ return `org-${safeName(org)}`;
7676
+ }
7677
+ function autosyncJobIdForOrgGraph(org) {
7678
+ return `org-graph-${safeName(org)}`;
7679
+ }
7680
+ function autosyncLockPath(jobId, homeDir = os3.homedir()) {
7681
+ return path21.join(autosyncLocksRoot(homeDir), `${safeName(jobId)}.lock`);
7682
+ }
7683
+ function defaultCommandRunner(command, args, options = {}) {
7684
+ return execFileSync4(command, args, {
7685
+ cwd: options.cwd,
7686
+ input: options.input,
7687
+ encoding: "utf8",
7688
+ stdio: options.input === void 0 ? ["ignore", "pipe", "pipe"] : ["pipe", "pipe", "pipe"]
7689
+ }).trim();
7690
+ }
7691
+ function readAutosyncConfig(homeDir = os3.homedir()) {
7692
+ const filePath = autosyncConfigPath(homeDir);
7693
+ if (!fs10.existsSync(filePath)) return void 0;
7694
+ return normalizeAutosyncConfig(JSON.parse(fs10.readFileSync(filePath, "utf8")));
7695
+ }
7696
+ function writeAutosyncConfig(config, homeDir = os3.homedir()) {
7697
+ const filePath = autosyncConfigPath(homeDir);
7698
+ fs10.mkdirSync(path21.dirname(filePath), { recursive: true });
7699
+ const tmp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
7700
+ fs10.writeFileSync(tmp, `${JSON.stringify(config, null, 2)}
7701
+ `, { mode: 384 });
7702
+ fs10.renameSync(tmp, filePath);
7703
+ }
7704
+ function installDefaultAutosync(options) {
7705
+ const homeDir = options.homeDir ?? os3.homedir();
7706
+ if (options.mode === "off") return disableAutosync({ ...options, homeDir });
7707
+ const gitRoot = detectGitRoot(options.cwd);
7708
+ if (!gitRoot) {
7709
+ return {
7710
+ enabled: false,
7711
+ configPath: autosyncConfigPath(homeDir),
7712
+ jobs: [],
7713
+ warnings: ["Autosync was not installed because no git repository was detected."]
7714
+ };
7715
+ }
7716
+ const now = (/* @__PURE__ */ new Date()).toISOString();
7717
+ const existing = readAutosyncConfig(homeDir) ?? {
7718
+ version: CONFIG_VERSION,
7719
+ enabled: true,
7720
+ updatedAt: now,
7721
+ jobs: []
7722
+ };
7723
+ const repo = detectGitHubRepo(gitRoot)?.fullName;
7724
+ const nodePath = path21.resolve(options.nodePath ?? process.execPath);
7725
+ const anchorScriptPath = path21.resolve(options.anchorScriptPath);
7726
+ const jobs = new Map(existing.jobs.map((job) => [job.id, job]));
7727
+ const installJobs = [];
7728
+ const repoJob = buildRepoJob({
7729
+ repoRoot: gitRoot,
7730
+ repo,
7731
+ nodePath,
7732
+ anchorScriptPath,
7733
+ homeDir,
7734
+ now,
7735
+ previous: jobs.get(autosyncJobIdForRepo(gitRoot))
7736
+ });
7737
+ jobs.set(repoJob.id, repoJob);
7738
+ installJobs.push(repoJob);
7739
+ for (const org of discoverConfiguredOrgs(options.orgBaseDir)) {
7740
+ const orgJob = buildOrgJob({
7741
+ org,
7742
+ nodePath,
7743
+ anchorScriptPath,
7744
+ homeDir,
7745
+ now,
7746
+ previous: jobs.get(autosyncJobIdForOrg(org))
7747
+ });
7748
+ const graphJob = buildOrgGraphJob({
7749
+ org,
7750
+ nodePath,
7751
+ anchorScriptPath,
7752
+ homeDir,
7753
+ now,
7754
+ previous: jobs.get(autosyncJobIdForOrgGraph(org))
7755
+ });
7756
+ jobs.set(orgJob.id, orgJob);
7757
+ jobs.set(graphJob.id, graphJob);
7758
+ installJobs.push(orgJob, graphJob);
7759
+ }
7760
+ const config = {
7761
+ version: CONFIG_VERSION,
7762
+ enabled: true,
7763
+ updatedAt: now,
7764
+ jobs: [...jobs.values()].sort((a, b) => a.id.localeCompare(b.id))
7765
+ };
7766
+ writeAutosyncConfig(config, homeDir);
7767
+ const warnings = [];
7768
+ const results = installJobs.map((job) => {
7769
+ try {
7770
+ return installSchedulerJob(job, {
7771
+ platform: options.platform ?? process.platform,
7772
+ homeDir,
7773
+ runner: options.runner ?? defaultCommandRunner
7774
+ });
7775
+ } catch (error) {
7776
+ warnings.push(`${job.label}: ${error instanceof Error ? error.message : String(error)}`);
7777
+ const artifact = schedulerArtifactForJob(job, options.platform ?? process.platform, homeDir);
7778
+ return {
7779
+ id: job.id,
7780
+ label: job.label,
7781
+ kind: job.kind,
7782
+ schedule: job.schedule,
7783
+ scheduler: artifact.scheduler,
7784
+ installed: false,
7785
+ nextRunHint: artifact.nextRunHint,
7786
+ logPath: job.logPath,
7787
+ message: "Scheduler install failed; rerun anchor init after fixing local scheduler access."
7788
+ };
7789
+ }
7790
+ });
7791
+ return {
7792
+ enabled: true,
7793
+ configPath: autosyncConfigPath(homeDir),
7794
+ jobs: results,
7795
+ warnings
7796
+ };
7797
+ }
7798
+ function disableAutosync(options = {}) {
7799
+ const homeDir = options.homeDir ?? os3.homedir();
7800
+ const config = readAutosyncConfig(homeDir);
7801
+ const warnings = [];
7802
+ if (config) {
7803
+ const disabled = {
7804
+ ...config,
7805
+ enabled: false,
7806
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
7807
+ jobs: config.jobs.map((job) => ({ ...job, enabled: false, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }))
7808
+ };
7809
+ writeAutosyncConfig(disabled, homeDir);
7810
+ for (const job of config.jobs) {
7811
+ try {
7812
+ removeSchedulerJob(job, {
7813
+ platform: options.platform ?? process.platform,
7814
+ homeDir,
7815
+ runner: options.runner ?? defaultCommandRunner
7816
+ });
7817
+ } catch (error) {
7818
+ warnings.push(`${job.label}: ${error instanceof Error ? error.message : String(error)}`);
7819
+ }
7820
+ }
7821
+ }
7822
+ return {
7823
+ enabled: false,
7824
+ configPath: autosyncConfigPath(homeDir),
7825
+ jobs: [],
7826
+ warnings
7827
+ };
7828
+ }
7829
+ function getAutosyncStatus(options = {}) {
7830
+ const homeDir = options.homeDir ?? os3.homedir();
7831
+ const platform = options.platform ?? process.platform;
7832
+ const config = readAutosyncConfig(homeDir);
7833
+ const scheduler = schedulerName(platform);
7834
+ if (!config) {
7835
+ return {
7836
+ enabled: false,
7837
+ configured: false,
7838
+ configPath: autosyncConfigPath(homeDir),
7839
+ scheduler,
7840
+ jobs: [],
7841
+ warnings: ["Autosync is not configured. Run anchor init to install local autosync."]
7842
+ };
7843
+ }
7844
+ const gitRoot = options.cwd ? detectGitRoot(options.cwd) : void 0;
7845
+ const matchingRepoJobId = gitRoot ? autosyncJobIdForRepo(gitRoot) : void 0;
7846
+ const jobs = config.jobs.map((job) => {
7847
+ const detected = schedulerArtifactExists(job, platform, homeDir);
7848
+ const stale = isAutosyncJobStale(job);
7849
+ const failing = job.lastRun?.status === "failed";
7850
+ return {
7851
+ id: job.id,
7852
+ label: job.label,
7853
+ kind: job.kind,
7854
+ schedule: job.schedule,
7855
+ enabled: job.enabled,
7856
+ scheduler,
7857
+ schedulerDetected: detected,
7858
+ logPath: job.logPath,
7859
+ lastRun: job.lastRun,
7860
+ stale,
7861
+ failing
7862
+ };
7863
+ });
7864
+ const relevantJobs = matchingRepoJobId ? jobs.filter((job) => job.id === matchingRepoJobId || job.kind !== "repo") : jobs;
7865
+ const warnings = [];
7866
+ if (!config.enabled) warnings.push("Autosync is disabled.");
7867
+ if (matchingRepoJobId && !jobs.some((job) => job.id === matchingRepoJobId)) {
7868
+ warnings.push("Autosync is not installed for this repository. Run anchor init.");
7869
+ }
7870
+ for (const job of relevantJobs) {
7871
+ if (job.enabled && !job.schedulerDetected) {
7872
+ warnings.push(`${job.label} scheduler file is missing. Run anchor init.`);
7873
+ }
7874
+ if (job.failing) warnings.push(`${job.label} last run failed.`);
7875
+ if (job.stale) warnings.push(`${job.label} has not completed recently.`);
7876
+ }
7877
+ return {
7878
+ enabled: config.enabled,
7879
+ configured: true,
7880
+ configPath: autosyncConfigPath(homeDir),
7881
+ scheduler,
7882
+ jobs: relevantJobs,
7883
+ lastRun: latestAutosyncRun(
7884
+ relevantJobs.map((job) => job.lastRun).filter((run) => Boolean(run))
7885
+ ),
7886
+ warnings
7887
+ };
7888
+ }
7889
+ function acquireAutosyncLock(jobId, homeDir = os3.homedir()) {
7890
+ const lockPath = autosyncLockPath(jobId, homeDir);
7891
+ fs10.mkdirSync(path21.dirname(lockPath), { recursive: true });
7892
+ try {
7893
+ fs10.writeFileSync(lockPath, JSON.stringify({ pid: process.pid, startedAt: (/* @__PURE__ */ new Date()).toISOString() }), {
7894
+ flag: "wx",
7895
+ mode: 384
7896
+ });
7897
+ } catch {
7898
+ const existing = readLock(lockPath);
7899
+ if (existing?.pid && processIsRunning(existing.pid)) {
7900
+ return {
7901
+ acquired: false,
7902
+ lockPath,
7903
+ release: () => void 0,
7904
+ message: `Autosync job ${jobId} is already running as pid ${existing.pid}.`
7905
+ };
7906
+ }
7907
+ fs10.rmSync(lockPath, { force: true });
7908
+ fs10.writeFileSync(lockPath, JSON.stringify({ pid: process.pid, startedAt: (/* @__PURE__ */ new Date()).toISOString() }), {
7909
+ flag: "wx",
7910
+ mode: 384
7911
+ });
7912
+ }
7913
+ return {
7914
+ acquired: true,
7915
+ lockPath,
7916
+ release: () => fs10.rmSync(lockPath, { force: true })
7917
+ };
7918
+ }
7919
+ function recordAutosyncRun(jobId, run, homeDir = os3.homedir()) {
7920
+ const config = readAutosyncConfig(homeDir);
7921
+ if (!config) return;
7922
+ writeAutosyncConfig(
7923
+ {
7924
+ ...config,
7925
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
7926
+ jobs: config.jobs.map(
7927
+ (job) => job.id === jobId ? { ...job, lastRun: run, updatedAt: (/* @__PURE__ */ new Date()).toISOString() } : job
7928
+ )
7929
+ },
7930
+ homeDir
7931
+ );
7932
+ }
7933
+ function resolveAutosyncJob(kind, input) {
7934
+ const config = readAutosyncConfig();
7935
+ if (!config) throw new Error("Autosync is not configured. Run anchor init.");
7936
+ const jobId = kind === "repo" ? autosyncJobIdForRepo(input.cwd ?? process.cwd()) : kind === "org" ? autosyncJobIdForOrg(requiredOrg(input.org)) : autosyncJobIdForOrgGraph(requiredOrg(input.org));
7937
+ const job = config.jobs.find((item) => item.id === jobId);
7938
+ if (!job) throw new Error(`Autosync job ${jobId} is not configured. Run anchor init.`);
7939
+ return job;
7940
+ }
7941
+ function buildRepoJob(input) {
7942
+ const id = autosyncJobIdForRepo(input.repoRoot);
7943
+ return {
7944
+ id,
7945
+ kind: "repo",
7946
+ label: `Repo autosync${input.repo ? `: ${input.repo}` : ""}`,
7947
+ schedule: "daily",
7948
+ enabled: true,
7949
+ repoRoot: input.repoRoot,
7950
+ repo: input.repo,
7951
+ nodePath: input.nodePath,
7952
+ anchorScriptPath: input.anchorScriptPath,
7953
+ args: ["internal", "autosync-run", "--kind", "repo", "--cwd", input.repoRoot],
7954
+ logPath: path21.join(autosyncLogsRoot(input.homeDir), `${id}.log`),
7955
+ lockPath: autosyncLockPath(id, input.homeDir),
7956
+ timeoutMs: REPO_TIMEOUT_MS,
7957
+ createdAt: input.previous?.createdAt ?? input.now,
7958
+ updatedAt: input.now,
7959
+ lastRun: input.previous?.lastRun
7960
+ };
7961
+ }
7962
+ function buildOrgJob(input) {
7963
+ const id = autosyncJobIdForOrg(input.org);
7964
+ return {
7965
+ id,
7966
+ kind: "org",
7967
+ label: `Org autosync: ${input.org}`,
7968
+ schedule: "daily",
7969
+ enabled: true,
7970
+ org: input.org,
7971
+ nodePath: input.nodePath,
7972
+ anchorScriptPath: input.anchorScriptPath,
7973
+ args: ["internal", "autosync-run", "--kind", "org", "--org", input.org, "--no-graph"],
7974
+ logPath: path21.join(autosyncLogsRoot(input.homeDir), `${id}.log`),
7975
+ lockPath: autosyncLockPath(id, input.homeDir),
7976
+ timeoutMs: ORG_TIMEOUT_MS,
7977
+ createdAt: input.previous?.createdAt ?? input.now,
7978
+ updatedAt: input.now,
7979
+ lastRun: input.previous?.lastRun
7980
+ };
7981
+ }
7982
+ function buildOrgGraphJob(input) {
7983
+ const id = autosyncJobIdForOrgGraph(input.org);
7984
+ return {
7985
+ id,
7986
+ kind: "org-graph",
7987
+ label: `Org graph autosync: ${input.org}`,
7988
+ schedule: "weekly",
7989
+ enabled: true,
7990
+ org: input.org,
7991
+ nodePath: input.nodePath,
7992
+ anchorScriptPath: input.anchorScriptPath,
7993
+ args: ["internal", "autosync-run", "--kind", "org-graph", "--org", input.org],
7994
+ logPath: path21.join(autosyncLogsRoot(input.homeDir), `${id}.log`),
7995
+ lockPath: autosyncLockPath(id, input.homeDir),
7996
+ timeoutMs: GRAPH_TIMEOUT_MS,
7997
+ createdAt: input.previous?.createdAt ?? input.now,
7998
+ updatedAt: input.now,
7999
+ lastRun: input.previous?.lastRun
8000
+ };
8001
+ }
8002
+ function discoverConfiguredOrgs(baseDir) {
8003
+ return listOrgNames(baseDir).filter((org) => {
8004
+ try {
8005
+ return loadOrgConfig(org, baseDir).repos.some((repo) => repo.enabled);
8006
+ } catch {
8007
+ return false;
8008
+ }
8009
+ });
8010
+ }
8011
+ function installSchedulerJob(job, options) {
8012
+ fs10.mkdirSync(path21.dirname(job.logPath), { recursive: true });
8013
+ const artifact = schedulerArtifactForJob(job, options.platform, options.homeDir);
8014
+ if (options.platform === "darwin") installLaunchdJob(job, artifact, options.runner);
8015
+ else if (options.platform === "linux") installLinuxJob(job, artifact, options);
8016
+ else if (options.platform === "win32") installWindowsJob(job, artifact, options.runner);
8017
+ else throw new Error(`Unsupported autosync scheduler platform: ${options.platform}`);
8018
+ return {
8019
+ id: job.id,
8020
+ label: job.label,
8021
+ kind: job.kind,
8022
+ schedule: job.schedule,
8023
+ scheduler: artifact.scheduler,
8024
+ installed: true,
8025
+ nextRunHint: artifact.nextRunHint,
8026
+ logPath: job.logPath,
8027
+ message: "Installed local scheduler job."
8028
+ };
8029
+ }
8030
+ function removeSchedulerJob(job, options) {
8031
+ const artifact = schedulerArtifactForJob(job, options.platform, options.homeDir);
8032
+ if (options.platform === "darwin") {
8033
+ const uid = process.getuid?.();
8034
+ if (artifact.filePath && uid !== void 0) {
8035
+ try {
8036
+ options.runner("launchctl", ["bootout", `gui/${uid}`, artifact.filePath]);
8037
+ } catch {
8038
+ }
8039
+ }
8040
+ if (artifact.filePath) fs10.rmSync(artifact.filePath, { force: true });
8041
+ return;
8042
+ }
8043
+ if (options.platform === "linux") {
8044
+ if (artifact.filePath) {
8045
+ const timerPath = artifact.filePath.replace(/\.service$/, ".timer");
8046
+ try {
8047
+ options.runner("systemctl", ["--user", "disable", "--now", path21.basename(timerPath)]);
8048
+ } catch {
8049
+ }
8050
+ fs10.rmSync(artifact.filePath, { force: true });
8051
+ fs10.rmSync(timerPath, { force: true });
8052
+ }
8053
+ removeCronBlock(job.id, options.runner);
8054
+ return;
8055
+ }
8056
+ if (options.platform === "win32") {
8057
+ try {
8058
+ options.runner("schtasks", ["/Delete", "/F", "/TN", windowsTaskName(job)]);
8059
+ } catch {
8060
+ }
8061
+ }
8062
+ }
8063
+ function schedulerArtifactForJob(job, platform, homeDir) {
8064
+ const time = scheduleTime(job);
8065
+ if (platform === "darwin") {
8066
+ const label = `com.anchor.autosync.${safeName(job.id)}`;
8067
+ return {
8068
+ scheduler: "launchd",
8069
+ label,
8070
+ filePath: path21.join(homeDir, "Library", "LaunchAgents", `${label}.plist`),
8071
+ nextRunHint: `${job.schedule} around ${time.hourLabel}`
8072
+ };
8073
+ }
8074
+ if (platform === "linux") {
8075
+ const unit = `anchor-autosync-${safeName(job.id)}`;
8076
+ return {
8077
+ scheduler: "systemd-user",
8078
+ label: unit,
8079
+ filePath: path21.join(homeDir, ".config", "systemd", "user", `${unit}.service`),
8080
+ nextRunHint: `${job.schedule} around ${time.hourLabel}`
8081
+ };
8082
+ }
8083
+ if (platform === "win32") {
8084
+ return {
8085
+ scheduler: "windows-task-scheduler",
8086
+ label: windowsTaskName(job),
8087
+ nextRunHint: `${job.schedule} around ${time.hourLabel}`
8088
+ };
8089
+ }
8090
+ return { scheduler: "unsupported", nextRunHint: "n/a" };
8091
+ }
8092
+ function schedulerArtifactExists(job, platform, homeDir) {
8093
+ const artifact = schedulerArtifactForJob(job, platform, homeDir);
8094
+ if (platform === "win32") return true;
8095
+ if (platform === "linux") {
8096
+ return Boolean(
8097
+ artifact.filePath && (fs10.existsSync(artifact.filePath) || fs10.existsSync(path21.join(homeDir, ".config", "systemd", "user", `${artifact.label}.timer`)) || cronHasBlock(job.id))
8098
+ );
8099
+ }
8100
+ return Boolean(artifact.filePath && fs10.existsSync(artifact.filePath));
8101
+ }
8102
+ function installLaunchdJob(job, artifact, runner) {
8103
+ if (!artifact.filePath || !artifact.label) throw new Error("Missing launchd artifact path.");
8104
+ fs10.mkdirSync(path21.dirname(artifact.filePath), { recursive: true });
8105
+ fs10.writeFileSync(artifact.filePath, launchdPlist(job, artifact.label), { mode: 420 });
8106
+ const uid = process.getuid?.();
8107
+ if (uid === void 0) return;
8108
+ try {
8109
+ runner("launchctl", ["bootout", `gui/${uid}`, artifact.filePath]);
8110
+ } catch {
8111
+ }
8112
+ runner("launchctl", ["bootstrap", `gui/${uid}`, artifact.filePath]);
8113
+ runner("launchctl", ["enable", `gui/${uid}/${artifact.label}`]);
8114
+ }
8115
+ function installLinuxJob(job, artifact, options) {
8116
+ if (!artifact.filePath || !artifact.label) throw new Error("Missing systemd artifact path.");
8117
+ const timerPath = artifact.filePath.replace(/\.service$/, ".timer");
8118
+ fs10.mkdirSync(path21.dirname(artifact.filePath), { recursive: true });
8119
+ fs10.writeFileSync(artifact.filePath, systemdService(job), { mode: 420 });
8120
+ fs10.writeFileSync(timerPath, systemdTimer(job), { mode: 420 });
8121
+ try {
8122
+ options.runner("systemctl", ["--user", "daemon-reload"]);
8123
+ options.runner("systemctl", ["--user", "enable", "--now", path21.basename(timerPath)]);
8124
+ } catch {
8125
+ installCronFallback(job, options.runner);
8126
+ }
8127
+ }
8128
+ function installWindowsJob(job, artifact, runner) {
8129
+ const time = scheduleTime(job);
8130
+ const args = [
8131
+ "/Create",
8132
+ "/F",
8133
+ "/TN",
8134
+ artifact.label ?? windowsTaskName(job),
8135
+ "/TR",
8136
+ `${quoteWindows(job.nodePath)} ${quoteWindows(job.anchorScriptPath)} ${job.args.map(quoteWindows).join(" ")}`,
8137
+ "/SC",
8138
+ job.schedule === "weekly" ? "WEEKLY" : "DAILY",
8139
+ "/ST",
8140
+ `${pad2(time.hour)}:${pad2(time.minute)}`
8141
+ ];
8142
+ if (job.schedule === "weekly") args.push("/D", "MON");
8143
+ runner("schtasks", args);
8144
+ }
8145
+ function launchdPlist(job, label) {
8146
+ const time = scheduleTime(job);
8147
+ const calendar = job.schedule === "weekly" ? `<dict><key>Weekday</key><integer>1</integer><key>Hour</key><integer>${time.hour}</integer><key>Minute</key><integer>${time.minute}</integer></dict>` : `<dict><key>Hour</key><integer>${time.hour}</integer><key>Minute</key><integer>${time.minute}</integer></dict>`;
8148
+ return `<?xml version="1.0" encoding="UTF-8"?>
8149
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
8150
+ <plist version="1.0">
8151
+ <dict>
8152
+ <key>Label</key><string>${xmlEscape(label)}</string>
8153
+ <key>ProgramArguments</key>
8154
+ <array>
8155
+ ${[job.nodePath, job.anchorScriptPath, ...job.args].map((arg) => ` <string>${xmlEscape(arg)}</string>`).join("\n")}
8156
+ </array>
8157
+ <key>StartCalendarInterval</key>${calendar}
8158
+ <key>StandardOutPath</key><string>${xmlEscape(job.logPath)}</string>
8159
+ <key>StandardErrorPath</key><string>${xmlEscape(job.logPath)}</string>
8160
+ <key>EnvironmentVariables</key>
8161
+ <dict>
8162
+ <key>HOME</key><string>${xmlEscape(os3.homedir())}</string>
8163
+ <key>PATH</key><string>${xmlEscape(process.env.PATH ?? "/usr/local/bin:/usr/bin:/bin")}</string>
8164
+ <key>SHELL</key><string>${xmlEscape(process.env.SHELL ?? "/bin/sh")}</string>
8165
+ <key>ANCHOR_PROGRESS</key><string>plain</string>
8166
+ </dict>
8167
+ </dict>
8168
+ </plist>
8169
+ `;
8170
+ }
8171
+ function systemdService(job) {
8172
+ return `[Unit]
8173
+ Description=${job.label}
8174
+
8175
+ [Service]
8176
+ Type=oneshot
8177
+ Environment=ANCHOR_PROGRESS=plain
8178
+ Environment=HOME=${systemdEscape(os3.homedir())}
8179
+ Environment=PATH=${systemdEscape(process.env.PATH ?? "/usr/local/bin:/usr/bin:/bin")}
8180
+ Environment=SHELL=${systemdEscape(process.env.SHELL ?? "/bin/sh")}
8181
+ ExecStart=${systemdEscape(job.nodePath)} ${[job.anchorScriptPath, ...job.args].map(systemdEscape).join(" ")}
8182
+ StandardOutput=append:${job.logPath}
8183
+ StandardError=append:${job.logPath}
8184
+ `;
8185
+ }
8186
+ function systemdTimer(job) {
8187
+ const time = scheduleTime(job);
8188
+ const calendar = job.schedule === "weekly" ? `Mon *-*-* ${pad2(time.hour)}:${pad2(time.minute)}:00` : `*-*-* ${pad2(time.hour)}:${pad2(time.minute)}:00`;
8189
+ return `[Unit]
8190
+ Description=${job.label} timer
8191
+
8192
+ [Timer]
8193
+ OnCalendar=${calendar}
8194
+ Persistent=true
8195
+
8196
+ [Install]
8197
+ WantedBy=timers.target
8198
+ `;
8199
+ }
8200
+ function installCronFallback(job, runner) {
8201
+ const existing = readCrontab(runner);
8202
+ const withoutBlock = removeCronBlockFromText(existing, job.id);
8203
+ const time = scheduleTime(job);
8204
+ const weekday = job.schedule === "weekly" ? "1" : "*";
8205
+ const command = `HOME=${shellQuote(os3.homedir())} PATH=${shellQuote(process.env.PATH ?? "/usr/local/bin:/usr/bin:/bin")} SHELL=${shellQuote(process.env.SHELL ?? "/bin/sh")} ANCHOR_PROGRESS=plain ${shellQuote(job.nodePath)} ${shellQuote(job.anchorScriptPath)} ${job.args.map(shellQuote).join(" ")} >> ${shellQuote(job.logPath)} 2>&1`;
8206
+ const block = [
8207
+ `# anchor autosync start ${job.id}`,
8208
+ `${time.minute} ${time.hour} * * ${weekday} ${command}`,
8209
+ `# anchor autosync end ${job.id}`
8210
+ ].join("\n");
8211
+ runner("crontab", ["-"], { input: `${withoutBlock.trim()}
8212
+ ${block}
8213
+ ` });
8214
+ }
8215
+ function removeCronBlock(jobId, runner) {
8216
+ const existing = readCrontab(runner);
8217
+ const next = removeCronBlockFromText(existing, jobId);
8218
+ if (next !== existing) runner("crontab", ["-"], { input: next.trim() ? `${next.trim()}
8219
+ ` : "" });
8220
+ }
8221
+ function readCrontab(runner) {
8222
+ try {
8223
+ return runner("crontab", ["-l"]);
8224
+ } catch {
8225
+ return "";
8226
+ }
8227
+ }
8228
+ function removeCronBlockFromText(text, jobId) {
8229
+ const start = `# anchor autosync start ${jobId}`;
8230
+ const end = `# anchor autosync end ${jobId}`;
8231
+ const lines = text.split("\n");
8232
+ const kept = [];
8233
+ let skipping = false;
8234
+ for (const line of lines) {
8235
+ if (line.trim() === start) {
8236
+ skipping = true;
8237
+ continue;
8238
+ }
8239
+ if (line.trim() === end) {
8240
+ skipping = false;
8241
+ continue;
8242
+ }
8243
+ if (!skipping) kept.push(line);
8244
+ }
8245
+ return kept.join("\n").trim();
8246
+ }
8247
+ function cronHasBlock(_jobId) {
8248
+ return false;
8249
+ }
8250
+ function scheduleTime(job) {
8251
+ const offset = stableHashNumber(job.id) % 180;
8252
+ const baseHour = job.schedule === "weekly" ? 3 : 9;
8253
+ const hour = (baseHour + Math.floor(offset / 60)) % 24;
8254
+ const minute = offset % 60;
8255
+ return { hour, minute, hourLabel: `${pad2(hour)}:${pad2(minute)}` };
8256
+ }
8257
+ function isAutosyncJobStale(job) {
8258
+ if (!job.enabled) return false;
8259
+ if (!job.lastRun?.finishedAt) return false;
8260
+ if (job.lastRun.status === "failed") return true;
8261
+ return Date.now() - Date.parse(job.lastRun.finishedAt) > STALE_RUN_MS;
8262
+ }
8263
+ function latestAutosyncRun(runs) {
8264
+ return runs.sort((a, b) => Date.parse(b.startedAt) - Date.parse(a.startedAt))[0];
8265
+ }
8266
+ function normalizeAutosyncConfig(value) {
8267
+ const candidate = value;
8268
+ return {
8269
+ version: CONFIG_VERSION,
8270
+ enabled: Boolean(candidate.enabled),
8271
+ updatedAt: candidate.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
8272
+ jobs: Array.isArray(candidate.jobs) ? candidate.jobs : []
8273
+ };
8274
+ }
8275
+ function readLock(lockPath) {
8276
+ try {
8277
+ return JSON.parse(fs10.readFileSync(lockPath, "utf8"));
8278
+ } catch {
8279
+ return void 0;
8280
+ }
8281
+ }
8282
+ function processIsRunning(pid) {
8283
+ try {
8284
+ process.kill(pid, 0);
8285
+ return true;
8286
+ } catch {
8287
+ return false;
8288
+ }
8289
+ }
8290
+ function requiredOrg(org) {
8291
+ if (!org) throw new Error("Pass --org <org>.");
8292
+ return org;
8293
+ }
8294
+ function schedulerName(platform) {
8295
+ if (platform === "darwin") return "launchd";
8296
+ if (platform === "linux") return "systemd-user";
8297
+ if (platform === "win32") return "windows-task-scheduler";
8298
+ return "unsupported";
8299
+ }
8300
+ function stableHashNumber(value) {
8301
+ let hash = 5381;
8302
+ for (const char of value) hash = (hash << 5) + hash + char.charCodeAt(0) >>> 0;
8303
+ return hash;
8304
+ }
8305
+ function stableHashText(value) {
8306
+ const hash = stableHashNumber(value);
8307
+ return hash.toString(36);
8308
+ }
8309
+ function safeName(value) {
8310
+ return value.replace(/[^A-Za-z0-9_.-]/g, "-");
8311
+ }
8312
+ function pad2(value) {
8313
+ return String(value).padStart(2, "0");
8314
+ }
8315
+ function xmlEscape(value) {
8316
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
8317
+ }
8318
+ function systemdEscape(value) {
8319
+ return value.includes(" ") ? `"${value.replaceAll('"', '\\"')}"` : value;
8320
+ }
8321
+ function shellQuote(value) {
8322
+ return `'${value.replaceAll("'", "'\\''")}'`;
8323
+ }
8324
+ function quoteWindows(value) {
8325
+ return `"${value.replaceAll('"', '\\"')}"`;
8326
+ }
8327
+ function windowsTaskName(job) {
8328
+ return `AnchorAutosync-${safeName(job.id)}`;
8329
+ }
8330
+
8331
+ // src/ci.ts
8332
+ import fs11 from "fs";
8333
+ import path22 from "path";
7502
8334
  function runAnchorCi(db, cwd, input = {}) {
7503
8335
  initializeSchema(db);
7504
8336
  const status = getIndexStatus(cwd, false);
7505
8337
  const minCoverage = input.minCoverage ?? 70;
7506
8338
  const rules = validateTeamRulesFile(cwd);
7507
8339
  const evidence = rules.ok ? checkTeamRuleEvidence(cwd) : void 0;
7508
- const evalsPath2 = path20.join(cwd, ANCHOR_EVALS_FILE);
7509
- const evals = fs9.existsSync(evalsPath2) ? runRetrievalEvals(db, cwd) : void 0;
8340
+ const evalsPath2 = path22.join(cwd, ANCHOR_EVALS_FILE);
8341
+ const evals = fs11.existsSync(evalsPath2) ? runRetrievalEvals(db, cwd) : void 0;
7510
8342
  const checks = [
7511
8343
  {
7512
8344
  name: "coverage",
@@ -9018,156 +9850,8 @@ async function fetchMergedPullRequests(options) {
9018
9850
  }
9019
9851
  }
9020
9852
 
9021
- // src/org/config.ts
9022
- import fs10 from "fs";
9023
- import os2 from "os";
9024
- import path21 from "path";
9025
- import { z as z2 } from "zod";
9026
- var ORG_REPO_GROUPS = ["backend", "frontend", "shared", "infra", "docs", "unknown"];
9027
- var OrgRepoSchema = z2.object({
9028
- fullName: z2.string().regex(/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/),
9029
- alias: z2.string().min(1),
9030
- group: z2.enum(ORG_REPO_GROUPS),
9031
- cloneUrl: z2.string().min(1),
9032
- defaultBranch: z2.string().min(1),
9033
- enabled: z2.boolean()
9034
- });
9035
- var OrgConfigSchema = z2.object({
9036
- version: z2.literal(1),
9037
- org: z2.string().regex(/^[A-Za-z0-9_.-]+$/),
9038
- repos: z2.array(OrgRepoSchema)
9039
- });
9040
- function validateOrgName(org) {
9041
- const trimmed = org.trim();
9042
- if (!/^[A-Za-z0-9_.-]+$/.test(trimmed)) {
9043
- throw new Error("Invalid org name. Use only letters, numbers, dot, underscore, and hyphen.");
9044
- }
9045
- return trimmed;
9046
- }
9047
- function validateOrgRepoFullName(fullName) {
9048
- const trimmed = fullName.trim();
9049
- if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(trimmed)) {
9050
- throw new Error("Invalid repo name. Use owner/name.");
9051
- }
9052
- return trimmed;
9053
- }
9054
- function validateOrgRepoGroup(group) {
9055
- if (!group) return "unknown";
9056
- if (ORG_REPO_GROUPS.includes(group)) return group;
9057
- throw new Error(`Invalid repo group: ${group}`);
9058
- }
9059
- function defaultOrgBaseDir() {
9060
- if (process.env.ANCHOR_ORG_HOME) return process.env.ANCHOR_ORG_HOME;
9061
- return path21.join(os2.homedir(), ".anchor", "orgs");
9062
- }
9063
- function orgRoot(org, baseDir = defaultOrgBaseDir()) {
9064
- return path21.join(baseDir, validateOrgName(org));
9065
- }
9066
- function orgConfigPath(org, baseDir = defaultOrgBaseDir()) {
9067
- return path21.join(orgRoot(org, baseDir), "org.json");
9068
- }
9069
- function orgDatabasePath(org, baseDir = defaultOrgBaseDir()) {
9070
- return path21.join(orgRoot(org, baseDir), "org.sqlite");
9071
- }
9072
- function orgReposRoot(org, baseDir = defaultOrgBaseDir()) {
9073
- return path21.join(orgRoot(org, baseDir), "repos");
9074
- }
9075
- function repoAliasFromFullName(fullName) {
9076
- return validateOrgRepoFullName(fullName).split("/")[1] ?? fullName.replace(/\W+/g, "-");
9077
- }
9078
- function defaultOrgCloneUrl(fullName) {
9079
- return `https://github.com/${validateOrgRepoFullName(fullName)}.git`;
9080
- }
9081
- function orgRepoLocalPath(org, repo, baseDir = defaultOrgBaseDir()) {
9082
- const safeAlias = repo.alias.replace(/[^A-Za-z0-9_.-]/g, "-") || repoAliasFromFullName(repo.fullName);
9083
- return path21.join(orgReposRoot(org, baseDir), safeAlias);
9084
- }
9085
- function parseOrgConfig(text) {
9086
- const parsed = OrgConfigSchema.parse(JSON.parse(text));
9087
- return parsed;
9088
- }
9089
- function atomicWriteJson(filePath, value) {
9090
- fs10.mkdirSync(path21.dirname(filePath), { recursive: true });
9091
- const tmp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
9092
- fs10.writeFileSync(tmp, `${JSON.stringify(value, null, 2)}
9093
- `, { mode: 384 });
9094
- fs10.renameSync(tmp, filePath);
9095
- }
9096
- function loadOrgConfig(org, baseDir = defaultOrgBaseDir()) {
9097
- const filePath = orgConfigPath(org, baseDir);
9098
- if (!fs10.existsSync(filePath)) {
9099
- throw new Error(
9100
- `Anchor org config not found at ${filePath}. Run anchor org init --org ${org}.`
9101
- );
9102
- }
9103
- return parseOrgConfig(fs10.readFileSync(filePath, "utf8"));
9104
- }
9105
- function maybeLoadOrgConfig(org, baseDir = defaultOrgBaseDir()) {
9106
- const filePath = orgConfigPath(org, baseDir);
9107
- if (!fs10.existsSync(filePath)) return void 0;
9108
- return loadOrgConfig(org, baseDir);
9109
- }
9110
- function saveOrgConfig(config, baseDir = defaultOrgBaseDir()) {
9111
- const parsed = OrgConfigSchema.parse(config);
9112
- atomicWriteJson(orgConfigPath(parsed.org, baseDir), parsed);
9113
- return parsed;
9114
- }
9115
- function initOrgConfig(org, baseDir = defaultOrgBaseDir()) {
9116
- const normalizedOrg = validateOrgName(org);
9117
- fs10.mkdirSync(orgReposRoot(normalizedOrg, baseDir), { recursive: true });
9118
- const existing = maybeLoadOrgConfig(normalizedOrg, baseDir);
9119
- if (existing) return existing;
9120
- return saveOrgConfig({ version: 1, org: normalizedOrg, repos: [] }, baseDir);
9121
- }
9122
- function addOrgRepoConfig(org, repoFullName, input = {}, baseDir = defaultOrgBaseDir()) {
9123
- const config = initOrgConfig(org, baseDir);
9124
- const fullName = validateOrgRepoFullName(repoFullName);
9125
- const existing = config.repos.find((repo) => repo.fullName === fullName);
9126
- const candidate = {
9127
- fullName,
9128
- alias: input.alias?.trim() || existing?.alias || repoAliasFromFullName(fullName),
9129
- group: validateOrgRepoGroup(input.group ?? existing?.group),
9130
- cloneUrl: input.cloneUrl?.trim() || existing?.cloneUrl || defaultOrgCloneUrl(fullName),
9131
- defaultBranch: input.defaultBranch?.trim() || existing?.defaultBranch || "main",
9132
- enabled: true
9133
- };
9134
- const repos = existing ? config.repos.map((repo) => repo.fullName === fullName ? candidate : repo) : [...config.repos, candidate];
9135
- return saveOrgConfig(
9136
- { ...config, repos: repos.sort((a, b) => a.fullName.localeCompare(b.fullName)) },
9137
- baseDir
9138
- );
9139
- }
9140
- function removeOrgRepoConfig(org, repoFullName, baseDir = defaultOrgBaseDir()) {
9141
- const config = loadOrgConfig(org, baseDir);
9142
- const fullName = validateOrgRepoFullName(repoFullName);
9143
- return saveOrgConfig(
9144
- {
9145
- ...config,
9146
- repos: config.repos.map(
9147
- (repo) => repo.fullName === fullName ? { ...repo, enabled: false } : repo
9148
- )
9149
- },
9150
- baseDir
9151
- );
9152
- }
9153
- function listOrgNames(baseDir = defaultOrgBaseDir()) {
9154
- if (!fs10.existsSync(baseDir)) return [];
9155
- return fs10.readdirSync(baseDir, { withFileTypes: true }).filter(
9156
- (entry) => entry.isDirectory() && fs10.existsSync(path21.join(baseDir, entry.name, "org.json"))
9157
- ).map((entry) => entry.name).sort();
9158
- }
9159
- function resolveOrgForTool(org, baseDir = defaultOrgBaseDir()) {
9160
- if (org) return validateOrgName(org);
9161
- const names = listOrgNames(baseDir);
9162
- if (names.length === 1) return names[0] ?? "";
9163
- if (names.length === 0) {
9164
- throw new Error("No Anchor org configured. Run anchor org init --org <org>.");
9165
- }
9166
- throw new Error(`Multiple Anchor orgs configured (${names.join(", ")}). Pass org explicitly.`);
9167
- }
9168
-
9169
9853
  // src/org/database.ts
9170
- import fs11 from "fs";
9854
+ import fs12 from "fs";
9171
9855
  var DEFAULT_EDGE_DISTRIBUTION = {
9172
9856
  strong: 0,
9173
9857
  moderate: 0,
@@ -9429,7 +10113,7 @@ function getOrgStatus(db, config, baseDir, options = {}) {
9429
10113
  db.prepare("SELECT * FROM org_repo_state WHERE org = ?").all(config.org).map((row) => [row.repo, row])
9430
10114
  );
9431
10115
  const clonedRepoCount = enabledRepos.filter(
9432
- (repo) => fs11.existsSync(orgRepoLocalPath(config.org, repo, baseDir))
10116
+ (repo) => fs12.existsSync(orgRepoLocalPath(config.org, repo, baseDir))
9433
10117
  ).length;
9434
10118
  const codeFileCount = count(db, "code_files");
9435
10119
  const codeChunkCount = count(db, "code_chunks");
@@ -9512,7 +10196,7 @@ function getOrgStatus(db, config, baseDir, options = {}) {
9512
10196
  return {
9513
10197
  ...repo,
9514
10198
  localPath,
9515
- cloned: fs11.existsSync(localPath),
10199
+ cloned: fs12.existsSync(localPath),
9516
10200
  currentCommit: state?.current_commit ?? void 0,
9517
10201
  lastPulledAt: state?.last_pulled_at ?? void 0,
9518
10202
  lastCodeIndexedAt: state?.last_code_indexed_at ?? void 0,
@@ -9524,20 +10208,20 @@ function getOrgStatus(db, config, baseDir, options = {}) {
9524
10208
  }
9525
10209
 
9526
10210
  // src/org/heartbeat.ts
9527
- import fs12 from "fs";
9528
- import path22 from "path";
10211
+ import fs13 from "fs";
10212
+ import path23 from "path";
9529
10213
  var HEARTBEAT_STALE_AFTER_MS = 2 * 60 * 1e3;
9530
10214
  function orgHeartbeatPath(org, baseDir) {
9531
- return path22.join(orgRoot(validateOrgName(org), baseDir), "sync-heartbeat.json");
10215
+ return path23.join(orgRoot(validateOrgName(org), baseDir), "sync-heartbeat.json");
9532
10216
  }
9533
10217
  function atomicWriteJson2(filePath, value) {
9534
- fs12.mkdirSync(path22.dirname(filePath), { recursive: true });
10218
+ fs13.mkdirSync(path23.dirname(filePath), { recursive: true });
9535
10219
  const tmp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
9536
- fs12.writeFileSync(tmp, `${JSON.stringify(value, null, 2)}
10220
+ fs13.writeFileSync(tmp, `${JSON.stringify(value, null, 2)}
9537
10221
  `, { mode: 384 });
9538
- fs12.renameSync(tmp, filePath);
10222
+ fs13.renameSync(tmp, filePath);
9539
10223
  }
9540
- function processIsRunning(pid) {
10224
+ function processIsRunning2(pid) {
9541
10225
  if (!Number.isInteger(pid) || pid <= 0) return false;
9542
10226
  try {
9543
10227
  process.kill(pid, 0);
@@ -9627,20 +10311,20 @@ function writeOrgHeartbeat(heartbeat, baseDir) {
9627
10311
  function clearOrgHeartbeat(org, baseDir) {
9628
10312
  try {
9629
10313
  const filePath = orgHeartbeatPath(org, baseDir);
9630
- if (fs12.existsSync(filePath)) fs12.unlinkSync(filePath);
10314
+ if (fs13.existsSync(filePath)) fs13.unlinkSync(filePath);
9631
10315
  } catch {
9632
10316
  }
9633
10317
  }
9634
10318
  function readOrgHeartbeat(org, baseDir) {
9635
10319
  const filePath = orgHeartbeatPath(org, baseDir);
9636
- if (!fs12.existsSync(filePath)) return void 0;
10320
+ if (!fs13.existsSync(filePath)) return void 0;
9637
10321
  try {
9638
- const heartbeat = parseHeartbeat(JSON.parse(fs12.readFileSync(filePath, "utf8")));
10322
+ const heartbeat = parseHeartbeat(JSON.parse(fs13.readFileSync(filePath, "utf8")));
9639
10323
  if (!heartbeat) return void 0;
9640
10324
  const now = Date.now();
9641
10325
  const startedAtMs = Date.parse(heartbeat.startedAt);
9642
10326
  const updatedAtMs = Date.parse(heartbeat.updatedAt);
9643
- const pidRunning = processIsRunning(heartbeat.pid);
10327
+ const pidRunning = processIsRunning2(heartbeat.pid);
9644
10328
  const lastUpdateAgeSeconds = Number.isFinite(updatedAtMs) ? Math.max(0, Math.floor((now - updatedAtMs) / 1e3)) : 0;
9645
10329
  return {
9646
10330
  ...heartbeat,
@@ -9655,11 +10339,11 @@ function readOrgHeartbeat(org, baseDir) {
9655
10339
  }
9656
10340
 
9657
10341
  // src/org/clone.ts
9658
- import { execFileSync as execFileSync4 } from "child_process";
9659
- import fs13 from "fs";
9660
- import path23 from "path";
10342
+ import { execFileSync as execFileSync5 } from "child_process";
10343
+ import fs14 from "fs";
10344
+ import path24 from "path";
9661
10345
  function defaultGitCommandRunner(command, args, options = {}) {
9662
- return execFileSync4(command, args, {
10346
+ return execFileSync5(command, args, {
9663
10347
  cwd: options.cwd,
9664
10348
  encoding: "utf8",
9665
10349
  stdio: ["ignore", "pipe", "pipe"]
@@ -9673,7 +10357,7 @@ function currentCommit(runner, localPath) {
9673
10357
  }
9674
10358
  }
9675
10359
  function plannedOrgCloneCommands(repo, localPath) {
9676
- if (!fs13.existsSync(path23.join(localPath, ".git"))) {
10360
+ if (!fs14.existsSync(path24.join(localPath, ".git"))) {
9677
10361
  return [
9678
10362
  {
9679
10363
  command: "git",
@@ -9702,8 +10386,8 @@ function plannedOrgCloneCommands(repo, localPath) {
9702
10386
  function cloneOrPullOrgRepo(input) {
9703
10387
  const runner = input.runner ?? defaultGitCommandRunner;
9704
10388
  const localPath = orgRepoLocalPath(input.org, input.repo, input.baseDir);
9705
- const existed = fs13.existsSync(path23.join(localPath, ".git"));
9706
- fs13.mkdirSync(path23.dirname(localPath), { recursive: true });
10389
+ const existed = fs14.existsSync(path24.join(localPath, ".git"));
10390
+ fs14.mkdirSync(path24.dirname(localPath), { recursive: true });
9707
10391
  const now = (/* @__PURE__ */ new Date()).toISOString();
9708
10392
  try {
9709
10393
  const commands = plannedOrgCloneCommands(input.repo, localPath);
@@ -9804,8 +10488,8 @@ function orgCloneStateFromResult(org, repo, result) {
9804
10488
 
9805
10489
  // src/org/graph.ts
9806
10490
  import crypto9 from "crypto";
9807
- import fs14 from "fs";
9808
- import path24 from "path";
10491
+ import fs15 from "fs";
10492
+ import path25 from "path";
9809
10493
  var MIN_FILE_EDGE_CONFIDENCE = 0.62;
9810
10494
  var MIN_REPO_EDGE_CONFIDENCE = 0.7;
9811
10495
  var MIN_VISIBLE_EVIDENCE = 2;
@@ -9850,10 +10534,10 @@ function evidenceJson(evidence) {
9850
10534
  return JSON.stringify(evidence);
9851
10535
  }
9852
10536
  function readPackageManifest(repoPath) {
9853
- const packagePath = path24.join(repoPath, "package.json");
9854
- if (!fs14.existsSync(packagePath)) return void 0;
10537
+ const packagePath = path25.join(repoPath, "package.json");
10538
+ if (!fs15.existsSync(packagePath)) return void 0;
9855
10539
  try {
9856
- return JSON.parse(fs14.readFileSync(packagePath, "utf8"));
10540
+ return JSON.parse(fs15.readFileSync(packagePath, "utf8"));
9857
10541
  } catch {
9858
10542
  return void 0;
9859
10543
  }
@@ -10596,7 +11280,7 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
10596
11280
  }
10597
11281
 
10598
11282
  // src/org/index.ts
10599
- import fs15 from "fs";
11283
+ import fs16 from "fs";
10600
11284
  var ORG_SYNC_RESUME_WINDOW_MS = 12 * 60 * 60 * 1e3;
10601
11285
  function readCommit(runner, cwd) {
10602
11286
  try {
@@ -10670,7 +11354,7 @@ async function indexOrgRepos(db, config, options = {}) {
10670
11354
  current: repoPosition,
10671
11355
  total: repos.length
10672
11356
  });
10673
- if (!fs15.existsSync(localPath)) throw new Error(missingCloneError(repo.fullName, localPath));
11357
+ if (!fs16.existsSync(localPath)) throw new Error(missingCloneError(repo.fullName, localPath));
10674
11358
  emit({
10675
11359
  stage: "org_repo_phase",
10676
11360
  org: config.org,
@@ -10730,7 +11414,8 @@ async function indexOrgRepos(db, config, options = {}) {
10730
11414
  const pullRequests = await fetchPullRequests({
10731
11415
  token: auth.token,
10732
11416
  repo: repo.fullName,
10733
- limit: 200,
11417
+ limit: options.all ? void 0 : 200,
11418
+ all: options.all,
10734
11419
  since,
10735
11420
  detailConcurrency: options.concurrency,
10736
11421
  onProgress: options.onFetchProgress
@@ -10748,8 +11433,8 @@ async function indexOrgRepos(db, config, options = {}) {
10748
11433
  history = indexPullRequests(db, pullRequests, {
10749
11434
  cwd: localPath,
10750
11435
  repo: repo.fullName,
10751
- historyCoverage: "limited",
10752
- historyLimit: 200,
11436
+ historyCoverage: options.all ? "all" : "limited",
11437
+ historyLimit: options.all ? void 0 : 200,
10753
11438
  historySince: since,
10754
11439
  onProgress: options.onPrIndexProgress
10755
11440
  });
@@ -11639,7 +12324,7 @@ function isTestPath2(filePath) {
11639
12324
  }
11640
12325
 
11641
12326
  // src/doctor.ts
11642
- import fs16 from "fs";
12327
+ import fs17 from "fs";
11643
12328
  function check(name, ok, message, fix) {
11644
12329
  return { name, ok, message, fix: ok ? void 0 : fix };
11645
12330
  }
@@ -11744,7 +12429,7 @@ async function runDoctor(options) {
11744
12429
  );
11745
12430
  }
11746
12431
  const dbPath = defaultDatabasePath(gitRoot ?? cwd);
11747
- const dbExists = fs16.existsSync(dbPath);
12432
+ const dbExists = fs17.existsSync(dbPath);
11748
12433
  checks.push(
11749
12434
  check(
11750
12435
  ".anchor/index.sqlite exists",
@@ -11811,6 +12496,34 @@ async function runDoctor(options) {
11811
12496
  );
11812
12497
  }
11813
12498
  }
12499
+ const autosyncStatus = getAutosyncStatus({ cwd: targetCwd });
12500
+ checks.push(
12501
+ check(
12502
+ "autosync configured",
12503
+ true,
12504
+ autosyncStatus.enabled ? `${autosyncStatus.jobs.length} autosync job(s) configured using ${autosyncStatus.scheduler}.` : "Autosync is not enabled."
12505
+ )
12506
+ );
12507
+ const missingSchedulers = autosyncStatus.jobs.filter(
12508
+ (job) => job.enabled && !job.schedulerDetected
12509
+ );
12510
+ checks.push(
12511
+ check(
12512
+ "autosync scheduler installed",
12513
+ !autosyncStatus.enabled || missingSchedulers.length === 0,
12514
+ missingSchedulers.length === 0 ? "Autosync scheduler files are present." : `${missingSchedulers.length} autosync scheduler job(s) are missing.`,
12515
+ "Run anchor init to reinstall local scheduler jobs."
12516
+ )
12517
+ );
12518
+ const failedJobs = autosyncStatus.jobs.filter((job) => job.failing);
12519
+ checks.push(
12520
+ check(
12521
+ "autosync last run healthy",
12522
+ !autosyncStatus.enabled || failedJobs.length === 0,
12523
+ failedJobs.length === 0 ? `Last autosync result: ${autosyncStatus.lastRun?.status ?? "not run yet"}.` : `${failedJobs.length} autosync job(s) failed recently.`,
12524
+ "Run anchor health and inspect ~/.anchor/logs/autosync/ for the failing job log."
12525
+ )
12526
+ );
11814
12527
  return { ok: checks.every((item) => item.ok), checks };
11815
12528
  }
11816
12529
 
@@ -11824,6 +12537,9 @@ function evaluateIndexHealth(status, rulesOk) {
11824
12537
  if (status.staleCodeIndex) warnings.push("Code index is older than 7 days or has never run.");
11825
12538
  if (!rulesOk) warnings.push("Team rules file is missing or invalid.");
11826
12539
  if (status.lastFailedRun) warnings.push(`Last failed index run: ${status.lastFailedRun}.`);
12540
+ if (status.autosync) {
12541
+ warnings.push(...status.autosync.warnings.map((warning) => `Autosync: ${warning}`));
12542
+ }
11827
12543
  const hasError = status.health === "missing_database" || status.health === "schema_invalid";
11828
12544
  const healthStatus = hasError ? "error" : warnings.length > 0 ? "warning" : "ok";
11829
12545
  return {
@@ -11837,11 +12553,13 @@ function evaluateIndexHealth(status, rulesOk) {
11837
12553
  coverageScore: status.coverageScore,
11838
12554
  coverageGrade: status.coverageGrade,
11839
12555
  coverageReasons: status.coverageReasons,
11840
- suggestedPrompts: status.suggestedPrompts
12556
+ suggestedPrompts: status.suggestedPrompts,
12557
+ autosync: status.autosync
11841
12558
  };
11842
12559
  }
11843
12560
  function getAnchorIndexHealth(cwd) {
11844
12561
  const indexStatus = getIndexStatus(cwd);
12562
+ indexStatus.autosync = getAutosyncStatus({ cwd });
11845
12563
  const rulesValidation = validateTeamRulesFile(cwd);
11846
12564
  return {
11847
12565
  ...evaluateIndexHealth(indexStatus, rulesValidation.ok),
@@ -11860,12 +12578,21 @@ export {
11860
12578
  GitHubGraphQLError,
11861
12579
  SCHEMA_SQL,
11862
12580
  TEAM_RULES_FILE,
12581
+ acquireAutosyncLock,
11863
12582
  addOrgRepoConfig,
11864
12583
  addRetrievalEval,
11865
12584
  addTeamRule,
11866
12585
  agentTargetLabel,
11867
12586
  anchorMcpEntry,
11868
12587
  architectureFilesFromDiff,
12588
+ autosyncConfigPath,
12589
+ autosyncJobIdForOrg,
12590
+ autosyncJobIdForOrgGraph,
12591
+ autosyncJobIdForRepo,
12592
+ autosyncLockPath,
12593
+ autosyncLocksRoot,
12594
+ autosyncLogsRoot,
12595
+ autosyncRoot,
11869
12596
  buildAnchorContextResult,
11870
12597
  buildArchitectureFromIndexedData,
11871
12598
  buildArchitectureIndex,
@@ -11900,6 +12627,7 @@ export {
11900
12627
  countValidTeamRules,
11901
12628
  createGitHubClient,
11902
12629
  createGitHubGraphQLRequester,
12630
+ defaultCommandRunner,
11903
12631
  defaultDatabasePath,
11904
12632
  defaultGitCommandRunner,
11905
12633
  defaultOrgBaseDir,
@@ -11909,6 +12637,7 @@ export {
11909
12637
  detectGitRoot,
11910
12638
  detectTestCommands,
11911
12639
  detectTestCommandsForFile,
12640
+ disableAutosync,
11912
12641
  discoverCodeFiles,
11913
12642
  discoverCodeFilesByPaths,
11914
12643
  emptyCodeIndexSummary,
@@ -11939,6 +12668,7 @@ export {
11939
12668
  getAnchorIndexHealth,
11940
12669
  getArchitectureContext,
11941
12670
  getArchitectureMapContext,
12671
+ getAutosyncStatus,
11942
12672
  getCodeIndexStateForRepo,
11943
12673
  getGitHubRateLimitDelayMs,
11944
12674
  getGraphQLFetchCheckpoint,
@@ -11972,6 +12702,7 @@ export {
11972
12702
  initPlaybooks,
11973
12703
  initRetrievalEvals,
11974
12704
  initializeSchema,
12705
+ installDefaultAutosync,
11975
12706
  isAnchorAgentTarget,
11976
12707
  isGitHubGraphQLResourceLimitError,
11977
12708
  isGitHubRateLimitError,
@@ -12009,10 +12740,12 @@ export {
12009
12740
  rankRelevantTests,
12010
12741
  rankTeamRules,
12011
12742
  rankWisdomUnits,
12743
+ readAutosyncConfig,
12012
12744
  readDiscoveredCodeFileContent,
12013
12745
  readGitHeadCommit,
12014
12746
  readOrgHeartbeat,
12015
12747
  rebuildOrgGraph,
12748
+ recordAutosyncRun,
12016
12749
  recordFeedback,
12017
12750
  recordIndexRun,
12018
12751
  recordOrgGraphState,
@@ -12027,6 +12760,7 @@ export {
12027
12760
  replaceCodeIndex,
12028
12761
  repoAliasFromFullName,
12029
12762
  requestWithGitHubRateLimit,
12763
+ resolveAutosyncJob,
12030
12764
  resolveGitHubToken,
12031
12765
  resolveOrgForTool,
12032
12766
  resolvePullRequestDetailConcurrency,
@@ -12060,6 +12794,7 @@ export {
12060
12794
  validateOrgRepoGroup,
12061
12795
  validateTeamRulesFile,
12062
12796
  watchCodebase,
12797
+ writeAutosyncConfig,
12063
12798
  writeOrgHeartbeat
12064
12799
  };
12065
12800
  //# sourceMappingURL=index.js.map