@sayknow-cli/utils 0.7.10 → 0.7.12

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 (49) hide show
  1. package/dist/types/broken-pipe.d.ts +4 -3
  2. package/dist/types/canonical-log-dir.d.ts +20 -0
  3. package/dist/types/cli-metadata.d.ts +33 -0
  4. package/dist/types/cli.d.ts +12 -0
  5. package/dist/types/crash-fingerprint.d.ts +82 -0
  6. package/dist/types/crash-journal.d.ts +84 -0
  7. package/dist/types/crash-redaction.d.ts +20 -0
  8. package/dist/types/dirs.d.ts +127 -36
  9. package/dist/types/env-file.d.ts +59 -0
  10. package/dist/types/env.d.ts +19 -1
  11. package/dist/types/error-classification.d.ts +12 -0
  12. package/dist/types/fetch-retry.d.ts +6 -3
  13. package/dist/types/header-value.d.ts +10 -0
  14. package/dist/types/index.d.ts +6 -0
  15. package/dist/types/logger.d.ts +22 -8
  16. package/dist/types/native-process.d.ts +12 -0
  17. package/dist/types/procmgr.d.ts +1 -35
  18. package/dist/types/safe-error.d.ts +20 -0
  19. package/dist/types/sanitize-text.d.ts +10 -0
  20. package/dist/types/shell-config.d.ts +58 -0
  21. package/dist/types/stream.d.ts +2 -1
  22. package/dist/types/which.d.ts +1 -1
  23. package/package.json +2 -2
  24. package/src/broken-pipe.ts +16 -4
  25. package/src/canonical-log-dir.ts +59 -0
  26. package/src/cli-metadata.ts +74 -0
  27. package/src/cli.ts +30 -2
  28. package/src/crash-fingerprint.ts +294 -0
  29. package/src/crash-journal.ts +292 -0
  30. package/src/crash-redaction.ts +558 -0
  31. package/src/dirs.ts +583 -165
  32. package/src/env-file.ts +326 -20
  33. package/src/env.ts +321 -35
  34. package/src/error-classification.ts +31 -0
  35. package/src/fetch-retry.ts +23 -10
  36. package/src/frontmatter.ts +28 -2
  37. package/src/glob.ts +13 -10
  38. package/src/header-value.ts +15 -0
  39. package/src/index.ts +6 -0
  40. package/src/logger.ts +252 -97
  41. package/src/native-process.ts +19 -0
  42. package/src/postmortem.ts +1 -55
  43. package/src/procmgr.ts +16 -212
  44. package/src/ptree.ts +3 -2
  45. package/src/safe-error.ts +40 -0
  46. package/src/sanitize-text.ts +14 -0
  47. package/src/shell-config.ts +329 -0
  48. package/src/stream.ts +32 -1
  49. package/src/which.ts +6 -5
@@ -10,9 +10,10 @@ export declare function isKnownSinkPeerClosedError(error: unknown): boolean;
10
10
  */
11
11
  export declare function isBrokenPipeError(error: unknown): boolean;
12
12
  /**
13
- * A classifier for process-level stdout `EPIPE` errors. Its direct-write
14
- * evidence is private to each factory instance, so only the owner that
15
- * intercepted `process.stdout.write` can mark an error for this classifier.
13
+ * A classifier for process-level stdout peer-closed errors (`EPIPE`, `EIO`,
14
+ * `EBADF`). Its direct-write evidence is private to each factory instance, so
15
+ * only the owner that intercepted `process.stdout.write` can mark an error
16
+ * for this classifier.
16
17
  */
17
18
  export interface ProcessStdoutEpipeClassifier {
18
19
  markDirectProcessStdoutWriteError(error: unknown): void;
@@ -0,0 +1,20 @@
1
+ interface ProjectEnvSnapshotLike {
2
+ values: Record<string, string>;
3
+ dynamic: Set<string>;
4
+ }
5
+ interface CanonicalLogDirInput {
6
+ home: string;
7
+ env: Record<string, string | undefined>;
8
+ projectEnv: ProjectEnvSnapshotLike;
9
+ xdgEligible: boolean;
10
+ pathExists?: (target: string) => boolean;
11
+ }
12
+ /**
13
+ * Resolve the canonical user log directory without mutating the environment.
14
+ *
15
+ * The caller supplies filesystem existence checks so this leaf stays free of
16
+ * filesystem and resolver side effects. Both the production directory resolver
17
+ * and the test preload use this exact path selection logic.
18
+ */
19
+ export declare function resolveCanonicalLogsDir(input: CanonicalLogDirInput): string;
20
+ export {};
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Pure CLI metadata leaf.
3
+ *
4
+ * The actual `skc` entry (`packages/coding-agent/src/cli.ts`) must stay inert on
5
+ * import: no directory resolution, logger construction, NSS lookup, native load
6
+ * or environment probing may run before argv routing. `dirs.ts` performs all of
7
+ * those at module scope, so the handful of constants the entry really needs live
8
+ * here and `dirs.ts` re-exports them for its existing callers.
9
+ *
10
+ * Only build-time package metadata may be imported here.
11
+ */
12
+ /** App name (e.g. "skc") */
13
+ export declare const APP_NAME: string;
14
+ /** Version (e.g. "1.0.0") */
15
+ export declare const VERSION: string;
16
+ /** Minimum Bun version */
17
+ export declare const MIN_BUN_VERSION: string;
18
+ /**
19
+ * Build the diagnostic shown when the Bun runtime executing `skc` is older
20
+ * than {@link MIN_BUN_VERSION}. This is the most common Windows native-install
21
+ * failure (issue #525): `bun install -g sayknow-cli` probes a recent Bun while
22
+ * the `skc` launcher resolves an older Bun still on PATH. The message names the
23
+ * exact detected runtime path and gives a platform-specific upgrade + PATH fix
24
+ * instead of a bare `bun upgrade`.
25
+ *
26
+ * Pure and platform-parameterized so it can be unit-tested cross-platform.
27
+ */
28
+ export declare function formatBunRuntimeError(opts: {
29
+ currentVersion: string;
30
+ minVersion: string;
31
+ execPath?: string;
32
+ platform?: NodeJS.Platform;
33
+ }): string;
@@ -6,6 +6,8 @@ export interface FlagDescriptor<K extends "string" | "boolean" | "integer" = "st
6
6
  multiple?: boolean;
7
7
  options?: readonly string[];
8
8
  required?: boolean;
9
+ /** The string value may be omitted; parsers decide whether to consume it. */
10
+ optionalValue?: boolean;
9
11
  }
10
12
  export interface ArgDescriptor {
11
13
  kind: "string";
@@ -21,6 +23,8 @@ interface FlagInput {
21
23
  multiple?: boolean;
22
24
  options?: readonly string[];
23
25
  required?: boolean;
26
+ /** The string value may be omitted; parsers decide whether to consume it. */
27
+ optionalValue?: boolean;
24
28
  }
25
29
  interface ArgInput {
26
30
  description?: string;
@@ -107,6 +111,14 @@ export interface CommandEntry {
107
111
  name: string;
108
112
  load: () => Promise<CommandCtor>;
109
113
  aliases?: string[];
114
+ /** Owns parsing, help, loading and failures for this family when supplied. */
115
+ dispatch?: (argv: string[], context: CommandEntryContext) => Promise<void>;
116
+ }
117
+ export interface CommandEntryContext {
118
+ bin: string;
119
+ version: string;
120
+ /** Canonical registered entry name, including when invoked through an alias. */
121
+ command: string;
110
122
  }
111
123
  export interface RunOptions {
112
124
  bin: string;
@@ -0,0 +1,82 @@
1
+ /** Algorithm version recorded beside every emitted fingerprint. */
2
+ export declare const CRASH_FINGERPRINT_VERSION = 1;
3
+ /** Hex length of a published fingerprint (128 bits). */
4
+ export declare const CRASH_FINGERPRINT_HEX_LENGTH = 32;
5
+ /** Matches exactly a published fingerprint. */
6
+ export declare const CRASH_FINGERPRINT_PATTERN: RegExp;
7
+ /** Marker used for the machine-readable identity line of each crash record. */
8
+ export declare const CRASH_RECORD_MARKER = "skc-crash-record.v1";
9
+ /** Marker embedded in an external issue body, outside crash-derived blocks. */
10
+ export declare const CRASH_ISSUE_MARKER_PREFIX = "skc-crash-fp.v1:";
11
+ /** Literal frame used when a stack carries no in-app frame at all. */
12
+ export declare const NO_APP_FRAME = "<no-app-frame>";
13
+ export interface CrashFingerprintInput {
14
+ readonly name: string;
15
+ readonly message: string;
16
+ readonly stack: string;
17
+ }
18
+ export interface CrashFingerprint {
19
+ /** 32 lowercase hex characters. */
20
+ readonly fingerprint: string;
21
+ /** Algorithm version (`fpv`). */
22
+ readonly version: number;
23
+ readonly errorName: string;
24
+ /** Normalized, placeholder-substituted message class (safe to display). */
25
+ readonly messageClass: string;
26
+ /** Normalized in-app frames that participated in the digest. */
27
+ readonly frames: readonly string[];
28
+ }
29
+ export interface CrashFingerprintOptions {
30
+ /** Install root used to relativize in-app frames. Defaults to the SKC install root. */
31
+ readonly installRoot?: string;
32
+ /** Home directory used for `<home>` substitution. Defaults to `os.homedir()`. */
33
+ readonly homeDir?: string;
34
+ }
35
+ /**
36
+ * Replace absolute path-like tokens with `<home>` / `<path>`.
37
+ *
38
+ * Semantically meaningful codes are untouched: this rule only fires on tokens
39
+ * that are recognizably absolute filesystem paths.
40
+ */
41
+ export declare function replaceAbsolutePaths(text: string, homeDir?: string): string;
42
+ /**
43
+ * Typed message normalization.
44
+ *
45
+ * Deliberately *not* "strip all digits": HTTP statuses, exit codes and errno
46
+ * names are the difference between distinct crash classes, so runs of three or
47
+ * fewer digits and alphabetic error codes survive verbatim (`404` stays
48
+ * distinct from `500`). Only high-entropy identifiers are collapsed.
49
+ */
50
+ export declare function normalizeCrashMessage(message: string, options?: CrashFingerprintOptions): string;
51
+ /**
52
+ * Normalized in-app frames, newest first, capped at three.
53
+ *
54
+ * A frame is `<install-root-relative path>#<function>` with no line or column
55
+ * numbers. Stacks with no in-app frame yield the single literal
56
+ * `<no-app-frame>`; distinct roots can merge under that literal, which is an
57
+ * accepted and documented v1 property.
58
+ */
59
+ export declare function normalizeCrashFrames(stack: string, options?: CrashFingerprintOptions): string[];
60
+ /** Compute the v1 fingerprint of an already-captured fatal diagnostic. */
61
+ export declare function computeCrashFingerprint(input: CrashFingerprintInput, options?: CrashFingerprintOptions): CrashFingerprint;
62
+ /**
63
+ * Compute the stable identity used for a handled tool error.
64
+ *
65
+ * Tool failures often carry command output or other per-occurrence detail in
66
+ * their message. That text is useful in the record body but is not the failure
67
+ * identity: handled errors group by error class and the first in-app frame
68
+ * where the failure originated, rather than the full wrapper stack.
69
+ */
70
+ export declare function computeHandledErrorFingerprint(input: CrashFingerprintInput, options?: CrashFingerprintOptions): CrashFingerprint;
71
+ /** The machine-readable identity line appended to every new crash record. */
72
+ export declare function formatCrashRecordMarker(fingerprint: string, version: number, recordId: string): string;
73
+ export interface CrashRecordMarker {
74
+ readonly fingerprint: string;
75
+ readonly version: number;
76
+ readonly recordId: string;
77
+ }
78
+ /**
79
+ * Parse an identity line. Records written before this feature carry no marker
80
+ * and are therefore `unmatchable`: this parser never guesses at them.
81
+ */
82
+ export declare function parseCrashRecordMarker(line: string): CrashRecordMarker | undefined;
@@ -0,0 +1,84 @@
1
+ /** Hard cap for one journal line, including its newline. */
2
+ export declare const CRASH_EVENT_MAX_BYTES = 512;
3
+ /** Journal line format tag. */
4
+ export declare const CRASH_EVENT_KIND = "skc-crash-event.v1";
5
+ /** Preview cap for the message class carried by an event. */
6
+ export declare const CRASH_EVENT_MESSAGE_MAX_BYTES = 256;
7
+ /** Bounded execution provenance carried only on newly written occurrences. */
8
+ export type CrashProvenance = "product" | "eval" | "bun_test";
9
+ /**
10
+ * Classify only process-level harness modes that are explicit and stable.
11
+ * Everything else is product provenance, including source checkouts, CLI
12
+ * invocations, SDK/ACP hosts, and native-loader failures.
13
+ */
14
+ export declare function detectCrashProvenance(argv?: readonly string[], env?: NodeJS.ProcessEnv, execArgv?: readonly string[]): CrashProvenance;
15
+ export type CrashEvent = CrashOccurrenceEvent | CrashRefusedEvent | CrashReportedEvent | CrashRelayedEvent | CrashAcknowledgedEvent | CrashNudgedEvent;
16
+ export interface CrashOccurrenceEvent {
17
+ readonly kind: "occurrence";
18
+ readonly fingerprint: string;
19
+ readonly fpv: number;
20
+ readonly recordId: string;
21
+ readonly at: number;
22
+ readonly errorName: string;
23
+ readonly messageClass: string;
24
+ /** Legacy events omit this and are treated as product crashes. */
25
+ readonly provenance?: CrashProvenance;
26
+ }
27
+ export interface CrashRefusedEvent {
28
+ readonly kind: "refused";
29
+ readonly fingerprint: string;
30
+ readonly fpv: number;
31
+ readonly recordId: string;
32
+ readonly contractVersion: string;
33
+ readonly at: number;
34
+ }
35
+ export interface CrashReportedEvent {
36
+ readonly kind: "reported";
37
+ readonly fingerprint: string;
38
+ readonly at: number;
39
+ readonly issueUrl: string;
40
+ /** Set when the submission was a "+1" comment rather than a new issue. */
41
+ readonly commented?: boolean;
42
+ }
43
+ export interface CrashRelayedEvent {
44
+ readonly kind: "relayed";
45
+ readonly fingerprint: string;
46
+ readonly at: number;
47
+ /** Sentry event id accepted upstream: 32 lowercase hex. */
48
+ readonly eventId: string;
49
+ /** Exact occurrence record represented by the accepted envelope. Absent on legacy pre-record-id relay lines. */
50
+ readonly recordId?: string;
51
+ }
52
+ export interface CrashAcknowledgedEvent {
53
+ readonly kind: "acknowledged";
54
+ readonly fingerprint: string;
55
+ readonly at: number;
56
+ }
57
+ export interface CrashNudgedEvent {
58
+ readonly kind: "nudged";
59
+ readonly at: number;
60
+ }
61
+ /**
62
+ * Serialize one event to a single journal line (newline included), bounded to
63
+ * `CRASH_EVENT_MAX_BYTES`. Oversized message previews are shortened, and if the
64
+ * line still does not fit the preview is dropped entirely rather than the event.
65
+ */
66
+ export declare function formatCrashEventLine(event: CrashEvent): string;
67
+ /** Parse one journal line. Anything unexpected yields `undefined`, never a throw. */
68
+ export declare function parseCrashEventLine(line: string): CrashEvent | undefined;
69
+ /**
70
+ * Append one event synchronously with `O_APPEND`. Never throws.
71
+ *
72
+ * No parse, no lock, no rename, no read: the only I/O is one bounded write to
73
+ * an append-only file, so concurrent writers interleave whole lines instead of
74
+ * corrupting each other's records.
75
+ */
76
+ export declare function appendCrashEvent(event: CrashEvent, journalPath: string): boolean;
77
+ /**
78
+ * Fatal-path entry point: at most one journal append per process lifetime.
79
+ *
80
+ * A crash raised while handling a crash must not spend the process's remaining
81
+ * moments on journal bookkeeping, so the latch is never cleared — the process
82
+ * is exiting anyway. Returns whether this call wrote a line.
83
+ */
84
+ export declare function appendFatalCrashEvent(event: CrashEvent, journalPath: string): boolean;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Best-effort credential scrubbing for durable crash records.
3
+ *
4
+ * This is a *persistence-time* scrub: it keeps obvious credential shapes out of
5
+ * a file SKC keeps indefinitely. It is explicitly NOT a privacy guarantee and
6
+ * must never be treated as one for data that leaves the machine — outbound
7
+ * text goes through `sanitizeExternalCrashV1` instead.
8
+ */
9
+ /** Whether a field name ends in a credential label recognized by the redactor. */
10
+ export declare function isCredentialKey(key: string): boolean;
11
+ /**
12
+ * Scrub credential material from crash text before it is persisted.
13
+ * Covers bearer/basic-style headers, key=value or JSON key forms of common
14
+ * credential names, and well-known vendor token shapes. Normal messages and
15
+ * stack frames are untouched; matches are replaced in place so surrounding
16
+ * diagnostic context survives.
17
+ */
18
+ export declare function redactCrashSecrets(text: string): string;
19
+ /** Every placeholder `redactCrashSecrets` can emit. Used by downstream normalizers. */
20
+ export declare const CRASH_REDACTION_MARKERS: readonly string[];
@@ -5,37 +5,38 @@
5
5
  * config root and SKC_CODING_AGENT_DIR (legacy alias PI_CODING_AGENT_DIR) to
6
6
  * override the agent directory.
7
7
  *
8
- * On Linux, if XDG_DATA_HOME / XDG_STATE_HOME / XDG_CACHE_HOME environment
9
- * variables are set, paths are redirected to XDG-compliant locations under
10
- * $XDG_*_HOME/skc/. This requires running `skc config migrate` first to
11
- * move data to the new locations. No filesystem existence checks are performed
12
- * — if the env var is set, skc trusts that the migration has been done.
8
+ * On Linux and macOS, a category (data, state, cache) is redirected to
9
+ * $XDG_*_HOME/skc/ only when the trusted XDG_*_HOME variable is set, the
10
+ * $XDG_*_HOME/skc directory already exists (`skc config init-xdg` creates it),
11
+ * and the agent directory is the default. Otherwise paths stay under the config
12
+ * root. `init-xdg` only creates the directories; existing data is not moved.
13
13
  */
14
- /** App name (e.g. "skc") */
15
- export declare const APP_NAME: string;
14
+ import { canonicalEnvKey, type ProjectEnvSnapshot } from "./env-file";
15
+ /**
16
+ * Entry metadata lives in the pure `cli-metadata` leaf so the actual CLI entry can
17
+ * import it without loading this module's load-time directory resolution. These
18
+ * re-exports keep the existing `@sayknow-cli/utils/dirs` surface unchanged.
19
+ */
20
+ export { APP_NAME, formatBunRuntimeError, MIN_BUN_VERSION, VERSION } from "./cli-metadata";
21
+ export { canonicalEnvKey };
16
22
  /** Config directory name (e.g. ".skc") */
17
23
  export declare const CONFIG_DIR_NAME: string;
18
- /** Version (e.g. "1.0.0") */
19
- export declare const VERSION: string;
20
- /** Minimum Bun version */
21
- export declare const MIN_BUN_VERSION: string;
22
24
  /**
23
- * Build the diagnostic shown when the Bun runtime executing `skc` is older
24
- * than {@link MIN_BUN_VERSION}. This is the most common Windows native-install
25
- * failure (issue #525): `bun install -g sayknow-cli` probes a recent Bun while
26
- * the `skc` launcher resolves an older Bun still on PATH. The message names the
27
- * exact detected runtime path and gives a platform-specific upgrade + PATH fix
28
- * instead of a bare `bun upgrade`.
29
- *
30
- * Pure and platform-parameterized so it can be unit-tested cross-platform.
25
+ * On macOS, strip /private prefix only when both paths resolve to the same location.
26
+ * This preserves aliases like /private/tmp -> /tmp without rewriting unrelated paths.
31
27
  */
32
- export declare function formatBunRuntimeError(opts: {
33
- currentVersion: string;
34
- minVersion: string;
35
- execPath?: string;
36
- platform?: NodeJS.Platform;
37
- }): string;
28
+ export declare function standardizeMacOSPath(p: string): string;
38
29
  export declare function resolveEquivalentPath(inputPath: string): string;
30
+ /**
31
+ * Return a stable key for comparing existing filesystem paths.
32
+ *
33
+ * Windows path spelling is not a safe identity: most directories are
34
+ * case-insensitive, while directories can opt into case-sensitive names. Use
35
+ * the filesystem object's identity when it is available instead of folding
36
+ * path text. If the path does not exist or the filesystem has no usable inode,
37
+ * retain the resolved spelling so distinct case-sensitive paths never alias.
38
+ */
39
+ export declare function pathIdentityKey(inputPath: string): string;
39
40
  export declare function normalizePathForComparison(inputPath: string, platform?: NodeJS.Platform): string;
40
41
  /** Return whether a relative path crosses above its root or is unexpectedly absolute. */
41
42
  export declare function relativePathEscapesRoot(relative: string): boolean;
@@ -45,15 +46,54 @@ export declare function relativePathWithinRoot(root: string, candidate: string):
45
46
  export declare function getProjectDir(): string;
46
47
  /** Set the project directory. */
47
48
  export declare function setProjectDir(dir: string): void;
48
- export declare function getConfigDirName(): string;
49
- /** Get the config agent directory name relative to home (e.g. ".skc/agent" or PI_CONFIG_DIR + "/agent"). */
50
49
  export declare function getConfigAgentDirName(): string;
50
+ /**
51
+ * Project dotenv as captured when this module loaded.
52
+ * A later edit or delete of those files does not change the returned provenance.
53
+ */
54
+ export declare function loadedProjectEnvSnapshot(): ProjectEnvSnapshot;
51
55
  /** Get the config root directory (~/.skc). */
52
56
  export declare function getConfigRootDir(): string;
53
- /** Set the coding agent directory. Creates a fresh resolver, invalidating all cached paths. */
57
+ /**
58
+ * The authoritative home for user-scope state.
59
+ *
60
+ * Provenance-checked and resolved at call time: a home established or changed
61
+ * after this module loaded is honored, while a home the project dotenv could
62
+ * have planted is rejected in favor of the OS account database. See
63
+ * {@link resolveTrustedHome}.
64
+ */
65
+ export declare function getTrustedHomeDir(): string;
66
+ /** Trusted config root, resolved at call time; preserves the configured nested config-dir name. */
67
+ export declare function getTrustedConfigRootDir(): string;
68
+ /**
69
+ * Set the coding agent directory. Creates a fresh resolver, invalidating all
70
+ * cached paths.
71
+ *
72
+ * This also exports `SKC_CODING_AGENT_DIR`, so child processes inherit the same
73
+ * selection and resolve the same storage lane.
74
+ */
54
75
  export declare function setAgentDir(dir: string): void;
76
+ /**
77
+ * Rebuild the resolver from the current trusted environment. Callers that
78
+ * temporarily used {@link setAgentDir} must first restore the original
79
+ * SKC_CODING_AGENT_DIR / PI_CODING_AGENT_DIR values, then call this function;
80
+ * an originally absent override remains absent and keeps following HOME.
81
+ */
82
+ export declare function resetAgentDirFromEnvironment(): void;
55
83
  /** Get the agent config directory (~/.skc/agent). */
56
84
  export declare function getAgentDir(): string;
85
+ /** Resolver-owned profile classification, stable across HOME refreshes. */
86
+ export declare function getAgentProfileAuthority(): "default" | "custom";
87
+ export declare function getConfigDirName(): string;
88
+ /**
89
+ * Join a file under the provenance-checked agent directory, never the XDG
90
+ * state category. Automatic crash relay must not follow `XDG_STATE_HOME`:
91
+ * a checkout `.env` can set that variable and create `$XDG_STATE_HOME/skc`,
92
+ * which the ordinary state resolver would then treat as the crash store.
93
+ */
94
+ export declare function getTrustedAgentFile(filename: string): string;
95
+ /** Whether the current checkout declares an environment key in its `.env`. */
96
+ export declare function isProjectEnvDeclaration(name: string): boolean;
57
97
  /** Get the project-local config directory (.skc). */
58
98
  export declare function getProjectAgentDir(cwd?: string): string;
59
99
  /** Get the reports directory (~/.skc/reports). */
@@ -62,17 +102,43 @@ export declare function getReportsDir(): string;
62
102
  export declare function getLogsDir(): string;
63
103
  /** Get the path to a dated log file (~/.skc/logs/skc.YYYY-MM-DD.log). */
64
104
  export declare function getLogPath(date?: Date): string;
105
+ /**
106
+ * The logs directory the process actually reads and writes.
107
+ *
108
+ * A trusted `SKC_LOG_DIR` redirects the sink — the test preload pins it to a
109
+ * per-process temp directory so `bun test` stops appending fixture
110
+ * `level:error` records to the operator's shared log (issue #5618) — and
111
+ * everything else falls back to {@link getLogsDir}.
112
+ *
113
+ * The override is provenance-checked through the same {@link trustedValue} rule
114
+ * as the config and agent directories: Bun loads `cwd/.env` into `process.env`
115
+ * before any module runs, so a repository could otherwise redirect where the
116
+ * operator's production logs are written. A dynamic (`$`/backtick) declaration
117
+ * is rejected there too, which fails closed onto the canonical path.
118
+ *
119
+ * This is deliberately *not* folded into {@link getLogsDir}: that function's
120
+ * config-root semantics are pinned by tests, and the preload sets `SKC_LOG_DIR`
121
+ * for every test process, so folding the override in would move it under every
122
+ * test. Writers and readers must both come through here instead — the split
123
+ * between the two is what let the transport and the log readers disagree.
124
+ */
125
+ export declare function getEffectiveLogsDir(): string;
126
+ /** Get the dated log file under {@link getEffectiveLogsDir}. */
127
+ export declare function getEffectiveLogPath(date?: Date): string;
65
128
  /**
66
129
  * Get the plugins directory (~/.skc/plugins or its XDG equivalent).
67
130
  *
68
131
  * No-arg form (production callers) goes through the XDG-aware DirResolver so
69
- * reads and writes always agree. The optional `home` parameter is for test
70
- * isolation: when it differs from `os.homedir()` it short-circuits the resolver
71
- * and returns `<home>/<configDir>/plugins` so tests with a temp HOME get a
72
- * deterministic path. Passing `os.homedir()` explicitly is identical to the
73
- * no-arg form — XDG semantics are preserved.
132
+ * reads and writes always agree. The optional `home` parameter names an explicit
133
+ * home: when it differs from the authoritative home resolved right now it
134
+ * short-circuits the resolver and returns `<home>/<configDir>/plugins`, giving
135
+ * callers that carry their own home (and tests with a temp HOME) a deterministic
136
+ * path. Passing the authoritative home explicitly is identical to the no-arg
137
+ * form — XDG semantics are preserved.
74
138
  */
75
139
  export declare function getPluginsDir(home?: string): string;
140
+ /** Get the default-profile plugin directory without ambient profile authority. */
141
+ export declare function getDefaultPluginsDir(): string;
76
142
  /** Where npm installs packages (~/.skc/plugins/node_modules). */
77
143
  export declare function getPluginsNodeModules(): string;
78
144
  /** Plugin manifest (~/.skc/plugins/package.json). */
@@ -113,10 +179,14 @@ export declare function getGpuCachePath(): string;
113
179
  * cache file without touching the rest of the config root.
114
180
  */
115
181
  export declare function getGithubCacheDbPath(): string;
182
+ /** Get the durable tool-choice capability cache path. */
183
+ export declare function getToolChoiceCapabilityCachePath(): string;
116
184
  /** Get the resolved Claude Code client-version cache path (~/.skc/cache/claude-code-version.json). */
117
185
  export declare function getClaudeCodeVersionCachePath(): string;
118
186
  /** Get the natives directory (~/.skc/natives). */
119
187
  export declare function getNativesDir(): string;
188
+ /** Get the local tool-issue report database path (~/.skc/tool-issues.db). */
189
+ export declare function getToolIssuesDbPath(): string;
120
190
  /** Get the stats database path (~/.skc/stats.db). */
121
191
  export declare function getStatsDbPath(): string;
122
192
  /** Get the autoresearch state directory (~/.skc/autoresearch). */
@@ -137,6 +207,10 @@ export declare function getModelDbPath(agentDir?: string): string;
137
207
  export declare function getSessionsDir(agentDir?: string): string;
138
208
  /** Get the content-addressed blob store directory (~/.skc/agent/blobs). */
139
209
  export declare function getBlobsDir(agentDir?: string): string;
210
+ /** Get the resident-text cache root for a profile agent directory. */
211
+ export declare function getResidentCacheRootDir(profileAgentDir: string): string;
212
+ /** Get the managed cold-history sidecar cache root for a profile agent directory. */
213
+ export declare function getSidecarCacheRootDir(profileAgentDir: string): string;
140
214
  /** Get the custom themes directory (~/.skc/agent/themes). */
141
215
  export declare function getCustomThemesDir(agentDir?: string): string;
142
216
  /** Get the tools directory (~/.skc/agent/tools). */
@@ -153,6 +227,16 @@ export declare function getMemoriesDir(agentDir?: string): string;
153
227
  export declare function getTerminalSessionsDir(agentDir?: string): string;
154
228
  /** Get the crash log path (~/.skc/agent/skc-crash.log). */
155
229
  export declare function getCrashLogPath(agentDir?: string): string;
230
+ /** Get the crash event journal path (~/.skc/agent/skc-crash-events.jsonl). */
231
+ export declare function getCrashEventsPath(agentDir?: string): string;
232
+ /** Get the compacted crash signature index path (~/.skc/agent/skc-crash-index.json). */
233
+ export declare function getCrashIndexPath(agentDir?: string): string;
234
+ /** Get the handled error log path (~/.skc/agent/skc-error.log). */
235
+ export declare function getHandledErrorLogPath(agentDir?: string): string;
236
+ /** Get the handled error event journal path (~/.skc/agent/skc-error-events.jsonl). */
237
+ export declare function getHandledErrorEventsPath(agentDir?: string): string;
238
+ /** Get the compacted handled error signature index path (~/.skc/agent/skc-error-index.json). */
239
+ export declare function getHandledErrorIndexPath(agentDir?: string): string;
156
240
  /** Get the debug log path (~/.skc/agent/skc-debug.log). */
157
241
  export declare function getDebugLogPath(agentDir?: string): string;
158
242
  /** Get the project-level Python modules directory (.skc/modules). */
@@ -161,7 +245,14 @@ export declare function getProjectModulesDir(cwd?: string): string;
161
245
  export declare function getProjectPromptsDir(cwd?: string): string;
162
246
  /** Get the project-level plugin overrides path (.skc/plugin-overrides.json). */
163
247
  export declare function getProjectPluginOverridesPath(cwd?: string): string;
164
- /** Get the primary MCP config file path (first candidate). */
165
- export declare function getMCPConfigPath(scope: "user" | "project", cwd?: string): string;
248
+ /**
249
+ * Get the primary MCP config file path (first candidate).
250
+ *
251
+ * User scope lives in the agent directory, so a profile override
252
+ * (`--agent-dir`, `SKC_CODING_AGENT_DIR`, `setAgentDir()`) moves it. Pass
253
+ * `agentDir` to resolve the scope of a session whose agent directory differs
254
+ * from the process-wide one.
255
+ */
256
+ export declare function getMCPConfigPath(scope: "user" | "project", cwd?: string, agentDir?: string): string;
166
257
  /** Get the SSH config file path. */
167
- export declare function getSSHConfigPath(scope: "user" | "project", cwd?: string): string;
258
+ export declare function getSSHConfigPath(scope: "user" | "project", cwd?: string, agentDir?: string): string;
@@ -17,5 +17,64 @@ export declare function parseShellEnvFile(filePath: string): Record<string, stri
17
17
  * Ignores lines that are empty or start with '#'. Trims whitespace.
18
18
  * Allows values to be quoted with single or double quotes.
19
19
  * Returns an object of key-value pairs.
20
+ *
21
+ * The trust guards (`trustedAgentDirOverrideFor`, `trustedConfigDirName`,
22
+ * `filterCredentialInheritedEnv`) decide provenance by comparing
23
+ * `process.env` against this parse, so the accepted syntax must be a superset
24
+ * of what Bun's own dotenv loader honors in `cwd/.env`: `export KEY=value`,
25
+ * whitespace around `=` or `:`, and `#` comments after unquoted values, including
26
+ * a backslash before `#` (quotes keep their `#`). Values that Bun would expand
27
+ * (`$VAR`, `${VAR}`, backticks,
28
+ * command substitution) are kept as their literal text: the trust rule only
29
+ * needs the parser to see the key at all, and an operator environment value
30
+ * cannot equal attacker-written expansion text, so a literal parse stays
31
+ * conservative.
20
32
  */
21
33
  export declare function parseEnvFile(filePath: string): Record<string, string>;
34
+ /** Parse dotenv content that has already been read from a trusted file. */
35
+ export declare function parseEnvFileContent(content: string): Record<string, string>;
36
+ /**
37
+ * What the caller's checkout declares through its dotenv files.
38
+ *
39
+ * `values` is the merged declaration set, later layers winning. `dynamic` holds
40
+ * the keys whose surviving declaration is one Bun expands at load time, or whose
41
+ * raw text is quoted in a way this line parser does not decode. Provenance
42
+ * refuses those keys instead of comparing a value Bun may have loaded differently.
43
+ */
44
+ export interface ProjectEnvSnapshot {
45
+ values: Record<string, string>;
46
+ dynamic: Set<string>;
47
+ /** True when at least one project dotenv layer could not be read safely. */
48
+ incomplete?: boolean;
49
+ }
50
+ export interface ProjectEnvSnapshotOptions {
51
+ /** Maximum bytes read from each dotenv layer; unsafe files mark the snapshot incomplete. */
52
+ maxFileBytes?: number;
53
+ }
54
+ /**
55
+ * Windows environment variable names are case-insensitive, so a project dotenv
56
+ * line `userprofile=...` is what `process.env.USERPROFILE` resolves to. Every
57
+ * provenance lookup is spelled in upper case, so the snapshot must be keyed the
58
+ * same way or the declaration is invisible to the guard while still being live
59
+ * in the process. POSIX names are case-sensitive and must not fold.
60
+ */
61
+ export declare function canonicalEnvKey(name: string): string;
62
+ /**
63
+ * The layered dotenv declarations Bun overlays into `process.env` for a cwd.
64
+ *
65
+ * Lives in this leaf module so every consumer shares ONE notion of provenance.
66
+ * `dirs.ts` resolves the config, agent and log directories from it, and
67
+ * `scripts/test-preload.ts` decides test isolation from it — a second, narrower
68
+ * reader (only `cwd/.env`, its own regex, its own dynamic test) is how a
69
+ * `SKC_LOG_DIR` declared in `.env.local` / `.env.$NODE_ENV` came to be honored
70
+ * by the preload and then rejected in production, silently routing test log
71
+ * records to the operator's canonical sink. This module imports only `node:fs`,
72
+ * `node:path` and the import-free `./spawn-env`, so importing it has no side
73
+ * effects and cannot freeze resolver state the way importing `dirs.ts` would.
74
+ *
75
+ * `.env.local` is deliberately skipped when `NODE_ENV === "test"`, matching the
76
+ * convention that a local override file is not part of a test run.
77
+ */
78
+ /** Files Bun overlays for a cwd. Unset NODE_ENV selects the development layer. */
79
+ export declare function projectEnvLayerFiles(nodeEnv: string | undefined): string[];
80
+ export declare function projectEnvSnapshot(cwd?: string, options?: ProjectEnvSnapshotOptions): ProjectEnvSnapshot;
@@ -1,5 +1,10 @@
1
1
  export { filterProcessEnv, isSafeEnvName, isSafeEnvValue } from "./spawn-env";
2
2
  export { isValidEnvName, parseEnvFile, parseShellEnvFile } from "./env-file";
3
+ /** Project dotenv as it was when this module loaded, before a later `chdir` or file delete. */
4
+ export declare function startupProjectEnvSnapshot(): {
5
+ values: Record<string, string>;
6
+ dynamic: Set<string>;
7
+ };
3
8
  export declare function $inheritedEnv(name: string): string | undefined;
4
9
  /**
5
10
  * Intentional re-export of Bun.env.
@@ -25,13 +30,26 @@ export declare function $pickenv(...keys: string[]): string | undefined;
25
30
  * only use values explicitly inherited from the launching shell or SKC/user-owned config files.
26
31
  */
27
32
  export declare function $credentialEnv(name: string): string | undefined;
33
+ /**
34
+ * Resolve a credential that may rotate in the trusted agent `.env` while this
35
+ * process remains alive.
36
+ *
37
+ * Presence in the agent file establishes that file as the credential's
38
+ * authority, even when the launching shell inherited an older value. This is
39
+ * what lets an atomic token rotation repair a long-lived shell instead of
40
+ * falling back to its revoked snapshot. Removal is authoritative too, so a
41
+ * deleted token cannot silently reappear from that snapshot. Project `.env`
42
+ * files are never consulted; shell values retain their normal precedence for
43
+ * names the agent file does not own.
44
+ */
45
+ export declare function $rotatingCredentialEnv(name: string): string | undefined;
28
46
  /**
29
47
  * Resolve the first credential env value from the given keys, excluding cwd/.env overlays.
30
48
  */
31
49
  export declare function $pickCredentialEnv(...keys: string[]): string | undefined;
32
50
  /**
33
51
  * Parses a positive decimal integer from `$env[name]`.
34
- * Empty, invalid, NaN, zero, or negative values return `defaultValue`.
52
+ * Empty, invalid, unsafe, zero, or negative values return `defaultValue`.
35
53
  */
36
54
  export declare function $envpos(name: string, defaultValue: number): number;
37
55
  /** True when `BUN_ENV` or `NODE_ENV` is the string `test`. */
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Cross-package classification for errors that describe an expected outcome
3
+ * rather than an unexpected failure.
4
+ *
5
+ * The symbol is registered globally so the marker survives package duplication
6
+ * and worker/VM boundaries without relying on a particular Error constructor.
7
+ */
8
+ export declare const DESIGNED_ERROR: unique symbol;
9
+ /** Mark an Error as a designed outcome before it crosses package boundaries. */
10
+ export declare function markDesignedError<T extends Error>(error: T): T;
11
+ /** Return true when a throwable carries the trusted designed-outcome marker. */
12
+ export declare function isDesignedError(error: unknown): boolean;