@ramarivera/coding-agent-langfuse 0.1.51 → 0.1.53

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/README.md CHANGED
@@ -63,13 +63,14 @@ There are two config layers:
63
63
  - Optional project-local overlays: every `.langfuse-ca.json` found while
64
64
  walking from the session cwd up to the scanned home directory
65
65
 
66
- Global config uses prefix rules:
66
+ Global config uses path-prefix or Git-remote rules:
67
67
 
68
68
  ```json
69
69
  {
70
70
  "rules": [
71
71
  {
72
72
  "pathPrefix": "/Users/example/dev/acme",
73
+ "gitRemoteIncludes": ["github.com/acme"],
73
74
  "tags": ["acme", "client:acme"],
74
75
  "metadata": {
75
76
  "project_group": "acme",
@@ -82,6 +83,11 @@ Global config uses prefix rules:
82
83
  }
83
84
  ```
84
85
 
86
+ Use `pathPrefix` for stable checkout roots. Use `gitRemoteIncludes` for
87
+ temporary agent worktrees, where the cwd may live under a random directory such
88
+ as `.codex/worktrees/<id>/repo` but the Git remote still identifies the real
89
+ project.
90
+
85
91
  Project-local config is intentionally smaller and overrides/extends the matched
86
92
  global rule. Parent overlays are applied before child overlays, so a config at
87
93
  `~/work/client-a/.langfuse-ca.json` can tag a whole workstream while nested
@@ -70,7 +70,8 @@ type OtlpOptions = {
70
70
  homeDir?: string;
71
71
  };
72
72
  type PathTagRule = {
73
- pathPrefix: string;
73
+ pathPrefix?: string;
74
+ gitRemoteIncludes?: string[];
74
75
  tags: string[];
75
76
  metadata: Record<string, unknown>;
76
77
  projectName?: string;
package/dist/backfill.js CHANGED
@@ -196,13 +196,20 @@ function projectMetadata(cwd) {
196
196
  projectFolder: projectName,
197
197
  };
198
198
  }
199
- function matchPathTags(cwd, config, homeDir) {
200
- if (!cwd)
199
+ function matchPathTags(cwd, config, homeDir, git) {
200
+ if (!cwd && !git?.remoteUrls?.length)
201
201
  return { tags: [], metadata: {} };
202
- const normalizedCwd = normalizeRulePath(cwd);
202
+ const normalizedCwd = cwd ? normalizeRulePath(cwd) : undefined;
203
+ const normalizedRemotes = (git?.remoteUrls ?? []).map((remote) => remote.toLowerCase());
203
204
  const matched = config.rules.filter((rule) => {
204
- const prefix = normalizeRulePath(rule.pathPrefix);
205
- return normalizedCwd === prefix || normalizedCwd.startsWith(`${prefix}/`);
205
+ const pathMatch = rule.pathPrefix && normalizedCwd
206
+ ? (() => {
207
+ const prefix = normalizeRulePath(rule.pathPrefix);
208
+ return normalizedCwd === prefix || normalizedCwd.startsWith(`${prefix}/`);
209
+ })()
210
+ : false;
211
+ const gitRemoteMatch = (rule.gitRemoteIncludes ?? []).some((needle) => normalizedRemotes.some((remote) => remote.includes(needle.toLowerCase())));
212
+ return pathMatch || gitRemoteMatch;
206
213
  });
207
214
  const globalMatch = matched.reduce((acc, rule) => ({
208
215
  tags: [...new Set([...acc.tags, ...rule.tags])],
@@ -238,14 +245,15 @@ function matchPathTags(cwd, config, homeDir) {
238
245
  }
239
246
  function mergeProjectMetadata(cwd, config, homeDir) {
240
247
  const project = projectMetadata(cwd);
241
- const pathTags = matchPathTags(cwd, config, homeDir);
248
+ const git = loadGitMetadata(cwd);
249
+ const pathTags = matchPathTags(cwd, config, homeDir, git);
242
250
  return {
243
251
  ...project,
244
252
  projectName: pathTags.projectName ?? project.projectName,
245
253
  projectFolder: pathTags.projectFolder ?? project.projectFolder,
246
254
  tags: pathTags.tags,
247
255
  metadata: pathTags.metadata,
248
- git: loadGitMetadata(cwd),
256
+ git,
249
257
  };
250
258
  }
251
259
  function loadGitMetadata(cwd) {
@@ -263,10 +271,16 @@ function loadGitMetadata(cwd) {
263
271
  const branch = gitOutput(gitCwd, ["branch", "--show-current"]) ??
264
272
  gitOutput(gitCwd, ["rev-parse", "--abbrev-ref", "HEAD"]);
265
273
  const commit = gitOutput(gitCwd, ["rev-parse", "--verify", "HEAD"]);
274
+ const remoteUrls = gitOutput(gitCwd, ["remote", "-v"])
275
+ ?.split("\n")
276
+ .map((line) => line.trim().split(/\s+/)[1])
277
+ .filter((remote) => Boolean(remote))
278
+ .filter((remote, index, remotes) => remotes.indexOf(remote) === index);
266
279
  const metadata = {
267
280
  worktreePath,
268
281
  branch: branch === "HEAD" ? undefined : branch,
269
282
  commit,
283
+ remoteUrls,
270
284
  };
271
285
  gitMetadataCache.set(gitCwd, metadata);
272
286
  return metadata;
@@ -511,8 +525,10 @@ function loadPathTagsConfig(path) {
511
525
  const rules = rawRules.map((rawRule, index) => {
512
526
  const rule = asRecord(rawRule);
513
527
  const pathPrefix = asString(rule.pathPrefix) ?? asString(rule.path_prefix);
514
- if (!pathPrefix) {
515
- throw new Error(`Invalid path tags config ${path}: rules[${index}].pathPrefix is required`);
528
+ const gitRemoteIncludes = stringArray(rule.gitRemoteIncludes ?? rule.git_remote_includes ?? rule.gitRemoteInclude ??
529
+ rule.git_remote_include);
530
+ if (!pathPrefix && gitRemoteIncludes.length === 0) {
531
+ throw new Error(`Invalid path tags config ${path}: rules[${index}] requires pathPrefix or gitRemoteIncludes`);
516
532
  }
517
533
  const tags = Array.isArray(rule.tags)
518
534
  ? rule.tags.filter((tag) => typeof tag === "string" && tag.trim().length > 0)
@@ -520,6 +536,7 @@ function loadPathTagsConfig(path) {
520
536
  const metadata = asRecord(rule.metadata);
521
537
  return {
522
538
  pathPrefix,
539
+ gitRemoteIncludes,
523
540
  tags,
524
541
  metadata,
525
542
  projectName: asString(rule.projectName) ?? asString(rule.project_name),
@@ -764,6 +781,13 @@ function getPath(value, keys) {
764
781
  function asString(value) {
765
782
  return typeof value === "string" ? value : undefined;
766
783
  }
784
+ function stringArray(value) {
785
+ if (typeof value === "string" && value.trim().length > 0)
786
+ return [value];
787
+ if (!Array.isArray(value))
788
+ return [];
789
+ return value.filter((item) => typeof item === "string" && item.trim().length > 0);
790
+ }
767
791
  function asNumber(value) {
768
792
  return typeof value === "number" && Number.isFinite(value)
769
793
  ? value
package/dist/service.js CHANGED
@@ -332,7 +332,7 @@ WantedBy=default.target
332
332
  `;
333
333
  }
334
334
  function renderLaunchdPlist(options, command) {
335
- const programArguments = launchdCommand(command);
335
+ const programArguments = launchdCommand(options, command);
336
336
  return `<?xml version="1.0" encoding="UTF-8"?>
337
337
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
338
338
  <plist version="1.0">
@@ -434,11 +434,18 @@ function defaultNpxPath(platform) {
434
434
  }
435
435
  return "npx";
436
436
  }
437
- function launchdCommand(command) {
437
+ function launchdCommand(options, command) {
438
438
  const [program, ...args] = command;
439
439
  if (!program)
440
440
  return command;
441
- return program.includes("/") ? command : ["/usr/bin/env", program, ...args];
441
+ const isolatedEnv = [
442
+ "/usr/bin/env",
443
+ "-i",
444
+ `HOME=${options.homeDir}`,
445
+ `PATH=${options.pathEnv}`,
446
+ `LANGFUSE_BACKFILL_ENDPOINT=${options.endpoint}`,
447
+ ];
448
+ return [...isolatedEnv, program, ...args];
442
449
  }
443
450
  function findExecutable(name, finder) {
444
451
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ramarivera/coding-agent-langfuse",
3
- "version": "0.1.51",
3
+ "version": "0.1.53",
4
4
  "description": "Universal coding-agent Langfuse backfiller and live OTLP helpers",
5
5
  "type": "module",
6
6
  "license": "MIT",