@sous-io/sous 0.2.35 → 0.2.36

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.35",
3
+ "version": "0.2.36",
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.35
14
+ version: 0.2.36
15
15
 
16
16
  description: >-
17
17
  The skills that teach an agent what sous is and how it works: which files sous
@@ -247,7 +247,9 @@ async function main() {
247
247
  async loadConfigs(globPattern) {
248
248
  const where = `loadConfigs(${JSON.stringify(globPattern)}) in ${currentFile}`;
249
249
  const pattern = resolveBuilderPath(globPattern, where);
250
- const matches = globSync(pattern, { absolute: true })
250
+ // glob reads `\` as an escape, so a Windows path is handed over with `/`
251
+ // separators (see src/lib/glob-pattern.ts; this file stays plain JS).
252
+ const matches = globSync(pattern.split(path.sep).join("/"), { absolute: true })
251
253
  .filter((p) => fs.statSync(p).isFile())
252
254
  .sort(bytewiseCompare);
253
255
  for (const match of matches) {
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Filesystem paths as glob patterns.
3
+ *
4
+ * glob and minimatch read `\` in a pattern as an escape character, never as a
5
+ * path separator, and want `/` on every platform. A pattern built with
6
+ * `path.join` or `path.resolve` on Windows is all backslashes, so it silently
7
+ * matched nothing: a recipe's skills, memories and config layers, an include
8
+ * glob and an `entryGlob` target all came up empty without an error.
9
+ */
10
+
11
+ import path from "node:path";
12
+
13
+ /**
14
+ * Writes a path or a path-shaped glob with `/` separators, so glob reads it the
15
+ * same way on Windows as everywhere else. A no-op where `/` is the separator, so
16
+ * a pattern's own escapes survive on every other platform.
17
+ *
18
+ * toGlobPattern("C:\\recipes\\skills\\**\\*"); // -> "C:/recipes/skills/**\/*" on Windows
19
+ *
20
+ * @param value - A filesystem path, or a glob pattern built from one.
21
+ */
22
+ export function toGlobPattern(value: string): string {
23
+ return path.sep === "\\" ? value.split(path.sep).join("/") : value;
24
+ }
@@ -4,6 +4,7 @@ import { globSync } from "glob";
4
4
  import { minimatch } from "minimatch";
5
5
  import { compareBytewise, hasGlob, type ViewFile } from "../services/ref-resolver/index.js";
6
6
  import { expandHome } from "./config-discovery.js";
7
+ import { toGlobPattern } from "./glob-pattern.js";
7
8
  import type {
8
9
  NamespaceResolution,
9
10
  NamespaceResolver,
@@ -382,7 +383,9 @@ export function resolveInclude(rawPath: string, opts: IncludeResolveOptions): In
382
383
  * @param pattern - An absolute glob pattern.
383
384
  */
384
385
  function expandPattern(pattern: string): string[] {
385
- return globSync(pattern, { absolute: true, nodir: true, dot: true }).sort(compareBytewise);
386
+ return globSync(toGlobPattern(pattern), { absolute: true, nodir: true, dot: true }).sort(
387
+ compareBytewise
388
+ );
386
389
  }
387
390
 
388
391
  /**
@@ -27,6 +27,7 @@ import fs from "node:fs";
27
27
  import path from "node:path";
28
28
  import { globSync } from "glob";
29
29
  import { parse as parseYaml } from "yaml";
30
+ import { toGlobPattern } from "../glob-pattern.js";
30
31
  import { parseJsoncText } from "./load-manifest.js";
31
32
  import { listLockedRecipes, readRecipeManifestIn } from "./locked-recipes.js";
32
33
 
@@ -224,11 +225,11 @@ export function listRecipeConfigLayers(
224
225
  if (content.kind !== "config") continue;
225
226
 
226
227
  const ignore = (content.exclude ?? []).map((pattern) =>
227
- path.join(recipe.dir, pattern)
228
+ toGlobPattern(path.join(recipe.dir, pattern))
228
229
  );
229
230
 
230
231
  for (const include of content.include) {
231
- for (const filePath of globSync(path.join(recipe.dir, include), {
232
+ for (const filePath of globSync(toGlobPattern(path.join(recipe.dir, include)), {
232
233
  absolute: true,
233
234
  ignore,
234
235
  })) {
@@ -22,6 +22,7 @@ import { globSync } from "glob";
22
22
  import { compareBytewise } from "../../services/ref-resolver/index.js";
23
23
  import { realPathInside } from "../include-resolver.js";
24
24
  import { inferGlobBase } from "../markdown-compiler.js";
25
+ import { toGlobPattern } from "../glob-pattern.js";
25
26
  import { compileRecipeKeyMatcher } from "./recipe-key-matcher.js";
26
27
  import {
27
28
  listLockedRecipes,
@@ -137,9 +138,11 @@ export function listMemories(options: MemoryListingOptions): MemoryFile[] {
137
138
  const mine = new Map<string, MemoryFile>();
138
139
  for (const content of manifest.contents) {
139
140
  if (content.kind !== "memories") continue;
140
- const ignore = (content.exclude ?? []).map((pattern) => path.join(recipe.dir, pattern));
141
+ const ignore = (content.exclude ?? []).map((pattern) =>
142
+ toGlobPattern(path.join(recipe.dir, pattern))
143
+ );
141
144
  for (const include of content.include) {
142
- const pattern = path.join(recipe.dir, include);
145
+ const pattern = toGlobPattern(path.join(recipe.dir, include));
143
146
  const base = inferGlobBase(pattern);
144
147
  for (const file of globSync(pattern, { absolute: true, ignore, dot: true })) {
145
148
  if (!isFile(file)) continue;
@@ -30,6 +30,7 @@ import path from "node:path";
30
30
  import { globSync } from "glob";
31
31
  import type { CompilationTarget } from "../markdown-compiler.js";
32
32
  import { inferGlobBase } from "../markdown-compiler.js";
33
+ import { toGlobPattern } from "../glob-pattern.js";
33
34
  import type { Settings, VarScope } from "../settings.js";
34
35
  import { normalizeConfigPath, substituteVarsStrict } from "../settings.js";
35
36
  import type { ContentKind } from "./formats/recipe-manifest.js";
@@ -186,11 +187,11 @@ export function buildRecipeTargets(options: RecipeTargetOptions): RecipeTargets
186
187
  const recipeScope = options.scopeFor?.(recipe) ?? options.scope ?? {};
187
188
 
188
189
  const ignore = (content.exclude ?? []).map((pattern) =>
189
- path.join(recipe.dir, pattern)
190
+ toGlobPattern(path.join(recipe.dir, pattern))
190
191
  );
191
192
 
192
193
  for (const include of content.include) {
193
- const pattern = path.join(recipe.dir, include);
194
+ const pattern = toGlobPattern(path.join(recipe.dir, include));
194
195
  const matched = globSync(pattern, { absolute: true, ignore }).filter(isFile);
195
196
  const globBase = normalizeConfigPath(inferGlobBase(pattern));
196
197
 
@@ -6,6 +6,7 @@ import { fileURLToPath } from "node:url";
6
6
  import { globSync } from "glob";
7
7
  import { inferGlobBase, type CompilationConfig, type CompilationTarget, type ResolvedRuntimeContext } from "./markdown-compiler.js";
8
8
  import { buildAliasMap, resolveAliasPrefix, type AliasMap } from "./include-resolver.js";
9
+ import { toGlobPattern } from "./glob-pattern.js";
9
10
  import {
10
11
  CONFD_DIR_NAME,
11
12
  ENV_DEFAULTS_NAME,
@@ -1010,7 +1011,7 @@ export function resolveCompilation(
1010
1011
  // base; the first base that matches any files wins, mirroring the first-existing-wins
1011
1012
  // rule of @include resolution.
1012
1013
  const rawPattern = substituteVarsStrict(target.entryGlob!, targetScope, `${where}.entryGlob`);
1013
- const patternCandidates = resolveAliasPrefix(rawPattern, aliases);
1014
+ const patternCandidates = resolveAliasPrefix(rawPattern, aliases).map(toGlobPattern);
1014
1015
  let pattern = patternCandidates[0];
1015
1016
  let matchedFiles: string[] = [];
1016
1017
  for (const candidate of patternCandidates) {
@@ -2,6 +2,7 @@ import chokidar from "chokidar";
2
2
  import { minimatch } from "minimatch";
3
3
  import path from "node:path";
4
4
  import type { WatchConfig } from "./settings.js";
5
+ import { toGlobPattern } from "./glob-pattern.js";
5
6
  import { log } from "../utils/formatting.js";
6
7
 
7
8
  /** Milliseconds to wait after a change before triggering a rebuild. */
@@ -98,7 +99,7 @@ export class WatchService {
98
99
  }
99
100
 
100
101
  const isExactFile = files.includes(filePath);
101
- const matchesGlob = globs.some(g => minimatch(filePath, g));
102
+ const matchesGlob = globs.some(g => minimatch(filePath, toGlobPattern(g)));
102
103
 
103
104
  if (isExactFile || matchesGlob) {
104
105
  schedule({ type: "partial", filePath });