@outerlayer/cli 0.17.0 → 0.19.0

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.
Files changed (160) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/README.md +36 -2
  3. package/dist/agent-setup-TPR4BBFV.js +27 -0
  4. package/dist/{artifact-link-cmd-2F47LT7F.js → artifact-link-cmd-W7XF5BFB.js} +4 -3
  5. package/dist/{build-BG2YENGK.js → build-TQ6X5PYN.js} +5 -4
  6. package/dist/build-info.json +1 -1
  7. package/dist/{builder-2D3WSOOY.js → builder-C4QNTYMS.js} +5 -6
  8. package/dist/{check-5XE7XMC4.js → check-UWQAL2MN.js} +56 -52
  9. package/dist/{chunk-R7PF5OOU.js → chunk-3CWUOFV6.js} +21 -14
  10. package/dist/{chunk-4NPZVMJU.js → chunk-3UZTIRLF.js} +60 -4
  11. package/dist/chunk-452TM7J4.js +502 -0
  12. package/dist/{chunk-NNRTPG7X.js → chunk-5GHGAF6P.js} +4 -4
  13. package/dist/{chunk-WVUIRTWA.js → chunk-5MYFOW5V.js} +196 -37
  14. package/dist/{chunk-HGECYFEI.js → chunk-5TN7GLYX.js} +138 -109
  15. package/dist/{chunk-VQYBXPI7.js → chunk-6D7GT22T.js} +5 -5
  16. package/dist/{chunk-G5NPRL2K.js → chunk-6U3MV7TZ.js} +687 -76
  17. package/dist/{chunk-42ZI52JW.js → chunk-6X4SQ5MY.js} +4 -3
  18. package/dist/{chunk-MAK5LYRF.js → chunk-7KSSBQTS.js} +278 -535
  19. package/dist/{chunk-NWQTTSBB.js → chunk-7SJPQWBM.js} +41 -16
  20. package/dist/{chunk-J5M7BSJQ.js → chunk-7VUK5RFS.js} +23 -4
  21. package/dist/{chunk-OZGHL5Y5.js → chunk-7YP7T6GV.js} +2 -2
  22. package/dist/{chunk-TI7B7UEO.js → chunk-AMFWYESY.js} +1 -1
  23. package/dist/{chunk-YRYMNBGQ.js → chunk-ANPNT6PM.js} +2 -1
  24. package/dist/{chunk-4KSQKQXE.js → chunk-BM63HB2M.js} +20 -16
  25. package/dist/{chunk-4Q7OMKAT.js → chunk-BQXFBI7T.js} +81 -5
  26. package/dist/{chunk-ADJRGVZC.js → chunk-BWSUPRDK.js} +1 -1
  27. package/dist/{chunk-3NSNIIBE.js → chunk-CG36IONK.js} +18 -17
  28. package/dist/chunk-CJJDJ7AT.js +56 -0
  29. package/dist/chunk-CRB3POP4.js +34 -0
  30. package/dist/{chunk-LWSJQJC6.js → chunk-DADWEUH3.js} +7 -7
  31. package/dist/{chunk-KG5ECZGJ.js → chunk-DJ3ZUSC3.js} +11 -11
  32. package/dist/{chunk-F47JBIAW.js → chunk-DSF2FWAZ.js} +2 -1
  33. package/dist/{chunk-N5FOE5PS.js → chunk-ECODQ7II.js} +4 -33
  34. package/dist/{chunk-HHJDN6AU.js → chunk-ESVW4MJY.js} +1 -1
  35. package/dist/{chunk-YO4PSLVB.js → chunk-FVMYKL6L.js} +1 -1
  36. package/dist/chunk-G2YNJIGF.js +44 -0
  37. package/dist/{chunk-WUYTWNMA.js → chunk-G3IJ3N6F.js} +5 -3
  38. package/dist/{chunk-QKFB4K7X.js → chunk-GGOMT6KF.js} +1 -1
  39. package/dist/{chunk-N6LURUEF.js → chunk-GOSFARMS.js} +1 -1
  40. package/dist/{chunk-S3NYLI2P.js → chunk-HPJNLETG.js} +93 -38
  41. package/dist/{chunk-IP7BIBXJ.js → chunk-IF2OSI5T.js} +35 -1
  42. package/dist/{chunk-C5Y5ZJJZ.js → chunk-IXYZUUYQ.js} +5 -4
  43. package/dist/chunk-JBJTC3UU.js +35 -0
  44. package/dist/chunk-KYS3TQAG.js +296 -0
  45. package/dist/chunk-LLM3I5YU.js +89 -0
  46. package/dist/{chunk-YU3VKZ5F.js → chunk-LRG3ZMZD.js} +6 -7
  47. package/dist/{chunk-V7NIVOFD.js → chunk-M2ZWWTWZ.js} +1 -1
  48. package/dist/{chunk-ERI5GBNI.js → chunk-MLAAT4S6.js} +3 -3
  49. package/dist/{chunk-O765JR4W.js → chunk-MTP2QEVO.js} +1 -1
  50. package/dist/{chunk-ZM2IMMYN.js → chunk-N2PXGQRL.js} +9 -1
  51. package/dist/{chunk-BWQ66M6N.js → chunk-NFIR3GEE.js} +98 -24
  52. package/dist/{chunk-POJMEEJJ.js → chunk-NNLA7LLQ.js} +1 -1
  53. package/dist/{chunk-PLMTLGYH.js → chunk-NTRCK4NH.js} +5 -4
  54. package/dist/{chunk-22RMZBGW.js → chunk-P53VEAOF.js} +1 -1
  55. package/dist/{chunk-RLYZLUQ7.js → chunk-QEFKA2QD.js} +2 -2
  56. package/dist/{chunk-JXYVA45C.js → chunk-QNLX477Q.js} +1200 -322
  57. package/dist/{chunk-UGP76IZ5.js → chunk-S5PCA3SN.js} +1 -1
  58. package/dist/{chunk-RMWLBUCJ.js → chunk-SADUQSO4.js} +1 -1
  59. package/dist/{chunk-GQD22KKN.js → chunk-T2Q7ISPE.js} +2 -1
  60. package/dist/chunk-TUSYOL2O.js +113 -0
  61. package/dist/{chunk-NBGDBTO2.js → chunk-TWF3RICI.js} +9 -8
  62. package/dist/{chunk-ONKJUM6R.js → chunk-UVG35MWF.js} +1 -1
  63. package/dist/{chunk-KMDLFI4P.js → chunk-VMJPFQNE.js} +1 -1
  64. package/dist/chunk-VRRGENS4.js +144 -0
  65. package/dist/{chunk-PTW6W5NR.js → chunk-VYAL7PUE.js} +1 -1
  66. package/dist/{chunk-OHVJA253.js → chunk-WFSUUUAN.js} +47 -30
  67. package/dist/{chunk-RSNSZ2LJ.js → chunk-X5H2WYMB.js} +1 -1
  68. package/dist/{chunk-HYZRY4KZ.js → chunk-XNGMQ27R.js} +16 -6
  69. package/dist/chunk-XSZD4AZ6.js +490 -0
  70. package/dist/chunk-YQR6E6OV.js +109 -0
  71. package/dist/{chunk-XCK2YAY5.js → chunk-YVZJ6GKF.js} +3 -2
  72. package/dist/{chunk-2MNPHL23.js → chunk-ZHAUSEEN.js} +38 -15
  73. package/dist/claude-launcher-FPPHZPMR.js +115 -0
  74. package/dist/{cli-A76AXELQ.js → cli-HROW5T7X.js} +567 -285
  75. package/dist/{command-R2QCVX55.js → command-OEAV7BVX.js} +42 -40
  76. package/dist/{config-N3C6DFQA.js → config-CLSGABD5.js} +2 -3
  77. package/dist/{connect-cmd-JBKAQNSF.js → connect-cmd-UNYKGDAL.js} +10 -9
  78. package/dist/context-cleanup-HSVYDGMI.js +9 -0
  79. package/dist/control-plane-launch-NYVDJESK.js +216 -0
  80. package/dist/{dist-LKDWQWZ7.js → dist-WO7NOJNG.js} +2 -2
  81. package/dist/{docker-4J77Q2BF.js → docker-BFU3AQGY.js} +7 -8
  82. package/dist/{doctor-4TAIARIG.js → doctor-23BBJWBX.js} +48 -42
  83. package/dist/{emit-artifact-cmd-MPZVR56N.js → emit-artifact-cmd-O6SCM7TV.js} +27 -24
  84. package/dist/emit-cmd-7SP3I6UZ.js +14 -0
  85. package/dist/{emit-criteria-cmd-EFGYI23V.js → emit-criteria-cmd-LQNERZRV.js} +17 -16
  86. package/dist/{emit-finding-cmd-5GF3V4LR.js → emit-finding-cmd-AJPKHIOV.js} +18 -17
  87. package/dist/{emit-result-cmd-I4OADKMD.js → emit-result-cmd-RUICOKWQ.js} +22 -19
  88. package/dist/emit-step-cmd-347XNGPQ.js +26 -0
  89. package/dist/{exec-client-CQV746UF.js → exec-client-7UMESIE2.js} +3 -4
  90. package/dist/governing-source-V5EEGPTA.js +34 -0
  91. package/dist/guest-init-KPBQOFE5.js +9 -0
  92. package/dist/{hook-fast-R4FVSZ2H.js → hook-fast-YAZG5MDX.js} +5 -2
  93. package/dist/host-key-U4B5QETR.js +8 -0
  94. package/dist/{import-capture-cmd-EQ35EARA.js → import-capture-cmd-KKFFOGWP.js} +7 -5
  95. package/dist/{import-ruler-cmd-BGAZXCQM.js → import-ruler-cmd-5P6JOXQN.js} +5 -3
  96. package/dist/index.js +8 -4
  97. package/dist/{init-PWU5YVL7.js → init-LWHDMZQG.js} +40 -38
  98. package/dist/init-MOINY2DD.js +53 -0
  99. package/dist/{init-cmd-XAVO7HYZ.js → init-cmd-HSMRID6A.js} +75 -30
  100. package/dist/{init-update-CVCWD6VS.js → init-update-LWX5MPPR.js} +29 -29
  101. package/dist/{install-HDBIPHTX.js → install-4SEFIIW2.js} +4 -5
  102. package/dist/{install-cmd-5HYMXLEW.js → install-cmd-QDOTR4OM.js} +5 -4
  103. package/dist/{login-browser-QZCS3WW6.js → login-browser-DX35MARZ.js} +5 -4
  104. package/dist/{logout-cmd-QAUFDJ6J.js → logout-cmd-5V5MMBSY.js} +3 -2
  105. package/dist/{logs-H55NWW4H.js → logs-AMLKC3VP.js} +3 -1
  106. package/dist/loop-SYYPVFYW.js +77 -0
  107. package/dist/machine-6X63G2KH.js +48 -0
  108. package/dist/{mcp-install-cmd-FDTPQQS6.js → mcp-install-cmd-GGWB3JJ2.js} +3 -2
  109. package/dist/{mcp-serve-cmd-3KVYEUXG.js → mcp-serve-cmd-N3UTY6EE.js} +11 -9
  110. package/dist/paths-7XP63OAX.js +7 -0
  111. package/dist/pending-532ABZYC.js +8 -0
  112. package/dist/{pidfile-UZRH774M.js → pidfile-3DM4OBKO.js} +4 -2
  113. package/dist/{record-C43U4PKS.js → record-3WP6V2K5.js} +3 -2
  114. package/dist/{runner-env-EN7YE7QX.js → runner-env-FRZFFHDF.js} +5 -6
  115. package/dist/{runner-key-6SLBI7PP.js → runner-key-276SJU5R.js} +40 -38
  116. package/dist/session-sender-GGIY5OAX.js +36 -0
  117. package/dist/{pending-MXLSRYMO.js → shell-function-NRD4KVUK.js} +1 -2
  118. package/dist/spec-cmd-SIZBRN7D.js +115 -0
  119. package/dist/{starter-pack-KF66HGZR.js → starter-pack-6LQVLBIA.js} +7 -5
  120. package/dist/{status-7VNQN5PW.js → status-NAK6DYSF.js} +46 -42
  121. package/dist/{statusline-fast-SVTMBMD7.js → statusline-fast-KN3BX67H.js} +2 -1
  122. package/dist/sync-cmd-G7GF4UTW.js +35 -0
  123. package/dist/{watch-23HSAEI6.js → watch-EVM5GSMP.js} +22 -15
  124. package/dist/{work-claim-cmd-LKL3GHFQ.js → work-claim-cmd-3J3QW56U.js} +26 -29
  125. package/dist/work-cmd-5EPPD33Y.js +24 -0
  126. package/dist/{work-comment-cmd-AWHP56M2.js → work-comment-cmd-2ACOZQAE.js} +16 -15
  127. package/dist/{work-launch-R5HTAP6X.js → work-launch-LEQ2MPM3.js} +3 -1
  128. package/dist/work-logs-cmd-QDD2NMJL.js +60 -0
  129. package/dist/{work-open-pr-cmd-IOM4AVQQ.js → work-open-pr-cmd-XNCEI4ZM.js} +18 -17
  130. package/dist/work-pr-cmd-H4OQR26N.js +25 -0
  131. package/package.json +3 -2
  132. package/skill-pack/maintained/outerlayer-amend/SKILL.md +4 -2
  133. package/skill-pack/maintained/outerlayer-cli/SKILL.md +9 -2
  134. package/skill-pack/maintained/outerlayer-cli/references/find-out-why-a-build-ended.md +76 -0
  135. package/skill-pack/shipped.json +18 -2
  136. package/skill-pack/template/build/SKILL.md +23 -10
  137. package/skill-pack/template/build/references/agent-briefs.md +3 -1
  138. package/skill-pack/template/build/references/criteria-judge.md +27 -0
  139. package/skill-pack/template/build/references/release.md +22 -29
  140. package/skill-pack/template/spec/SKILL.md +35 -10
  141. package/skill-pack/validators/acceptance-criteria.yaml +25 -26
  142. package/dist/agent-setup-T624CWPD.js +0 -27
  143. package/dist/chunk-6Y5RUTHO.js +0 -39
  144. package/dist/chunk-BYT5O5RV.js +0 -61
  145. package/dist/chunk-WTE3MQML.js +0 -133
  146. package/dist/chunk-XCCLFVXM.js +0 -401
  147. package/dist/chunk-YZ5F4PIS.js +0 -797
  148. package/dist/context-adopt-NFHCLVUB.js +0 -108
  149. package/dist/context-materialize-IV2TUUFO.js +0 -22
  150. package/dist/emit-cmd-IJ3GVRFD.js +0 -12
  151. package/dist/guest-init-LJP5GPZF.js +0 -9
  152. package/dist/host-key-YMLXY6QB.js +0 -7
  153. package/dist/init-6XPDRDTC.js +0 -51
  154. package/dist/loop-FK3LPMMZ.js +0 -73
  155. package/dist/machine-SQXEBAM2.js +0 -46
  156. package/dist/paths-OYKMVYJP.js +0 -6
  157. package/dist/session-sender-FO3LCREV.js +0 -29
  158. package/dist/sync-cmd-GVPPFZ3G.js +0 -28
  159. package/dist/work-cmd-Z2WXLZPH.js +0 -23
  160. package/dist/work-pr-cmd-HNEUIEN7.js +0 -24
@@ -1,797 +0,0 @@
1
- #!/usr/bin/env node
2
- import { createRequire } from 'node:module';
3
- import { readPin, pinExists, readBuildHold, readContextManifest, manifestFilesPresent, writeDegraded, writeSourceCache, SOURCE_CACHE_TTL_RETRY_MS, SOURCE_CACHE_TTL_SUCCESS_MS, touchLastCheck, isLastCheckFresh, readDegraded, clearDegraded, writeContextManifest, writePinIfChanged, readSourceCache, isSourceCacheFresh } from './chunk-WTE3MQML.js';
4
- import { removeRetiredSkills } from './chunk-C5Y5ZJJZ.js';
5
- import { appendContextProvenance } from './chunk-ZJSNLVCE.js';
6
- import { defaultFetch } from './chunk-YO4PSLVB.js';
7
- import { withTokenHeader } from './chunk-BYT5O5RV.js';
8
- import { loadMaintainedFiles, writeMaintainedFiles } from './chunk-POJMEEJJ.js';
9
- import { normalizeRepoName, parseGoverns, CONTEXT_SOURCE_CONFIG_PATH, parseOuterlayerConfig, classifyTree, emitTree } from './chunk-OHVJA253.js';
10
- import { resolveGatewayCredentials } from './chunk-PLMTLGYH.js';
11
- import { ensureContextExcludes, ensurePreCommitGuardHook } from './chunk-XCCLFVXM.js';
12
- import { homeFromEnv } from './chunk-VNVZDWO3.js';
13
- import { execFileSync } from 'child_process';
14
- import { lstatSync, existsSync, mkdirSync, writeFileSync, chmodSync, unlinkSync, readdirSync, rmSync, readFileSync, copyFileSync } from 'fs';
15
- import { join, isAbsolute, dirname, relative } from 'path';
16
-
17
- createRequire(import.meta.url);
18
- var CONFIG_PATH = ".outerlayer/config.json";
19
- var CONTENT_KINDS = /* @__PURE__ */ new Set(["skill", "skill-reference"]);
20
- function overlayMaintainedSkills(repoRoot, unsafe) {
21
- const sources = loadMaintainedFiles().filter((file) => !unsafe(file.path));
22
- writeMaintainedFiles(repoRoot, sources);
23
- const paths = sources.map((file) => file.path);
24
- const configAbs = join(repoRoot, CONFIG_PATH);
25
- if (!existsSync(configAbs)) return paths;
26
- const config = parseOuterlayerConfig(readFileSync(configAbs, "utf8"));
27
- if (!config.ok || !config.config) return paths;
28
- const { entries } = classifyTree(sources.map((file) => file.path));
29
- const input = {
30
- entries,
31
- contents: new Map(entries.filter((e) => CONTENT_KINDS.has(e.kind)).map((e) => [e.path, sources.find((f) => f.path === e.path).content])),
32
- assetPaths: [],
33
- targets: config.config.targets
34
- };
35
- const compiled = emitTree(input);
36
- if (compiled.errors.length > 0) return paths;
37
- for (const file of compiled.files) {
38
- if (unsafe(file.path)) continue;
39
- const abs = join(repoRoot, file.path);
40
- mkdirSync(dirname(abs), { recursive: true });
41
- if (!existsSync(abs) || readFileSync(abs, "utf8") !== file.content) writeFileSync(abs, file.content);
42
- paths.push(file.path);
43
- }
44
- for (const copy of compiled.copies) {
45
- if (unsafe(copy.toPath)) continue;
46
- const abs = join(repoRoot, copy.toPath);
47
- mkdirSync(dirname(abs), { recursive: true });
48
- copyFileSync(join(repoRoot, copy.fromSourcePath), abs);
49
- paths.push(copy.toPath);
50
- }
51
- return paths;
52
- }
53
-
54
- // src/context-materialize.ts
55
- var defaultGitRunner = (args, opts) => {
56
- try {
57
- const stdout = execFileSync("git", args, {
58
- cwd: opts.cwd,
59
- timeout: opts.timeoutMs,
60
- input: opts.input,
61
- // Control-plane trees are small config/doc surfaces (AGENTS.md,
62
- // .claude/**, .outerlayer/**) — generous but bounded so a runaway
63
- // cat-file stream can't exhaust memory.
64
- maxBuffer: 64 * 1024 * 1024,
65
- stdio: ["pipe", "pipe", "pipe"],
66
- env: (opts.env ?? ((base) => base))({
67
- ...process.env,
68
- // classifyGitFailure greps English stderr substrings
69
- // ("Authentication failed", "Permission denied", …) — a git running
70
- // under a non-English locale would translate those and silently
71
- // misclassify every failure as "unreachable" instead of "no-access".
72
- LC_ALL: "C",
73
- // A bare https:// remote with no cached credential answer otherwise
74
- // blocks on an interactive username/password prompt with no
75
- // terminal to read from here, hanging until OUR OWN timeout instead
76
- // of failing fast with a classifiable stderr message.
77
- GIT_TERMINAL_PROMPT: "0"
78
- })
79
- });
80
- return { ok: true, stdout: Buffer.isBuffer(stdout) ? stdout : Buffer.from(stdout), stderr: "" };
81
- } catch (err) {
82
- const e = err;
83
- return {
84
- ok: false,
85
- stdout: e.stdout ? Buffer.from(e.stdout) : Buffer.alloc(0),
86
- stderr: e.stderr ? Buffer.from(e.stderr).toString("utf8") : e.message ?? String(err)
87
- };
88
- }
89
- };
90
- function gitText(git, cwd, args, timeoutMs) {
91
- const result = git(args, { cwd, timeoutMs });
92
- return result.ok ? result.stdout.toString("utf8").trim() : void 0;
93
- }
94
- var NOT_GOVERNED_MESSAGE = "this repository's control plane no longer governs it";
95
- var ADOPTION_MESSAGE = "committed context files shadow the control plane \u2014 remove them in a commit";
96
- var CANNOT_IDENTIFY_MESSAGE = "this repository is governed, but the control plane cannot be identified \u2014 check OuterLayer connectivity/config";
97
- function designationAuthRejectedMessage(credential) {
98
- return credential.startsWith("olu_") ? "the OuterLayer login was rejected (401/403) \u2014 run `outerlayer login` to sign in again" : "the OuterLayer API key was rejected (401/403) \u2014 check apiKey in ~/.outerlayer/config.json";
99
- }
100
- var PARTIAL_WRITE_MESSAGE = "control-plane content could not be fully written \u2014 running on previous context";
101
- var OVERLAY_FAILED_MESSAGE = "control-plane content was written but the maintained skills could not be refreshed \u2014 retrying next session";
102
- var COMMIT_GUARD_FOREIGN_NOT_INSTALLED_MESSAGE = "the materialized-context commit guard is NOT installed \u2014 a pre-commit hook this clone did not write occupies that slot \u2014 resolve the conflict or run `outerlayer hooks install-git`";
103
- var COMMIT_GUARD_HOOKSPATH_NOT_INSTALLED_MESSAGE = "the materialized-context commit guard is NOT installed \u2014 hooks are managed via core.hooksPath, which this clone never writes to automatically \u2014 adopt it yourself: `outerlayer hooks install-git --print`";
104
- function guardNotInstalledMessage(guard) {
105
- if (guard.adopted) return void 0;
106
- if (guard.status === "skipped-foreign") return COMMIT_GUARD_FOREIGN_NOT_INSTALLED_MESSAGE;
107
- if (guard.status === "skipped-custom-hooks-path") return COMMIT_GUARD_HOOKSPATH_NOT_INSTALLED_MESSAGE;
108
- return void 0;
109
- }
110
- var FAILURE_DEBOUNCE_MS = 2 * 60 * 1e3;
111
- function hasGovernedEvidence(hasPin, manifest) {
112
- return hasPin || manifest !== void 0;
113
- }
114
- function hasSymlinkComponent(root, relPath) {
115
- let current = root;
116
- for (const segment of relPath.split("/")) {
117
- current = join(current, segment);
118
- let st;
119
- try {
120
- st = lstatSync(current);
121
- } catch {
122
- return false;
123
- }
124
- if (st.isSymbolicLink()) return true;
125
- }
126
- return false;
127
- }
128
- function unreachableMessage(lastGoodSha) {
129
- return lastGoodSha ? `could not verify the control plane \u2014 running on previously materialized context at \`${lastGoodSha.slice(0, 7)}\` (it may have moved)` : "this repository is governed but its context has never been materialized and the control plane is unreachable \u2014 running without governed context";
130
- }
131
- function noAccessMessage(lastGoodSha) {
132
- const base = "you do not have access to the control plane";
133
- const remedy = " \u2014 check your git credentials (SSH key or token) and org/SSO access, then retry";
134
- return lastGoodSha ? `${base}; running on previously materialized context at \`${lastGoodSha.slice(0, 7)}\`${remedy}` : `${base}; running with repository defaults${remedy}`;
135
- }
136
- function classifyGitFailure(stderr) {
137
- if (/authentication failed|permission denied \(publickey\)|\b403\b|saml sso|access denied/i.test(stderr)) {
138
- return "no-access";
139
- }
140
- return "unreachable";
141
- }
142
- async function materializeContext(deps) {
143
- const home = deps.home ?? homeFromEnv();
144
- const now = deps.now ?? Date.now;
145
- const git = deps.git ?? defaultGitRunner;
146
- const fetchImpl = deps.fetchImpl ?? defaultFetch;
147
- const repoRoot = gitText(git, deps.cwd, ["rev-parse", "--show-toplevel"], 2e3);
148
- if (repoRoot === void 0) {
149
- return { status: "not-a-repo", sha: null, verification: "none" };
150
- }
151
- const gitCommonDir = resolveGitCommonDir(git, repoRoot);
152
- if (gitCommonDir === void 0) {
153
- return { status: "not-a-repo", sha: null, verification: "none" };
154
- }
155
- const pin = readPin(repoRoot);
156
- const hasPin = pin !== void 0 || pinExists(repoRoot);
157
- const record = (sha2, verification, repository2, ref2) => {
158
- appendContextProvenance(
159
- { t: new Date(now()).toISOString(), sessionId: deps.sessionId, repoRoot, repository: repository2, ref: ref2, sha: sha2, verification },
160
- home
161
- );
162
- };
163
- const selfOutcome = () => {
164
- if (hasPin) {
165
- writeDegraded(gitCommonDir, { at: new Date(now()).toISOString(), kind: "not-governed", message: NOT_GOVERNED_MESSAGE });
166
- return { status: "not-governed", message: NOT_GOVERNED_MESSAGE, sha: null, verification: "none" };
167
- }
168
- clearDegraded(gitCommonDir);
169
- return { status: "self", sha: null, verification: "none" };
170
- };
171
- const hold = readBuildHold(gitCommonDir);
172
- if (hold !== void 0) {
173
- const heldManifest = readContextManifest(gitCommonDir);
174
- if (heldManifest !== void 0 && heldManifest.sha === hold.sha && pin?.sha === hold.sha && manifestFilesPresent(repoRoot, heldManifest)) {
175
- ensureContextExcludes(gitCommonDir);
176
- recordMaintainedOverlay(repoRoot, gitCommonDir, heldManifest);
177
- const guardMessage2 = guardNotInstalledMessage(ensurePreCommitGuardHook(repoRoot, gitCommonDir));
178
- record(hold.sha, "verified", hold.repository, hold.ref);
179
- return {
180
- status: "debounced",
181
- sha: hold.sha,
182
- verification: "verified",
183
- ...guardMessage2 ? { message: guardMessage2 } : {}
184
- };
185
- }
186
- }
187
- const ownRepoName = deriveOwnRepoName(git, repoRoot);
188
- if (ownRepoName === void 0) {
189
- return selfOutcome();
190
- }
191
- const designation = await resolveDesignation({ git, fetchImpl, home, cloud: deps.cloud, now, gitCommonDir, ownRepoName, repoRoot, lookup: deps.designationLookup ?? { timeoutMs: 2e3, attempts: 1 } });
192
- const answered = { designation: "answered" };
193
- if (designation.status === "self") {
194
- return { ...selfOutcome(), ...answered };
195
- }
196
- if (designation.status === "unknown") {
197
- const message = designation.authRejected ?? CANNOT_IDENTIFY_MESSAGE;
198
- if (hasPin) {
199
- writeDegraded(gitCommonDir, { at: new Date(now()).toISOString(), kind: "designation-unverified", message });
200
- record(null, "none");
201
- return { status: "designation-unverified", message, sha: null, verification: "none", designation: "unknown" };
202
- }
203
- if (designation.authRejected) {
204
- writeDegraded(gitCommonDir, { at: new Date(now()).toISOString(), kind: "designation-unverified", message });
205
- }
206
- writeSourceCache(gitCommonDir, { checkedAt: new Date(now()).toISOString(), status: "self", ttlMs: SOURCE_CACHE_TTL_RETRY_MS });
207
- return { status: "self", sha: null, verification: "none", designation: "unknown", ...designation.authRejected ? { authRejected: true } : {}, lookupFailure: designation.lookupFailure };
208
- }
209
- let repository;
210
- let ref;
211
- let unverifiedIdentity;
212
- let storedSha;
213
- let preFetched;
214
- let unreadableCandidates = [];
215
- if (designation.status === "candidates") {
216
- const sortedCandidates = [...designation.candidates].sort((a, b) => a.repository.localeCompare(b.repository));
217
- const claimants = [];
218
- const unfetched = [];
219
- const access = await deps.controlPlaneAccess?.(sortedCandidates);
220
- for (const candidate of sortedCandidates) {
221
- const candidateUrl = deps.resolveControlPlaneUrl ? deps.resolveControlPlaneUrl(candidate.repository, candidate.ref, repoRoot) : buildControlPlaneUrl(git, repoRoot, candidate.repository);
222
- const candidateHost = parseRemoteHost(candidateUrl).host || "unknown-host";
223
- const candidateCacheDir = ensureBareCache(git, home, candidate.repository, candidateHost);
224
- const candidateLocalRef = `refs/outerlayer/${sanitizeRefName(candidate.ref)}`;
225
- const token = access !== void 0 && normalizeRepoName(access.repository) === normalizeRepoName(candidate.repository) ? access.token : void 0;
226
- const candidateFetch = git(["fetch", "--depth", "1", candidateUrl, `+${candidate.ref}:${candidateLocalRef}`], {
227
- cwd: candidateCacheDir,
228
- timeoutMs: 1e4,
229
- ...token === void 0 ? {} : { env: (base) => withTokenHeader(base, candidateUrl, token) }
230
- });
231
- if (!candidateFetch.ok) {
232
- unfetched.push(candidate.repository);
233
- continue;
234
- }
235
- const candidateSha = gitText(git, candidateCacheDir, ["rev-parse", candidateLocalRef], 2e3);
236
- if (candidateSha === void 0) {
237
- unfetched.push(candidate.repository);
238
- continue;
239
- }
240
- const candidateConfig = readBlobAtPath(git, candidateCacheDir, candidateSha, CONTEXT_SOURCE_CONFIG_PATH);
241
- const candidateGoverns = candidateConfig === void 0 ? { governs: [] } : parseGoverns(candidateConfig);
242
- const candidateGovernsList = "error" in candidateGoverns ? [] : candidateGoverns.governs;
243
- if (candidateGovernsList.some((r) => normalizeRepoName(r) === normalizeRepoName(ownRepoName))) {
244
- claimants.push({ repository: candidate.repository, ref: candidate.ref, sha: candidateSha, cacheRepoDir: candidateCacheDir });
245
- }
246
- }
247
- if (claimants.length === 0) {
248
- writeSourceCache(gitCommonDir, { checkedAt: new Date(now()).toISOString(), status: "self", ttlMs: SOURCE_CACHE_TTL_SUCCESS_MS });
249
- return { ...selfOutcome(), ...answered, ...unfetched.length > 0 ? { unfetched } : {} };
250
- }
251
- if (claimants.length > 1) {
252
- const names = claimants.map((c) => c.repository).sort();
253
- const message = `${names.join(" and ")} both claim to govern this repository \u2014 resolve the designation before context can materialize`;
254
- writeDegraded(gitCommonDir, { at: new Date(now()).toISOString(), kind: "conflict", message });
255
- const manifestForConflict = readContextManifest(gitCommonDir);
256
- const lastGood = manifestForConflict?.sha ?? null;
257
- const verification = manifestForConflict ? "unverified" : "none";
258
- record(lastGood, verification);
259
- return { status: "conflict", message, sha: lastGood, verification };
260
- }
261
- unreadableCandidates = unfetched;
262
- const winner = claimants[0];
263
- writeSourceCache(gitCommonDir, {
264
- checkedAt: new Date(now()).toISOString(),
265
- status: "governed",
266
- repository: winner.repository,
267
- ref: winner.ref,
268
- ttlMs: SOURCE_CACHE_TTL_SUCCESS_MS
269
- });
270
- repository = winner.repository;
271
- ref = winner.ref;
272
- preFetched = { sha: winner.sha, cacheRepoDir: winner.cacheRepoDir };
273
- } else {
274
- repository = designation.repository;
275
- ref = designation.ref;
276
- unverifiedIdentity = designation.unverifiedIdentity;
277
- storedSha = designation.sha;
278
- }
279
- const manifest = readContextManifest(gitCommonDir);
280
- const evidenced = hasGovernedEvidence(hasPin, manifest) || storedSha !== void 0 && !unverifiedIdentity;
281
- const clearOrFlagIdentity = () => {
282
- if (unverifiedIdentity) {
283
- writeDegraded(gitCommonDir, {
284
- at: new Date(now()).toISOString(),
285
- kind: "designation-unverified",
286
- message: CANNOT_IDENTIFY_MESSAGE,
287
- repository,
288
- ref
289
- });
290
- } else {
291
- clearDegraded(gitCommonDir);
292
- }
293
- };
294
- let sha;
295
- let cacheRepoDir;
296
- if (preFetched) {
297
- sha = preFetched.sha;
298
- cacheRepoDir = preFetched.cacheRepoDir;
299
- } else {
300
- if (storedSha !== void 0 && manifest !== void 0 && manifest.sha === storedSha && pin?.sha === storedSha && manifestFilesPresent(repoRoot, manifest)) {
301
- touchLastCheck(gitCommonDir, now());
302
- clearOrFlagIdentity();
303
- ensureContextExcludes(gitCommonDir);
304
- recordMaintainedOverlay(repoRoot, gitCommonDir, manifest);
305
- const guardMessage2 = guardNotInstalledMessage(ensurePreCommitGuardHook(repoRoot, gitCommonDir));
306
- writeSourceCache(gitCommonDir, {
307
- checkedAt: new Date(now()).toISOString(),
308
- status: "governed",
309
- repository,
310
- ref,
311
- sha: storedSha,
312
- ttlMs: SOURCE_CACHE_TTL_SUCCESS_MS
313
- });
314
- const verification = unverifiedIdentity ? "unverified" : "verified";
315
- record(storedSha, verification, repository, ref);
316
- return {
317
- status: "debounced",
318
- sha: storedSha,
319
- verification,
320
- ...guardMessage2 ? { message: guardMessage2 } : {}
321
- };
322
- }
323
- if (storedSha === void 0 && isLastCheckFresh(gitCommonDir, now()) && manifest !== void 0 && pin?.sha === manifest.sha && manifestFilesPresent(repoRoot, manifest)) {
324
- const degraded = readDegraded(gitCommonDir);
325
- const verification = degraded ? "unverified" : "verified";
326
- ensureContextExcludes(gitCommonDir);
327
- recordMaintainedOverlay(repoRoot, gitCommonDir, manifest);
328
- const guardMessage2 = guardNotInstalledMessage(ensurePreCommitGuardHook(repoRoot, gitCommonDir));
329
- record(manifest.sha, verification, repository, ref);
330
- return {
331
- status: "debounced",
332
- sha: manifest.sha,
333
- verification,
334
- ...guardMessage2 ? { message: guardMessage2 } : {}
335
- };
336
- }
337
- const recentFailure = readDegraded(gitCommonDir);
338
- if (recentFailure && (recentFailure.kind === "unreachable" || recentFailure.kind === "no-access") && recentFailure.repository === repository && recentFailure.ref === ref && now() - Date.parse(recentFailure.at) < FAILURE_DEBOUNCE_MS) {
339
- const lastGood = manifest?.sha ?? null;
340
- const verification = lastGood ? "unverified" : "none";
341
- if (evidenced) {
342
- record(lastGood, verification, repository, ref);
343
- return { status: recentFailure.kind, message: recentFailure.message, sha: lastGood, verification };
344
- }
345
- return { status: recentFailure.kind, sha: null, verification: "none" };
346
- }
347
- const remoteUrl = deps.resolveControlPlaneUrl ? deps.resolveControlPlaneUrl(repository, ref, repoRoot) : buildControlPlaneUrl(git, repoRoot, repository);
348
- const cacheHost = parseRemoteHost(remoteUrl).host || "unknown-host";
349
- const lsRemote = storedSha === void 0 ? git(["ls-remote", remoteUrl, ref], { cwd: repoRoot, timeoutMs: 2e3 }) : void 0;
350
- if (lsRemote?.ok) {
351
- const remoteSha = parseLsRemoteSha(lsRemote.stdout.toString("utf8"));
352
- if (remoteSha && manifest !== void 0 && manifest.sha === remoteSha && pin?.sha === remoteSha && manifestFilesPresent(repoRoot, manifest)) {
353
- touchLastCheck(gitCommonDir, now());
354
- clearOrFlagIdentity();
355
- ensureContextExcludes(gitCommonDir);
356
- recordMaintainedOverlay(repoRoot, gitCommonDir, manifest);
357
- const guardMessage2 = guardNotInstalledMessage(ensurePreCommitGuardHook(repoRoot, gitCommonDir));
358
- writeSourceCache(gitCommonDir, { checkedAt: new Date(now()).toISOString(), status: "governed", repository, ref, ttlMs: SOURCE_CACHE_TTL_SUCCESS_MS });
359
- record(remoteSha, "verified", repository, ref);
360
- return {
361
- status: "debounced",
362
- sha: remoteSha,
363
- verification: "verified",
364
- ...guardMessage2 ? { message: guardMessage2 } : {}
365
- };
366
- }
367
- }
368
- cacheRepoDir = ensureBareCache(git, home, repository, cacheHost);
369
- const localRef = storedSha === void 0 ? `refs/outerlayer/${sanitizeRefName(ref)}` : `refs/outerlayer/sha-${sanitizeRefName(storedSha)}`;
370
- const wanted = storedSha === void 0 ? `+${ref}:${localRef}` : `+${storedSha}:${localRef}`;
371
- const access = storedSha === void 0 ? void 0 : await deps.controlPlaneAccess?.([{ repository, ref }]);
372
- const token = access !== void 0 && normalizeRepoName(access.repository) === normalizeRepoName(repository) ? access.token : void 0;
373
- const fetchResult = git(["fetch", "--depth", "1", remoteUrl, wanted], {
374
- cwd: cacheRepoDir,
375
- timeoutMs: 1e4,
376
- ...token === void 0 ? {} : { env: (base) => withTokenHeader(base, remoteUrl, token) }
377
- });
378
- const commitNote = (message) => storedSha === void 0 ? message : `could not fetch commit \`${storedSha.slice(0, 7)}\` of ${repository}; ${message}`;
379
- if (!fetchResult.ok) {
380
- const kind = classifyGitFailure(fetchResult.stderr);
381
- const lastGood = manifest?.sha ?? null;
382
- const verification = lastGood ? "unverified" : "none";
383
- const doctorMessage = commitNote(kind === "no-access" ? noAccessMessage(lastGood) : unreachableMessage(lastGood));
384
- writeDegraded(gitCommonDir, { at: new Date(now()).toISOString(), kind, message: doctorMessage, repository, ref, lastGoodSha: lastGood ?? void 0 });
385
- if (evidenced) {
386
- record(lastGood, verification, repository, ref);
387
- return { status: kind, message: doctorMessage, sha: lastGood, verification };
388
- }
389
- return { status: kind, sha: null, verification: "none" };
390
- }
391
- const fetchedSha = gitText(git, cacheRepoDir, ["rev-parse", localRef], 2e3);
392
- if (fetchedSha === void 0 || storedSha !== void 0 && fetchedSha !== storedSha) {
393
- const lastGood = manifest?.sha ?? null;
394
- const verification = lastGood ? "unverified" : "none";
395
- const doctorMessage = commitNote(unreachableMessage(lastGood));
396
- writeDegraded(gitCommonDir, { at: new Date(now()).toISOString(), kind: "unreachable", message: doctorMessage, repository, ref, lastGoodSha: lastGood ?? void 0 });
397
- if (evidenced) {
398
- record(lastGood, verification, repository, ref);
399
- return { status: "unreachable", message: doctorMessage, sha: lastGood, verification };
400
- }
401
- return { status: "unreachable", sha: null, verification: "none" };
402
- }
403
- sha = fetchedSha;
404
- const governed = storedSha !== void 0 || (() => {
405
- const configContent = readBlobAtPath(git, cacheRepoDir, sha, CONTEXT_SOURCE_CONFIG_PATH);
406
- const parsedGoverns = configContent === void 0 ? { governs: [] } : parseGoverns(configContent);
407
- const governsList = "error" in parsedGoverns ? [] : parsedGoverns.governs;
408
- return governsList.some((r) => normalizeRepoName(r) === normalizeRepoName(ownRepoName));
409
- })();
410
- if (!governed) {
411
- writeSourceCache(gitCommonDir, { checkedAt: new Date(now()).toISOString(), status: "self", ttlMs: SOURCE_CACHE_TTL_SUCCESS_MS });
412
- if (hasPin) {
413
- writeDegraded(gitCommonDir, { at: new Date(now()).toISOString(), kind: "not-governed", message: NOT_GOVERNED_MESSAGE });
414
- return { status: "not-governed", message: NOT_GOVERNED_MESSAGE, sha: pin?.sha ?? null, verification: "none" };
415
- }
416
- clearDegraded(gitCommonDir);
417
- return { status: "self", sha: null, verification: "none" };
418
- }
419
- }
420
- const tracked = git(["ls-files", "-z", "--", "AGENTS.md", ".claude", ".outerlayer"], { cwd: repoRoot, timeoutMs: 2e3 });
421
- if (tracked.ok && tracked.stdout.length > 0) {
422
- writeDegraded(gitCommonDir, { at: new Date(now()).toISOString(), kind: "adoption-conflict", message: ADOPTION_MESSAGE, repository, ref });
423
- const lastGood = manifest?.sha ?? null;
424
- record(lastGood, lastGood ? "unverified" : "none", repository, ref);
425
- return { status: "adoption-conflict", message: ADOPTION_MESSAGE, sha: lastGood, verification: lastGood ? "unverified" : "none" };
426
- }
427
- const files = listMaterializedFiles(git, cacheRepoDir, sha);
428
- const writeResult = writeFilesVerbatim(git, cacheRepoDir, files, repoRoot);
429
- const partialWrite = () => {
430
- const lastGood = manifest?.sha ?? null;
431
- const verification = lastGood ? "unverified" : "none";
432
- writeDegraded(gitCommonDir, {
433
- at: new Date(now()).toISOString(),
434
- kind: "unreachable",
435
- message: PARTIAL_WRITE_MESSAGE,
436
- repository,
437
- ref,
438
- lastGoodSha: lastGood ?? void 0
439
- });
440
- if (!evidenced) return { status: "unreachable", sha: null, verification: "none" };
441
- record(lastGood, verification, repository, ref);
442
- return { status: "unreachable", message: PARTIAL_WRITE_MESSAGE, sha: lastGood, verification };
443
- };
444
- if (!writeResult.ok) {
445
- return partialWrite();
446
- }
447
- let overlayPaths = [];
448
- let retiredPaths = /* @__PURE__ */ new Set();
449
- let overlayFailed = false;
450
- try {
451
- retiredPaths = new Set(removeRetiredSkills(repoRoot, (rel) => hasSymlinkComponent(repoRoot, rel)));
452
- overlayPaths = overlayMaintainedSkills(repoRoot, (rel) => hasSymlinkComponent(repoRoot, rel));
453
- } catch {
454
- overlayFailed = true;
455
- }
456
- const writtenPaths = [.../* @__PURE__ */ new Set([...writeResult.writtenPaths, ...overlayPaths])].filter((path) => !retiredPaths.has(path));
457
- const deleteResult = manifest ? deleteRemovedFiles(repoRoot, manifest.files, writtenPaths) : { skipped: [] };
458
- if (overlayFailed && deleteResult.skipped.length === 0) {
459
- writeContextManifest(gitCommonDir, { schemaVersion: 1, sha, files: writtenPaths, materializedAt: new Date(now()).toISOString() });
460
- writeDegraded(gitCommonDir, {
461
- at: new Date(now()).toISOString(),
462
- kind: "unreachable",
463
- message: OVERLAY_FAILED_MESSAGE,
464
- repository,
465
- ref,
466
- lastGoodSha: sha
467
- });
468
- if (!evidenced) return { status: "unreachable", sha: null, verification: "none" };
469
- record(sha, "unverified", repository, ref);
470
- return { status: "unreachable", message: OVERLAY_FAILED_MESSAGE, sha, verification: "unverified" };
471
- }
472
- if (deleteResult.skipped.length > 0) {
473
- return partialWrite();
474
- }
475
- const newManifest = {
476
- schemaVersion: 1,
477
- sha,
478
- files: writtenPaths,
479
- materializedAt: new Date(now()).toISOString()
480
- };
481
- writeContextManifest(gitCommonDir, newManifest);
482
- ensureContextExcludes(gitCommonDir);
483
- const guardMessage = guardNotInstalledMessage(ensurePreCommitGuardHook(repoRoot, gitCommonDir));
484
- writePinIfChanged(repoRoot, sha);
485
- touchLastCheck(gitCommonDir, now());
486
- clearOrFlagIdentity();
487
- writeSourceCache(gitCommonDir, {
488
- checkedAt: new Date(now()).toISOString(),
489
- status: "governed",
490
- repository,
491
- ref,
492
- ...storedSha === void 0 ? {} : { sha: storedSha },
493
- ttlMs: SOURCE_CACHE_TTL_SUCCESS_MS
494
- });
495
- record(sha, "verified", repository, ref);
496
- return {
497
- status: "materialized",
498
- sha,
499
- verification: "verified",
500
- ...guardMessage ? { message: guardMessage } : {},
501
- ...unreadableCandidates.length > 0 ? { unfetched: unreadableCandidates } : {}
502
- };
503
- }
504
- function recordMaintainedOverlay(repoRoot, gitCommonDir, manifest) {
505
- let overlayPaths;
506
- let retired;
507
- try {
508
- retired = new Set(removeRetiredSkills(repoRoot, (rel) => hasSymlinkComponent(repoRoot, rel)));
509
- overlayPaths = overlayMaintainedSkills(repoRoot, (rel) => hasSymlinkComponent(repoRoot, rel));
510
- } catch {
511
- return;
512
- }
513
- const known = new Set(manifest.files);
514
- const added = overlayPaths.filter((path) => !known.has(path));
515
- if (added.length > 0 || retired.size > 0) {
516
- writeContextManifest(gitCommonDir, { ...manifest, files: [...manifest.files.filter((path) => !retired.has(path)), ...added] });
517
- }
518
- }
519
- async function resolveDesignation(ctx) {
520
- const cache = readSourceCache(ctx.gitCommonDir);
521
- if (cache && isSourceCacheFresh(cache, ctx.now()) && !(cache.status === "governed" && cache.sha !== void 0)) {
522
- return cache.status === "governed" && cache.repository && cache.ref ? { status: "governed", repository: cache.repository, ref: cache.ref } : { status: "self" };
523
- }
524
- let authRejected;
525
- let lookupFailure;
526
- const destination = resolveGatewayCredentials({
527
- home: ctx.home,
528
- env: {},
529
- cwd: ctx.repoRoot,
530
- ...ctx.cloud === void 0 ? {} : { url: ctx.cloud.url, apiKey: ctx.cloud.apiKey, appId: ctx.cloud.appId }
531
- });
532
- if (destination.ok) {
533
- const lookup = await fetchContextSource(
534
- { url: destination.url, apiKey: destination.credential, appId: destination.appId },
535
- ctx.ownRepoName,
536
- ctx.fetchImpl,
537
- ctx.lookup
538
- );
539
- if (lookup.kind === "rejected") authRejected = designationAuthRejectedMessage(destination.credential);
540
- if (lookup.kind === "failed") lookupFailure = lookup.failure;
541
- if (lookup.kind === "answered") {
542
- if (lookup.source !== void 0) {
543
- if (lookup.source === null) {
544
- writeSourceCache(ctx.gitCommonDir, {
545
- checkedAt: new Date(ctx.now()).toISOString(),
546
- status: "self",
547
- ttlMs: SOURCE_CACHE_TTL_SUCCESS_MS
548
- });
549
- return { status: "self" };
550
- }
551
- return { status: "governed", repository: lookup.source.repository, ref: lookup.source.ref, sha: lookup.source.sha };
552
- }
553
- const sources = lookup.sources ?? [];
554
- if (sources.length === 0) {
555
- writeSourceCache(ctx.gitCommonDir, {
556
- checkedAt: new Date(ctx.now()).toISOString(),
557
- status: "self",
558
- ttlMs: SOURCE_CACHE_TTL_SUCCESS_MS
559
- });
560
- return { status: "self" };
561
- }
562
- return { status: "candidates", candidates: sources };
563
- }
564
- }
565
- if (cache) {
566
- return cache.status === "governed" && cache.repository && cache.ref ? {
567
- status: "governed",
568
- repository: cache.repository,
569
- ref: cache.ref,
570
- unverifiedIdentity: true,
571
- ...cache.sha === void 0 ? {} : { sha: cache.sha }
572
- } : { status: "self" };
573
- }
574
- return { status: "unknown", authRejected, lookupFailure };
575
- }
576
- async function fetchContextSource(config, ownRepoName, fetchImpl, lookup) {
577
- const url = `${config.url.replace(/\/$/, "")}/v1/context/source?repo=${encodeURIComponent(ownRepoName)}`;
578
- for (let attempt = 1; ; attempt++) {
579
- const outcome = await askContextSource(url, config, fetchImpl, lookup.timeoutMs);
580
- if (outcome.kind !== "failed" || !isRetryable(outcome.failure) || attempt >= lookup.attempts) return outcome;
581
- }
582
- }
583
- function isRetryable(failure) {
584
- return failure.kind !== "status" || failure.status >= 500;
585
- }
586
- async function askContextSource(url, config, fetchImpl, timeoutMs) {
587
- const controller = new AbortController();
588
- let timedOut = false;
589
- const timer = setTimeout(() => {
590
- timedOut = true;
591
- controller.abort();
592
- }, timeoutMs);
593
- try {
594
- const res = await fetchImpl(url, {
595
- headers: { Authorization: `Bearer ${config.apiKey}`, "x-outerlayer-app-id": config.appId },
596
- signal: controller.signal
597
- });
598
- if (res.status === 401 || res.status === 403) {
599
- return { kind: "rejected" };
600
- }
601
- if (!res.ok) return { kind: "failed", failure: { kind: "status", status: res.status } };
602
- const body = await res.json();
603
- const data = body.data;
604
- const source = validSource(data?.source);
605
- if (data === void 0 || data === null || source === void 0 && !Array.isArray(data.sources)) {
606
- return { kind: "failed", failure: { kind: "network", message: "the answer named no sources" } };
607
- }
608
- return { kind: "answered", source, sources: data.sources };
609
- } catch (err) {
610
- return {
611
- kind: "failed",
612
- failure: timedOut ? { kind: "timeout", timeoutMs } : { kind: "network", message: err instanceof Error ? err.message : String(err) }
613
- };
614
- } finally {
615
- clearTimeout(timer);
616
- }
617
- }
618
- function validSource(source) {
619
- if (source === null) return null;
620
- const candidate = source;
621
- if (typeof candidate?.repository === "string" && typeof candidate.ref === "string" && typeof candidate.sha === "string") {
622
- return { repository: candidate.repository, ref: candidate.ref, sha: candidate.sha };
623
- }
624
- return void 0;
625
- }
626
- function resolveGitCommonDir(git, repoRoot) {
627
- const abs = gitText(git, repoRoot, ["rev-parse", "--path-format=absolute", "--git-common-dir"], 2e3);
628
- if (abs !== void 0) return abs;
629
- const rel = gitText(git, repoRoot, ["rev-parse", "--git-common-dir"], 2e3);
630
- if (rel === void 0) return void 0;
631
- return isAbsolute(rel) ? rel : join(repoRoot, rel);
632
- }
633
- function deriveOwnRepoName(git, repoRoot) {
634
- const url = gitText(git, repoRoot, ["config", "--get", "remote.origin.url"], 2e3);
635
- if (url === void 0) return void 0;
636
- return parseOwnerRepo(url);
637
- }
638
- function parseOwnerRepo(url) {
639
- const stripped = url.replace(/\.git$/, "");
640
- const scpMatch = stripped.match(/^[\w.-]+@[\w.-]+:(.+)$/);
641
- if (scpMatch) return scpMatch[1];
642
- try {
643
- const parsed = new URL(stripped);
644
- return parsed.pathname.replace(/^\//, "");
645
- } catch {
646
- return void 0;
647
- }
648
- }
649
- function parseRemoteHost(url) {
650
- const scpMatch = url.match(/^([\w.-]+)@([\w.-]+):/);
651
- if (scpMatch) return { scheme: "ssh", host: scpMatch[2] };
652
- try {
653
- const parsed = new URL(url);
654
- return { scheme: parsed.protocol === "https:" ? "https" : "ssh", host: parsed.host };
655
- } catch {
656
- return { scheme: "https", host: "github.com" };
657
- }
658
- }
659
- function buildControlPlaneUrl(git, repoRoot, repository) {
660
- const ownUrl = gitText(git, repoRoot, ["config", "--get", "remote.origin.url"], 2e3);
661
- const { scheme, host } = ownUrl ? parseRemoteHost(ownUrl) : { scheme: "https", host: "github.com" };
662
- return scheme === "ssh" ? `git@${host}:${repository}.git` : `https://${host}/${repository}.git`;
663
- }
664
- function cacheDirName(repository, host) {
665
- const parts = repository.split("/");
666
- return [host, ...parts].join("__").replace(/[^a-zA-Z0-9._-]/g, "-");
667
- }
668
- function sanitizeRefName(ref) {
669
- return ref.replace(/[^a-zA-Z0-9._-]/g, "-");
670
- }
671
- function ensureBareCache(git, home, repository, host) {
672
- const dir = join(home, ".outerlayer", "control-planes", `${cacheDirName(repository, host)}.git`);
673
- if (!existsSync(join(dir, "HEAD"))) {
674
- mkdirSync(dirname(dir), { recursive: true });
675
- git(["init", "--bare", "-q", dir], { cwd: dirname(dir), timeoutMs: 5e3 });
676
- }
677
- return dir;
678
- }
679
- function parseLsRemoteSha(output) {
680
- const lines = output.split("\n").filter((l) => l.trim().length > 0);
681
- const peeled = lines.find((l) => l.split(/\s+/)[1]?.endsWith("^{}"));
682
- const line = peeled ?? lines[0];
683
- return line?.split(/\s+/)[0];
684
- }
685
- var MATERIALIZED_ROOTS = ["AGENTS.md", ".claude", ".outerlayer"];
686
- function listMaterializedFiles(git, cacheRepoDir, sha) {
687
- const result = git(["ls-tree", "-r", "-z", sha, "--", ...MATERIALIZED_ROOTS], { cwd: cacheRepoDir, timeoutMs: 1e4 });
688
- if (!result.ok) return [];
689
- const entries = [];
690
- for (const raw of result.stdout.toString("utf8").split("\0")) {
691
- if (!raw) continue;
692
- const tabIdx = raw.indexOf(" ");
693
- if (tabIdx === -1) continue;
694
- const meta = raw.slice(0, tabIdx).split(" ");
695
- const [mode, type, objSha] = meta;
696
- const path = raw.slice(tabIdx + 1);
697
- if (mode === void 0 || objSha === void 0 || path === void 0) continue;
698
- if (mode === "120000") continue;
699
- if (type !== "blob") continue;
700
- if (path.includes("..") || isAbsolute(path)) continue;
701
- entries.push({ mode, type: "blob", sha: objSha, path });
702
- }
703
- return entries;
704
- }
705
- function readBlobAtPath(git, cacheRepoDir, sha, path) {
706
- const result = git(["show", `${sha}:${path}`], { cwd: cacheRepoDir, timeoutMs: 5e3 });
707
- return result.ok ? result.stdout.toString("utf8") : void 0;
708
- }
709
- function parseBatchReply(files, stdout) {
710
- const parsed = [];
711
- let pos = 0;
712
- for (const file of files) {
713
- const headerEnd = stdout.indexOf(10, pos);
714
- if (headerEnd === -1) return void 0;
715
- const header = stdout.subarray(pos, headerEnd).toString("utf8");
716
- const parts = header.split(" ");
717
- if (parts[0] !== file.sha) return void 0;
718
- if (parts[1] === "missing") return void 0;
719
- const size = parts[1] === "blob" && parts[2] !== void 0 ? Number(parts[2]) : NaN;
720
- if (!Number.isFinite(size) || stdout.length < headerEnd + 1 + size + 1) return void 0;
721
- const contentStart = headerEnd + 1;
722
- parsed.push({ path: file.path, content: stdout.subarray(contentStart, contentStart + size), mode: file.mode });
723
- pos = contentStart + size + 1;
724
- }
725
- return parsed;
726
- }
727
- function writeFilesVerbatim(git, cacheRepoDir, files, workpieceRoot) {
728
- if (files.length === 0) return { writtenPaths: [], ok: true };
729
- const input = files.map((f) => f.sha).join("\n") + "\n";
730
- const result = git(["cat-file", "--batch"], { cwd: cacheRepoDir, timeoutMs: 15e3, input });
731
- if (!result.ok) return { writtenPaths: [], ok: false };
732
- const parsed = parseBatchReply(files, result.stdout);
733
- if (parsed === void 0) return { writtenPaths: [], ok: false };
734
- const writtenPaths = [];
735
- let anySkipped = false;
736
- for (const file of parsed) {
737
- if (hasSymlinkComponent(workpieceRoot, file.path)) {
738
- anySkipped = true;
739
- continue;
740
- }
741
- const dest = join(workpieceRoot, file.path);
742
- mkdirSync(dirname(dest), { recursive: true });
743
- const mode = file.mode === "100755" ? 493 : 420;
744
- writeFileSync(dest, file.content, { mode });
745
- chmodSync(dest, mode);
746
- writtenPaths.push(file.path);
747
- }
748
- return { writtenPaths, ok: !anySkipped };
749
- }
750
- function isDeletableManifestPath(rel) {
751
- if (isAbsolute(rel)) return false;
752
- if (rel.split("/").some((segment) => segment === "..")) return false;
753
- return rel === "AGENTS.md" || rel.startsWith(".claude/") || rel.startsWith(".outerlayer/");
754
- }
755
- function deleteRemovedFiles(workpieceRoot, oldFiles, newFiles) {
756
- const keep = new Set(newFiles);
757
- const dirsToPrune = /* @__PURE__ */ new Set();
758
- const skipped = [];
759
- for (const rel of oldFiles) {
760
- if (keep.has(rel)) continue;
761
- if (!isDeletableManifestPath(rel)) continue;
762
- if (hasSymlinkComponent(workpieceRoot, rel)) {
763
- skipped.push(rel);
764
- continue;
765
- }
766
- const abs = join(workpieceRoot, rel);
767
- try {
768
- unlinkSync(abs);
769
- dirsToPrune.add(dirname(abs));
770
- } catch {
771
- }
772
- }
773
- for (const dir of dirsToPrune) {
774
- pruneEmptyDirs(dir, workpieceRoot);
775
- }
776
- return { skipped };
777
- }
778
- function pruneEmptyDirs(dir, stopAt) {
779
- let current = dir;
780
- while (current !== stopAt && current.startsWith(stopAt) && relative(stopAt, current) !== "") {
781
- let entries;
782
- try {
783
- entries = readdirSync(current);
784
- } catch {
785
- return;
786
- }
787
- if (entries.length > 0) return;
788
- try {
789
- rmSync(current, { recursive: false });
790
- } catch {
791
- return;
792
- }
793
- current = dirname(current);
794
- }
795
- }
796
-
797
- export { defaultGitRunner, hasSymlinkComponent, materializeContext, parseOwnerRepo, parseRemoteHost, resolveGitCommonDir };