@edcalderon/versioning 1.1.0 → 1.1.2

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.
package/README.md CHANGED
@@ -118,10 +118,21 @@ Features:
118
118
  Maintains a fast **re-entry** layer (current state + next micro-step) and a slow **roadmap/backlog** layer (long-term plan).
119
119
 
120
120
  Canonical files:
121
+
122
+ Single-project (default):
121
123
  - `.versioning/reentry.status.json` (machine)
122
124
  - `.versioning/REENTRY.md` (generated, minimal diffs)
123
125
  - `.versioning/ROADMAP.md` (human-first; only a small managed header block is auto-updated)
124
126
 
127
+ Multi-project (scoped by `--project <name>`):
128
+ - `.versioning/projects/<project>/reentry.status.json`
129
+ - `.versioning/projects/<project>/REENTRY.md`
130
+ - `.versioning/projects/<project>/ROADMAP.md`
131
+
132
+ Notes:
133
+ - `--project` must match an existing workspace app/package (it can be a slug like `trader`, a scoped package name like `@ed/trader`, or a path like `apps/trader`).
134
+ - The canonical project key is the last path segment (e.g. `@ed/trader` → `trader`).
135
+
125
136
  Commands:
126
137
 
127
138
  ```bash
@@ -129,11 +140,23 @@ Commands:
129
140
  versioning reentry init
130
141
  versioning reentry sync
131
142
 
143
+ # Fast layer (scoped)
144
+ versioning reentry init --project trader
145
+ versioning reentry sync --project trader
146
+
132
147
  # Slow layer
133
148
  versioning roadmap init --title "My Project"
134
149
  versioning roadmap list
135
150
  versioning roadmap set-milestone --id "now-01" --title "Ship X"
136
- versioning roadmap add --section "Now (1–2 weeks)" --id "now-02" --item "Add observability"
151
+ versioning roadmap add --section Now --id "now-02" --item "Add observability"
152
+
153
+ # Slow layer (scoped)
154
+ versioning roadmap init --project trader --title "Trader"
155
+ versioning roadmap list --project trader
156
+ versioning roadmap add --project trader --section Now --item "Wire user-data ORDER_* events"
157
+
158
+ # Detect stale/mismatched scoped roadmaps
159
+ versioning roadmap validate
137
160
  ```
138
161
 
139
162
  Backward compatibility:
@@ -3,10 +3,15 @@ export interface ValidationResult {
3
3
  valid: boolean;
4
4
  errors: string[];
5
5
  }
6
+ type DeepPartial<T> = {
7
+ [K in keyof T]?: T[K] extends Array<infer U> ? Array<DeepPartial<U>> : T[K] extends object ? DeepPartial<T[K]> : T[K];
8
+ };
9
+ export declare function canonicalProjectKey(project?: string): string | undefined;
6
10
  export declare class ConfigManager {
7
- static loadConfig(rootConfig: any): ReentryStatusConfig;
8
- static mergeWithDefaults(partial: Partial<ReentryStatusConfig>): ReentryStatusConfig;
11
+ static loadConfig(rootConfig: any, project?: string): ReentryStatusConfig;
12
+ static mergeWithDefaults(partial: DeepPartial<ReentryStatusConfig>, project?: string): ReentryStatusConfig;
9
13
  static validateConfig(config: ReentryStatusConfig): ValidationResult;
10
14
  static getSyncTargets(config: ReentryStatusConfig): SyncTarget[];
11
15
  }
16
+ export {};
12
17
  //# sourceMappingURL=config-manager.d.ts.map
@@ -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,
@@ -5,7 +5,7 @@ export interface RoadmapRenderOptions {
5
5
  projectTitle?: string;
6
6
  }
7
7
  export declare class RoadmapRenderer {
8
- static defaultRoadmapPath(): string;
8
+ static defaultRoadmapPath(baseDir?: string): string;
9
9
  static renderManagedBlock(status?: Pick<ReentryStatus, 'milestone' | 'roadmapFile'>): string;
10
10
  static renderTemplate(options?: RoadmapRenderOptions, status?: Pick<ReentryStatus, 'milestone' | 'roadmapFile'>): string;
11
11
  /**
@@ -5,8 +5,8 @@ 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
10
  }
11
11
  static renderManagedBlock(status) {
12
12
  const milestoneText = status?.milestone
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  const commander_1 = require("commander");
37
37
  const fs = __importStar(require("fs-extra"));
38
+ const path = __importStar(require("path"));
38
39
  const config_manager_1 = require("./reentry-status/config-manager");
39
40
  const constants_1 = require("./reentry-status/constants");
40
41
  const file_manager_1 = require("./reentry-status/file-manager");
@@ -50,7 +51,7 @@ const reentry_status_manager_1 = require("./reentry-status/reentry-status-manage
50
51
  const extension = {
51
52
  name: constants_1.REENTRY_EXTENSION_NAME,
52
53
  description: 'Maintains canonical re-entry status and synchronizes to files, GitHub Issues, and Obsidian notes',
53
- version: '1.1.0',
54
+ version: '1.1.2',
54
55
  hooks: {
55
56
  postVersion: async (type, version, options) => {
56
57
  try {
@@ -107,16 +108,103 @@ const extension = {
107
108
  register: async (program, rootConfig) => {
108
109
  const fileManager = new file_manager_1.FileManager();
109
110
  const manager = new reentry_status_manager_1.ReentryStatusManager({ fileManager });
111
+ const discoverWorkspaceProjects = async (configPath) => {
112
+ const rootDir = path.dirname(configPath);
113
+ const slugs = new Set();
114
+ const names = new Set();
115
+ const considerPackageJson = async (packageJsonPath) => {
116
+ try {
117
+ if (!(await fs.pathExists(packageJsonPath)))
118
+ return;
119
+ const pkg = await fs.readJson(packageJsonPath);
120
+ const name = typeof pkg?.name === 'string' ? String(pkg.name).trim() : '';
121
+ if (!name)
122
+ return;
123
+ names.add(name);
124
+ const slug = name.includes('/') ? name.split('/').pop() : name;
125
+ if (slug)
126
+ slugs.add(String(slug));
127
+ }
128
+ catch {
129
+ // ignore
130
+ }
131
+ };
132
+ const scanOneLevel = async (baseDir) => {
133
+ const abs = path.join(rootDir, baseDir);
134
+ if (!(await fs.pathExists(abs)))
135
+ return;
136
+ const entries = await fs.readdir(abs, { withFileTypes: true });
137
+ for (const entry of entries) {
138
+ if (!entry.isDirectory())
139
+ continue;
140
+ const dirName = entry.name;
141
+ if (dirName === 'node_modules' || dirName === 'dist' || dirName === '.git' || dirName === 'archive')
142
+ continue;
143
+ slugs.add(dirName);
144
+ await considerPackageJson(path.join(abs, dirName, 'package.json'));
145
+ }
146
+ };
147
+ const scanTwoLevelsUnderApps = async () => {
148
+ const absApps = path.join(rootDir, 'apps');
149
+ if (!(await fs.pathExists(absApps)))
150
+ return;
151
+ const entries = await fs.readdir(absApps, { withFileTypes: true });
152
+ for (const entry of entries) {
153
+ if (!entry.isDirectory())
154
+ continue;
155
+ const groupDir = path.join(absApps, entry.name);
156
+ if (entry.name === 'node_modules' || entry.name === 'dist' || entry.name === '.git' || entry.name === 'archive')
157
+ continue;
158
+ const nested = await fs.readdir(groupDir, { withFileTypes: true });
159
+ for (const n of nested) {
160
+ if (!n.isDirectory())
161
+ continue;
162
+ if (n.name === 'node_modules' || n.name === 'dist' || n.name === '.git' || n.name === 'archive')
163
+ continue;
164
+ slugs.add(n.name);
165
+ await considerPackageJson(path.join(groupDir, n.name, 'package.json'));
166
+ }
167
+ }
168
+ };
169
+ await scanOneLevel('apps');
170
+ await scanTwoLevelsUnderApps();
171
+ await scanOneLevel('packages');
172
+ return { slugs, names };
173
+ };
174
+ const validateProjectOption = async (configPath, project) => {
175
+ const canonical = (0, config_manager_1.canonicalProjectKey)(project);
176
+ if (!canonical)
177
+ return undefined;
178
+ const { slugs, names } = await discoverWorkspaceProjects(configPath);
179
+ const raw = String(project ?? '').trim();
180
+ const ok = slugs.has(canonical) || names.has(raw) || names.has(`@ed/${canonical}`) || names.has(`@edcalderon/${canonical}`);
181
+ if (!ok) {
182
+ const available = Array.from(slugs).sort().slice(0, 40);
183
+ const suffix = slugs.size > 40 ? '…' : '';
184
+ throw new Error(`Unknown project scope: '${raw}'. Expected an existing workspace app/package (try one of: ${available.join(', ')}${suffix}).`);
185
+ }
186
+ return canonical;
187
+ };
110
188
  const loadRootConfigFile = async (configPath) => {
111
189
  if (!(await fs.pathExists(configPath))) {
112
190
  throw new Error(`Config file not found: ${configPath}. Run 'versioning init' to create one.`);
113
191
  }
114
192
  return await fs.readJson(configPath);
115
193
  };
116
- const ensureReentryInitialized = async (configPath, migrate) => {
117
- const cfg = await loadRootConfigFile(configPath);
118
- const reentryCfg = config_manager_1.ConfigManager.loadConfig(cfg);
119
- await fs.ensureDir(constants_1.REENTRY_STATUS_DIRNAME);
194
+ const ensureReentryInitialized = async (configPath, migrate, project) => {
195
+ const validatedProject = await validateProjectOption(configPath, project);
196
+ const rawCfg = await loadRootConfigFile(configPath);
197
+ const resolved = config_manager_1.ConfigManager.loadConfig(rawCfg, validatedProject);
198
+ const cfg = {
199
+ ...rawCfg,
200
+ reentryStatus: {
201
+ ...(rawCfg.reentryStatus ?? {}),
202
+ files: resolved.files,
203
+ },
204
+ };
205
+ const reentryCfg = config_manager_1.ConfigManager.loadConfig(cfg, validatedProject);
206
+ await fs.ensureDir(path.dirname(reentryCfg.files.jsonPath));
207
+ const defaultRoadmapPath = path.join(path.dirname(reentryCfg.files.jsonPath), constants_1.ROADMAP_MD_FILENAME);
120
208
  const existingJson = await fileManager.readFileIfExists(reentryCfg.files.jsonPath);
121
209
  if (existingJson) {
122
210
  const parsed = status_renderer_1.StatusRenderer.parseJson(existingJson);
@@ -126,12 +214,17 @@ const extension = {
126
214
  ...parsed,
127
215
  schemaVersion: '1.1',
128
216
  milestone: parsed.milestone ?? null,
129
- roadmapFile: parsed.roadmapFile || roadmap_renderer_1.RoadmapRenderer.defaultRoadmapPath()
217
+ roadmapFile: defaultRoadmapPath
130
218
  };
131
219
  await fileManager.writeStatusJson(cfg, migrated);
132
- return migrated;
220
+ return { cfg, status: migrated };
133
221
  }
134
- return parsed;
222
+ const normalized = {
223
+ ...parsed,
224
+ schemaVersion: '1.1',
225
+ roadmapFile: parsed.roadmapFile || defaultRoadmapPath,
226
+ };
227
+ return { cfg, status: normalized };
135
228
  }
136
229
  const initial = {
137
230
  schemaVersion: '1.1',
@@ -144,7 +237,7 @@ const extension = {
144
237
  versioningInfo: {}
145
238
  },
146
239
  milestone: null,
147
- roadmapFile: roadmap_renderer_1.RoadmapRenderer.defaultRoadmapPath(),
240
+ roadmapFile: defaultRoadmapPath,
148
241
  currentPhase: 'planning',
149
242
  milestones: [],
150
243
  blockers: [],
@@ -162,7 +255,7 @@ const extension = {
162
255
  }
163
256
  };
164
257
  await fileManager.writeStatusFiles(cfg, initial);
165
- return initial;
258
+ return { cfg, status: initial };
166
259
  };
167
260
  program
168
261
  .command('reentry')
@@ -170,9 +263,10 @@ const extension = {
170
263
  .addCommand(new commander_1.Command('init')
171
264
  .description('Initialize re-entry status files')
172
265
  .option('-c, --config <file>', 'config file path', 'versioning.config.json')
266
+ .option('-p, --project <name>', 'project scope (separate ROADMAP/REENTRY/status per project)')
173
267
  .option('--migrate', 'rewrite v1.0 schema to v1.1 (no semantic changes)', false)
174
268
  .action(async (options) => {
175
- const status = await ensureReentryInitialized(options.config, Boolean(options.migrate));
269
+ const { status } = await ensureReentryInitialized(options.config, Boolean(options.migrate), options.project);
176
270
  console.log(`✅ Initialized re-entry status (schema ${status.schemaVersion})`);
177
271
  }))
178
272
  .addCommand(new commander_1.Command('set')
@@ -180,10 +274,10 @@ const extension = {
180
274
  .option('--phase <phase>', 'Set current phase')
181
275
  .option('--next <text>', 'Set next micro-step (replaces first nextSteps entry)')
182
276
  .option('-c, --config <file>', 'config file path', 'versioning.config.json')
277
+ .option('-p, --project <name>', 'project scope (separate ROADMAP/REENTRY/status per project)')
183
278
  .option('--migrate', 'rewrite v1.0 schema to v1.1 (no semantic changes)', false)
184
279
  .action(async (options) => {
185
- const cfg = await loadRootConfigFile(options.config);
186
- const status = await ensureReentryInitialized(options.config, Boolean(options.migrate));
280
+ const { cfg, status } = await ensureReentryInitialized(options.config, Boolean(options.migrate), options.project);
187
281
  const nextStepText = typeof options.next === 'string' ? options.next.trim() : '';
188
282
  const phase = typeof options.phase === 'string' ? options.phase.trim() : '';
189
283
  const updated = {
@@ -201,11 +295,11 @@ const extension = {
201
295
  .addCommand(new commander_1.Command('sync')
202
296
  .description('Ensure generated status files exist and are up to date (idempotent)')
203
297
  .option('-c, --config <file>', 'config file path', 'versioning.config.json')
298
+ .option('-p, --project <name>', 'project scope (separate ROADMAP/REENTRY/status per project)')
204
299
  .option('--migrate', 'rewrite v1.0 schema to v1.1 (no semantic changes)', false)
205
300
  .action(async (options) => {
206
- const cfg = await loadRootConfigFile(options.config);
207
- const reentryCfg = config_manager_1.ConfigManager.loadConfig(cfg);
208
- const status = await ensureReentryInitialized(options.config, Boolean(options.migrate));
301
+ const { cfg, status } = await ensureReentryInitialized(options.config, Boolean(options.migrate), options.project);
302
+ const reentryCfg = config_manager_1.ConfigManager.loadConfig(cfg, options.project);
209
303
  // Ensure ROADMAP exists (light touch: only managed block is updated).
210
304
  const roadmapPath = status.roadmapFile || roadmap_renderer_1.RoadmapRenderer.defaultRoadmapPath();
211
305
  const existing = await fileManager.readFileIfExists(roadmapPath);
@@ -296,14 +390,19 @@ const extension = {
296
390
  .addCommand(new commander_1.Command('init')
297
391
  .description(`Create ${constants_1.REENTRY_STATUS_DIRNAME}/${constants_1.ROADMAP_MD_FILENAME} if missing and ensure managed header block`)
298
392
  .option('-c, --config <file>', 'config file path', 'versioning.config.json')
393
+ .option('-p, --project <name>', 'project scope (separate ROADMAP/REENTRY/status per project)')
299
394
  .option('-t, --title <title>', 'project title for ROADMAP.md template', 'Untitled')
300
395
  .action(async (options) => {
301
- const cfg = await loadRootConfigFile(options.config);
302
- const status = await ensureReentryInitialized(options.config, false);
396
+ const projectKey = await validateProjectOption(options.config, options.project);
397
+ const { cfg, status } = await ensureReentryInitialized(options.config, false, projectKey);
303
398
  const roadmapPath = status.roadmapFile || roadmap_renderer_1.RoadmapRenderer.defaultRoadmapPath();
399
+ // If a project is specified and title is left default, prefer a non-stale title.
400
+ const title = projectKey && String(options.title).trim() === 'Untitled'
401
+ ? String(options.project ?? projectKey)
402
+ : String(options.title);
304
403
  const existing = await fileManager.readFileIfExists(roadmapPath);
305
404
  if (!existing) {
306
- await fileManager.writeFileIfChanged(roadmapPath, roadmap_renderer_1.RoadmapRenderer.renderTemplate({ projectTitle: options.title }, { milestone: status.milestone, roadmapFile: roadmapPath }));
405
+ await fileManager.writeFileIfChanged(roadmapPath, roadmap_renderer_1.RoadmapRenderer.renderTemplate({ projectTitle: title }, { milestone: status.milestone, roadmapFile: roadmapPath }));
307
406
  console.log(`✅ Created ${roadmapPath}`);
308
407
  return;
309
408
  }
@@ -317,12 +416,40 @@ const extension = {
317
416
  }
318
417
  // Keep REENTRY.md consistent with roadmap references.
319
418
  await fileManager.writeReentryMarkdown(cfg, status);
419
+ }))
420
+ .addCommand(new commander_1.Command('validate')
421
+ .description('Validate that project roadmaps correspond to existing workspaces (detect stale roadmaps)')
422
+ .option('-c, --config <file>', 'config file path', 'versioning.config.json')
423
+ .action(async (options) => {
424
+ const { slugs } = await discoverWorkspaceProjects(String(options.config));
425
+ const projectsDir = path.join(path.dirname(String(options.config)), constants_1.REENTRY_STATUS_DIRNAME, 'projects');
426
+ if (!(await fs.pathExists(projectsDir))) {
427
+ console.log('✅ No project roadmaps found');
428
+ return;
429
+ }
430
+ const entries = await fs.readdir(projectsDir, { withFileTypes: true });
431
+ const stale = [];
432
+ for (const entry of entries) {
433
+ if (!entry.isDirectory())
434
+ continue;
435
+ const key = entry.name;
436
+ if (!slugs.has(key)) {
437
+ stale.push(key);
438
+ }
439
+ }
440
+ if (stale.length === 0) {
441
+ console.log('✅ All project roadmaps match a workspace');
442
+ return;
443
+ }
444
+ console.warn(`⚠️ Stale project roadmaps found (no matching workspace): ${stale.join(', ')}`);
445
+ process.exitCode = 1;
320
446
  }))
321
447
  .addCommand(new commander_1.Command('list')
322
448
  .description('List roadmap milestones parsed from ROADMAP.md')
323
449
  .option('-c, --config <file>', 'config file path', 'versioning.config.json')
450
+ .option('-p, --project <name>', 'project scope (separate ROADMAP/REENTRY/status per project)')
324
451
  .action(async (options) => {
325
- const status = await ensureReentryInitialized(options.config, false);
452
+ const { status } = await ensureReentryInitialized(options.config, false, options.project);
326
453
  const roadmapPath = status.roadmapFile || roadmap_renderer_1.RoadmapRenderer.defaultRoadmapPath();
327
454
  const content = await fileManager.readFileIfExists(roadmapPath);
328
455
  if (!content) {
@@ -346,9 +473,9 @@ const extension = {
346
473
  .requiredOption('--id <id>', 'Milestone id (must match a [id] in ROADMAP.md)')
347
474
  .requiredOption('--title <title>', 'Milestone title')
348
475
  .option('-c, --config <file>', 'config file path', 'versioning.config.json')
476
+ .option('-p, --project <name>', 'project scope (separate ROADMAP/REENTRY/status per project)')
349
477
  .action(async (options) => {
350
- const cfg = await loadRootConfigFile(options.config);
351
- const status = await ensureReentryInitialized(options.config, false);
478
+ const { cfg, status } = await ensureReentryInitialized(options.config, false, options.project);
352
479
  const next = {
353
480
  ...status,
354
481
  schemaVersion: '1.1',
@@ -365,8 +492,9 @@ const extension = {
365
492
  .requiredOption('--item <item>', 'Item text')
366
493
  .option('--id <id>', 'Optional explicit id (e.g., now-02)')
367
494
  .option('-c, --config <file>', 'config file path', 'versioning.config.json')
495
+ .option('-p, --project <name>', 'project scope (separate ROADMAP/REENTRY/status per project)')
368
496
  .action(async (options) => {
369
- const status = await ensureReentryInitialized(options.config, false);
497
+ const { status } = await ensureReentryInitialized(options.config, false, options.project);
370
498
  const roadmapPath = status.roadmapFile || roadmap_renderer_1.RoadmapRenderer.defaultRoadmapPath();
371
499
  const content = await fileManager.readFileIfExists(roadmapPath);
372
500
  if (!content) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@edcalderon/versioning",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "description": "A comprehensive versioning and changelog management tool for monorepos",
5
5
  "main": "dist/index.js",
6
6
  "bin": {