@edcalderon/versioning 1.1.0 → 1.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.
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.ConfigManager = void 0;
37
+ exports.canonicalProjectKey = canonicalProjectKey;
37
38
  const path = __importStar(require("path"));
38
39
  const constants_1 = require("./constants");
39
40
  function isNonEmptyString(value) {
@@ -45,12 +46,78 @@ function defaultFilesConfig() {
45
46
  markdownPath: path.join(constants_1.REENTRY_STATUS_DIRNAME, constants_1.REENTRY_STATUS_MD_FILENAME)
46
47
  };
47
48
  }
49
+ function canonicalProjectKey(project) {
50
+ const raw = typeof project === 'string' ? project.trim() : '';
51
+ if (!raw)
52
+ return undefined;
53
+ // Prefer the last segment for scoped package names (e.g. "@ed/trader" -> "trader").
54
+ if (raw.startsWith('@') && raw.includes('/')) {
55
+ const parts = raw.split('/').filter(Boolean);
56
+ const last = parts[parts.length - 1]?.trim();
57
+ return last || undefined;
58
+ }
59
+ // Also handle path-like inputs (e.g. "apps/trader" -> "trader").
60
+ if (raw.includes('/')) {
61
+ const parts = raw.split('/').filter(Boolean);
62
+ const last = parts[parts.length - 1]?.trim();
63
+ return last || undefined;
64
+ }
65
+ return raw;
66
+ }
67
+ function toProjectDir(project) {
68
+ const raw = canonicalProjectKey(project) ?? '';
69
+ if (!raw)
70
+ return constants_1.REENTRY_STATUS_DIRNAME;
71
+ const safe = raw
72
+ .toLowerCase()
73
+ .replace(/[^a-z0-9._-]+/g, '-')
74
+ .replace(/^-+|-+$/g, '');
75
+ return path.join(constants_1.REENTRY_STATUS_DIRNAME, 'projects', safe || 'project');
76
+ }
77
+ function defaultFilesConfigForProject(project) {
78
+ const dir = toProjectDir(project);
79
+ return {
80
+ jsonPath: path.join(dir, constants_1.REENTRY_STATUS_JSON_FILENAME),
81
+ markdownPath: path.join(dir, constants_1.REENTRY_STATUS_MD_FILENAME),
82
+ };
83
+ }
48
84
  class ConfigManager {
49
- static loadConfig(rootConfig) {
50
- const partial = (rootConfig && typeof rootConfig === 'object' ? rootConfig.reentryStatus : undefined);
51
- return ConfigManager.mergeWithDefaults(partial ?? {});
85
+ static loadConfig(rootConfig, project) {
86
+ const canonicalProject = canonicalProjectKey(project);
87
+ const raw = (rootConfig && typeof rootConfig === 'object' ? rootConfig.reentryStatus : undefined);
88
+ const basePartial = { ...(raw ?? {}) };
89
+ delete basePartial.projects;
90
+ const projectPartial = canonicalProject && raw && typeof raw === 'object' && raw.projects && typeof raw.projects === 'object'
91
+ ? raw.projects[String(canonicalProject)] ?? raw.projects[String(project)]
92
+ : undefined;
93
+ if (!projectPartial) {
94
+ return ConfigManager.mergeWithDefaults(basePartial, canonicalProject);
95
+ }
96
+ const merged = {
97
+ ...basePartial,
98
+ ...projectPartial,
99
+ hooks: {
100
+ ...basePartial.hooks,
101
+ ...projectPartial.hooks,
102
+ },
103
+ files: {
104
+ ...basePartial.files,
105
+ ...projectPartial.files,
106
+ },
107
+ template: projectPartial.template
108
+ ? {
109
+ includeSections: projectPartial.template.includeSections ?? basePartial.template?.includeSections ?? [],
110
+ excludeSections: projectPartial.template.excludeSections ?? basePartial.template?.excludeSections ?? [],
111
+ customSections: projectPartial.template.customSections ?? basePartial.template?.customSections,
112
+ }
113
+ : basePartial.template,
114
+ github: projectPartial.github ?? basePartial.github,
115
+ obsidian: projectPartial.obsidian ?? basePartial.obsidian,
116
+ };
117
+ return ConfigManager.mergeWithDefaults(merged, canonicalProject);
52
118
  }
53
- static mergeWithDefaults(partial) {
119
+ static mergeWithDefaults(partial, project) {
120
+ const defaults = defaultFilesConfigForProject(project);
54
121
  const merged = {
55
122
  enabled: partial.enabled ?? true,
56
123
  autoSync: partial.autoSync ?? true,
@@ -60,8 +127,8 @@ class ConfigManager {
60
127
  postRelease: partial.hooks?.postRelease ?? false
61
128
  },
62
129
  files: {
63
- jsonPath: partial.files?.jsonPath ?? defaultFilesConfig().jsonPath,
64
- markdownPath: partial.files?.markdownPath ?? defaultFilesConfig().markdownPath
130
+ jsonPath: partial.files?.jsonPath ?? defaults.jsonPath,
131
+ markdownPath: partial.files?.markdownPath ?? defaults.markdownPath
65
132
  },
66
133
  github: partial.github,
67
134
  obsidian: partial.obsidian,
@@ -0,0 +1,27 @@
1
+ export interface GitContextInfo {
2
+ branch: string;
3
+ commit: string;
4
+ commitMessage: string;
5
+ author: string;
6
+ timestamp: string;
7
+ changedFiles: string[];
8
+ diffSummary: {
9
+ insertions: number;
10
+ deletions: number;
11
+ filesChanged: number;
12
+ };
13
+ }
14
+ /**
15
+ * Collects the current git context automatically from the working directory.
16
+ * This is used to auto-fill the reentry status with real git data.
17
+ */
18
+ export declare function collectGitContext(): Promise<GitContextInfo>;
19
+ /**
20
+ * Infer the current project phase based on git data and recent changes.
21
+ */
22
+ export declare function inferPhase(context: GitContextInfo, currentVersion: string): string;
23
+ /**
24
+ * Generate a suggested next step based on the last commit context.
25
+ */
26
+ export declare function suggestNextStep(context: GitContextInfo): string;
27
+ //# sourceMappingURL=git-context.d.ts.map
@@ -0,0 +1,94 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.collectGitContext = collectGitContext;
7
+ exports.inferPhase = inferPhase;
8
+ exports.suggestNextStep = suggestNextStep;
9
+ const simple_git_1 = __importDefault(require("simple-git"));
10
+ /**
11
+ * Collects the current git context automatically from the working directory.
12
+ * This is used to auto-fill the reentry status with real git data.
13
+ */
14
+ async function collectGitContext() {
15
+ const git = (0, simple_git_1.default)();
16
+ try {
17
+ const [log, branch, diff] = await Promise.all([
18
+ git.log({ maxCount: 1 }),
19
+ git.branch(),
20
+ git.diffSummary(['HEAD~1', 'HEAD']).catch(() => null),
21
+ ]);
22
+ const latest = log.latest;
23
+ const statusResult = await git.status();
24
+ return {
25
+ branch: branch.current || '',
26
+ commit: latest?.hash?.substring(0, 7) || '',
27
+ commitMessage: latest?.message || '',
28
+ author: latest?.author_name || '',
29
+ timestamp: latest?.date || new Date().toISOString(),
30
+ changedFiles: statusResult.files.map(f => f.path),
31
+ diffSummary: {
32
+ insertions: diff?.insertions ?? 0,
33
+ deletions: diff?.deletions ?? 0,
34
+ filesChanged: diff?.changed ?? 0,
35
+ },
36
+ };
37
+ }
38
+ catch {
39
+ return {
40
+ branch: '',
41
+ commit: '',
42
+ commitMessage: '',
43
+ author: '',
44
+ timestamp: new Date().toISOString(),
45
+ changedFiles: [],
46
+ diffSummary: { insertions: 0, deletions: 0, filesChanged: 0 },
47
+ };
48
+ }
49
+ }
50
+ /**
51
+ * Infer the current project phase based on git data and recent changes.
52
+ */
53
+ function inferPhase(context, currentVersion) {
54
+ const msg = context.commitMessage.toLowerCase();
55
+ if (msg.startsWith('fix:') || msg.startsWith('hotfix:') || msg.includes('bugfix')) {
56
+ return 'maintenance';
57
+ }
58
+ if (msg.startsWith('test:') || msg.includes('test')) {
59
+ return 'testing';
60
+ }
61
+ if (msg.startsWith('feat:') || msg.startsWith('feature:')) {
62
+ return 'development';
63
+ }
64
+ if (msg.startsWith('chore: release') || msg.includes('deploy') || msg.includes('staging')) {
65
+ return 'staging';
66
+ }
67
+ if (msg.startsWith('docs:') || msg.startsWith('chore:')) {
68
+ return 'maintenance';
69
+ }
70
+ return 'development';
71
+ }
72
+ /**
73
+ * Generate a suggested next step based on the last commit context.
74
+ */
75
+ function suggestNextStep(context) {
76
+ const msg = context.commitMessage.toLowerCase();
77
+ if (msg.startsWith('feat:')) {
78
+ return `Write tests for: ${context.commitMessage.replace(/^feat:\s*/i, '').substring(0, 60)}`;
79
+ }
80
+ if (msg.startsWith('fix:')) {
81
+ return `Verify fix and add regression test for: ${context.commitMessage.replace(/^fix:\s*/i, '').substring(0, 50)}`;
82
+ }
83
+ if (msg.startsWith('test:')) {
84
+ return 'Review test coverage and consider edge cases';
85
+ }
86
+ if (msg.startsWith('chore: release')) {
87
+ return 'Verify deployment and update documentation';
88
+ }
89
+ if (msg.startsWith('docs:')) {
90
+ return 'Continue with next feature or bugfix';
91
+ }
92
+ return `Review changes from: ${context.commitMessage.substring(0, 60)}`;
93
+ }
94
+ //# sourceMappingURL=git-context.js.map
@@ -9,6 +9,7 @@ export * from './obsidian-sync-adapter';
9
9
  export * from './obsidian-cli-client';
10
10
  export * from './roadmap-parser';
11
11
  export * from './roadmap-renderer';
12
+ export * from './git-context';
12
13
  export * from './reentry-status-manager';
13
14
  export * from './status-renderer';
14
15
  //# sourceMappingURL=index.d.ts.map
@@ -25,6 +25,7 @@ __exportStar(require("./obsidian-sync-adapter"), exports);
25
25
  __exportStar(require("./obsidian-cli-client"), exports);
26
26
  __exportStar(require("./roadmap-parser"), exports);
27
27
  __exportStar(require("./roadmap-renderer"), exports);
28
+ __exportStar(require("./git-context"), exports);
28
29
  __exportStar(require("./reentry-status-manager"), exports);
29
30
  __exportStar(require("./status-renderer"), exports);
30
31
  //# sourceMappingURL=index.js.map
@@ -3,9 +3,16 @@ export declare const ROADMAP_MANAGED_START = "<!-- roadmap:managed:start -->";
3
3
  export declare const ROADMAP_MANAGED_END = "<!-- roadmap:managed:end -->";
4
4
  export interface RoadmapRenderOptions {
5
5
  projectTitle?: string;
6
+ projectSlug?: string;
7
+ monorepoName?: string;
6
8
  }
7
9
  export declare class RoadmapRenderer {
8
- static defaultRoadmapPath(): string;
10
+ static defaultRoadmapPath(baseDir?: string): string;
11
+ /**
12
+ * Extract the project name from the roadmap file path for auto-identification.
13
+ * e.g. ".versioning/projects/trader/ROADMAP.md" → "trader"
14
+ */
15
+ static extractProjectFromPath(roadmapFile: string): string | null;
9
16
  static renderManagedBlock(status?: Pick<ReentryStatus, 'milestone' | 'roadmapFile'>): string;
10
17
  static renderTemplate(options?: RoadmapRenderOptions, status?: Pick<ReentryStatus, 'milestone' | 'roadmapFile'>): string;
11
18
  /**
@@ -5,31 +5,48 @@ const constants_1 = require("./constants");
5
5
  exports.ROADMAP_MANAGED_START = '<!-- roadmap:managed:start -->';
6
6
  exports.ROADMAP_MANAGED_END = '<!-- roadmap:managed:end -->';
7
7
  class RoadmapRenderer {
8
- static defaultRoadmapPath() {
9
- return `${constants_1.REENTRY_STATUS_DIRNAME}/${constants_1.ROADMAP_MD_FILENAME}`;
8
+ static defaultRoadmapPath(baseDir = constants_1.REENTRY_STATUS_DIRNAME) {
9
+ return `${baseDir}/${constants_1.ROADMAP_MD_FILENAME}`;
10
+ }
11
+ /**
12
+ * Extract the project name from the roadmap file path for auto-identification.
13
+ * e.g. ".versioning/projects/trader/ROADMAP.md" → "trader"
14
+ */
15
+ static extractProjectFromPath(roadmapFile) {
16
+ const normalized = roadmapFile.replace(/\\/g, '/');
17
+ const match = /\.versioning\/projects\/([^/]+)\/ROADMAP\.md$/.exec(normalized);
18
+ return match ? match[1] : null;
10
19
  }
11
20
  static renderManagedBlock(status) {
12
21
  const milestoneText = status?.milestone
13
22
  ? `${status.milestone.title} (id: ${status.milestone.id})`
14
23
  : '—';
15
24
  const roadmapFile = status?.roadmapFile ?? RoadmapRenderer.defaultRoadmapPath();
16
- // Keep this block stable: no timestamps.
17
- return [
25
+ const projectSlug = RoadmapRenderer.extractProjectFromPath(roadmapFile);
26
+ // Build the managed block with project identification
27
+ const lines = [
18
28
  exports.ROADMAP_MANAGED_START,
19
29
  '> Managed by `@edcalderon/versioning` reentry-status-extension.',
20
30
  `> Canonical roadmap file: ${roadmapFile}`,
21
- `> Active milestone: ${milestoneText}`,
22
- '> ',
23
- '> Everything outside this block is user-editable.',
24
- exports.ROADMAP_MANAGED_END,
25
- ''
26
- ].join('\n');
31
+ ];
32
+ if (projectSlug) {
33
+ lines.push(`> Project: **${projectSlug}**`);
34
+ }
35
+ lines.push(`> Active milestone: ${milestoneText}`, '> ', '> Everything outside this block is user-editable.', exports.ROADMAP_MANAGED_END, '');
36
+ return lines.join('\n');
27
37
  }
28
38
  static renderTemplate(options = {}, status) {
29
39
  const title = options.projectTitle?.trim() ? options.projectTitle.trim() : 'Untitled';
40
+ const roadmapFile = status?.roadmapFile ?? RoadmapRenderer.defaultRoadmapPath();
41
+ const projectSlug = options.projectSlug || RoadmapRenderer.extractProjectFromPath(roadmapFile);
42
+ const monorepo = options.monorepoName || '@ed/monorepo';
43
+ const headerLines = [`# Project Roadmap – ${title}`, ''];
44
+ // Add project metadata section
45
+ if (projectSlug) {
46
+ headerLines.push(`> 📦 **Project:** \`${projectSlug}\` | **Monorepo:** \`${monorepo}\``, '');
47
+ }
30
48
  return [
31
- `# Project Roadmap – ${title}`,
32
- '',
49
+ ...headerLines,
33
50
  RoadmapRenderer.renderManagedBlock(status),
34
51
  '## North Star',
35
52
  '',