@savvy-web/silk-effects 10.1.0 → 10.2.0

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.
@@ -1,10 +1,9 @@
1
- import { parseKitMarkdown } from "../utils/markdown-emit.js";
1
+ import { parseKitMarkdownBody } from "../utils/markdown-emit.js";
2
2
  import { contentStructure } from "../rules/content-structure.js";
3
3
  import { dependencyTableFormat } from "../rules/dependency-table-format.js";
4
4
  import { headingHierarchy } from "../rules/heading-hierarchy.js";
5
5
  import { requiredSections } from "../rules/required-sections.js";
6
6
  import { uncategorizedContent } from "../rules/uncategorized-content.js";
7
- import { stripFrontmatter } from "../utils/strip-frontmatter.js";
8
7
  import { readFileSync, readdirSync } from "node:fs";
9
8
  import { join } from "node:path";
10
9
 
@@ -141,7 +140,7 @@ var ChangesetLinter = class ChangesetLinter {
141
140
  * @returns Array of {@link LintMessage} diagnostics (empty if the content is valid)
142
141
  */
143
142
  static validateContent(content, filePath = "<input>") {
144
- const root = parseKitMarkdown(stripFrontmatter(content));
143
+ const root = parseKitMarkdownBody(content);
145
144
  return RULES.flatMap((rule) => rule(root).map((finding) => ({
146
145
  file: filePath,
147
146
  rule: finding.rule,
@@ -1,5 +1,5 @@
1
1
  import { Result } from "effect";
2
- import { Code, Markdown, MarkdownTransform, Mdast } from "@effected/markdown";
2
+ import { Code, Markdown, MarkdownParseOptions, MarkdownTransform, Mdast, Root } from "@effected/markdown";
3
3
 
4
4
  // src/changesets/utils/markdown-emit.ts
5
5
  /**
@@ -65,6 +65,31 @@ function parseKitMarkdown(text) {
65
65
  const parsed = unwrap(Markdown.parseResult(text), "markdown parse: canonical parse failed");
66
66
  return unwrap(Mdast.fromMdastResult(Mdast.toMdast(parsed)), "markdown parse: fidelity strip failed");
67
67
  }
68
+ const FRONTMATTER_CAPTURE = MarkdownParseOptions.make({ frontmatter: true });
69
+ /**
70
+ * Parse a changeset file into a kit root with its frontmatter block removed.
71
+ *
72
+ * @remarks
73
+ * Parses with the kit's frontmatter capture on, so a `---` block at offset 0
74
+ * is recognized whatever its line endings (CRLF included), then drops the
75
+ * `frontmatter` head node. The text is never sliced, so every remaining
76
+ * node's position is relative to the whole file — the lines a linter reports
77
+ * match the lines an editor shows. A fence pair later in the document is not
78
+ * frontmatter and parses as ordinary content.
79
+ *
80
+ * @param text - The raw changeset source, frontmatter included
81
+ * @returns The parsed kit root, without its frontmatter node
82
+ *
83
+ * @internal
84
+ */
85
+ function parseKitMarkdownBody(text) {
86
+ const parsed = unwrap(Markdown.parseResult(text, FRONTMATTER_CAPTURE), "markdown parse: canonical parse failed");
87
+ const body = new Root({
88
+ children: parsed.children.filter((node) => node.type !== "frontmatter"),
89
+ position: parsed.position
90
+ });
91
+ return unwrap(Mdast.fromMdastResult(Mdast.toMdast(body)), "markdown parse: fidelity strip failed");
92
+ }
68
93
  /**
69
94
  * Serialize a kit root, or a single kit block node, to canonical markdown.
70
95
  *
@@ -81,4 +106,4 @@ function emitKitMarkdown(node) {
81
106
  return unwrap(Markdown.stringifyResult(ensureFencedCode(node)), "markdown emit: canonical stringify failed");
82
107
  }
83
108
 
84
- export { emitKitMarkdown, parseKitMarkdown };
109
+ export { emitKitMarkdown, parseKitMarkdown, parseKitMarkdownBody };
@@ -1,22 +1,29 @@
1
1
  import { readCache, writeCache } from "./cache.js";
2
- import { Effect } from "effect";
2
+ import { Effect, Schema } from "effect";
3
3
  import { resolve } from "node:path";
4
4
  import { Git } from "@effected/git";
5
- import { ChildProcess, ChildProcessSpawner } from "effect/process";
5
+ import { Run } from "@effected/commands";
6
+ import { ChildProcess } from "effect/process";
6
7
 
7
8
  // src/commitlint/hook/diagnostics/open-issues.ts
8
9
  /**
9
10
  * Open-issue lookup via gh CLI, cached on disk.
10
11
  *
11
12
  * @remarks
12
- * The `gh` invocations are not git, so they stay hand-rolled — the spawn
13
- * mechanism moved from promisified `node:child_process.execFile` onto
14
- * `effect/process` `ChildProcess`. Any failure (gh missing, not
15
- * logged in, no repo, malformed JSON) degrades to `null`, preserving the
16
- * never-fails contract of the v3 implementation.
13
+ * The `gh` invocations run through `@effected/commands`' `Run`: the repo
14
+ * lookup is `Run.text`, the issue list is `Run.json` decoded against
15
+ * {@link OpenIssue}. Any failure (gh missing, not logged in, no repo, a
16
+ * non-zero exit, output that is not JSON or not an issue list) degrades to
17
+ * `null` and caches nothing, preserving the never-fails contract.
17
18
  *
18
19
  * @internal
19
20
  */
21
+ /** One open issue as `gh issue list --json number,title` prints it. */
22
+ const OpenIssue = Schema.Struct({
23
+ number: Schema.Number,
24
+ title: Schema.String
25
+ });
26
+ const OpenIssues = Schema.Array(OpenIssue);
20
27
  const ISSUES_CACHE_TTL_SECONDS = 600;
21
28
  /** Relative path under the project root where the open-issues cache lives. */
22
29
  const ISSUES_CACHE_RELATIVE_PATH = ".claude/cache/issues.json";
@@ -43,17 +50,16 @@ function readOpenIssuesFromCache(cachePath, ttlSeconds = 600) {
43
50
  }
44
51
  function fetchAndCacheOpenIssues(cachePath) {
45
52
  return Effect.gen(function* () {
46
- const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
47
- const repo = (yield* spawner.string(ChildProcess.make("gh", [
53
+ const repo = yield* Run.text(ChildProcess.make("gh", [
48
54
  "repo",
49
55
  "view",
50
56
  "--json",
51
57
  "nameWithOwner",
52
58
  "-q",
53
59
  ".nameWithOwner"
54
- ]))).trim();
60
+ ]));
55
61
  if (!repo) return null;
56
- const listStdout = yield* spawner.string(ChildProcess.make("gh", [
62
+ const parsed = [...yield* Run.json(ChildProcess.make("gh", [
57
63
  "issue",
58
64
  "list",
59
65
  "--repo",
@@ -64,8 +70,7 @@ function fetchAndCacheOpenIssues(cachePath) {
64
70
  "20",
65
71
  "--json",
66
72
  "number,title"
67
- ]));
68
- const parsed = yield* Effect.try(() => JSON.parse(listStdout));
73
+ ]), OpenIssues)];
69
74
  yield* writeCache(cachePath, parsed);
70
75
  return parsed;
71
76
  }).pipe(Effect.orElseSucceed(() => null));
@@ -78,4 +83,4 @@ function readOrFetchOpenIssues(cachePath) {
78
83
  });
79
84
  }
80
85
 
81
- export { ISSUES_CACHE_RELATIVE_PATH, fetchAndCacheOpenIssues, readOpenIssuesFromCache, readOrFetchOpenIssues, resolveIssuesCachePath };
86
+ export { ISSUES_CACHE_RELATIVE_PATH, OpenIssue, fetchAndCacheOpenIssues, readOpenIssuesFromCache, readOrFetchOpenIssues, resolveIssuesCachePath };
@@ -1,62 +1,35 @@
1
- import { existsSync } from "node:fs";
2
- import { join } from "node:path";
3
- import { readFile } from "node:fs/promises";
1
+ import { Result } from "effect";
2
+ import { detectPackageManagerSync } from "@effected/workspaces";
3
+ import { nodeSyncOps } from "@effected/workspaces/node-sync";
4
4
 
5
5
  // src/commitlint/hook/diagnostics/package-manager.ts
6
6
  /**
7
7
  * Package manager detection for the commit-time hooks.
8
8
  *
9
- * Mirrors the husky hook detection: prefer `package.json#devEngines.packageManager`
10
- * (the first entry when it is an array), then the legacy `packageManager` field,
11
- * then lockfile presence in priority order pnpm \> yarn \> bun \> npm.
9
+ * Delegates to `@effected/workspaces`' `detectPackageManagerSync` — the same
10
+ * detector `Lint`'s `Command.detectPackageManager` runs — so silk-effects has
11
+ * one answer to "which manager owns this root". The kit refuses to guess when
12
+ * no evidence matches (and fails on an unreadable root `package.json`); the
13
+ * hook hints still need a command to print, so those cases fall back to `npm`.
12
14
  *
13
15
  * @internal
14
16
  */
15
- const VALID_PMS = /* @__PURE__ */ new Set([
16
- "pnpm",
17
- "yarn",
18
- "bun",
19
- "npm"
20
- ]);
21
- const toPackageManager = (name) => typeof name === "string" && VALID_PMS.has(name) ? name : null;
22
- const devEnginesName = (manifest) => {
23
- const devEngines = manifest.devEngines;
24
- if (typeof devEngines !== "object" || devEngines === null) return void 0;
25
- const declared = devEngines.packageManager;
26
- const first = Array.isArray(declared) ? declared[0] : declared;
27
- return typeof first === "object" && first !== null ? first.name : void 0;
28
- };
29
- function parsePackageManagerField(packageJsonContent) {
30
- let parsed;
31
- try {
32
- parsed = JSON.parse(packageJsonContent);
33
- } catch {
34
- return null;
35
- }
36
- if (typeof parsed !== "object" || parsed === null) return null;
37
- const manifest = parsed;
38
- const fromDevEngines = toPackageManager(devEnginesName(manifest));
39
- if (fromDevEngines !== null) return fromDevEngines;
40
- const field = manifest.packageManager;
41
- if (typeof field !== "string" || field.length === 0) return null;
42
- return toPackageManager(field.split("@")[0]);
43
- }
44
- function detectFromLockfiles(presence) {
45
- if (presence.pnpm) return "pnpm";
46
- if (presence.yarn) return "yarn";
47
- if (presence.bun) return "bun";
48
- return "npm";
49
- }
17
+ /**
18
+ * Detect the package manager owning `root`, falling back to `npm`.
19
+ *
20
+ * @remarks
21
+ * Precedence is the kit's (`PackageManagerDetector`): `pnpm-workspace.yaml`,
22
+ * then a bun or yarn lockfile only when a manifest field also names that
23
+ * manager, then `package.json#workspaces` (npm), `pnpm-lock.yaml`,
24
+ * `package-lock.json`, and finally `devEngines.packageManager` over the
25
+ * top-level `packageManager` field. `root` is probed as given, without ascent.
26
+ *
27
+ * @param root - The directory to probe
28
+ * @returns The detected manager, or `"npm"` when none can be attributed
29
+ */
50
30
  async function detectPackageManager(root) {
51
- try {
52
- const fromField = parsePackageManagerField(await readFile(join(root, "package.json"), "utf8"));
53
- if (fromField !== null) return fromField;
54
- } catch {}
55
- return detectFromLockfiles({
56
- pnpm: existsSync(join(root, "pnpm-lock.yaml")),
57
- yarn: existsSync(join(root, "yarn.lock")),
58
- bun: existsSync(join(root, "bun.lock"))
59
- });
31
+ const detected = detectPackageManagerSync(root, nodeSyncOps);
32
+ return Result.isSuccess(detected) ? detected.success.name : "npm";
60
33
  }
61
34
 
62
- export { detectFromLockfiles, detectPackageManager, parsePackageManagerField };
35
+ export { detectPackageManager };
@@ -18,7 +18,7 @@ import { inferTicketId, readBranchInfo } from "./hook/diagnostics/branch.js";
18
18
  import { readCache, writeCache } from "./hook/diagnostics/cache.js";
19
19
  import { parseHuskyConfigPath, readCommitlintConfigPath } from "./hook/diagnostics/commitlint-config.js";
20
20
  import { ISSUES_CACHE_RELATIVE_PATH, fetchAndCacheOpenIssues, readOpenIssuesFromCache, readOrFetchOpenIssues, resolveIssuesCachePath } from "./hook/diagnostics/open-issues.js";
21
- import { detectFromLockfiles, detectPackageManager, parsePackageManagerField } from "./hook/diagnostics/package-manager.js";
21
+ import { detectPackageManager } from "./hook/diagnostics/package-manager.js";
22
22
  import { buildSigningDiagnostic, parseGpgKeyExpiry, readSigningDiagnostic } from "./hook/diagnostics/signing.js";
23
23
  import { closesTrailerRule, hasClosingTrailer } from "./hook/rules/closes-trailer.js";
24
24
  import { forbiddenContentRule } from "./hook/rules/forbidden-content.js";
@@ -69,7 +69,6 @@ var commitlint_exports = /* @__PURE__ */ __exportAll({
69
69
  default: () => CommitlintConfig,
70
70
  defaultPromptConfig: () => defaultPromptConfig,
71
71
  detectDCO: () => detectDCO,
72
- detectFromLockfiles: () => detectFromLockfiles,
73
72
  detectPackageManager: () => detectPackageManager,
74
73
  detectScopes: () => detectScopes,
75
74
  emojiPromptConfig: () => emojiPromptConfig,
@@ -84,7 +83,6 @@ var commitlint_exports = /* @__PURE__ */ __exportAll({
84
83
  parseBashCommand: () => parseBashCommand,
85
84
  parseGpgKeyExpiry: () => parseGpgKeyExpiry,
86
85
  parseHuskyConfigPath: () => parseHuskyConfigPath,
87
- parsePackageManagerField: () => parsePackageManagerField,
88
86
  partitionHits: () => partitionHits,
89
87
  planLeakageRule: () => planLeakageRule,
90
88
  postToolUseAdvise: () => postToolUseAdvise,
@@ -141,4 +139,4 @@ var CommitlintConfig = class {
141
139
  constructor() {}
142
140
  };
143
141
 
144
- export { COMMIT_TYPES, COMMIT_TYPE_DEFINITIONS, CommitlintConfig, CommitlintConfig as default, DCO_SIGNOFF_TEXT, ERROR_EXPLANATIONS, ERROR_SUGGESTIONS, HookSilencer, ISSUES_CACHE_RELATIVE_PATH, PostToolUseEnvelope, PreToolUseEnvelope, SessionStartEnvelope, TDD_SCOPE_PATTERN, TDD_STATES, TYPE_EMOJIS, TYPE_EMOJIS_UNICODE, buildSigningDiagnostic, closesTrailerRule, commitlint_exports, createPromptConfig, createTypeEnum, defaultPromptConfig, detectDCO, detectFromLockfiles, detectPackageManager, detectScopes, emojiPromptConfig, fetchAndCacheOpenIssues, forbiddenContentRule, format, getExplanation, getSuggestion, getTypeEmoji, hasClosingTrailer, inferTicketId, parseBashCommand, parseGpgKeyExpiry, parseHuskyConfigPath, parsePackageManagerField, partitionHits, planLeakageRule, postToolUseAdvise, preToolUseAdvise, preToolUseAllow, preToolUseDeny, preToolUseSilent, prompter, readBranchInfo, readCache, readCommitlintConfigPath, readOpenIssuesFromCache, readOrFetchOpenIssues, readSigningDiagnostic, resolveIssuesCachePath, sessionStartContext, signingFlagConflictRule, softWrapRule, staticConfig, verbosityRule, writeCache };
142
+ export { COMMIT_TYPES, COMMIT_TYPE_DEFINITIONS, CommitlintConfig, CommitlintConfig as default, DCO_SIGNOFF_TEXT, ERROR_EXPLANATIONS, ERROR_SUGGESTIONS, HookSilencer, ISSUES_CACHE_RELATIVE_PATH, PostToolUseEnvelope, PreToolUseEnvelope, SessionStartEnvelope, TDD_SCOPE_PATTERN, TDD_STATES, TYPE_EMOJIS, TYPE_EMOJIS_UNICODE, buildSigningDiagnostic, closesTrailerRule, commitlint_exports, createPromptConfig, createTypeEnum, defaultPromptConfig, detectDCO, detectPackageManager, detectScopes, emojiPromptConfig, fetchAndCacheOpenIssues, forbiddenContentRule, format, getExplanation, getSuggestion, getTypeEmoji, hasClosingTrailer, inferTicketId, parseBashCommand, parseGpgKeyExpiry, parseHuskyConfigPath, partitionHits, planLeakageRule, postToolUseAdvise, preToolUseAdvise, preToolUseAllow, preToolUseDeny, preToolUseSilent, prompter, readBranchInfo, readCache, readCommitlintConfigPath, readOpenIssuesFromCache, readOrFetchOpenIssues, readSigningDiagnostic, resolveIssuesCachePath, sessionStartContext, signingFlagConflictRule, softWrapRule, staticConfig, verbosityRule, writeCache };
package/index.d.ts CHANGED
@@ -975,8 +975,8 @@ interface LintMessage {
975
975
  * Line number where the issue was detected (1-based).
976
976
  *
977
977
  * @remarks
978
- * Line numbers are relative to the content after YAML frontmatter
979
- * stripping (the blank line after the closing `---` counts as line 1).
978
+ * Line numbers are relative to the whole file, frontmatter block
979
+ * included, so they match the lines an editor shows.
980
980
  */
981
981
  line: number;
982
982
  /**
@@ -5456,10 +5456,13 @@ declare function readCache<T>(path: string, ttlSeconds: number): Effect.Effect<T
5456
5456
  declare function writeCache<T>(path: string, data: T, when?: Date): Effect.Effect<void>;
5457
5457
  declare function parseHuskyConfigPath(huskyContent: string, root: string): string | null;
5458
5458
  declare function readCommitlintConfigPath(root: string): Promise<string | null>;
5459
- interface OpenIssue {
5460
- number: number;
5461
- title: string;
5462
- }
5459
+ /** One open issue as `gh issue list --json number,title` prints it. */
5460
+ declare const OpenIssue: Schema.Struct<{
5461
+ readonly number: Schema.Number;
5462
+ readonly title: Schema.String;
5463
+ }>;
5464
+ /** The decoded {@link (OpenIssue:variable)}. */
5465
+ type OpenIssue = typeof OpenIssue.Type;
5463
5466
  declare const ISSUES_CACHE_TTL_SECONDS = 600;
5464
5467
  /** Relative path under the project root where the open-issues cache lives. */
5465
5468
  declare const ISSUES_CACHE_RELATIVE_PATH = ".claude/cache/issues.json";
@@ -5480,13 +5483,19 @@ declare function readOpenIssuesFromCache(cachePath: string, ttlSeconds?: number)
5480
5483
  declare function fetchAndCacheOpenIssues(cachePath: string): Effect.Effect<OpenIssue[] | null, never, ChildProcessSpawner.ChildProcessSpawner>;
5481
5484
  declare function readOrFetchOpenIssues(cachePath: string): Effect.Effect<OpenIssue[] | null, never, ChildProcessSpawner.ChildProcessSpawner>;
5482
5485
  type PackageManager$1 = "pnpm" | "yarn" | "bun" | "npm";
5483
- interface LockfilePresence {
5484
- pnpm: boolean;
5485
- yarn: boolean;
5486
- bun: boolean;
5487
- }
5488
- declare function parsePackageManagerField(packageJsonContent: string): PackageManager$1 | null;
5489
- declare function detectFromLockfiles(presence: LockfilePresence): PackageManager$1;
5486
+ /**
5487
+ * Detect the package manager owning `root`, falling back to `npm`.
5488
+ *
5489
+ * @remarks
5490
+ * Precedence is the kit's (`PackageManagerDetector`): `pnpm-workspace.yaml`,
5491
+ * then a bun or yarn lockfile only when a manifest field also names that
5492
+ * manager, then `package.json#workspaces` (npm), `pnpm-lock.yaml`,
5493
+ * `package-lock.json`, and finally `devEngines.packageManager` over the
5494
+ * top-level `packageManager` field. `root` is probed as given, without ascent.
5495
+ *
5496
+ * @param root - The directory to probe
5497
+ * @returns The detected manager, or `"npm"` when none can be attributed
5498
+ */
5490
5499
  declare function detectPackageManager(root: string): Promise<PackageManager$1>;
5491
5500
  interface SigningDiagnostic {
5492
5501
  format: "gpg" | "ssh" | "none";
@@ -5591,7 +5600,7 @@ declare const VERBOSITY_LINE_THRESHOLD = 12;
5591
5600
  declare const VERBOSITY_WORD_THRESHOLD = 150;
5592
5601
  declare const verbosityRule: Rule<VerbosityInput, never>;
5593
5602
  declare namespace index_d_exports$1 {
5594
- export { BranchInfo, COMMIT_TYPES, COMMIT_TYPE_DEFINITIONS, ClosesTrailerCtx, ClosesTrailerInput, CommitType, CommitTypeDefinition, CommitlintConfig, CommitlintPlugin, CommitlintUserConfig, ConfigOptions, DCO_SIGNOFF_TEXT, DEFAULT_BODY_MAX_LINE_LENGTH, ERROR_EXPLANATIONS, ERROR_SUGGESTIONS, ForbiddenContentInput, FormatterResult, HookOutput, RuleSeverity$1 as HookRuleSeverity, HookSilencer, ISSUES_CACHE_RELATIVE_PATH, ISSUES_CACHE_TTL_SECONDS, Inquirer, LintOutcome, LockfilePresence, OpenIssue, PackageManager$1 as PackageManager, ParsedCommit, ParsedKind, PlanLeakageInput, PostToolUseEnvelope, PostToolUseEnvelope as PostToolUseEnvelopeType, PreToolUseEnvelope, PreToolUseEnvelope as PreToolUseEnvelopeType, PromptConfig, PromptConfigOptions, PromptQuestion, PromptSettings, PrompterOptions, Question, RawSigningInputs, ReleaseFormat, ResolvedPromptConfig, Rule, RuleApplicability, RuleConfigTuple, RuleHit, RuleResult, RuleSeverity, RulesConfig, SessionStartEnvelope, SessionStartEnvelope as SessionStartEnvelopeType, SigningDiagnostic, SigningFlagCtx, SigningFlagInput, SoftWrapInput, TDD_SCOPE_PATTERN, TDD_STATES, TYPE_EMOJIS, TYPE_EMOJIS_UNICODE, TypeEnumEntry, VERBOSITY_LINE_THRESHOLD, VERBOSITY_WORD_THRESHOLD, VerbosityInput, buildSigningDiagnostic, closesTrailerRule, createPromptConfig, createTypeEnum, CommitlintConfig as default, defaultPromptConfig, detectDCO, detectFromLockfiles, detectPackageManager, detectScopes, emojiPromptConfig, fetchAndCacheOpenIssues, forbiddenContentRule, format, getExplanation, getSuggestion, getTypeEmoji, hasClosingTrailer, inferTicketId, parseBashCommand, parseGpgKeyExpiry, parseHuskyConfigPath, parsePackageManagerField, partitionHits, planLeakageRule, postToolUseAdvise, preToolUseAdvise, preToolUseAllow, preToolUseDeny, preToolUseSilent, prompter, readBranchInfo, readCache, readCommitlintConfigPath, readOpenIssuesFromCache, readOrFetchOpenIssues, readSigningDiagnostic, resolveIssuesCachePath, sessionStartContext, signingFlagConflictRule, softWrapRule, staticConfig, verbosityRule, writeCache };
5603
+ export { BranchInfo, COMMIT_TYPES, COMMIT_TYPE_DEFINITIONS, ClosesTrailerCtx, ClosesTrailerInput, CommitType, CommitTypeDefinition, CommitlintConfig, CommitlintPlugin, CommitlintUserConfig, ConfigOptions, DCO_SIGNOFF_TEXT, DEFAULT_BODY_MAX_LINE_LENGTH, ERROR_EXPLANATIONS, ERROR_SUGGESTIONS, ForbiddenContentInput, FormatterResult, HookOutput, RuleSeverity$1 as HookRuleSeverity, HookSilencer, ISSUES_CACHE_RELATIVE_PATH, ISSUES_CACHE_TTL_SECONDS, Inquirer, LintOutcome, OpenIssue, PackageManager$1 as PackageManager, ParsedCommit, ParsedKind, PlanLeakageInput, PostToolUseEnvelope, PostToolUseEnvelope as PostToolUseEnvelopeType, PreToolUseEnvelope, PreToolUseEnvelope as PreToolUseEnvelopeType, PromptConfig, PromptConfigOptions, PromptQuestion, PromptSettings, PrompterOptions, Question, RawSigningInputs, ReleaseFormat, ResolvedPromptConfig, Rule, RuleApplicability, RuleConfigTuple, RuleHit, RuleResult, RuleSeverity, RulesConfig, SessionStartEnvelope, SessionStartEnvelope as SessionStartEnvelopeType, SigningDiagnostic, SigningFlagCtx, SigningFlagInput, SoftWrapInput, TDD_SCOPE_PATTERN, TDD_STATES, TYPE_EMOJIS, TYPE_EMOJIS_UNICODE, TypeEnumEntry, VERBOSITY_LINE_THRESHOLD, VERBOSITY_WORD_THRESHOLD, VerbosityInput, buildSigningDiagnostic, closesTrailerRule, createPromptConfig, createTypeEnum, CommitlintConfig as default, defaultPromptConfig, detectDCO, detectPackageManager, detectScopes, emojiPromptConfig, fetchAndCacheOpenIssues, forbiddenContentRule, format, getExplanation, getSuggestion, getTypeEmoji, hasClosingTrailer, inferTicketId, parseBashCommand, parseGpgKeyExpiry, parseHuskyConfigPath, partitionHits, planLeakageRule, postToolUseAdvise, preToolUseAdvise, preToolUseAllow, preToolUseDeny, preToolUseSilent, prompter, readBranchInfo, readCache, readCommitlintConfigPath, readOpenIssuesFromCache, readOrFetchOpenIssues, readSigningDiagnostic, resolveIssuesCachePath, sessionStartContext, signingFlagConflictRule, softWrapRule, staticConfig, verbosityRule, writeCache };
5595
5604
  }
5596
5605
  /**
5597
5606
  * Dynamic commitlint configuration factory.
@@ -8021,6 +8030,15 @@ declare class ReposLockdown extends ReposLockdown_base {
8021
8030
  */
8022
8031
  static readonly layer: Layer.Layer<ReposLockdown, never, FileSystem.FileSystem | Path.Path>;
8023
8032
  }
8033
+ /**
8034
+ * Default repo name for `add`: the last segment of the remote's repository
8035
+ * path, as `@effected/git`'s `GitRemote.repositoryPath` reads it (one trailing
8036
+ * `/` and one trailing `.git` dropped). Every remote form git accepts is
8037
+ * covered, scp without a slash included — `git@host:repo.git` is `repo`.
8038
+ *
8039
+ * @internal
8040
+ */
8041
+ declare const repoSlug: (url: string) => string;
8024
8042
  /**
8025
8043
  * Minimum age (in milliseconds) a `.lock` file must reach before `sync` will
8026
8044
  * remove it. An ACTIVE git process can legitimately hold
@@ -8151,7 +8169,7 @@ declare class ReposManager extends ReposManager_base {
8151
8169
  static readonly layer: Layer.Layer<ReposManager, never, ReposConfigStore | Git | FileSystem.FileSystem | Path.Path | ReposLockdown>;
8152
8170
  }
8153
8171
  declare namespace index_d_exports$3 {
8154
- export { DriftKind, GitSubmoduleError, GitSubmoduleErrorBase, MANIFEST_PATH, NOTE_LIMIT, NoteNotFoundError, NoteNotFoundErrorBase, REPOS_DIR, RepoDrift, RepoEntry, RepoName, RepoNotFoundError, RepoNotFoundErrorBase, RepoNote, RepoOrientation, RepoStatusEntry, ReposAddResult, ReposConfigError, ReposConfigErrorBase, ReposConfigStore, ReposConfigStoreShape, ReposDeregisterResult, ReposDrift, ReposDriftReport, ReposDriftShape, ReposLockdown, ReposLockdownError, ReposLockdownErrorBase, ReposLockdownShape, ReposManager, ReposManagerShape, ReposManifestFile, ReposNoteResult, ReposPinResult, ReposRemoveResult, ReposRenameResult, ReposRestoreResult, ReposStatusReport, ReposSyncReport, STALE_LOCK_MAX_AGE_MS, resolveModuleDir, submoduleNameFromKey };
8172
+ export { DriftKind, GitSubmoduleError, GitSubmoduleErrorBase, MANIFEST_PATH, NOTE_LIMIT, NoteNotFoundError, NoteNotFoundErrorBase, REPOS_DIR, RepoDrift, RepoEntry, RepoName, RepoNotFoundError, RepoNotFoundErrorBase, RepoNote, RepoOrientation, RepoStatusEntry, ReposAddResult, ReposConfigError, ReposConfigErrorBase, ReposConfigStore, ReposConfigStoreShape, ReposDeregisterResult, ReposDrift, ReposDriftReport, ReposDriftShape, ReposLockdown, ReposLockdownError, ReposLockdownErrorBase, ReposLockdownShape, ReposManager, ReposManagerShape, ReposManifestFile, ReposNoteResult, ReposPinResult, ReposRemoveResult, ReposRenameResult, ReposRestoreResult, ReposStatusReport, ReposSyncReport, STALE_LOCK_MAX_AGE_MS, repoSlug, resolveModuleDir, submoduleNameFromKey };
8155
8173
  }
8156
8174
  /**
8157
8175
  * Strip leading semver range operators from a version string.
@@ -1,7 +1,8 @@
1
1
  import { Command } from "../utils/Command.js";
2
2
  import { Filter } from "../utils/Filter.js";
3
- import { Data, Effect, FileSystem, Path, Schema, Stream } from "effect";
3
+ import { Data, Effect, FileSystem, Option, Path, Schema, Stream } from "effect";
4
4
  import { Git } from "@effected/git";
5
+ import { Walker } from "@effected/walker";
5
6
  import { ChildProcess, ChildProcessSpawner } from "effect/process";
6
7
 
7
8
  // src/lint/handlers/TextFiles.ts
@@ -279,12 +280,8 @@ var TextFiles = class TextFiles {
279
280
  const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
280
281
  const fs = yield* FileSystem.FileSystem;
281
282
  const pathService = yield* Path.Path;
282
- let cwd = pathService.dirname(path);
283
- while (!(yield* fs.exists(cwd))) {
284
- const parent = pathService.dirname(cwd);
285
- if (parent === cwd) break;
286
- cwd = parent;
287
- }
283
+ const chain = yield* Walker.ascend(pathService.dirname(path));
284
+ const cwd = Option.getOrElse(yield* Walker.firstMatch(chain, (dir) => fs.exists(dir)), () => chain[chain.length - 1] ?? pathService.dirname(path));
288
285
  const relative = pathService.relative(cwd, path).split(pathService.sep).join("/");
289
286
  const command = ChildProcess.make("git", [
290
287
  "cat-file",
@@ -2,10 +2,10 @@ import { Result } from "effect";
2
2
  import { existsSync } from "node:fs";
3
3
  import { dirname, join, resolve } from "node:path";
4
4
  import { detectPackageManagerSync } from "@effected/workspaces";
5
- import { execSync } from "node:child_process";
6
5
  import { LocalExec, Tool, makeToolDiscoverySync } from "@effected/commands";
7
- import { nodeSpawnSync } from "@effected/commands/node-sync";
8
6
  import { nodeSyncOps } from "@effected/workspaces/node-sync";
7
+ import { execSync } from "node:child_process";
8
+ import { nodeSpawnSync } from "@effected/commands/node-sync";
9
9
 
10
10
  // src/lint/utils/Command.ts
11
11
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@savvy-web/silk-effects",
3
- "version": "10.1.0",
3
+ "version": "10.2.0",
4
4
  "private": false,
5
5
  "description": "Shared Effect library for Silk Suite conventions",
6
6
  "homepage": "https://github.com/savvy-web/systems/tree/main/packages/silk-effects",
@@ -38,20 +38,20 @@
38
38
  "@changesets/pre": "^3.0.0",
39
39
  "@changesets/read": "^1.0.1",
40
40
  "@effected/github-references": "^0.11.0",
41
- "@effected/glob": "^0.14.0",
42
- "@effected/jsonc": "^0.20.0",
41
+ "@effected/glob": "^0.15.0",
42
+ "@effected/jsonc": "^0.20.1",
43
43
  "@effected/markdown": "^0.23.0",
44
- "@effected/package-json": "^0.26.0",
44
+ "@effected/package-json": "^0.26.1",
45
45
  "@effected/toml": "^0.16.0",
46
- "@effected/walker": "^0.20.1",
46
+ "@effected/walker": "^0.21.0",
47
47
  "@effected/yaml": "^0.25.1",
48
48
  "@manypkg/get-packages": "^3.1.0",
49
49
  "@savvy-web/silk-core": "^0.5.2",
50
50
  "shell-quote": "^1.12.0"
51
51
  },
52
52
  "peerDependencies": {
53
- "@effected/commands": "^0.16.0",
54
- "@effected/git": "^0.25.0",
53
+ "@effected/commands": "^0.17.0",
54
+ "@effected/git": "^0.26.0",
55
55
  "@effected/templates": "^0.14.0",
56
56
  "@effected/workspaces": "^0.39.0",
57
57
  "effect": "^4.0.0"
package/repos/index.js CHANGED
@@ -7,7 +7,7 @@ import { RepoStatusEntry, ReposAddResult, ReposDeregisterResult, ReposNoteResult
7
7
  import { ReposConfigStore } from "./services/config-store.js";
8
8
  import { ReposLockdown, resolveModuleDir } from "./services/lockdown.js";
9
9
  import { ReposDrift, submoduleNameFromKey } from "./services/drift.js";
10
- import { ReposManager, STALE_LOCK_MAX_AGE_MS } from "./services/manager.js";
10
+ import { ReposManager, STALE_LOCK_MAX_AGE_MS, repoSlug } from "./services/manager.js";
11
11
 
12
12
  // src/repos/index.ts
13
13
  var repos_exports = /* @__PURE__ */ __exportAll({
@@ -47,8 +47,9 @@ var repos_exports = /* @__PURE__ */ __exportAll({
47
47
  ReposStatusReport: () => ReposStatusReport,
48
48
  ReposSyncReport: () => ReposSyncReport,
49
49
  STALE_LOCK_MAX_AGE_MS: () => STALE_LOCK_MAX_AGE_MS,
50
+ repoSlug: () => repoSlug,
50
51
  resolveModuleDir: () => resolveModuleDir,
51
52
  submoduleNameFromKey: () => submoduleNameFromKey
52
53
  });
53
54
 
54
- export { DriftKind, GitSubmoduleError, GitSubmoduleErrorBase, MANIFEST_PATH, NoteNotFoundError, NoteNotFoundErrorBase, REPOS_DIR, RepoDrift, RepoEntry, RepoName, RepoNotFoundError, RepoNotFoundErrorBase, RepoNote, RepoOrientation, RepoStatusEntry, ReposAddResult, ReposConfigError, ReposConfigErrorBase, ReposConfigStore, ReposDeregisterResult, ReposDrift, ReposDriftReport, ReposLockdown, ReposLockdownError, ReposLockdownErrorBase, ReposManager, ReposManifestFile, ReposNoteResult, ReposPinResult, ReposRemoveResult, ReposRenameResult, ReposRestoreResult, ReposStatusReport, ReposSyncReport, STALE_LOCK_MAX_AGE_MS, repos_exports, resolveModuleDir, submoduleNameFromKey };
55
+ export { DriftKind, GitSubmoduleError, GitSubmoduleErrorBase, MANIFEST_PATH, NoteNotFoundError, NoteNotFoundErrorBase, REPOS_DIR, RepoDrift, RepoEntry, RepoName, RepoNotFoundError, RepoNotFoundErrorBase, RepoNote, RepoOrientation, RepoStatusEntry, ReposAddResult, ReposConfigError, ReposConfigErrorBase, ReposConfigStore, ReposDeregisterResult, ReposDrift, ReposDriftReport, ReposLockdown, ReposLockdownError, ReposLockdownErrorBase, ReposManager, ReposManifestFile, ReposNoteResult, ReposPinResult, ReposRemoveResult, ReposRenameResult, ReposRestoreResult, ReposStatusReport, ReposSyncReport, STALE_LOCK_MAX_AGE_MS, repoSlug, repos_exports, resolveModuleDir, submoduleNameFromKey };
@@ -5,7 +5,7 @@ import { ReposConfigStore } from "./config-store.js";
5
5
  import { ReposLockdown, resolveModuleDir } from "./lockdown.js";
6
6
  import { submoduleNameFromKey } from "./drift.js";
7
7
  import { Clock, Context, Effect, Exit, FileSystem, Layer, Option, Path, Result, Schema } from "effect";
8
- import { Git, GitConfig, Gitmodules, LsRemoteEntry } from "@effected/git";
8
+ import { Git, GitConfig, GitRemote, Gitmodules, LsRemoteEntry } from "@effected/git";
9
9
  import { createHash } from "node:crypto";
10
10
 
11
11
  // src/repos/services/manager.ts
@@ -15,6 +15,22 @@ import { createHash } from "node:crypto";
15
15
  */
16
16
  const STALE_LOCKS = ["index.lock", "shallow.lock"];
17
17
  /**
18
+ * Default repo name for `add`: the last segment of the remote's repository
19
+ * path, as `@effected/git`'s `GitRemote.repositoryPath` reads it (one trailing
20
+ * `/` and one trailing `.git` dropped). Every remote form git accepts is
21
+ * covered, scp without a slash included — `git@host:repo.git` is `repo`.
22
+ *
23
+ * @internal
24
+ */
25
+ const repoSlug = (url) => {
26
+ const trimmed = url.trim();
27
+ const path = Option.match(GitRemote.parse(trimmed), {
28
+ onNone: () => trimmed,
29
+ onSome: GitRemote.repositoryPath
30
+ });
31
+ return path.split("/").filter((segment) => segment.length > 0).pop() ?? path;
32
+ };
33
+ /**
18
34
  * Minimum age (in milliseconds) a `.lock` file must reach before `sync` will
19
35
  * remove it. An ACTIVE git process can legitimately hold
20
36
  * `index.lock`/`shallow.lock` for the duration of its own run; removing a
@@ -191,11 +207,6 @@ var ReposManager = class extends Context.Service()("@savvy-web/silk-effects/Repo
191
207
  boundaryMarked
192
208
  };
193
209
  });
194
- /** Last path segment of a repo URL, with a trailing `.git` stripped. */
195
- const repoSlug = (url) => {
196
- const last = url.split("/").filter((segment) => segment.length > 0).pop() ?? url;
197
- return last.endsWith(".git") ? last.slice(0, -4) : last;
198
- };
199
210
  /**
200
211
  * Fetch `ref` shallow into a submodule via the kit's `Git.fetchAny`:
201
212
  * tag-form fetch first (tags need the explicit `fetch origin tag <ref>`
@@ -728,4 +739,4 @@ var ReposManager = class extends Context.Service()("@savvy-web/silk-effects/Repo
728
739
  }));
729
740
  };
730
741
 
731
- export { ReposManager, STALE_LOCK_MAX_AGE_MS };
742
+ export { ReposManager, STALE_LOCK_MAX_AGE_MS, repoSlug };
@@ -3,8 +3,8 @@ import { DryRunParseError, NotATurboRepoError, TurboExecError, TurboNotInstalled
3
3
  import { TurboDryRun } from "../schemas/DryRun.js";
4
4
  import { Context, Effect, FileSystem, Layer, Schema } from "effect";
5
5
  import { Git } from "@effected/git";
6
- import { ChildProcess, ChildProcessSpawner } from "effect/process";
7
6
  import { Run, Tool, ToolDiscovery } from "@effected/commands";
7
+ import { ChildProcess, ChildProcessSpawner } from "effect/process";
8
8
 
9
9
  // src/turbo/services/TurboInspector.ts
10
10
  /**
@@ -1,26 +0,0 @@
1
- // src/changesets/utils/strip-frontmatter.ts
2
- /**
3
- * Strip leading YAML frontmatter from a markdown string.
4
- *
5
- * Removes the `---\n...\n---\n` block at the start of the content.
6
- * If no frontmatter is present, the content is returned as-is.
7
- *
8
- * @param content - Raw markdown string potentially containing frontmatter
9
- * @returns The markdown content with frontmatter removed
10
- *
11
- * @example
12
- * ```typescript
13
- * import { stripFrontmatter } from "../utils/strip-frontmatter.js";
14
- *
15
- * const raw = "---\n\"pkg\": minor\n---\n\n## Features\n\n- New API";
16
- * const body = stripFrontmatter(raw);
17
- * // "\n## Features\n\n- New API"
18
- * ```
19
- *
20
- * @internal
21
- */
22
- function stripFrontmatter(content) {
23
- return content.replace(/^---\n[\s\S]*?\n---\n?/, "");
24
- }
25
-
26
- export { stripFrontmatter };