@schnick371/devsteps-shared 0.8.5 → 1.0.0-next.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 +8 -3
- package/dist/constants/index.d.ts +26 -0
- package/dist/constants/index.d.ts.map +1 -1
- package/dist/constants/index.js +26 -0
- package/dist/constants/index.js.map +1 -1
- package/dist/core/add.d.ts +7 -0
- package/dist/core/add.d.ts.map +1 -1
- package/dist/core/add.js +9 -2
- package/dist/core/add.js.map +1 -1
- package/dist/core/archive.d.ts +7 -0
- package/dist/core/archive.d.ts.map +1 -1
- package/dist/core/archive.js +30 -20
- package/dist/core/archive.js.map +1 -1
- package/dist/core/auto-migrate-detect.d.ts +61 -0
- package/dist/core/auto-migrate-detect.d.ts.map +1 -0
- package/dist/core/auto-migrate-detect.js +68 -0
- package/dist/core/auto-migrate-detect.js.map +1 -0
- package/dist/core/auto-migrate-impl.d.ts +32 -0
- package/dist/core/auto-migrate-impl.d.ts.map +1 -0
- package/dist/core/auto-migrate-impl.js +120 -0
- package/dist/core/auto-migrate-impl.js.map +1 -0
- package/dist/core/auto-migrate.d.ts +16 -131
- package/dist/core/auto-migrate.d.ts.map +1 -1
- package/dist/core/auto-migrate.js +25 -318
- package/dist/core/auto-migrate.js.map +1 -1
- package/dist/core/auto-migrate.test.js +11 -7
- package/dist/core/auto-migrate.test.js.map +1 -1
- package/dist/core/bulk-update.d.ts +7 -0
- package/dist/core/bulk-update.d.ts.map +1 -1
- package/dist/core/bulk-update.js +8 -1
- package/dist/core/bulk-update.js.map +1 -1
- package/dist/core/config.d.ts +7 -0
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +7 -0
- package/dist/core/config.js.map +1 -1
- package/dist/core/context.d.ts +9 -0
- package/dist/core/context.d.ts.map +1 -1
- package/dist/core/context.js +9 -0
- package/dist/core/context.js.map +1 -1
- package/dist/core/get.d.ts +7 -0
- package/dist/core/get.d.ts.map +1 -1
- package/dist/core/get.js +8 -1
- package/dist/core/get.js.map +1 -1
- package/dist/core/index-rebuild.d.ts +3 -0
- package/dist/core/index-rebuild.d.ts.map +1 -1
- package/dist/core/index-rebuild.js +11 -8
- package/dist/core/index-rebuild.js.map +1 -1
- package/dist/core/index-rebuild.test.js +6 -15
- package/dist/core/index-rebuild.test.js.map +1 -1
- package/dist/core/index-refs-core.d.ts +114 -0
- package/dist/core/index-refs-core.d.ts.map +1 -0
- package/dist/core/index-refs-core.js +306 -0
- package/dist/core/index-refs-core.js.map +1 -0
- package/dist/core/index-refs.d.ts +9 -124
- package/dist/core/index-refs.d.ts.map +1 -1
- package/dist/core/index-refs.js +19 -345
- package/dist/core/index-refs.js.map +1 -1
- package/dist/core/index-refs.test.js +3 -3
- package/dist/core/index-refs.test.js.map +1 -1
- package/dist/core/index.d.ts +19 -10
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +19 -10
- package/dist/core/index.js.map +1 -1
- package/dist/core/link.d.ts +33 -0
- package/dist/core/link.d.ts.map +1 -0
- package/dist/core/link.js +102 -0
- package/dist/core/link.js.map +1 -0
- package/dist/core/list.d.ts +8 -0
- package/dist/core/list.d.ts.map +1 -1
- package/dist/core/list.js +82 -5
- package/dist/core/list.js.map +1 -1
- package/dist/core/unlink.d.ts +26 -0
- package/dist/core/unlink.d.ts.map +1 -0
- package/dist/core/unlink.js +78 -0
- package/dist/core/unlink.js.map +1 -0
- package/dist/core/update.d.ts +7 -0
- package/dist/core/update.d.ts.map +1 -1
- package/dist/core/update.js +16 -8
- package/dist/core/update.js.map +1 -1
- package/dist/core/validation.d.ts +26 -1
- package/dist/core/validation.d.ts.map +1 -1
- package/dist/core/validation.js +65 -4
- package/dist/core/validation.js.map +1 -1
- package/dist/index.d.ts +11 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -4
- package/dist/index.js.map +1 -1
- package/dist/schemas/analysis.d.ts +206 -0
- package/dist/schemas/analysis.d.ts.map +1 -0
- package/dist/schemas/analysis.js +138 -0
- package/dist/schemas/analysis.js.map +1 -0
- package/dist/schemas/cbp-loops.d.ts +154 -0
- package/dist/schemas/cbp-loops.d.ts.map +1 -0
- package/dist/schemas/cbp-loops.js +160 -0
- package/dist/schemas/cbp-loops.js.map +1 -0
- package/dist/schemas/cbp-loops.test.d.ts +10 -0
- package/dist/schemas/cbp-loops.test.d.ts.map +1 -0
- package/dist/schemas/cbp-loops.test.js +268 -0
- package/dist/schemas/cbp-loops.test.js.map +1 -0
- package/dist/schemas/cbp-mandate.d.ts +107 -0
- package/dist/schemas/cbp-mandate.d.ts.map +1 -0
- package/dist/schemas/cbp-mandate.js +107 -0
- package/dist/schemas/cbp-mandate.js.map +1 -0
- package/dist/schemas/cbp-mandate.test.d.ts +10 -0
- package/dist/schemas/cbp-mandate.test.d.ts.map +1 -0
- package/dist/schemas/cbp-mandate.test.js +188 -0
- package/dist/schemas/cbp-mandate.test.js.map +1 -0
- package/dist/schemas/index-refs.schema.d.ts +4 -1
- package/dist/schemas/index-refs.schema.d.ts.map +1 -1
- package/dist/schemas/index-refs.schema.js +24 -3
- package/dist/schemas/index-refs.schema.js.map +1 -1
- package/dist/schemas/index.d.ts +17 -7
- package/dist/schemas/index.d.ts.map +1 -1
- package/dist/schemas/index.js +14 -1
- package/dist/schemas/index.js.map +1 -1
- package/dist/schemas/project.d.ts +7 -0
- package/dist/schemas/project.d.ts.map +1 -1
- package/dist/schemas/project.js +7 -0
- package/dist/schemas/project.js.map +1 -1
- package/dist/schemas/relationships.d.ts +3 -0
- package/dist/schemas/relationships.d.ts.map +1 -1
- package/dist/schemas/relationships.js +3 -0
- package/dist/schemas/relationships.js.map +1 -1
- package/dist/types/commands.d.ts +7 -0
- package/dist/types/commands.d.ts.map +1 -1
- package/dist/types/commands.js +7 -0
- package/dist/types/commands.js.map +1 -1
- package/dist/types/index-refs.types.d.ts +3 -0
- package/dist/types/index-refs.types.d.ts.map +1 -1
- package/dist/types/index-refs.types.js +3 -0
- package/dist/types/index-refs.types.js.map +1 -1
- package/dist/types/index.d.ts +9 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +7 -0
- package/dist/types/index.js.map +1 -1
- package/dist/utils/cache.d.ts +7 -0
- package/dist/utils/cache.d.ts.map +1 -1
- package/dist/utils/cache.js +7 -0
- package/dist/utils/cache.js.map +1 -1
- package/dist/utils/index.d.ts +9 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +26 -3
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/init-helpers.d.ts +30 -0
- package/dist/utils/init-helpers.d.ts.map +1 -0
- package/dist/utils/init-helpers.js +134 -0
- package/dist/utils/init-helpers.js.map +1 -0
- package/package.json +1 -1
|
@@ -1,155 +1,40 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Copyright © 2025 Thomas Hertel (the@devsteps.dev)
|
|
3
|
+
* Licensed under the Apache License, Version 2.0
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
+
* Auto-Migration Module — Orchestrator
|
|
6
|
+
*
|
|
7
|
+
* Provides auto-detection and migration for DevSteps projects.
|
|
5
8
|
* Safely migrates from legacy index.json to refs-style distributed index.
|
|
6
9
|
*
|
|
7
|
-
* Safe to call repeatedly
|
|
10
|
+
* Safe to call repeatedly — only migrates once.
|
|
8
11
|
*
|
|
9
12
|
* @see STORY-073 External Project Migration Auto-Detection
|
|
10
13
|
* @see EPIC-018 Index Architecture Refactoring
|
|
11
14
|
*/
|
|
15
|
+
import { type AutoMigrationOptions, type MigrationCheckResult, type MigrationStats, checkMigrationNeeded, getMigrationStatusMessage, needsItemsDirectoryMigration } from './auto-migrate-detect.js';
|
|
16
|
+
import { migrateItemsDirectory, performMigration } from './auto-migrate-impl.js';
|
|
17
|
+
export type { AutoMigrationOptions, MigrationCheckResult, MigrationStats };
|
|
18
|
+
export { checkMigrationNeeded, getMigrationStatusMessage, needsItemsDirectoryMigration };
|
|
19
|
+
export { performMigration, migrateItemsDirectory };
|
|
12
20
|
/**
|
|
13
|
-
*
|
|
14
|
-
*/
|
|
15
|
-
export interface MigrationCheckResult {
|
|
16
|
-
/** Whether migration is needed */
|
|
17
|
-
needed: boolean;
|
|
18
|
-
/** Whether legacy index exists */
|
|
19
|
-
hasLegacy: boolean;
|
|
20
|
-
/** Whether refs-style index exists */
|
|
21
|
-
hasRefs: boolean;
|
|
22
|
-
/** Number of items in legacy index (if exists) */
|
|
23
|
-
itemCount?: number;
|
|
24
|
-
/** Human-readable status message */
|
|
25
|
-
message: string;
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Auto-migration options
|
|
29
|
-
*/
|
|
30
|
-
export interface AutoMigrationOptions {
|
|
31
|
-
/** Skip backup creation (advanced users only) */
|
|
32
|
-
skipBackup?: boolean;
|
|
33
|
-
/** Silent mode - suppress console output */
|
|
34
|
-
silent?: boolean;
|
|
35
|
-
/** Dry-run mode - check only, don't migrate */
|
|
36
|
-
dryRun?: boolean;
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* Migration statistics
|
|
40
|
-
*/
|
|
41
|
-
export interface MigrationStats {
|
|
42
|
-
totalItems: number;
|
|
43
|
-
byType: Record<string, number>;
|
|
44
|
-
byStatus: Record<string, number>;
|
|
45
|
-
byPriority: Record<string, number>;
|
|
46
|
-
backupPath?: string;
|
|
47
|
-
archivePath?: string;
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
* Check if migration is needed
|
|
51
|
-
*
|
|
52
|
-
* Migration is needed when:
|
|
53
|
-
* - Legacy index.json exists
|
|
54
|
-
* - Refs-style index/ doesn't exist
|
|
55
|
-
*
|
|
56
|
-
* @param devstepsDir Path to .devsteps directory
|
|
57
|
-
* @returns Migration check result with details
|
|
58
|
-
*
|
|
59
|
-
* @example
|
|
60
|
-
* ```typescript
|
|
61
|
-
* const result = checkMigrationNeeded('/path/to/.devsteps');
|
|
62
|
-
* if (result.needed) {
|
|
63
|
-
* console.log(result.message);
|
|
64
|
-
* await ensureIndexMigrated('/path/to/.devsteps');
|
|
65
|
-
* }
|
|
66
|
-
* ```
|
|
67
|
-
*/
|
|
68
|
-
export declare function checkMigrationNeeded(devstepsDir: string): MigrationCheckResult;
|
|
69
|
-
/**
|
|
70
|
-
* Perform index migration (internal implementation)
|
|
71
|
-
*
|
|
72
|
-
* This is the core migration logic without console output.
|
|
73
|
-
* Use ensureIndexMigrated() for auto-migration with user feedback.
|
|
74
|
-
*
|
|
75
|
-
* @param devstepsDir Path to .devsteps directory
|
|
76
|
-
* @param options Migration options
|
|
77
|
-
* @returns Migration statistics
|
|
78
|
-
* @throws Error if migration fails or preconditions not met
|
|
79
|
-
* @internal
|
|
80
|
-
*/
|
|
81
|
-
export declare function performMigration(devstepsDir: string, options?: AutoMigrationOptions): Promise<MigrationStats>;
|
|
82
|
-
/**
|
|
83
|
-
* Get user-friendly status message
|
|
84
|
-
*
|
|
85
|
-
* @param devstepsDir Path to .devsteps directory
|
|
86
|
-
* @returns Human-readable status message
|
|
87
|
-
*
|
|
88
|
-
* @example
|
|
89
|
-
* ```typescript
|
|
90
|
-
* console.log(getMigrationStatusMessage('/path/to/.devsteps'));
|
|
91
|
-
* // Output: "📦 DevSteps project using refs-style index ✓"
|
|
92
|
-
* ```
|
|
93
|
-
*/
|
|
94
|
-
export declare function getMigrationStatusMessage(devstepsDir: string): string;
|
|
95
|
-
/**
|
|
96
|
-
* Automatically migrate if needed
|
|
97
|
-
*
|
|
98
|
-
* Safe to call repeatedly - only migrates once.
|
|
99
|
-
* Checks if migration is needed and runs it if necessary.
|
|
21
|
+
* Automatically migrate if needed — safe to call repeatedly.
|
|
100
22
|
*
|
|
101
23
|
* @param devstepsDir Path to .devsteps directory
|
|
102
24
|
* @param options Migration options
|
|
103
25
|
* @returns True if migration was performed, false if not needed
|
|
104
26
|
* @throws Error if migration fails
|
|
105
|
-
*
|
|
106
|
-
* @example
|
|
107
|
-
* ```typescript
|
|
108
|
-
* // Auto-migrate with user feedback
|
|
109
|
-
* const migrated = await ensureIndexMigrated('/path/to/.devsteps');
|
|
110
|
-
* if (migrated) {
|
|
111
|
-
* console.log('✨ Migration complete!');
|
|
112
|
-
* }
|
|
113
|
-
*
|
|
114
|
-
* // Silent auto-migration (for background tasks)
|
|
115
|
-
* await ensureIndexMigrated('/path/to/.devsteps', { silent: true });
|
|
116
|
-
*
|
|
117
|
-
* // Dry-run (check only)
|
|
118
|
-
* const needed = await ensureIndexMigrated('/path/to/.devsteps', { dryRun: true });
|
|
119
|
-
* ```
|
|
120
27
|
*/
|
|
121
28
|
export declare function ensureIndexMigrated(devstepsDir: string, options?: AutoMigrationOptions): Promise<boolean>;
|
|
122
29
|
/**
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
* Returns true if using flat structure (epics/, stories/)
|
|
126
|
-
* instead of nested structure (items/epics/, items/stories/)
|
|
127
|
-
*/
|
|
128
|
-
export declare function needsItemsDirectoryMigration(devstepsDir: string): boolean;
|
|
129
|
-
/**
|
|
130
|
-
* Migrate from flat directory structure to items/ subdirectory
|
|
131
|
-
*
|
|
132
|
-
* Moves files from:
|
|
133
|
-
* .devsteps/epics/EPIC-001.json → .devsteps/items/epics/EPIC-001.json
|
|
134
|
-
* .devsteps/stories/... → .devsteps/items/stories/...
|
|
135
|
-
*
|
|
136
|
-
* @param devstepsDir Path to .devsteps directory
|
|
137
|
-
* @param options Migration options
|
|
138
|
-
* @returns Migration statistics
|
|
139
|
-
*/
|
|
140
|
-
export declare function migrateItemsDirectory(devstepsDir: string, options?: AutoMigrationOptions): {
|
|
141
|
-
moved: number;
|
|
142
|
-
types: Record<string, number>;
|
|
143
|
-
};
|
|
144
|
-
/**
|
|
145
|
-
* Ensure index is migrated AND items are in correct directory structure
|
|
30
|
+
* Ensure index is migrated AND items are in correct directory structure.
|
|
146
31
|
*
|
|
147
|
-
* Combines
|
|
32
|
+
* Combines:
|
|
148
33
|
* 1. Legacy index.json → refs-style index/ (if needed)
|
|
149
34
|
* 2. Flat structure → items/ subdirectory (if needed)
|
|
150
|
-
* 3. Cleanup
|
|
35
|
+
* 3. Cleanup orphaned legacy index.json if refs-style is valid
|
|
151
36
|
*
|
|
152
|
-
* Safe to call on every operation
|
|
37
|
+
* Safe to call on every operation — only migrates when needed.
|
|
153
38
|
*/
|
|
154
39
|
export declare function ensureFullMigration(devstepsDir: string, options?: AutoMigrationOptions): Promise<void>;
|
|
155
40
|
//# sourceMappingURL=auto-migrate.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auto-migrate.d.ts","sourceRoot":"","sources":["../../src/core/auto-migrate.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"auto-migrate.d.ts","sourceRoot":"","sources":["../../src/core/auto-migrate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EACzB,KAAK,cAAc,EACnB,oBAAoB,EACpB,yBAAyB,EACzB,4BAA4B,EAC7B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAGjF,YAAY,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,cAAc,EAAE,CAAC;AAC3E,OAAO,EAAE,oBAAoB,EAAE,yBAAyB,EAAE,4BAA4B,EAAE,CAAC;AAEzF,OAAO,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,CAAC;AAEnD;;;;;;;GAOG;AACH,wBAAsB,mBAAmB,CACvC,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,OAAO,CAAC,CAuClB;AAED;;;;;;;;;GASG;AACH,wBAAsB,mBAAmB,CACvC,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,IAAI,CAAC,CAwBf"}
|
|
@@ -1,249 +1,40 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Copyright © 2025 Thomas Hertel (the@devsteps.dev)
|
|
3
|
+
* Licensed under the Apache License, Version 2.0
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
+
* Auto-Migration Module — Orchestrator
|
|
6
|
+
*
|
|
7
|
+
* Provides auto-detection and migration for DevSteps projects.
|
|
5
8
|
* Safely migrates from legacy index.json to refs-style distributed index.
|
|
6
9
|
*
|
|
7
|
-
* Safe to call repeatedly
|
|
10
|
+
* Safe to call repeatedly — only migrates once.
|
|
8
11
|
*
|
|
9
12
|
* @see STORY-073 External Project Migration Auto-Detection
|
|
10
13
|
* @see EPIC-018 Index Architecture Refactoring
|
|
11
14
|
*/
|
|
12
|
-
import { existsSync,
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
* Migration is needed when:
|
|
20
|
-
* - Legacy index.json exists
|
|
21
|
-
* - Refs-style index/ doesn't exist
|
|
22
|
-
*
|
|
23
|
-
* @param devstepsDir Path to .devsteps directory
|
|
24
|
-
* @returns Migration check result with details
|
|
25
|
-
*
|
|
26
|
-
* @example
|
|
27
|
-
* ```typescript
|
|
28
|
-
* const result = checkMigrationNeeded('/path/to/.devsteps');
|
|
29
|
-
* if (result.needed) {
|
|
30
|
-
* console.log(result.message);
|
|
31
|
-
* await ensureIndexMigrated('/path/to/.devsteps');
|
|
32
|
-
* }
|
|
33
|
-
* ```
|
|
34
|
-
*/
|
|
35
|
-
export function checkMigrationNeeded(devstepsDir) {
|
|
36
|
-
if (!existsSync(devstepsDir)) {
|
|
37
|
-
return {
|
|
38
|
-
needed: false,
|
|
39
|
-
hasLegacy: false,
|
|
40
|
-
hasRefs: false,
|
|
41
|
-
message: 'Not a DevSteps project (no .devsteps directory)',
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
const hasLegacy = hasLegacyIndex(devstepsDir);
|
|
45
|
-
const hasRefs = hasRefsStyleIndex(devstepsDir);
|
|
46
|
-
// Already migrated
|
|
47
|
-
if (!hasLegacy && hasRefs) {
|
|
48
|
-
return {
|
|
49
|
-
needed: false,
|
|
50
|
-
hasLegacy: false,
|
|
51
|
-
hasRefs: true,
|
|
52
|
-
message: 'Project already using refs-style index ✓',
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
// Both exist - error state (migration partially completed?)
|
|
56
|
-
if (hasLegacy && hasRefs) {
|
|
57
|
-
return {
|
|
58
|
-
needed: false,
|
|
59
|
-
hasLegacy: true,
|
|
60
|
-
hasRefs: true,
|
|
61
|
-
message: 'Both index formats exist - manual intervention required',
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
// No index at all - new project
|
|
65
|
-
if (!hasLegacy && !hasRefs) {
|
|
66
|
-
return {
|
|
67
|
-
needed: false,
|
|
68
|
-
hasLegacy: false,
|
|
69
|
-
hasRefs: false,
|
|
70
|
-
message: 'No index found - new project or not initialized',
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
// Migration needed
|
|
74
|
-
let itemCount;
|
|
75
|
-
try {
|
|
76
|
-
const legacy = loadLegacyIndex(devstepsDir);
|
|
77
|
-
itemCount = legacy.items.length;
|
|
78
|
-
}
|
|
79
|
-
catch (error) {
|
|
80
|
-
// Ignore - legacy index might be corrupted
|
|
81
|
-
}
|
|
82
|
-
return {
|
|
83
|
-
needed: true,
|
|
84
|
-
hasLegacy: true,
|
|
85
|
-
hasRefs: false,
|
|
86
|
-
itemCount,
|
|
87
|
-
message: `Migration needed: ${itemCount ?? 'unknown'} items to migrate`,
|
|
88
|
-
};
|
|
89
|
-
}
|
|
15
|
+
import { existsSync, renameSync } from 'node:fs';
|
|
16
|
+
import { getIndexPaths, hasRefsStyleIndex, loadAllIndexes } from './index-refs.js';
|
|
17
|
+
import { checkMigrationNeeded, getMigrationStatusMessage, needsItemsDirectoryMigration, } from './auto-migrate-detect.js';
|
|
18
|
+
import { migrateItemsDirectory, performMigration } from './auto-migrate-impl.js';
|
|
19
|
+
export { checkMigrationNeeded, getMigrationStatusMessage, needsItemsDirectoryMigration };
|
|
20
|
+
// Re-export impl functions (callers may import directly from auto-migrate)
|
|
21
|
+
export { performMigration, migrateItemsDirectory };
|
|
90
22
|
/**
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
* This is the core migration logic without console output.
|
|
94
|
-
* Use ensureIndexMigrated() for auto-migration with user feedback.
|
|
95
|
-
*
|
|
96
|
-
* @param devstepsDir Path to .devsteps directory
|
|
97
|
-
* @param options Migration options
|
|
98
|
-
* @returns Migration statistics
|
|
99
|
-
* @throws Error if migration fails or preconditions not met
|
|
100
|
-
* @internal
|
|
101
|
-
*/
|
|
102
|
-
export async function performMigration(devstepsDir, options = {}) {
|
|
103
|
-
const { skipBackup = false } = options;
|
|
104
|
-
const paths = getIndexPaths(devstepsDir);
|
|
105
|
-
// Pre-flight checks
|
|
106
|
-
if (!existsSync(devstepsDir)) {
|
|
107
|
-
throw new Error('.devsteps directory not found. Is this a DevSteps project?');
|
|
108
|
-
}
|
|
109
|
-
if (!hasLegacyIndex(devstepsDir)) {
|
|
110
|
-
throw new Error('No index.json found. Nothing to migrate.');
|
|
111
|
-
}
|
|
112
|
-
if (hasRefsStyleIndex(devstepsDir)) {
|
|
113
|
-
throw new Error('Refs-style index already exists. Aborting to prevent data loss.');
|
|
114
|
-
}
|
|
115
|
-
const stats = {
|
|
116
|
-
totalItems: 0,
|
|
117
|
-
byType: {},
|
|
118
|
-
byStatus: {},
|
|
119
|
-
byPriority: {},
|
|
120
|
-
};
|
|
121
|
-
// Backup old index
|
|
122
|
-
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
123
|
-
if (!skipBackup) {
|
|
124
|
-
const backupPath = `${paths.legacy}.backup-${timestamp}`;
|
|
125
|
-
const indexContent = readFileSync(paths.legacy, 'utf-8');
|
|
126
|
-
writeFileSync(backupPath, indexContent);
|
|
127
|
-
stats.backupPath = backupPath;
|
|
128
|
-
}
|
|
129
|
-
// Load and validate old index
|
|
130
|
-
const indexContent = readFileSync(paths.legacy, 'utf-8');
|
|
131
|
-
const legacyIndex = JSON.parse(indexContent);
|
|
132
|
-
const itemCount = legacyIndex.items.length;
|
|
133
|
-
// Initialize new index structure
|
|
134
|
-
initializeRefsStyleIndex(devstepsDir, legacyIndex.counters || {});
|
|
135
|
-
// Migrate items
|
|
136
|
-
for (const item of legacyIndex.items) {
|
|
137
|
-
// Load full metadata for each item to get eisenhower quadrant
|
|
138
|
-
const typeFolder = TYPE_TO_DIRECTORY[item.type];
|
|
139
|
-
const metadataPath = join(devstepsDir, typeFolder, `${item.id}.json`);
|
|
140
|
-
if (!existsSync(metadataPath)) {
|
|
141
|
-
// Skip items with missing metadata
|
|
142
|
-
continue;
|
|
143
|
-
}
|
|
144
|
-
const metadata = JSON.parse(readFileSync(metadataPath, 'utf-8'));
|
|
145
|
-
// Ensure eisenhower field exists (old items don't have it)
|
|
146
|
-
if (!metadata.eisenhower) {
|
|
147
|
-
metadata.eisenhower = 'not-urgent-important'; // Default Q2
|
|
148
|
-
}
|
|
149
|
-
addItemToIndex(devstepsDir, metadata);
|
|
150
|
-
stats.totalItems++;
|
|
151
|
-
stats.byType[item.type] = (stats.byType[item.type] || 0) + 1;
|
|
152
|
-
stats.byStatus[item.status] = (stats.byStatus[item.status] || 0) + 1;
|
|
153
|
-
stats.byPriority[metadata.eisenhower] = (stats.byPriority[metadata.eisenhower] || 0) + 1;
|
|
154
|
-
}
|
|
155
|
-
// Verify migration
|
|
156
|
-
const newIndexes = loadAllIndexes(devstepsDir);
|
|
157
|
-
// Count items in new index
|
|
158
|
-
let newItemCount = 0;
|
|
159
|
-
for (const items of newIndexes.byType.values()) {
|
|
160
|
-
newItemCount += items.length;
|
|
161
|
-
}
|
|
162
|
-
if (newItemCount !== stats.totalItems) {
|
|
163
|
-
throw new Error(`Item count mismatch! Migrated: ${stats.totalItems}, Verified: ${newItemCount}`);
|
|
164
|
-
}
|
|
165
|
-
// Check for duplicates
|
|
166
|
-
const allIds = new Set();
|
|
167
|
-
for (const items of newIndexes.byType.values()) {
|
|
168
|
-
for (const id of items) {
|
|
169
|
-
if (allIds.has(id)) {
|
|
170
|
-
throw new Error(`Duplicate item detected: ${id}`);
|
|
171
|
-
}
|
|
172
|
-
allIds.add(id);
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
// Archive old index
|
|
176
|
-
const archivePath = `${paths.legacy}.archived-${timestamp}`;
|
|
177
|
-
renameSync(paths.legacy, archivePath);
|
|
178
|
-
stats.archivePath = archivePath;
|
|
179
|
-
return stats;
|
|
180
|
-
}
|
|
181
|
-
/**
|
|
182
|
-
* Get user-friendly status message
|
|
183
|
-
*
|
|
184
|
-
* @param devstepsDir Path to .devsteps directory
|
|
185
|
-
* @returns Human-readable status message
|
|
186
|
-
*
|
|
187
|
-
* @example
|
|
188
|
-
* ```typescript
|
|
189
|
-
* console.log(getMigrationStatusMessage('/path/to/.devsteps'));
|
|
190
|
-
* // Output: "📦 DevSteps project using refs-style index ✓"
|
|
191
|
-
* ```
|
|
192
|
-
*/
|
|
193
|
-
export function getMigrationStatusMessage(devstepsDir) {
|
|
194
|
-
const result = checkMigrationNeeded(devstepsDir);
|
|
195
|
-
if (!existsSync(devstepsDir)) {
|
|
196
|
-
return '❌ Not a DevSteps project';
|
|
197
|
-
}
|
|
198
|
-
if (result.needed) {
|
|
199
|
-
const count = result.itemCount ?? 'unknown';
|
|
200
|
-
return `🔄 Migration available: ${count} items can be migrated to refs-style index`;
|
|
201
|
-
}
|
|
202
|
-
if (result.hasRefs) {
|
|
203
|
-
return '✅ Project using refs-style index';
|
|
204
|
-
}
|
|
205
|
-
if (result.hasLegacy && result.hasRefs) {
|
|
206
|
-
return '⚠️ Both index formats detected - manual cleanup needed';
|
|
207
|
-
}
|
|
208
|
-
return '📦 DevSteps project (no index yet)';
|
|
209
|
-
}
|
|
210
|
-
/**
|
|
211
|
-
* Automatically migrate if needed
|
|
212
|
-
*
|
|
213
|
-
* Safe to call repeatedly - only migrates once.
|
|
214
|
-
* Checks if migration is needed and runs it if necessary.
|
|
23
|
+
* Automatically migrate if needed — safe to call repeatedly.
|
|
215
24
|
*
|
|
216
25
|
* @param devstepsDir Path to .devsteps directory
|
|
217
26
|
* @param options Migration options
|
|
218
27
|
* @returns True if migration was performed, false if not needed
|
|
219
28
|
* @throws Error if migration fails
|
|
220
|
-
*
|
|
221
|
-
* @example
|
|
222
|
-
* ```typescript
|
|
223
|
-
* // Auto-migrate with user feedback
|
|
224
|
-
* const migrated = await ensureIndexMigrated('/path/to/.devsteps');
|
|
225
|
-
* if (migrated) {
|
|
226
|
-
* console.log('✨ Migration complete!');
|
|
227
|
-
* }
|
|
228
|
-
*
|
|
229
|
-
* // Silent auto-migration (for background tasks)
|
|
230
|
-
* await ensureIndexMigrated('/path/to/.devsteps', { silent: true });
|
|
231
|
-
*
|
|
232
|
-
* // Dry-run (check only)
|
|
233
|
-
* const needed = await ensureIndexMigrated('/path/to/.devsteps', { dryRun: true });
|
|
234
|
-
* ```
|
|
235
29
|
*/
|
|
236
30
|
export async function ensureIndexMigrated(devstepsDir, options = {}) {
|
|
237
31
|
const { silent = false, dryRun = false } = options;
|
|
238
|
-
// Check if migration needed
|
|
239
32
|
const check = checkMigrationNeeded(devstepsDir);
|
|
240
33
|
if (!check.needed) {
|
|
241
|
-
if (!silent && check.hasRefs)
|
|
34
|
+
if (!silent && check.hasRefs)
|
|
242
35
|
console.log('✅ Project already using refs-style index');
|
|
243
|
-
}
|
|
244
36
|
return false;
|
|
245
37
|
}
|
|
246
|
-
// Dry-run mode - just report
|
|
247
38
|
if (dryRun) {
|
|
248
39
|
if (!silent) {
|
|
249
40
|
console.log('🔍 Migration Check (Dry-Run)');
|
|
@@ -254,7 +45,6 @@ export async function ensureIndexMigrated(devstepsDir, options = {}) {
|
|
|
254
45
|
}
|
|
255
46
|
return true;
|
|
256
47
|
}
|
|
257
|
-
// Run migration
|
|
258
48
|
if (!silent) {
|
|
259
49
|
console.log('📦 DevSteps Project Detected');
|
|
260
50
|
console.log(`🔍 Legacy index.json found (${check.itemCount ?? 'unknown'} items)`);
|
|
@@ -262,136 +52,53 @@ export async function ensureIndexMigrated(devstepsDir, options = {}) {
|
|
|
262
52
|
console.log('🔄 Auto-migrating...\n');
|
|
263
53
|
}
|
|
264
54
|
try {
|
|
265
|
-
// Run migration
|
|
266
55
|
const stats = await performMigration(devstepsDir, options);
|
|
267
56
|
if (!silent) {
|
|
268
|
-
if (stats.backupPath)
|
|
57
|
+
if (stats.backupPath)
|
|
269
58
|
console.log(` ✅ Backed up to: ${stats.backupPath}`);
|
|
270
|
-
}
|
|
271
59
|
console.log(` ✅ Verified: ${stats.totalItems} items migrated`);
|
|
272
60
|
console.log('✨ Migration complete!\n');
|
|
273
61
|
}
|
|
274
62
|
return true;
|
|
275
63
|
}
|
|
276
64
|
catch (error) {
|
|
277
|
-
if (!silent)
|
|
65
|
+
if (!silent)
|
|
278
66
|
console.error('❌ Migration failed:', error instanceof Error ? error.message : String(error));
|
|
279
|
-
}
|
|
280
67
|
throw error;
|
|
281
68
|
}
|
|
282
69
|
}
|
|
283
70
|
/**
|
|
284
|
-
*
|
|
285
|
-
*
|
|
286
|
-
* Returns true if using flat structure (epics/, stories/)
|
|
287
|
-
* instead of nested structure (items/epics/, items/stories/)
|
|
288
|
-
*/
|
|
289
|
-
export function needsItemsDirectoryMigration(devstepsDir) {
|
|
290
|
-
// Check if old flat structure exists
|
|
291
|
-
const hasOldStructure = existsSync(join(devstepsDir, 'epics')) ||
|
|
292
|
-
existsSync(join(devstepsDir, 'stories')) ||
|
|
293
|
-
existsSync(join(devstepsDir, 'tasks'));
|
|
294
|
-
// Check if new items/ structure exists
|
|
295
|
-
const hasNewStructure = existsSync(join(devstepsDir, 'items'));
|
|
296
|
-
// Need migration if old exists and new doesn't
|
|
297
|
-
return hasOldStructure && !hasNewStructure;
|
|
298
|
-
}
|
|
299
|
-
/**
|
|
300
|
-
* Migrate from flat directory structure to items/ subdirectory
|
|
301
|
-
*
|
|
302
|
-
* Moves files from:
|
|
303
|
-
* .devsteps/epics/EPIC-001.json → .devsteps/items/epics/EPIC-001.json
|
|
304
|
-
* .devsteps/stories/... → .devsteps/items/stories/...
|
|
305
|
-
*
|
|
306
|
-
* @param devstepsDir Path to .devsteps directory
|
|
307
|
-
* @param options Migration options
|
|
308
|
-
* @returns Migration statistics
|
|
309
|
-
*/
|
|
310
|
-
export function migrateItemsDirectory(devstepsDir, options = {}) {
|
|
311
|
-
const { silent = false } = options;
|
|
312
|
-
if (!silent) {
|
|
313
|
-
console.log('📂 Migrating to items/ directory structure...');
|
|
314
|
-
}
|
|
315
|
-
const oldDirs = ['epics', 'stories', 'tasks', 'bugs', 'spikes', 'tests', 'requirements', 'features'];
|
|
316
|
-
const stats = { moved: 0, types: {} };
|
|
317
|
-
// Create items/ directory
|
|
318
|
-
const itemsDir = join(devstepsDir, 'items');
|
|
319
|
-
if (!existsSync(itemsDir)) {
|
|
320
|
-
mkdirSync(itemsDir, { recursive: true });
|
|
321
|
-
}
|
|
322
|
-
// Move each type directory
|
|
323
|
-
for (const dirName of oldDirs) {
|
|
324
|
-
const oldPath = join(devstepsDir, dirName);
|
|
325
|
-
const newPath = join(itemsDir, dirName);
|
|
326
|
-
if (!existsSync(oldPath))
|
|
327
|
-
continue;
|
|
328
|
-
// Create new subdirectory
|
|
329
|
-
mkdirSync(newPath, { recursive: true });
|
|
330
|
-
// Move all files
|
|
331
|
-
const files = readdirSync(oldPath);
|
|
332
|
-
let count = 0;
|
|
333
|
-
for (const file of files) {
|
|
334
|
-
const srcPath = join(oldPath, file);
|
|
335
|
-
const dstPath = join(newPath, file);
|
|
336
|
-
// Use renameSync for atomic move
|
|
337
|
-
renameSync(srcPath, dstPath);
|
|
338
|
-
count++;
|
|
339
|
-
}
|
|
340
|
-
stats.types[dirName] = count;
|
|
341
|
-
stats.moved += count;
|
|
342
|
-
// Remove old empty directory (best effort)
|
|
343
|
-
try {
|
|
344
|
-
rmdirSync(oldPath);
|
|
345
|
-
}
|
|
346
|
-
catch {
|
|
347
|
-
// Directory not empty or other error - ignore
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
if (!silent) {
|
|
351
|
-
console.log(` ✅ Moved ${stats.moved} files to items/ subdirectories`);
|
|
352
|
-
}
|
|
353
|
-
return stats;
|
|
354
|
-
}
|
|
355
|
-
/**
|
|
356
|
-
* Ensure index is migrated AND items are in correct directory structure
|
|
71
|
+
* Ensure index is migrated AND items are in correct directory structure.
|
|
357
72
|
*
|
|
358
|
-
* Combines
|
|
73
|
+
* Combines:
|
|
359
74
|
* 1. Legacy index.json → refs-style index/ (if needed)
|
|
360
75
|
* 2. Flat structure → items/ subdirectory (if needed)
|
|
361
|
-
* 3. Cleanup
|
|
76
|
+
* 3. Cleanup orphaned legacy index.json if refs-style is valid
|
|
362
77
|
*
|
|
363
|
-
* Safe to call on every operation
|
|
78
|
+
* Safe to call on every operation — only migrates when needed.
|
|
364
79
|
*/
|
|
365
80
|
export async function ensureFullMigration(devstepsDir, options = {}) {
|
|
366
81
|
const { silent = false } = options;
|
|
367
|
-
// Step 1: Index migration (legacy → refs-style)
|
|
368
82
|
await ensureIndexMigrated(devstepsDir, options);
|
|
369
|
-
// Step 2: Directory structure migration (flat → items/)
|
|
370
83
|
if (needsItemsDirectoryMigration(devstepsDir)) {
|
|
371
84
|
migrateItemsDirectory(devstepsDir, options);
|
|
372
85
|
}
|
|
373
|
-
//
|
|
374
|
-
// This can happen if index migration completed but archiving failed
|
|
86
|
+
// Cleanup: archive stale legacy index.json if refs-style is valid
|
|
375
87
|
const paths = getIndexPaths(devstepsDir);
|
|
376
88
|
if (hasRefsStyleIndex(devstepsDir) && existsSync(paths.legacy)) {
|
|
377
|
-
// Verify refs-style index is valid before removing legacy
|
|
378
89
|
try {
|
|
379
90
|
const indexes = loadAllIndexes(devstepsDir);
|
|
380
91
|
if (indexes.byType.size > 0) {
|
|
381
|
-
// Refs-style index is valid - safe to remove legacy
|
|
382
92
|
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
383
93
|
const archivePath = `${paths.legacy}.archived-${timestamp}`;
|
|
384
94
|
renameSync(paths.legacy, archivePath);
|
|
385
|
-
if (!silent)
|
|
95
|
+
if (!silent)
|
|
386
96
|
console.log(` 🗑️ Archived legacy index: ${archivePath}`);
|
|
387
|
-
}
|
|
388
97
|
}
|
|
389
98
|
}
|
|
390
|
-
catch (
|
|
391
|
-
|
|
392
|
-
if (!silent) {
|
|
99
|
+
catch (_error) {
|
|
100
|
+
if (!silent)
|
|
393
101
|
console.warn(' ⚠️ Keeping legacy index.json (refs-style index validation failed)');
|
|
394
|
-
}
|
|
395
102
|
}
|
|
396
103
|
}
|
|
397
104
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auto-migrate.js","sourceRoot":"","sources":["../../src/core/auto-migrate.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"auto-migrate.js","sourceRoot":"","sources":["../../src/core/auto-migrate.ts"],"names":[],"mappings":"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"}
|