@sous-io/sous 0.2.34 → 0.2.35

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sous-io/sous",
3
- "version": "0.2.34",
3
+ "version": "0.2.35",
4
4
  "description": "Compiles AI coding agent configuration (CLAUDE.md, skills, memories) from LiquidJS templates",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -11,7 +11,7 @@ formatVersion: 1
11
11
 
12
12
  namespace: core
13
13
  name: sous-skills
14
- version: 0.2.34
14
+ version: 0.2.35
15
15
 
16
16
  description: >-
17
17
  The skills that teach an agent what sous is and how it works: which files sous
@@ -16,6 +16,7 @@ import fs from "node:fs";
16
16
  import path from "node:path";
17
17
  import { spawnSync } from "node:child_process";
18
18
  import { ConfigError } from "../errors.js";
19
+ import { LF_CHECKOUT_OPTIONS } from "./store/hash.js";
19
20
 
20
21
  /** What one git invocation produced. */
21
22
  export type GitResult = {
@@ -211,15 +212,20 @@ export function cloneRepo(
211
212
  const depth = options.depth ?? 1;
212
213
  const runner = options.runner ?? runGit;
213
214
 
215
+ // A linked checkout is hashed when its recipes are released or compiled, so
216
+ // it keeps the committed bytes whatever this machine's line-ending settings are.
214
217
  if (depth > 0) {
215
- const shallow = runner(["clone", "--depth", String(depth), "--", url, destDir], {});
218
+ const shallow = runner(
219
+ ["clone", ...LF_CHECKOUT_OPTIONS, "--depth", String(depth), "--", url, destDir],
220
+ {}
221
+ );
216
222
  if (shallow.status === 0) return { depth, fellBackToFullClone: false };
217
223
  // git leaves the destination behind on some failures; clear it so the retry
218
224
  // is not refused by its own leftovers.
219
225
  fs.rmSync(destDir, { recursive: true, force: true });
220
226
  }
221
227
 
222
- runGitOrThrow(["clone", "--", url, destDir], {
228
+ runGitOrThrow(["clone", ...LF_CHECKOUT_OPTIONS, "--", url, destDir], {
223
229
  runner: options.runner,
224
230
  what: `Cloning ${url}`,
225
231
  });
@@ -14,6 +14,7 @@ import { spawn } from "node:child_process";
14
14
  import fs from "node:fs/promises";
15
15
  import path from "node:path";
16
16
  import { ConfigError } from "../../errors.js";
17
+ import { LF_CHECKOUT_OPTIONS } from "../store/hash.js";
17
18
 
18
19
  /** What a finished subprocess reports back. */
19
20
  export type CommandResult = {
@@ -174,6 +175,9 @@ export async function fetchSubtree(options: {
174
175
  "1",
175
176
  "--filter=blob:none",
176
177
  "--sparse",
178
+ // The fetched subtree is hashed against its pin, so its bytes must be the
179
+ // committed ones whatever this machine's line-ending settings are.
180
+ ...LF_CHECKOUT_OPTIONS,
177
181
  "--branch",
178
182
  tag,
179
183
  // Everything after this is a path or a URL, never an option, whatever it
@@ -35,6 +35,23 @@ import fs from "node:fs/promises";
35
35
  import path from "node:path";
36
36
  import { STORE_ENTRY_FILENAME } from "../formats/common.js";
37
37
 
38
+ /**
39
+ * `git clone` options that check files out exactly as they were committed.
40
+ *
41
+ * A content hash is over raw bytes, and Git for Windows installs with
42
+ * `core.autocrlf=true` by default, which rewrites every text file to CRLF on
43
+ * checkout. A recipe published with LF then hashed differently on such a
44
+ * machine and failed its own pin. `--config` also writes these into the
45
+ * clone's own `.git/config`, so later checkouts in a linked working copy keep
46
+ * the same bytes.
47
+ */
48
+ export const LF_CHECKOUT_OPTIONS = [
49
+ "--config",
50
+ "core.autocrlf=false",
51
+ "--config",
52
+ "core.eol=lf",
53
+ ] as const;
54
+
38
55
  /** Directory name never included in a content hash. */
39
56
  const GIT_DIR_NAME = ".git";
40
57