@yagni-app/code 1.0.6 → 1.0.8

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 (92) hide show
  1. package/README.md +91 -46
  2. package/dist/claudeCompat.d.ts +59 -0
  3. package/dist/claudeCompat.js +109 -2
  4. package/dist/claudePlugins.d.ts +45 -5
  5. package/dist/claudePlugins.js +129 -21
  6. package/dist/cli.js +16 -4
  7. package/dist/doctor.d.ts +21 -8
  8. package/dist/doctor.js +53 -28
  9. package/dist/extension/askUserQuestionTool.js +7 -2
  10. package/dist/extension/condensedTools.d.ts +12 -1
  11. package/dist/extension/condensedTools.js +17 -9
  12. package/dist/extension/config.d.ts +6 -0
  13. package/dist/extension/hooks.d.ts +3 -3
  14. package/dist/extension/hooks.js +30 -5
  15. package/dist/extension/index.d.ts +19 -0
  16. package/dist/extension/index.js +248 -43
  17. package/dist/extension/mcp/cliConfig.d.ts +1 -1
  18. package/dist/extension/mcp/cliConfig.js +1 -1
  19. package/dist/extension/mcp/config.d.ts +24 -2
  20. package/dist/extension/mcp/config.js +75 -3
  21. package/dist/extension/mcp/manager.d.ts +3 -1
  22. package/dist/extension/mcp/manager.js +2 -2
  23. package/dist/extension/mcp/panel.d.ts +0 -1
  24. package/dist/extension/mcp/panel.js +13 -3
  25. package/dist/extension/mcp/startup.js +8 -6
  26. package/dist/extension/permission/gate.d.ts +101 -2
  27. package/dist/extension/permission/gate.js +485 -42
  28. package/dist/extension/permissionRules/bashFileArgs.d.ts +39 -0
  29. package/dist/extension/permissionRules/bashFileArgs.js +236 -0
  30. package/dist/extension/permissionRules/engine.d.ts +50 -0
  31. package/dist/extension/permissionRules/engine.js +238 -0
  32. package/dist/extension/permissionRules/loadConfig.d.ts +64 -0
  33. package/dist/extension/permissionRules/loadConfig.js +105 -0
  34. package/dist/extension/permissionRules/parser.d.ts +38 -0
  35. package/dist/extension/permissionRules/parser.js +136 -0
  36. package/dist/extension/permissionRules/pathRules.d.ts +60 -0
  37. package/dist/extension/permissionRules/pathRules.js +122 -0
  38. package/dist/extension/permissionRules/shellRules.d.ts +52 -0
  39. package/dist/extension/permissionRules/shellRules.js +221 -0
  40. package/dist/extension/pipeline/invocation.d.ts +3 -6
  41. package/dist/extension/pipeline/invocation.js +3 -6
  42. package/dist/extension/pipeline/runner.d.ts +0 -1
  43. package/dist/extension/pipeline/runner.js +6 -14
  44. package/dist/extension/plugins/inventory.d.ts +88 -0
  45. package/dist/extension/plugins/inventory.js +144 -0
  46. package/dist/extension/plugins/panel.d.ts +45 -0
  47. package/dist/extension/plugins/panel.js +293 -0
  48. package/dist/extension/sandbox/bash.d.ts +99 -0
  49. package/dist/extension/sandbox/bash.js +190 -0
  50. package/dist/extension/sandbox/config.d.ts +115 -0
  51. package/dist/extension/sandbox/config.js +388 -0
  52. package/dist/extension/sandbox/manager.d.ts +108 -0
  53. package/dist/extension/sandbox/manager.js +243 -0
  54. package/dist/extension/sandbox/panel.d.ts +111 -0
  55. package/dist/extension/sandbox/panel.js +342 -0
  56. package/dist/extension/sandbox/session.d.ts +85 -0
  57. package/dist/extension/sandbox/session.js +829 -0
  58. package/dist/extension/settingsFiles.d.ts +50 -0
  59. package/dist/extension/settingsFiles.js +206 -0
  60. package/dist/extension/telemetry/attrs.d.ts +96 -0
  61. package/dist/extension/telemetry/attrs.js +149 -0
  62. package/dist/extension/telemetry/config.d.ts +103 -0
  63. package/dist/extension/telemetry/config.js +193 -0
  64. package/dist/extension/telemetry/index.d.ts +7 -0
  65. package/dist/extension/telemetry/index.js +7 -0
  66. package/dist/extension/telemetry/probe.d.ts +29 -0
  67. package/dist/extension/telemetry/probe.js +122 -0
  68. package/dist/extension/telemetry/register.d.ts +47 -0
  69. package/dist/extension/telemetry/register.js +207 -0
  70. package/dist/extension/telemetry/sdk.d.ts +63 -0
  71. package/dist/extension/telemetry/sdk.js +207 -0
  72. package/dist/extension/telemetry/tracker.d.ts +131 -0
  73. package/dist/extension/telemetry/tracker.js +551 -0
  74. package/dist/extension/vendor/IGNORE-LICENSE-MIT +21 -0
  75. package/dist/extension/vendor/ignore.d.ts +86 -0
  76. package/dist/extension/vendor/ignore.js +788 -0
  77. package/dist/goHeadless.d.ts +1 -1
  78. package/dist/goHeadless.js +2 -2
  79. package/dist/launch.d.ts +4 -3
  80. package/dist/launch.js +7 -4
  81. package/dist/mcpCommand.d.ts +10 -1
  82. package/dist/mcpCommand.js +42 -10
  83. package/dist/otel.d.ts +67 -90
  84. package/dist/otel.js +152 -195
  85. package/dist/paths.d.ts +13 -0
  86. package/dist/paths.js +18 -0
  87. package/dist/pluginCommand.d.ts +43 -0
  88. package/dist/pluginCommand.js +499 -0
  89. package/dist/pluginStore.d.ts +170 -0
  90. package/dist/pluginStore.js +554 -0
  91. package/dist/upgrade.js +10 -1
  92. package/package.json +19 -3
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Shared settings-file plumbing for the three YAGNI Code settings tiers:
3
+ * user ~/.yagni-code/config.json (always trusted)
4
+ * project .yagni-code/config.json under the cwd (shared, committed)
5
+ * local .yagni-code/config.local.json under cwd (personal, gitignored)
6
+ *
7
+ * Owns the atomic config mutation (promoted from sandbox/session.ts, the
8
+ * single copy now) and the global-gitignore helper (Claude Code
9
+ * addFileGlobRuleToGitignore parity): on the first write to the local file,
10
+ * the LOCAL_GITIGNORE_ENTRY glob lands in the GLOBAL git ignore file so
11
+ * personal settings are never committed. Fail-soft by design — a gitignore
12
+ * failure never fails the settings write.
13
+ */
14
+ export declare function mutateConfigJson(target: string, mutate: (config: Record<string, unknown>) => void): void;
15
+ /** The project-local settings path for a session cwd. */
16
+ export declare function localConfigPath(cwd: string): string;
17
+ /**
18
+ * Mutate the project-local settings file. Every write fire-and-forgets
19
+ * ensureLocalGitignored (Claude parity: the local tier is added to the
20
+ * global git ignore on write, not on session start). The helper is
21
+ * fire-and-forget BY DESIGN — the write's success notice is not gated on
22
+ * two git spawns; when the gitignore step is skipped or fails, the sink
23
+ * warn (gitignore_skipped / gitignore_update_failed) is the designed
24
+ * signal, surfaced via the diagnostics trail.
25
+ *
26
+ * `sinkSource` attributes those sink events to the caller's surface
27
+ * ("permission-rules" for rule saves, "sandbox" for panel/toggle writes) —
28
+ * the diagnostics trail then says which surface's write failed to get
29
+ * ignore-protected, not a blanket permission-rules line for every write.
30
+ */
31
+ export declare function mutateLocalConfig(cwd: string, mutate: (config: Record<string, unknown>) => void, sinkSource?: string): void;
32
+ /**
33
+ * The global git ignore path git actually reads. Git consults
34
+ * $XDG_CONFIG_HOME/git/ignore (defaulting ~/.config/git/ignore) — honoring
35
+ * XDG is a deliberate divergence from Claude (which hardcodes ~/.config):
36
+ * writing a file git will not read when XDG is set would be a silent no-op.
37
+ */
38
+ export declare function globalGitignorePath(env?: NodeJS.ProcessEnv): string;
39
+ /**
40
+ * Ensure the LOCAL_GITIGNORE_ENTRY glob (covering config.local.json at any
41
+ * depth) is ignored by the user's GLOBAL git config so local settings are
42
+ * never committed. Skip when:
43
+ * - cwd is not inside a git repo (nothing to protect);
44
+ * - `git check-ignore` already matches (local or global patterns cover it);
45
+ * - the global ignore file already carries the literal entry.
46
+ * Any failure logs one warn sink event and returns — the settings write it
47
+ * accompanies has already succeeded and must not be walked back.
48
+ */
49
+ export declare function ensureLocalGitignored(cwd: string, env?: NodeJS.ProcessEnv, sinkSource?: string): Promise<void>;
50
+ //# sourceMappingURL=settingsFiles.d.ts.map
@@ -0,0 +1,206 @@
1
+ /**
2
+ * Shared settings-file plumbing for the three YAGNI Code settings tiers:
3
+ * user ~/.yagni-code/config.json (always trusted)
4
+ * project .yagni-code/config.json under the cwd (shared, committed)
5
+ * local .yagni-code/config.local.json under cwd (personal, gitignored)
6
+ *
7
+ * Owns the atomic config mutation (promoted from sandbox/session.ts, the
8
+ * single copy now) and the global-gitignore helper (Claude Code
9
+ * addFileGlobRuleToGitignore parity): on the first write to the local file,
10
+ * the LOCAL_GITIGNORE_ENTRY glob lands in the GLOBAL git ignore file so
11
+ * personal settings are never committed. Fail-soft by design — a gitignore
12
+ * failure never fails the settings write.
13
+ */
14
+ import { spawn } from "node:child_process";
15
+ import { randomUUID } from "node:crypto";
16
+ import { existsSync, lstatSync, mkdirSync, readFileSync, appendFileSync, writeFileSync, renameSync, chmodSync } from "node:fs";
17
+ import { homedir } from "node:os";
18
+ import { dirname, join } from "node:path";
19
+ import { logEvent } from "./errorSink.js";
20
+ const LOCAL_GITIGNORE_ENTRY = "**/.yagni-code/config.local.json";
21
+ function isPlainRecord(v) {
22
+ return typeof v === "object" && v !== null && !Array.isArray(v);
23
+ }
24
+ /**
25
+ * Safe config-file mutation shared by every settings writer:
26
+ * - a PARSE FAILURE aborts (never treats a corrupt/half-written file as
27
+ * empty — that would wipe activeProfile/permissions);
28
+ * - the write is ATOMIC (tmp file + rename) so a crash never truncates;
29
+ * - symlinked targets are rejected (a hostile repo's config symlink must
30
+ * not redirect writes to arbitrary paths);
31
+ * - the mutator runs only on a parsed object; a thrown mutator aborts.
32
+ * Throws on any failure — callers decide fail-soft messaging.
33
+ */
34
+ /**
35
+ * Reject when the target's EXISTING parent directory is a symlink — a
36
+ * hostile repo can commit a symlinked `.yagni-code` DIRECTORY, and every
37
+ * local-settings write would then land outside the repo via the symlinked
38
+ * parent (the final-component lstat guard cannot see this). The parent is
39
+ * the repo-committable component (`.yagni-code` for project/local targets,
40
+ * the state home for user targets); ancestors above it are the user's own
41
+ * filesystem, not repo-controlled. Absent parent is fine — mkdirSync
42
+ * creates it fresh, and mkdir does not follow a symlinked path segment it
43
+ * creates itself. Fail closed.
44
+ */
45
+ function assertRealParentDir(target) {
46
+ const dir = dirname(target);
47
+ try {
48
+ const st = lstatSync(dir);
49
+ if (!st.isDirectory() || st.isSymbolicLink()) {
50
+ throw new Error(`${dir} is not a real directory (symlinked parent?) — refusing to write through it`);
51
+ }
52
+ }
53
+ catch (err) {
54
+ if (err.code === "ENOENT")
55
+ return; // absent — created fresh below
56
+ throw err;
57
+ }
58
+ }
59
+ export function mutateConfigJson(target, mutate) {
60
+ let parsed;
61
+ try {
62
+ parsed = JSON.parse(readFileSync(target, "utf-8"));
63
+ }
64
+ catch (err) {
65
+ if (err.code === "ENOENT") {
66
+ parsed = {}; // genuinely fresh file — fine
67
+ }
68
+ else {
69
+ throw new Error(`config at ${target} is not valid JSON — refusing to rewrite it`);
70
+ }
71
+ }
72
+ if (!isPlainRecord(parsed))
73
+ throw new Error(`config at ${target} is not an object`);
74
+ const config = { ...parsed };
75
+ mutate(config);
76
+ mkdirSync(dirname(target), { recursive: true });
77
+ // Reject symlinked targets: lstat must show a regular file (or absent).
78
+ try {
79
+ const st = lstatSync(target);
80
+ if (!st.isFile())
81
+ throw new Error(`${target} is not a regular file (symlink?) — refusing`);
82
+ }
83
+ catch (err) {
84
+ if (err.code !== "ENOENT")
85
+ throw err;
86
+ }
87
+ // And a symlinked PARENT directory: a committed symlinked .yagni-code
88
+ // dir would redirect the write outside the project — the final-component
89
+ // guard above cannot see it.
90
+ assertRealParentDir(target);
91
+ const tmp = `${target}.yagni-tmp-${process.pid}-${randomUUID().slice(0, 8)}`;
92
+ writeFileSync(tmp, JSON.stringify(config, null, 2) + "\n", { mode: 0o600 });
93
+ // chmod: writeFile honors mode only on create — guarantee 0600 even when a
94
+ // same-pid retry reuses an existing tmp file.
95
+ chmodSync(tmp, 0o600);
96
+ renameSync(tmp, target);
97
+ }
98
+ /** The project-local settings path for a session cwd. */
99
+ export function localConfigPath(cwd) {
100
+ return join(cwd, ".yagni-code", "config.local.json");
101
+ }
102
+ /**
103
+ * Mutate the project-local settings file. Every write fire-and-forgets
104
+ * ensureLocalGitignored (Claude parity: the local tier is added to the
105
+ * global git ignore on write, not on session start). The helper is
106
+ * fire-and-forget BY DESIGN — the write's success notice is not gated on
107
+ * two git spawns; when the gitignore step is skipped or fails, the sink
108
+ * warn (gitignore_skipped / gitignore_update_failed) is the designed
109
+ * signal, surfaced via the diagnostics trail.
110
+ *
111
+ * `sinkSource` attributes those sink events to the caller's surface
112
+ * ("permission-rules" for rule saves, "sandbox" for panel/toggle writes) —
113
+ * the diagnostics trail then says which surface's write failed to get
114
+ * ignore-protected, not a blanket permission-rules line for every write.
115
+ */
116
+ export function mutateLocalConfig(cwd, mutate, sinkSource = "permission-rules") {
117
+ mutateConfigJson(localConfigPath(cwd), mutate);
118
+ void ensureLocalGitignored(cwd, process.env, sinkSource);
119
+ }
120
+ // ---------------------------------------------------------------------------
121
+ // Global-gitignore helper (Claude addFileGlobRuleToGitignore parity)
122
+ // ---------------------------------------------------------------------------
123
+ /** Run git <args> in cwd; resolves exit code (never throws). env threads
124
+ * through so the caller's PATH governs git resolution. */
125
+ function git(cwd, args, env) {
126
+ return new Promise((resolve) => {
127
+ try {
128
+ const child = spawn("git", args, { cwd, env, stdio: "ignore" });
129
+ child.on("error", () => resolve(GIT_NOT_INSTALLED));
130
+ child.on("exit", (code) => resolve(code ?? GIT_NOT_INSTALLED));
131
+ }
132
+ catch {
133
+ resolve(GIT_NOT_INSTALLED);
134
+ }
135
+ });
136
+ }
137
+ const GIT_NOT_INSTALLED = -1;
138
+ /**
139
+ * The global git ignore path git actually reads. Git consults
140
+ * $XDG_CONFIG_HOME/git/ignore (defaulting ~/.config/git/ignore) — honoring
141
+ * XDG is a deliberate divergence from Claude (which hardcodes ~/.config):
142
+ * writing a file git will not read when XDG is set would be a silent no-op.
143
+ */
144
+ export function globalGitignorePath(env = process.env) {
145
+ const xdg = env.XDG_CONFIG_HOME && env.XDG_CONFIG_HOME.trim() !== "" ? env.XDG_CONFIG_HOME : null;
146
+ return join(xdg ?? join(homedir(), ".config"), "git", "ignore");
147
+ }
148
+ /**
149
+ * Ensure the LOCAL_GITIGNORE_ENTRY glob (covering config.local.json at any
150
+ * depth) is ignored by the user's GLOBAL git config so local settings are
151
+ * never committed. Skip when:
152
+ * - cwd is not inside a git repo (nothing to protect);
153
+ * - `git check-ignore` already matches (local or global patterns cover it);
154
+ * - the global ignore file already carries the literal entry.
155
+ * Any failure logs one warn sink event and returns — the settings write it
156
+ * accompanies has already succeeded and must not be walked back.
157
+ */
158
+ export async function ensureLocalGitignored(cwd, env = process.env, sinkSource = "permission-rules") {
159
+ try {
160
+ const inside = await git(cwd, ["rev-parse", "--is-inside-work-tree"], env);
161
+ // -1 = git missing or failed to spawn — protection silently skipped; a
162
+ // warn makes that visible (the file exists un-ignored). 128 = cleanly
163
+ // not a repo — the documented skip, nothing to surface.
164
+ if (inside === GIT_NOT_INSTALLED) {
165
+ logEvent({
166
+ source: sinkSource,
167
+ level: "warn",
168
+ event: "gitignore_skipped",
169
+ fields: { reason: "git-unavailable" },
170
+ });
171
+ return;
172
+ }
173
+ // Not inside a repo (128) or a failed rev-parse: skip, nothing to do.
174
+ if (inside !== 0)
175
+ return;
176
+ // 0 = already ignored by some pattern (project .gitignore or global).
177
+ if ((await git(cwd, ["check-ignore", ".yagni-code/config.local.json"], env)) === 0)
178
+ return;
179
+ const path = globalGitignorePath(env);
180
+ mkdirSync(dirname(path), { recursive: true });
181
+ if (existsSync(path)) {
182
+ const content = readFileSync(path, "utf-8");
183
+ if (content.includes(LOCAL_GITIGNORE_ENTRY))
184
+ return;
185
+ appendFileSync(path, `${content.endsWith("\n") ? "" : "\n"}${LOCAL_GITIGNORE_ENTRY}\n`);
186
+ }
187
+ else {
188
+ writeFileSync(path, `${LOCAL_GITIGNORE_ENTRY}\n`, { mode: 0o644 });
189
+ }
190
+ }
191
+ catch (err) {
192
+ logEvent({
193
+ source: sinkSource,
194
+ level: "warn",
195
+ event: "gitignore_update_failed",
196
+ fields: {
197
+ path: globalGitignorePath(env),
198
+ // The thrown message names the failing operation and the local
199
+ // filesystem path (mkdir/append of the ignore file) — no secret
200
+ // surface, and it makes EACCES-vs-ENOSPC diagnosable.
201
+ error: err instanceof Error ? err.message : String(err),
202
+ },
203
+ });
204
+ }
205
+ }
206
+ //# sourceMappingURL=settingsFiles.js.map
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Attribute, metric, and event names.
3
+ *
4
+ * Two vocabularies ride on every signal on purpose:
5
+ *
6
+ * - Claude Code's telemetry shape (`session.id`, `input_tokens`,
7
+ * `tool_name`, the `<prefix>.token.usage` counter, the `api_request`
8
+ * event …) so a team's existing Claude Code dashboards swap to YAGNI Code
9
+ * by changing the prefix from `claude_code` to `yagni_code`;
10
+ * - the OpenTelemetry GenAI semantic conventions (`gen_ai.*`) on the LLM
11
+ * and tool spans, which is what vendor LLM-observability products
12
+ * (Datadog LLM Observability, Grafana's GenAI views) key on.
13
+ *
14
+ * Names are literal strings rather than semconv-package imports: the set is
15
+ * small, stable, and part of our documented customer contract.
16
+ */
17
+ export declare const PREFIX = "yagni_code";
18
+ export declare const ATTR_SESSION_ID = "session.id";
19
+ export declare const ATTR_APP_VERSION = "app.version";
20
+ export declare const ATTR_APP_ENTRYPOINT = "app.entrypoint";
21
+ export declare const ATTR_ORGANIZATION_ID = "organization.id";
22
+ export declare const ATTR_USER_EMAIL = "user.email";
23
+ export declare const ATTR_TERMINAL_TYPE = "terminal.type";
24
+ export declare const ATTR_GEN_AI_OPERATION_NAME = "gen_ai.operation.name";
25
+ export declare const ATTR_GEN_AI_PROVIDER_NAME = "gen_ai.provider.name";
26
+ export declare const ATTR_GEN_AI_SYSTEM = "gen_ai.system";
27
+ export declare const ATTR_GEN_AI_REQUEST_MODEL = "gen_ai.request.model";
28
+ export declare const ATTR_GEN_AI_RESPONSE_MODEL = "gen_ai.response.model";
29
+ export declare const ATTR_GEN_AI_RESPONSE_ID = "gen_ai.response.id";
30
+ export declare const ATTR_GEN_AI_FINISH_REASONS = "gen_ai.response.finish_reasons";
31
+ /**
32
+ * Token usage. The semconv registry says `gen_ai.usage.input_tokens` "SHOULD
33
+ * include all types of input tokens, including cached tokens", and defines the
34
+ * cache counters as subsets of it. pi reports the Anthropic way (non-cached
35
+ * input, cache read, cache write as three disjoint numbers), so the tracker
36
+ * adds them up for the `gen_ai.*` layer. Consumers such as Datadog LLM
37
+ * Observability divide cache_read by input_tokens; feeding them the disjoint
38
+ * number produced cache ratios in the tens of thousands of percent.
39
+ *
40
+ * The flat Claude Code attributes (`input_tokens`, `cache_read_tokens`, …) and
41
+ * the `<prefix>.token.usage` metric deliberately keep Claude Code's disjoint
42
+ * semantics so cross-harness comparisons stay apples to apples.
43
+ */
44
+ export declare const ATTR_GEN_AI_INPUT_TOKENS = "gen_ai.usage.input_tokens";
45
+ export declare const ATTR_GEN_AI_OUTPUT_TOKENS = "gen_ai.usage.output_tokens";
46
+ export declare const ATTR_GEN_AI_TOTAL_TOKENS = "gen_ai.usage.total_tokens";
47
+ export declare const ATTR_GEN_AI_CACHE_READ_TOKENS = "gen_ai.usage.cache_read.input_tokens";
48
+ export declare const ATTR_GEN_AI_CACHE_WRITE_TOKENS = "gen_ai.usage.cache_write.input_tokens";
49
+ /** Pre-semconv spellings emitted through 1.0.x; kept for one release so
50
+ * dashboards keyed on them keep working. */
51
+ export declare const ATTR_GEN_AI_CACHE_READ_TOKENS_LEGACY = "gen_ai.usage.cache_read_input_tokens";
52
+ export declare const ATTR_GEN_AI_CACHE_CREATION_TOKENS_LEGACY = "gen_ai.usage.cache_creation_input_tokens";
53
+ /** The one cost attribute Datadog's OTLP ingestion documents. Carries the
54
+ * same sell-rate figure as the flat `cost_usd`, so the vendor Cost view
55
+ * populates for the opaque tier id, which no vendor price table knows.
56
+ * Unit is NANODOLLARS (integer), not dollars: Datadog copies the value
57
+ * unscaled into its nanodollar cost metric. */
58
+ export declare const ATTR_GEN_AI_COST_ESTIMATED_TOTAL = "gen_ai.cost.estimated_total";
59
+ export declare const ATTR_GEN_AI_CONVERSATION_ID = "gen_ai.conversation.id";
60
+ export declare const ATTR_GEN_AI_AGENT_NAME = "gen_ai.agent.name";
61
+ export declare const ATTR_GEN_AI_TOOL_NAME = "gen_ai.tool.name";
62
+ export declare const ATTR_GEN_AI_TOOL_CALL_ID = "gen_ai.tool.call.id";
63
+ export declare const ATTR_GEN_AI_TOOL_TYPE = "gen_ai.tool.type";
64
+ export declare const ATTR_ERROR_TYPE = "error.type";
65
+ export declare const ATTR_HTTP_STATUS_CODE = "http.response.status_code";
66
+ /** The provider name every span carries. The backing model is never exposed:
67
+ * `gen_ai.request.model` is the opaque YAGNI tier id. */
68
+ export declare const GEN_AI_PROVIDER = "yagni";
69
+ export declare const SPAN_INTERACTION = "yagni_code.interaction";
70
+ export declare const SPAN_TURN = "yagni_code.turn";
71
+ export declare const SPAN_LLM_REQUEST = "yagni_code.llm_request";
72
+ export declare const SPAN_TOOL = "yagni_code.tool";
73
+ export declare const METRIC_SESSION_COUNT = "yagni_code.session.count";
74
+ export declare const METRIC_LINES_OF_CODE = "yagni_code.lines_of_code.count";
75
+ export declare const METRIC_PULL_REQUEST_COUNT = "yagni_code.pull_request.count";
76
+ export declare const METRIC_COMMIT_COUNT = "yagni_code.commit.count";
77
+ export declare const METRIC_COST_USAGE = "yagni_code.cost.usage";
78
+ export declare const METRIC_TOKEN_USAGE = "yagni_code.token.usage";
79
+ export declare const METRIC_CODE_EDIT_DECISION = "yagni_code.code_edit_tool.decision";
80
+ export declare const METRIC_ACTIVE_TIME = "yagni_code.active_time.total";
81
+ export declare const EVENT_USER_PROMPT = "user_prompt";
82
+ export declare const EVENT_ASSISTANT_RESPONSE = "assistant_response";
83
+ export declare const EVENT_TOOL_RESULT = "tool_result";
84
+ export declare const EVENT_API_REQUEST = "api_request";
85
+ export declare const EVENT_API_ERROR = "api_error";
86
+ export declare const EVENT_TOOL_DECISION = "tool_decision";
87
+ export declare const EVENT_PERMISSION_MODE_CHANGED = "permission_mode_changed";
88
+ /** Claude Code token-type values on `<prefix>.token.usage`. */
89
+ export type TokenType = "input" | "output" | "cacheRead" | "cacheCreation";
90
+ /** Claude Code decision sources on tool_decision / code_edit_tool.decision. */
91
+ export type DecisionSource = "config" | "hook" | "user_permanent" | "user_temporary" | "user_abort" | "user_reject";
92
+ export type Decision = "accept" | "reject";
93
+ export declare function clampString(s: string, max?: number): string;
94
+ /** Language label from a file path's extension (Claude Code's `language` attr). */
95
+ export declare function languageFromPath(path: string | undefined): string | undefined;
96
+ //# sourceMappingURL=attrs.d.ts.map
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Attribute, metric, and event names.
3
+ *
4
+ * Two vocabularies ride on every signal on purpose:
5
+ *
6
+ * - Claude Code's telemetry shape (`session.id`, `input_tokens`,
7
+ * `tool_name`, the `<prefix>.token.usage` counter, the `api_request`
8
+ * event …) so a team's existing Claude Code dashboards swap to YAGNI Code
9
+ * by changing the prefix from `claude_code` to `yagni_code`;
10
+ * - the OpenTelemetry GenAI semantic conventions (`gen_ai.*`) on the LLM
11
+ * and tool spans, which is what vendor LLM-observability products
12
+ * (Datadog LLM Observability, Grafana's GenAI views) key on.
13
+ *
14
+ * Names are literal strings rather than semconv-package imports: the set is
15
+ * small, stable, and part of our documented customer contract.
16
+ */
17
+ export const PREFIX = "yagni_code";
18
+ // ── Resource / standard attributes ──────────────────────────────────────────
19
+ export const ATTR_SESSION_ID = "session.id";
20
+ export const ATTR_APP_VERSION = "app.version";
21
+ export const ATTR_APP_ENTRYPOINT = "app.entrypoint";
22
+ export const ATTR_ORGANIZATION_ID = "organization.id";
23
+ export const ATTR_USER_EMAIL = "user.email";
24
+ export const ATTR_TERMINAL_TYPE = "terminal.type";
25
+ // ── GenAI semantic conventions ──────────────────────────────────────────────
26
+ export const ATTR_GEN_AI_OPERATION_NAME = "gen_ai.operation.name";
27
+ export const ATTR_GEN_AI_PROVIDER_NAME = "gen_ai.provider.name";
28
+ export const ATTR_GEN_AI_SYSTEM = "gen_ai.system";
29
+ export const ATTR_GEN_AI_REQUEST_MODEL = "gen_ai.request.model";
30
+ export const ATTR_GEN_AI_RESPONSE_MODEL = "gen_ai.response.model";
31
+ export const ATTR_GEN_AI_RESPONSE_ID = "gen_ai.response.id";
32
+ export const ATTR_GEN_AI_FINISH_REASONS = "gen_ai.response.finish_reasons";
33
+ /**
34
+ * Token usage. The semconv registry says `gen_ai.usage.input_tokens` "SHOULD
35
+ * include all types of input tokens, including cached tokens", and defines the
36
+ * cache counters as subsets of it. pi reports the Anthropic way (non-cached
37
+ * input, cache read, cache write as three disjoint numbers), so the tracker
38
+ * adds them up for the `gen_ai.*` layer. Consumers such as Datadog LLM
39
+ * Observability divide cache_read by input_tokens; feeding them the disjoint
40
+ * number produced cache ratios in the tens of thousands of percent.
41
+ *
42
+ * The flat Claude Code attributes (`input_tokens`, `cache_read_tokens`, …) and
43
+ * the `<prefix>.token.usage` metric deliberately keep Claude Code's disjoint
44
+ * semantics so cross-harness comparisons stay apples to apples.
45
+ */
46
+ export const ATTR_GEN_AI_INPUT_TOKENS = "gen_ai.usage.input_tokens";
47
+ export const ATTR_GEN_AI_OUTPUT_TOKENS = "gen_ai.usage.output_tokens";
48
+ export const ATTR_GEN_AI_TOTAL_TOKENS = "gen_ai.usage.total_tokens";
49
+ export const ATTR_GEN_AI_CACHE_READ_TOKENS = "gen_ai.usage.cache_read.input_tokens";
50
+ export const ATTR_GEN_AI_CACHE_WRITE_TOKENS = "gen_ai.usage.cache_write.input_tokens";
51
+ /** Pre-semconv spellings emitted through 1.0.x; kept for one release so
52
+ * dashboards keyed on them keep working. */
53
+ export const ATTR_GEN_AI_CACHE_READ_TOKENS_LEGACY = "gen_ai.usage.cache_read_input_tokens";
54
+ export const ATTR_GEN_AI_CACHE_CREATION_TOKENS_LEGACY = "gen_ai.usage.cache_creation_input_tokens";
55
+ /** The one cost attribute Datadog's OTLP ingestion documents. Carries the
56
+ * same sell-rate figure as the flat `cost_usd`, so the vendor Cost view
57
+ * populates for the opaque tier id, which no vendor price table knows.
58
+ * Unit is NANODOLLARS (integer), not dollars: Datadog copies the value
59
+ * unscaled into its nanodollar cost metric. */
60
+ export const ATTR_GEN_AI_COST_ESTIMATED_TOTAL = "gen_ai.cost.estimated_total";
61
+ export const ATTR_GEN_AI_CONVERSATION_ID = "gen_ai.conversation.id";
62
+ export const ATTR_GEN_AI_AGENT_NAME = "gen_ai.agent.name";
63
+ export const ATTR_GEN_AI_TOOL_NAME = "gen_ai.tool.name";
64
+ export const ATTR_GEN_AI_TOOL_CALL_ID = "gen_ai.tool.call.id";
65
+ export const ATTR_GEN_AI_TOOL_TYPE = "gen_ai.tool.type";
66
+ export const ATTR_ERROR_TYPE = "error.type";
67
+ export const ATTR_HTTP_STATUS_CODE = "http.response.status_code";
68
+ /** The provider name every span carries. The backing model is never exposed:
69
+ * `gen_ai.request.model` is the opaque YAGNI tier id. */
70
+ export const GEN_AI_PROVIDER = "yagni";
71
+ // ── Span names (Claude Code's beta trace tree, prefixed) ────────────────────
72
+ export const SPAN_INTERACTION = `${PREFIX}.interaction`;
73
+ export const SPAN_TURN = `${PREFIX}.turn`;
74
+ export const SPAN_LLM_REQUEST = `${PREFIX}.llm_request`;
75
+ export const SPAN_TOOL = `${PREFIX}.tool`;
76
+ // ── Metric names (Claude Code's eight counters, prefixed) ───────────────────
77
+ export const METRIC_SESSION_COUNT = `${PREFIX}.session.count`;
78
+ export const METRIC_LINES_OF_CODE = `${PREFIX}.lines_of_code.count`;
79
+ export const METRIC_PULL_REQUEST_COUNT = `${PREFIX}.pull_request.count`;
80
+ export const METRIC_COMMIT_COUNT = `${PREFIX}.commit.count`;
81
+ export const METRIC_COST_USAGE = `${PREFIX}.cost.usage`;
82
+ export const METRIC_TOKEN_USAGE = `${PREFIX}.token.usage`;
83
+ export const METRIC_CODE_EDIT_DECISION = `${PREFIX}.code_edit_tool.decision`;
84
+ export const METRIC_ACTIVE_TIME = `${PREFIX}.active_time.total`;
85
+ // ── Event names (Claude Code's log events, prefixed in the body) ────────────
86
+ export const EVENT_USER_PROMPT = "user_prompt";
87
+ export const EVENT_ASSISTANT_RESPONSE = "assistant_response";
88
+ export const EVENT_TOOL_RESULT = "tool_result";
89
+ export const EVENT_API_REQUEST = "api_request";
90
+ export const EVENT_API_ERROR = "api_error";
91
+ export const EVENT_TOOL_DECISION = "tool_decision";
92
+ export const EVENT_PERMISSION_MODE_CHANGED = "permission_mode_changed";
93
+ /** Cap on any string attribute (Claude Code's 60 KB content limit). */
94
+ const MAX_ATTR_BYTES = 60 * 1024;
95
+ export function clampString(s, max = MAX_ATTR_BYTES) {
96
+ if (Buffer.byteLength(s, "utf8") <= max)
97
+ return s;
98
+ let end = max;
99
+ while (end > 0 && Buffer.byteLength(s.slice(0, end), "utf8") > max - 16)
100
+ end -= 64;
101
+ return `${s.slice(0, Math.max(0, end))}…[truncated]`;
102
+ }
103
+ /** Language label from a file path's extension (Claude Code's `language` attr). */
104
+ export function languageFromPath(path) {
105
+ if (!path)
106
+ return undefined;
107
+ const m = /\.([A-Za-z0-9]+)$/.exec(path);
108
+ if (!m)
109
+ return undefined;
110
+ const ext = m[1].toLowerCase();
111
+ const map = {
112
+ ts: "typescript",
113
+ tsx: "typescript",
114
+ mts: "typescript",
115
+ cts: "typescript",
116
+ js: "javascript",
117
+ jsx: "javascript",
118
+ mjs: "javascript",
119
+ cjs: "javascript",
120
+ py: "python",
121
+ rb: "ruby",
122
+ go: "go",
123
+ rs: "rust",
124
+ java: "java",
125
+ kt: "kotlin",
126
+ swift: "swift",
127
+ c: "c",
128
+ h: "c",
129
+ cc: "cpp",
130
+ cpp: "cpp",
131
+ hpp: "cpp",
132
+ cs: "csharp",
133
+ php: "php",
134
+ sh: "shell",
135
+ bash: "shell",
136
+ zsh: "shell",
137
+ sql: "sql",
138
+ md: "markdown",
139
+ json: "json",
140
+ yaml: "yaml",
141
+ yml: "yaml",
142
+ toml: "toml",
143
+ html: "html",
144
+ css: "css",
145
+ scss: "scss",
146
+ };
147
+ return map[ext] ?? ext;
148
+ }
149
+ //# sourceMappingURL=attrs.js.map
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Telemetry export configuration, resolved from the standard OpenTelemetry
3
+ * environment contract (the same variables Claude Code reads), so a machine
4
+ * or workspace already configured for Claude Code lights up YAGNI Code with
5
+ * no translation.
6
+ *
7
+ * Pure: reads an env record, never the network or disk. The launcher decides
8
+ * WHETHER a session exports (see the CLI's otel.ts: env, workspace settings,
9
+ * or a repo's committed config) and sets `YAGNI_OTEL_EXPORT=1` plus the OTLP
10
+ * variables in the child env; this module turns that env into per-signal
11
+ * exporter settings.
12
+ *
13
+ * Endpoint rules follow the OTLP exporter spec:
14
+ * - a per-signal endpoint (`OTEL_EXPORTER_OTLP_TRACES_ENDPOINT`, …) is used
15
+ * verbatim;
16
+ * - the generic `OTEL_EXPORTER_OTLP_ENDPOINT` is a BASE: http transports
17
+ * append `/v1/<signal>`, gRPC uses it as-is;
18
+ * - nothing configured falls back to the local-collector defaults.
19
+ *
20
+ * Content policy is enforced here, not configurable: prompt and response
21
+ * text never export. `OTEL_LOG_USER_PROMPTS` and `OTEL_LOG_TOOL_DETAILS` are
22
+ * deliberately NOT honored — session content leaving the machine is a
23
+ * contract change, not an env knob.
24
+ */
25
+ export type OtlpProtocol = "grpc" | "http/protobuf" | "http/json";
26
+ export type Signal = "traces" | "metrics" | "logs";
27
+ export declare const SIGNALS: readonly Signal[];
28
+ export interface SignalConfig {
29
+ enabled: boolean;
30
+ protocol: OtlpProtocol;
31
+ /** The fully resolved URL this signal exports to. */
32
+ url: string;
33
+ headers: Record<string, string>;
34
+ /** Batch / periodic export interval in ms. */
35
+ exportIntervalMs: number;
36
+ }
37
+ export interface TelemetryIdentity {
38
+ /** Bound workspace id (YAGNI_WORKSPACE_ID) → `organization.id`. */
39
+ organizationId?: string;
40
+ /**
41
+ * The signed-in user's email → `user.email`. From `YAGNI_USER_EMAIL` when
42
+ * the launcher forwards it; otherwise learned from the /context boot fetch
43
+ * (`TelemetryHandle.setUserEmail`). Gated by `includeAccountId`.
44
+ */
45
+ userEmail?: string;
46
+ /** Shipping CLI version (YAGNI_CODE_VERSION) → `app.version`. */
47
+ appVersion?: string;
48
+ /** Detected terminal (TERM_PROGRAM / TMUX) → `terminal.type`. */
49
+ terminalType?: string;
50
+ /** `cli` for an interactive launch, `sdk-cli` for a headless /go run. */
51
+ entrypoint: "cli" | "sdk-cli";
52
+ }
53
+ export interface TelemetryConfig {
54
+ enabled: boolean;
55
+ serviceName: string;
56
+ serviceVersion?: string;
57
+ /** OTEL_RESOURCE_ATTRIBUTES, decoded. Applied to the resource and (by
58
+ * default) to every metric and event. */
59
+ resourceAttributes: Record<string, string>;
60
+ traces: SignalConfig;
61
+ metrics: SignalConfig;
62
+ logs: SignalConfig;
63
+ metricsTemporality: "delta" | "cumulative";
64
+ /** Cardinality controls (Claude Code parity). */
65
+ includeSessionId: boolean;
66
+ includeVersion: boolean;
67
+ includeAccountId: boolean;
68
+ includeResourceAttributes: boolean;
69
+ includeEntrypoint: boolean;
70
+ identity: TelemetryIdentity;
71
+ }
72
+ export declare const DEFAULT_SERVICE_NAME = "yagni-code";
73
+ type Env = Record<string, string | undefined>;
74
+ export declare function normalizeProtocol(p: string | undefined): OtlpProtocol | undefined;
75
+ /**
76
+ * Parse a `k=v,k2=v2` list (OTEL_EXPORTER_OTLP_HEADERS / OTEL_RESOURCE_ATTRIBUTES).
77
+ * Values may be percent-encoded per the W3C baggage rules the spec references;
78
+ * a malformed escape keeps the raw value. Splits on the FIRST `=` only so a
79
+ * base64 value's trailing `==` survives.
80
+ */
81
+ export declare function parseKvList(s: string | undefined): Record<string, string>;
82
+ /**
83
+ * Reduce an endpoint that carries a signal path to its base
84
+ * (`https://otlp.datadoghq.com/v1/traces` → `https://otlp.datadoghq.com`).
85
+ * Pre-parity workspace configs stored the full trace URL; treating it as a
86
+ * base keeps them exporting all three signals without an admin re-save.
87
+ */
88
+ export declare function stripSignalPath(endpoint: string): string;
89
+ /**
90
+ * Resolve one signal's URL from the env, per the OTLP exporter spec.
91
+ * Exported for the launcher's doctor so its report names the exact URL a
92
+ * session will post to.
93
+ */
94
+ export declare function resolveSignalUrl(env: Env, signal: Signal, protocol: OtlpProtocol): string;
95
+ /** Personal kill switch, beats every other source (both spellings accepted). */
96
+ export declare function telemetryDisabled(env: Env): boolean;
97
+ /** The launcher's "this session exports" gate. */
98
+ export declare const OTEL_EXPORT_ENV = "YAGNI_OTEL_EXPORT";
99
+ export declare function resolveTelemetryConfig(env?: Env): TelemetryConfig;
100
+ /** Signals this config will actually export (for doctor / status lines). */
101
+ export declare function enabledSignals(config: TelemetryConfig): Signal[];
102
+ export {};
103
+ //# sourceMappingURL=config.d.ts.map