@lemoncode/lemony 0.1.1 → 0.1.2

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/catalog/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.1
1
+ 0.1.2
File without changes
File without changes
@@ -324,6 +324,50 @@ glob_to_regex() {
324
324
  GLOB_RE="^${re}$"
325
325
  }
326
326
 
327
+ # Normalize an absolute payload file path to a repo-relative one, assigning the
328
+ # result to the global REPO_RELATIVE_PATH. `applies_to` globs are repo-relative
329
+ # (what the frontmatter documents and every shipped playbook assumes), but Claude
330
+ # Code sends `file_path` ABSOLUTE — so without this every glob that does not start
331
+ # with `**/` can never match and the playbook it guards is silently inert: the
332
+ # `**/` idiom compiles to a leading `(.*/)?` that happens to swallow the whole
333
+ # absolute prefix, hiding the problem behind the one glob shape that appears to work.
334
+ #
335
+ # Fast path: a fork-free textual prefix strip, correct whenever `pwd` (repo_root)
336
+ # and the payload path agree on symlink resolution — the overwhelmingly common
337
+ # case (repos live under an unsymlinked $HOME). Fallback (only when the textual
338
+ # strip finds no prefix): the two producers may disagree on symlink resolution
339
+ # (macOS /tmp → /private/tmp; repo_root is `$(pwd)`, filepath is the tool
340
+ # payload), so resolve both sides with `pwd -P` — bash-3.2-safe, no `realpath`
341
+ # dependency — and retry. A path genuinely outside the repo keeps its absolute
342
+ # form; callers MUST treat a still-absolute result as "outside the repo" and skip
343
+ # matching, because an absolute path does not reliably fail to match a
344
+ # repo-relative glob: a leading `**/` compiles to an optional `(.*/)?` that
345
+ # swallows the whole absolute prefix, so `**/src/**/*.ts` would otherwise match
346
+ # `/some/other/repo/src/app.ts` and gate a write in an unrelated repository.
347
+ _relativize_to_repo() {
348
+ local repo_root="$1"
349
+ local filepath="$2"
350
+
351
+ # Fast path: quoted prefix forces a literal (non-glob) strip.
352
+ REPO_RELATIVE_PATH="${filepath#"$repo_root"/}"
353
+ [ "$REPO_RELATIVE_PATH" != "$filepath" ] && return 0
354
+
355
+ # Fallback: resolve symlinks on both sides and retry. `cd` may fail for a
356
+ # not-yet-created parent dir on a new-file Write, or a path outside the repo —
357
+ # in either case fall through to keeping the original absolute path.
358
+ local dir="${filepath%/*}" base="${filepath##*/}" real_root real_dir
359
+ real_root="$(cd "$repo_root" 2>/dev/null && pwd -P)" || real_root=""
360
+ real_dir="$(cd "$dir" 2>/dev/null && pwd -P)" || real_dir=""
361
+ if [ -n "$real_root" ] && [ -n "$real_dir" ]; then
362
+ local real_path="$real_dir/$base"
363
+ REPO_RELATIVE_PATH="${real_path#"$real_root"/}"
364
+ [ "$REPO_RELATIVE_PATH" != "$real_path" ] && return 0
365
+ fi
366
+
367
+ # Genuinely outside the repo (or unresolvable): keep the original path.
368
+ REPO_RELATIVE_PATH="$filepath"
369
+ }
370
+
327
371
  # Populate MATCHED_PLAYBOOKS with playbooks whose `applies_to` list contains a
328
372
  # glob that matches the given file path. One awk fork reads every playbook's
329
373
  # frontmatter; glob→regex translation and matching are in-process (no fork).
@@ -338,6 +382,21 @@ playbook_scan_for_path() {
338
382
  # empty under `set -u`, even though arr was explicitly initialized to `()`.
339
383
  [ ${#ALL_PLAYBOOKS[@]} -gt 0 ] || return 0
340
384
 
385
+ # Match repo-relative globs against a repo-relative path, not the absolute
386
+ # payload path. One-time normalization — the fast path is fork-free.
387
+ _relativize_to_repo "$repo_root" "$filepath"
388
+ local match_path="$REPO_RELATIVE_PATH"
389
+
390
+ # Still absolute => the target lives outside the repo root, so no
391
+ # repo-relative glob applies to it. Bail BEFORE the match loop rather than
392
+ # relying on the globs not to match: a leading `**/` becomes an optional
393
+ # `(.*/)?` which swallows any absolute prefix, so `**/src/**/*.ts` matches
394
+ # `/elsewhere/src/app.ts` and the hook blocks a write in another repository
395
+ # until this repo's playbooks are read. Enforcement stops at the repo edge.
396
+ case "$match_path" in
397
+ /*) return 0 ;;
398
+ esac
399
+
341
400
  local file field value seen="|"
342
401
  GLOB_RE=""
343
402
  while IFS=$'\t' read -r file field value; do
@@ -347,7 +406,7 @@ playbook_scan_for_path() {
347
406
  # loop `break`ed on first match) — track which files already matched.
348
407
  case "$seen" in *"|${file}|"*) continue ;; esac
349
408
  glob_to_regex "$value"
350
- if [[ "$filepath" =~ $GLOB_RE ]]; then
409
+ if [[ "$match_path" =~ $GLOB_RE ]]; then
351
410
  MATCHED_PLAYBOOKS+=("$file")
352
411
  seen="${seen}${file}|"
353
412
  fi
File without changes
File without changes
File without changes
File without changes
package/dist/cli.mjs CHANGED
@@ -45,7 +45,8 @@ const formatIssue = (schema, issue) => {
45
45
  const valid = validKeysAt(schema, issue.path);
46
46
  return issue.keys.map((key) => {
47
47
  const guess = valid ? nearestKey(key, valid) : null;
48
- return `Unknown key "${key}" in ${where}.${guess ? ` Did you mean "${guess}"?` : ""}`;
48
+ const hint = guess ? ` Did you mean "${guess}"?` : "";
49
+ return `Unknown key "${key}" in ${where}.${hint}`;
49
50
  });
50
51
  }
51
52
  if (issue.code === "invalid_format" && issue.format === "regex") {
@@ -206,7 +207,8 @@ const setConfigValues = (rawYaml, updates) => {
206
207
  };
207
208
  const writeConfigValues = async (repoRoot, updates) => {
208
209
  const configPath = join(repoRoot, HARNESS_CONFIG_FILENAME);
209
- await writeFile(configPath, setConfigValues(await readFile(configPath, "utf8"), updates));
210
+ const raw = await readFile(configPath, "utf8");
211
+ await writeFile(configPath, setConfigValues(raw, updates));
210
212
  };
211
213
  //#endregion
212
214
  //#region src/config/pointer.schema.ts
@@ -1079,7 +1081,8 @@ const runValidate = async (inputs) => {
1079
1081
  filesScanned: 0,
1080
1082
  violations: []
1081
1083
  };
1082
- const fileCheck = validateTokenFile(await readFile(tokenPath, "utf8"));
1084
+ const raw = await readFile(tokenPath, "utf8");
1085
+ const fileCheck = validateTokenFile(raw);
1083
1086
  if (!fileCheck.ok) return {
1084
1087
  ok: false,
1085
1088
  tokensFound: true,
@@ -1410,7 +1413,8 @@ const linearizeChannel = (channel) => {
1410
1413
  const relativeLuminance = ({ r, g, b }) => .2126 * linearizeChannel(r) + .7152 * linearizeChannel(g) + .0722 * linearizeChannel(b);
1411
1414
  const contrastRatio = (foreground, background) => {
1412
1415
  const bg = background.a < 1 ? flatten(background, WHITE) : background;
1413
- const l1 = relativeLuminance(foreground.a < 1 ? flatten(foreground, bg) : foreground);
1416
+ const fg = foreground.a < 1 ? flatten(foreground, bg) : foreground;
1417
+ const l1 = relativeLuminance(fg);
1414
1418
  const l2 = relativeLuminance(bg);
1415
1419
  const light = Math.max(l1, l2);
1416
1420
  const dark = Math.min(l1, l2);
@@ -1457,7 +1461,9 @@ const runContrast = async (inputs) => {
1457
1461
  };
1458
1462
  const tokens = collectTokens(parsed);
1459
1463
  const problems = [];
1460
- const pairs = measurePairs(discoverPairs(tokens, problems), declaredModes(tokens), tokens);
1464
+ const specs = discoverPairs(tokens, problems);
1465
+ const modes = declaredModes(tokens);
1466
+ const pairs = measurePairs(specs, modes, tokens);
1461
1467
  return {
1462
1468
  ok: problems.length === 0 && pairs.every((pair) => pair.passes),
1463
1469
  tokensFound: true,
@@ -3303,7 +3309,8 @@ const runStatus = async (deps) => {
3303
3309
  const config = await readHarnessConfig(repoRoot);
3304
3310
  const pointer = await readPointer(repoRoot, deps.readGitUserEmail);
3305
3311
  const { branch, behind } = await deps.gitBehind(repoRoot);
3306
- const openDiscoveries = await countOpenDiscoveries(createTaskTrackerProvider(config.task_storage.type, deps.runCommand), config.task_storage.repo);
3312
+ const provider = createTaskTrackerProvider(config.task_storage.type, deps.runCommand);
3313
+ const openDiscoveries = await countOpenDiscoveries(provider, config.task_storage.repo);
3307
3314
  return {
3308
3315
  vendorVersion: config.vendor_version,
3309
3316
  taskStorageRepo: config.task_storage.repo,
@@ -3659,7 +3666,10 @@ const hasConflictMarkers = (content) => CONFLICT_FENCE.test(content);
3659
3666
  //#endregion
3660
3667
  //#region src/merge/three-way-merge.ts
3661
3668
  const threeWayMerge = (base, client, vendor) => {
3662
- const regions = diff3Regions(base.split("\n"), client.split("\n"), vendor.split("\n"));
3669
+ const baseLines = base.split("\n");
3670
+ const clientLines = client.split("\n");
3671
+ const vendorLines = vendor.split("\n");
3672
+ const regions = diff3Regions(baseLines, clientLines, vendorLines);
3663
3673
  const out = [];
3664
3674
  let conflicted = false;
3665
3675
  for (const region of regions) {
@@ -4824,12 +4834,13 @@ const update = async (args) => {
4824
4834
  const repoRoot = cwd();
4825
4835
  const onConflict = parseOnConflict(args);
4826
4836
  const dryRun = args.includes("--dry-run");
4837
+ const allowDowngrade = args.includes("--allow-downgrade");
4827
4838
  const result = await runUpdate({
4828
4839
  repoRoot,
4829
4840
  vendorRoot: VENDOR_ROOT,
4830
4841
  onConflict,
4831
4842
  dryRun,
4832
- allowDowngrade: args.includes("--allow-downgrade"),
4843
+ allowDowngrade,
4833
4844
  runCommand: makeRunCommand()
4834
4845
  });
4835
4846
  const verb = (applied, preview) => dryRun ? preview : applied;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lemoncode/lemony",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Lemony — a Harness for AI Coding. Vendor package: installer, agent role catalog, generic skill catalog, hooks, and templates for a Spec-Driven Development workflow.",
5
5
  "type": "module",
6
6
  "private": false,
@@ -28,7 +28,7 @@
28
28
  ],
29
29
  "engines": {
30
30
  "node": ">=24",
31
- "npm": ">=11"
31
+ "pnpm": ">=11.5.1"
32
32
  },
33
33
  "publishConfig": {
34
34
  "registry": "https://registry.npmjs.org",
@@ -43,6 +43,24 @@
43
43
  "agents",
44
44
  "scaffolding"
45
45
  ],
46
+ "devDependencies": {
47
+ "@changesets/cli": "2.31.1",
48
+ "@stryker-mutator/core": "9.6.1",
49
+ "@stryker-mutator/vitest-runner": "9.6.1",
50
+ "@types/node": "24.13.3",
51
+ "husky": "9.1.7",
52
+ "lint-staged": "17.2.0",
53
+ "oxlint": "1.75.0",
54
+ "prettier": "3.9.6",
55
+ "tsdown": "0.22.14",
56
+ "tsx": "4.23.1",
57
+ "typescript": "7.0.2",
58
+ "vitest": "4.1.10"
59
+ },
60
+ "dependencies": {
61
+ "yaml": "2.9.0",
62
+ "zod": "4.4.3"
63
+ },
46
64
  "scripts": {
47
65
  "build": "tsdown",
48
66
  "check-types": "tsc --noEmit",
@@ -60,30 +78,7 @@
60
78
  "notice:check": "tsx scripts/generate-notice.ts --check",
61
79
  "telemetry:aggregate": "tsx scripts/aggregate-telemetry.ts",
62
80
  "changeset": "changeset",
63
- "changeset:version": "changeset version && tsx scripts/sync-catalog-version.ts",
81
+ "changeset:version": "changeset version && pnpm install --lockfile-only && tsx scripts/sync-catalog-version.ts",
64
82
  "changeset:publish": "node --run build && changeset publish"
65
- },
66
- "devDependencies": {
67
- "@changesets/cli": "2.31.0",
68
- "@stryker-mutator/core": "9.6.1",
69
- "@stryker-mutator/vitest-runner": "9.6.1",
70
- "@types/node": "24.13.2",
71
- "oxlint": "1.71.0",
72
- "prettier": "3.9.1",
73
- "tsdown": "0.22.3",
74
- "tsx": "4.22.4",
75
- "typescript": "6.0.3",
76
- "vitest": "4.1.9"
77
- },
78
- "dependencies": {
79
- "yaml": "2.9.0",
80
- "zod": "4.4.3"
81
- },
82
- "overrides": {
83
- "typed-rest-client": {
84
- "qs": "6.15.2"
85
- },
86
- "js-yaml": "4.2.0",
87
- "vite": "8.0.16"
88
83
  }
89
- }
84
+ }