DashboardcraftSession 01m4PRNenVfd

Session 01m4PRNenVfd

Full ID: 01m4PRNenVfdOBcS2
Messages: 16
Distillations: 4
Time range: 2026-07-15 10:01:17 — 2026-07-15 10:06:58

Conversation (16 messages)

I'm working in the getsentry/craft repo (Sentry's release automation CLI). I need to understand how git release TAGS are computed and used, so I can add support for PREFIXED release tags like cli@1.2.3 and mcp@1.2.3 (instead of the default 1.2.3 or v1.2.3).

Please explore VERY THOROUGHLY and report back with exact file paths and line numbers plus code snippets:

  1. Where the release git tag string is constructed. Search for how craft turns a version into a tag. Look at src/commands/publish.ts and src/commands/prepare.ts. Is there a tagPrefix or tagName concept already? Search for "tagPrefix", "tag_prefix", "v${", "getTagName", etc.

  2. The .craft.yml config schema. Find src/schemas/project_config.ts (or similar). Report all top-level config keys and their types — especially anything related to tags, versioning, releaseBranchPrefix, changelog. Is there any existing prefix concept?

  3. How the prepare command creates the release branch and commit, and how publish finds the tag/commit to publish. How does publish locate the git tag for a given version?

  4. The version parsing/utilities in src/utils/version.ts — report the SemVer interface, parseVersion, and any functions dealing with tag name formatting or prefixes (like leading "v").

  5. Search the whole repo for any existing handling of a "v" prefix or configurable tag prefix on tags. Grep for patterns like ^v, 'v', tagPrefix, tag_prefix.

  6. How the GitHub target creates GitHub Releases — does it use the tag name directly? File likely src/targets/github.ts.

Report exact file paths and line numbers, and include representative code snippets. Be comprehensive.

gateway · 2026-07-15 10:01:17

I'll explore the craft repo thoroughly to understand how git release tags are computed and used. Let me start by examining the key files in parallel.

anthropic/claude-opus-4.8 · 2026-07-15 10:01:17 · ~50 tokens
result
<path>/home/byk/Code/getsentry/craft/src/utils/version.ts</path>
<type>file</type>
<content>
1: import { getGitTagPrefix } from '../config';
2: 
3: /**
4:  * Regular expression for matching semver versions.
5:  *
6:  * Modified to match version components
7:  * Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
8:  * @see https://github.com/sindresorhus/semver-regex
9:  */
10: const semverRegex = () =>
11:   /\bv?(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(?:-?([\da-z-]+(?:\.[\da-z-]+)*))?(?:\+([\da-z-]+(?:\.[\da-z-]+)*))?\b/gi;
12: 
13: /**
14:  * Extracts a version number from the given text.
15:  *
16:  * In case the version contains a leading "v", it is stripped from the result.
17:  * All semantic versions are supported. See {@link http://semver.org/} for
18:  * more information. ...
gateway · 2026-07-15 10:01:51

There's already a versionToTag function and getGitTagPrefix concept. Let me dig deeper into the config, commands, and targets.

anthropic/claude-opus-4.8 · 2026-07-15 10:01:51 · ~44 tokens
result
Found 25 matches
/home/byk/Code/getsentry/craft/src/config.ts:
  Line 332: export function getGitTagPrefix(): string {

  Line 335:   return (githubTarget?.tagPrefix as string | undefined) || '';


/home/byk/Code/getsentry/craft/src/utils/version.ts:
  Line 1: import { getGitTagPrefix } from '../config';

  Line 132:  * @param tagPrefix Git tag prefix

  Line 135: export function versionToTag(version: string, tagPrefix?: string): string {

  Line 136:   const prefix = tagPrefix === undefined ? getGitTagPrefix() : tagPrefix;


/home/byk/Code/getsentry/craft/src/utils/calver.ts:
  Line 3: import { getGitTagPrefix } from '../config';

  Line 80:   const gitTagPrefix = getGitTagPrefix();


/home/byk/Code/getsentry/craft/src/utils/__tests__/calver.test.ts:
  Line 8: // Mock the config module to control tagPrefix

  Line 10:   getGitTagPrefix: vi.fn(() => ''),

  Line 13: import { getGitTagPrefix } from '../../config';

  Line 15: const mockGetGitTagPrefix = getGitTagPrefix as Mock;

  Line ...
result
<path>/home/byk/Code/getsentry/craft/src/config.ts</path>
<type>file</type>
<content>
1: import { existsSync, lstatSync, readFileSync } from 'fs';
2: import path from 'path';
3: 
4: import { load } from 'js-yaml';
5: import GitUrlParse from 'git-url-parse';
6: import { createGitClient } from './utils/git';
7: import { ZodError } from 'zod';
8: 
9: import { logger } from './logger';
10: import {
11:   CraftProjectConfig,
12:   CraftProjectConfigSchema,
13:   GitHubGlobalConfig,
14:   ArtifactProviderName,
15:   StatusProviderName,
16:   TargetConfig,
17:   ChangelogPolicy,
18:   VersioningPolicy,
19: } from './schemas/project_config';
20: import { ConfigurationError } from './utils/errors';
21: import { isCompiledGitHubAction } from './utils/detection';
22: import {
23:   getPackageVersion,
24:   parseVersion,
25:   versionGreaterOrEqualThan,
26: } from './utils/version';
27: // Note: We import getTargetByName lazily in expandWorkspaceTargets to avoid
28: // circular dependency: config ...
gateway · 2026-07-15 10:02:13

Key finding: tagPrefix already exists but is tied to the github target and is global (via getGitTagPrefix()). Let me now read the schema, both commands, the github target, and calver.

anthropic/claude-opus-4.8 · 2026-07-15 10:02:13 · ~63 tokens
result
<path>/home/byk/Code/getsentry/craft/src/schemas/project_config.ts</path>
<type>file</type>
<content>
1: import { z } from 'zod';
2: 
3: /**
4:  * DEPRECATED: Use changelog.policy instead. Different policies for changelog management
5:  */
6: export const enum ChangelogPolicy {
7:   Auto = 'auto',
8:   Simple = 'simple',
9:   None = 'none',
10: }
11: 
12: /**
13:  * Name of the status provider
14:  */
15: export const enum StatusProviderName {
16:   GitHub = 'github',
17: }
18: 
19: /**
20:  * Name of the artifact provider
21:  */
22: export const enum ArtifactProviderName {
23:   GCS = 'gcs',
24:   GitHub = 'github',
25:   None = 'none',
26: }
27: 
28: /**
29:  * Default versioning policy when no version argument is provided. ...
result
<path>/home/byk/Code/getsentry/craft/src/utils/calver.ts</path>
<type>file</type>
<content>
1: import type { SimpleGit } from 'simple-git';
2: 
3: import { getGitTagPrefix } from '../config';
4: import { logger } from '../logger';
5: 
6: /**
7:  * Configuration for CalVer versioning
8:  */
9: export interface CalVerConfig {
10:   /** Days to go back for date calculation */
11:   offset: number;
12:   /** strftime-like format for date part */
13:   format: string;
14: }
15: 
16: /**
17:  * Default CalVer configuration
18:  */
19: export const DEFAULT_CALVER_CONFIG: CalVerConfig = {
20:   offset: 14,
21:   format: '%y.%-m',
22: };
23: 
24: /**
25:  * Formats a date according to a strftime-like format string.
26:  *
27:  * Supported format specifiers:
28:  * - %y: 2-digit year (e.g., "24" for 2024)
29:  * - %Y: 4-digit year (e.g., "2024")
30:  * - %m: Zero-padded month (e.g., "01" for January)
31:  * - %-m: Month without zero padding (e.g., "1" for January)
32:  * - %d: Zero-padded day ...
gateway · 2026-07-15 10:02:43

Now let me read the two commands and the github target.

anthropic/claude-opus-4.8 · 2026-07-15 10:02:43 · ~19 tokens
result
<path>/home/byk/Code/getsentry/craft/src/commands/prepare.ts</path>
<type>file</type>
<content>
1: import { existsSync, promises as fsPromises } from 'fs';
2: import { join, relative } from 'path';
3: 
4: import * as shellQuote from 'shell-quote';
5: import type { SimpleGit, StatusResult } from 'simple-git';
6: import type { Arguments, Argv, CommandBuilder } from 'yargs';
7: import { createDryRunIsolation, safeFs } from '../utils/dryRun';
8: 
9: import {
10:   CONFIG_FILE_NAME,
11:   DEFAULT_RELEASE_BRANCH_NAME,
12:   getConfigFileDir,
13:   getConfiguration,
14:   getGlobalGitHubConfig,
15:   getVersioningPolicy,
16:   loadConfigurationFromString,
17:   requiresMinVersion,
18: } from '../config';
19: import { logger } from '../logger';
20: import {
21:   ChangelogPolicy,
22:   type TargetConfig,
23:   VersioningPolicy,
24: } from '../schemas/project_config';
25: import { sleep } from '../utils/async';
26: import {
27:   type BumpType,
28:   calculateNextVersion,
29:   ...
result
<path>/home/byk/Code/getsentry/craft/src/targets/github.ts</path>
<type>file</type>
<content>
1: import { Octokit, RestEndpointMethodTypes } from '@octokit/rest';
2: import { createReadStream, promises, statSync } from 'fs';
3: import { basename } from 'path';
4: 
5: import { getConfiguration } from '../config';
6: import {
7:   ChangelogPolicy,
8:   GitHubGlobalConfig,
9:   TargetConfig,
10:   TypedTargetConfig,
11: } from '../schemas/project_config';
12: import {
13:   Changeset,
14:   DEFAULT_CHANGELOG_PATH,
15:   findChangeset,
16: } from '../utils/changelog';
17: import { getGitHubClient } from '../utils/githubApi';
18: import { isDryRun } from '../utils/helpers';
19: import { safeExec } from '../utils/dryRun';
20: import {
21:   isPreviewRelease,
22:   parseVersion,
23:   SemVer,
24:   versionGreaterOrEqualThan,
25:   versionToTag,
26: } from '../utils/version';
27: import { BaseTarget } from './base';
28: import { BaseArtifactProvider } from '../artifact_providers/base';
29: ...
gateway · 2026-07-15 10:03:23

Now let me read the publish command and the git utils to understand how publish locates the tag/commit for a version.

anthropic/claude-opus-4.8 · 2026-07-15 10:03:23 · ~39 tokens
result
<path>/home/byk/Code/getsentry/craft/src/commands/publish.ts</path>
<type>file</type>
<content>
1: import { Arguments, Argv, CommandBuilder } from 'yargs';
2: import chalk from 'chalk';
3: import { existsSync, mkdirSync, readFileSync } from 'fs';
4: 
5: import { safeFs } from '../utils/dryRun';
6: import { dirname, join } from 'path';
7: import * as shellQuote from 'shell-quote';
8: import stringLength from 'string-length';
9: 
10: import {
11:   getConfiguration,
12:   getStatusProviderFromConfig,
13:   getArtifactProviderFromConfig,
14:   DEFAULT_RELEASE_BRANCH_NAME,
15:   getGlobalGitHubConfig,
16:   expandWorkspaceTargets,
17:   getNoMergeConfig,
18: } from '../config';
19: import { formatTable, logger } from '../logger';
20: import { TargetConfig } from '../schemas/project_config';
21: import { getAllTargetNames, getTargetByName, SpecialTarget } from '../targets';
22: import { BaseTarget } from '../targets/base';
23: import {
24:   ConfigurationError,
25:   handleGlobalError,
26: ...
result
<path>/home/byk/Code/getsentry/craft/src/utils/git.ts</path>
<type>file</type>
<content>
1: import simpleGit, {
2:   type SimpleGit,
3:   type LogOptions,
4:   type Options,
5:   type StatusResult,
6: } from 'simple-git';
7: 
8: import { getConfigFileDir } from '../config';
9: import { ConfigurationError } from './errors';
10: import { createDryRunGit } from './dryRun';
11: import { logger } from '../logger';
12: import { distance as levenshtein } from 'fastest-levenshtein';
13: 
14: export interface GitChange {
15:   hash: string;
16:   title: string;
17:   body: string;
18:   pr: string | null;
19: }
20: 
21: // This regex relies on the default GitHub behavior where it appends the PR
22: // number to the end of the commit title as: `fix: Commit title (#123)`.
23: // This makes it very cheap and quick to extract the associated PR number just
24: // from the commit log locally. ...
gateway · 2026-07-15 10:04:26

I have a very complete picture now. Let me check the upm.ts target (which uses tagPrefix), the config test, and do a final broad grep for "v" prefix handling and any tag-related patterns I may have missed.

anthropic/claude-opus-4.8 · 2026-07-15 10:04:26 · ~70 tokens
result
<path>/home/byk/Code/getsentry/craft/src/targets/upm.ts</path>
<type>file</type>
<content>
40:   public readonly name: string = 'upm';
41:   /** GitHub client */
42:   public readonly github: Octokit;
43:   /** Internal GitHub Target */
44:   private readonly githubTarget: GitHubTarget;
45: 
46:   public constructor(
47:     config: TargetConfig,
48:     artifactProvider: BaseArtifactProvider,
49:     githubRepo: GitHubGlobalConfig,
50:   ) {
51:     super(config, artifactProvider, githubRepo);
52: 
53:     this.github = getGitHubClient();
54: 
55:     const githubTargetConfig = {
56:       name: 'github',
57:       tagPrefix: config.tagPrefix,
58:       owner: config.releaseRepoOwner,
59:       repo: config.releaseRepoName,
60:     };
61: 
62:     this.githubTarget = new GitHubTarget(
63:       githubTargetConfig,
64:       new NoneArtifactProvider(),
65:       githubRepo,
66:     );
67:   }
68: 
69:   /**
70:    * Fetches the artifact for the provided revision. ...
result
Found 36 matches
/home/byk/Code/getsentry/craft/src/commands/prepare.ts:
  Line 50:   getLatestTag,

  Line 761:     const latestTag = await getLatestTag(git);

  Line 869:     const oldVersion = await getLatestTag(git);


/home/byk/Code/getsentry/craft/src/commands/changelog.ts:
  Line 5: import { getGitClient, getLatestTag } from '../utils/git';

  Line 58:     since = await getLatestTag(git);


/home/byk/Code/getsentry/craft/src/commands/__tests__/changelog-versioning-policy.test.ts:
  Line 15:   getLatestTag: vi.fn().mockResolvedValue('1.0.0'),


/home/byk/Code/getsentry/craft/src/targets/github.ts:
  Line 25:   versionToTag,

  Line 47:   tagOnly?: boolean;

  Line 49:   floatingTags?: string[];

  Line 63:   tagOnly: boolean;

  Line 69:   floatingTags: string[];

  Line 127:       tagOnly: !!typedConfig.tagOnly,

  Line 128:       floatingTags: typedConfig.floatingTags || [],

  Line 197:     const tag = versionToTag(version, this.githubConfig.tagPrefix);

  Line 259:       ...
gateway · 2026-07-15 10:05:25

Let me do a final targeted grep for any ^v / leading-v handling and check the changelog command's use of getLatestTag, plus the registry target which may also use tags.

anthropic/claude-opus-4.8 · 2026-07-15 10:05:25 · ~57 tokens
result
Found 8 matches
/home/byk/Code/getsentry/craft/src/index.ts:
  Line 106:       coerce: level => level[0].toUpperCase() + level.slice(1).toLowerCase(),


/home/byk/Code/getsentry/craft/src/utils/changelog.ts:
  Line 1339:   const capitalized = remainder.charAt(0).toUpperCase() + remainder.slice(1);

  Line 2152:         hash.slice(1),


/home/byk/Code/getsentry/craft/src/targets/awsLambdaLayer.ts:
  Line 217:             `v${version} for ${runtimeNames}`,


/home/byk/Code/getsentry/craft/src/utils/version.ts:
  Line 27:     ? version.substr(1)


/home/byk/Code/getsentry/craft/src/utils/packagePath.ts:
  Line 29:   return path.join('apps', ...packageDirs.slice(1));


/home/byk/Code/getsentry/craft/src/commands/prepare.ts:
  Line 202:     if (version.startsWith('v')) {


/home/byk/Code/getsentry/craft/src/utils/awsLambdaLayerManager.ts:
  Line 82:       Description: `Sentry AWS Serverless SDK v${this.sdkVersion}`,
result
Found 48 matches
/home/byk/Code/getsentry/craft/src/targets/github.ts:
  Line 25:   versionToTag,

  Line 46:   tagPrefix?: string;

  Line 59:   tagPrefix: string;

  Line 80:   tag_name: string;

  Line 126:       tagPrefix: typedConfig.tagPrefix || '',

  Line 197:     const tag = versionToTag(version, this.githubConfig.tagPrefix);

  Line 207:         tag_name: tag,

  Line 223:       tag_name: tag,

  Line 259:       const tag = versionToTag(version, this.githubConfig.tagPrefix);

  Line 324:     return releases.filter(r => r.tag_name === tag && r.draft === true);

  Line 377:     this.logger.debug(`Deleting release: "${release.tag_name}"...`);

  Line 381:         `Refusing to delete release "${release.tag_name}" because it is not a draft`,

  Line 508:         `Uploading asset "${name}" to ${this.githubConfig.owner}/${this.githubConfig.repo}:${release.tag_name}\n`,

  Line 609:    * "tagPrefix" is respected when creating a tag name. ...
gateway · 2026-07-15 10:06:58

I now have a comprehensive understanding of the entire tag lifecycle. Let me compile the report.


Craft Release Tag System — Comprehensive Exploration Report

Executive Summary

Craft already has a tagPrefix concept, but with an important architectural constraint that directly impacts your goal: tagPrefix is defined on the github target config, and is globally resolved via getGitTagPrefix() which searches for the first target named github. There is currently no per-target or multi-package prefixing (like cli@ vs mcp@). Tag construction is centralized in a single function: versionToTag(version, tagPrefix) in src/utils/version.ts, which just does string concatenation ${prefix}${version}.

The critical mismatch for prefixed tags like cli@1.2.3: the tag prefix is used write-side (creating tags/releases), but the read-side (getLatestTag, CalVer tag scanning, publish branch lookup) does not filter by prefix — publish locates commits via release branches, not tags, and version parsing (getVersion/parseVersion) uses a semver regex that would happily strip cli@ context away.


1. Where the release git tag string is constructed

The single source of truth: versionToTag()

src/utils/version.ts:126-138

/**
 * Returns the Git version based on the provided version.
 * If no tag prefix is provided, it is taken from the configuration.
 */
export function versionToTag(version: string, tagPrefix?: string): string {
  const prefix = tagPrefix === undefined ? getGitTagPrefix() : tagPrefix;
  return `${prefix}${version}`;
}

This is the only place a (version, prefix) -> tag conversion happens. Every tag write goes through it.

getGitTagPrefix() — the global resolver

src/config.ts:329-336

/**
 * Gets git tag prefix from configuration
 */
export function getGitTagPrefix(): string {
  const targets = getConfiguration().targets || [];
  const githubTarget ...
anthropic/claude-opus-4.8 · 2026-07-15 10:06:58 · ~5.3K tokens

Distillations (4)

Gen 0 2026-07-15 10:07:08 · 173 tokens

* 🔴 (10:06) [tool:result] User received 8 matches from searching for code patterns in multiple files across the craft repository. * 🔴 (10:06) [tool:result] User received 48 matches from searching for code patterns in multiple files across the craft repository, specifically in github-related files. * 🟡 (10:06) Assistant provided a comprehensive exploration report on Craft Release Tag System, de…

Gen 0 2026-07-15 10:07:02 · 77 tokens

* 🔴 (10:04) User stated always revert back to using the GitHub. * 🔴 (10:05) User provided code snippet: upm.ts file. * 🔴 (10:05) User provided code snippet: git.ts file. * 🔴 [item-purchased] (10:05) User bought new headphones.

Gen 0 2026-07-15 10:03:56 · 112 tokens

* 🔴 (10:03) User stated never swept in by accident. * 🔴 (10:03) User stated never return: it terminates the process with the. * 🔴 (10:03) User stated never be marked as latest. * 🔴 (10:03) User provided code snippet: Switched to branch "${branchName}"`);. * 🔴 (10:03) User requested replace @-mentions with bold formatting in the.

Gen 0 2026-07-15 10:03:34 · 360 tokens

* 🔴 (10:01) User stated needs to understand how git release TAGS are computed and used in getsentry/craft repo. * 🔴 (10:01) User wants to add support for PREFIXED release tags like `cli@1.2.3` and `mcp@1.2.3`. * 🔴 (10:01) User asked to explore `src/commands/publish.ts`, `src/commands/prepare.ts`, `src/config.ts`, `src/utils/version.ts`, `.craft.yml` schema in `src/schemas/project_config.ts`, a…