@edcalderon/versioning 1.0.11 → 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/CHANGELOG.md +46 -0
- package/README.md +50 -0
- package/dist/cli.js +3 -1
- package/dist/extensions/reentry-status/config-manager.d.ts +17 -0
- package/dist/extensions/reentry-status/config-manager.js +187 -0
- package/dist/extensions/reentry-status/constants.d.ts +6 -0
- package/dist/extensions/reentry-status/constants.js +9 -0
- package/dist/extensions/reentry-status/dirty-detection.d.ts +4 -0
- package/dist/extensions/reentry-status/dirty-detection.js +18 -0
- package/dist/extensions/reentry-status/file-manager.d.ts +29 -0
- package/dist/extensions/reentry-status/file-manager.js +144 -0
- package/dist/extensions/reentry-status/github-rest-client.d.ts +29 -0
- package/dist/extensions/reentry-status/github-rest-client.js +72 -0
- package/dist/extensions/reentry-status/github-sync-adapter.d.ts +39 -0
- package/dist/extensions/reentry-status/github-sync-adapter.js +80 -0
- package/dist/extensions/reentry-status/index.d.ts +14 -0
- package/dist/extensions/reentry-status/index.js +30 -0
- package/dist/extensions/reentry-status/models.d.ts +159 -0
- package/dist/extensions/reentry-status/models.js +3 -0
- package/dist/extensions/reentry-status/obsidian-cli-client.d.ts +15 -0
- package/dist/extensions/reentry-status/obsidian-cli-client.js +96 -0
- package/dist/extensions/reentry-status/obsidian-sync-adapter.d.ts +24 -0
- package/dist/extensions/reentry-status/obsidian-sync-adapter.js +80 -0
- package/dist/extensions/reentry-status/reentry-status-manager.d.ts +30 -0
- package/dist/extensions/reentry-status/reentry-status-manager.js +193 -0
- package/dist/extensions/reentry-status/roadmap-parser.d.ts +13 -0
- package/dist/extensions/reentry-status/roadmap-parser.js +32 -0
- package/dist/extensions/reentry-status/roadmap-renderer.d.ts +19 -0
- package/dist/extensions/reentry-status/roadmap-renderer.js +92 -0
- package/dist/extensions/reentry-status/status-renderer.d.ts +10 -0
- package/dist/extensions/reentry-status/status-renderer.js +176 -0
- package/dist/extensions/reentry-status-extension.d.ts +4 -0
- package/dist/extensions/reentry-status-extension.js +528 -0
- package/examples/reentry-status/README.md +18 -0
- package/examples/reentry-status/REENTRY.md.example +14 -0
- package/examples/reentry-status/ROADMAP.md.example +25 -0
- package/examples/reentry-status/reentry.status.v1.1.example.json +42 -0
- package/examples/reentry-status/versioning.config.reentryStatus.example.json +44 -0
- package/package.json +2 -1
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ReentryStatusManager = void 0;
|
|
4
|
+
const config_manager_1 = require("./config-manager");
|
|
5
|
+
const file_manager_1 = require("./file-manager");
|
|
6
|
+
const dirty_detection_1 = require("./dirty-detection");
|
|
7
|
+
const github_rest_client_1 = require("./github-rest-client");
|
|
8
|
+
const github_sync_adapter_1 = require("./github-sync-adapter");
|
|
9
|
+
const obsidian_cli_client_1 = require("./obsidian-cli-client");
|
|
10
|
+
const obsidian_sync_adapter_1 = require("./obsidian-sync-adapter");
|
|
11
|
+
const roadmap_renderer_1 = require("./roadmap-renderer");
|
|
12
|
+
const status_renderer_1 = require("./status-renderer");
|
|
13
|
+
class ReentryStatusManager {
|
|
14
|
+
constructor(options = {}) {
|
|
15
|
+
this.fileManager = options.fileManager ?? new file_manager_1.FileManager();
|
|
16
|
+
this.createGitHubSyncer = options.createGitHubSyncer;
|
|
17
|
+
this.createObsidianSyncer = options.createObsidianSyncer;
|
|
18
|
+
this.isObsidianAvailable = options.isObsidianAvailable;
|
|
19
|
+
}
|
|
20
|
+
createInitialStatus() {
|
|
21
|
+
return {
|
|
22
|
+
schemaVersion: '1.1',
|
|
23
|
+
version: '0.0.0',
|
|
24
|
+
lastUpdated: new Date(0).toISOString(),
|
|
25
|
+
updatedBy: 'unknown',
|
|
26
|
+
context: {
|
|
27
|
+
trigger: 'manual',
|
|
28
|
+
gitInfo: { branch: '', commit: '', author: '', timestamp: new Date(0).toISOString() },
|
|
29
|
+
versioningInfo: {}
|
|
30
|
+
},
|
|
31
|
+
milestone: null,
|
|
32
|
+
roadmapFile: roadmap_renderer_1.RoadmapRenderer.defaultRoadmapPath(),
|
|
33
|
+
currentPhase: 'planning',
|
|
34
|
+
milestones: [],
|
|
35
|
+
blockers: [],
|
|
36
|
+
nextSteps: [],
|
|
37
|
+
risks: [],
|
|
38
|
+
dependencies: [],
|
|
39
|
+
versioning: {
|
|
40
|
+
currentVersion: '0.0.0',
|
|
41
|
+
previousVersion: '0.0.0',
|
|
42
|
+
versionType: 'patch'
|
|
43
|
+
},
|
|
44
|
+
syncMetadata: {
|
|
45
|
+
lastSyncAttempt: new Date(0).toISOString(),
|
|
46
|
+
lastSuccessfulSync: new Date(0).toISOString()
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
async loadOrInit(rootConfig) {
|
|
51
|
+
const existing = await this.fileManager.loadStatus(rootConfig);
|
|
52
|
+
if (existing)
|
|
53
|
+
return existing;
|
|
54
|
+
const initial = this.createInitialStatus();
|
|
55
|
+
await this.fileManager.writeStatusFiles(rootConfig, initial);
|
|
56
|
+
return initial;
|
|
57
|
+
}
|
|
58
|
+
async updateStatus(rootConfig, updater) {
|
|
59
|
+
const current = await this.loadOrInit(rootConfig);
|
|
60
|
+
const next = updater(current);
|
|
61
|
+
await this.fileManager.writeStatusFiles(rootConfig, next);
|
|
62
|
+
return next;
|
|
63
|
+
}
|
|
64
|
+
async applyContext(rootConfig, context) {
|
|
65
|
+
return await this.updateStatus(rootConfig, (current) => ({
|
|
66
|
+
...current,
|
|
67
|
+
schemaVersion: '1.1',
|
|
68
|
+
context,
|
|
69
|
+
lastUpdated: new Date().toISOString()
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
72
|
+
async ensureRoadmapExists(rootConfig, status) {
|
|
73
|
+
const roadmapPath = status.roadmapFile || roadmap_renderer_1.RoadmapRenderer.defaultRoadmapPath();
|
|
74
|
+
const existing = await this.fileManager.readFileIfExists(roadmapPath);
|
|
75
|
+
if (!existing) {
|
|
76
|
+
await this.fileManager.writeFileIfChanged(roadmapPath, roadmap_renderer_1.RoadmapRenderer.renderTemplate({}, { milestone: status.milestone, roadmapFile: roadmapPath }));
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const upserted = roadmap_renderer_1.RoadmapRenderer.upsertManagedBlock(existing, { milestone: status.milestone, roadmapFile: roadmapPath });
|
|
80
|
+
if (upserted.changed) {
|
|
81
|
+
await this.fileManager.writeFileIfChanged(roadmapPath, upserted.content);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async syncAll(rootConfig, targets) {
|
|
85
|
+
const config = config_manager_1.ConfigManager.loadConfig(rootConfig);
|
|
86
|
+
const actualTargets = targets ?? config_manager_1.ConfigManager.getSyncTargets(config);
|
|
87
|
+
const status = await this.loadOrInit(rootConfig);
|
|
88
|
+
const results = [];
|
|
89
|
+
// Always keep local files up to date if requested.
|
|
90
|
+
if (actualTargets.includes('files')) {
|
|
91
|
+
const started = Date.now();
|
|
92
|
+
try {
|
|
93
|
+
await this.fileManager.writeStatusFiles(rootConfig, status);
|
|
94
|
+
await this.ensureRoadmapExists(rootConfig, status);
|
|
95
|
+
results.push({ target: 'files', success: true, timestamp: new Date().toISOString(), duration: Date.now() - started });
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
99
|
+
results.push({
|
|
100
|
+
target: 'files',
|
|
101
|
+
success: false,
|
|
102
|
+
timestamp: new Date().toISOString(),
|
|
103
|
+
duration: Date.now() - started,
|
|
104
|
+
error: { message, recoverable: false }
|
|
105
|
+
});
|
|
106
|
+
throw error;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const reentryMarkdown = status_renderer_1.StatusRenderer.renderMarkdown(status);
|
|
110
|
+
if (actualTargets.includes('github') && config.github?.enabled) {
|
|
111
|
+
const started = Date.now();
|
|
112
|
+
try {
|
|
113
|
+
const syncer = this.createGitHubSyncer
|
|
114
|
+
? this.createGitHubSyncer(config.github)
|
|
115
|
+
: new github_sync_adapter_1.GitHubSyncAdapter(config.github, new github_rest_client_1.GitHubRestClient(config.github.auth.token));
|
|
116
|
+
const result = await syncer.sync(status, reentryMarkdown);
|
|
117
|
+
results.push(result);
|
|
118
|
+
const body = syncer.renderIssueBody(status, reentryMarkdown);
|
|
119
|
+
const bodyHash = (0, dirty_detection_1.sha256)(body);
|
|
120
|
+
if (result.details?.created || result.details?.updated) {
|
|
121
|
+
await this.fileManager.writeStatusFiles(rootConfig, {
|
|
122
|
+
...status,
|
|
123
|
+
schemaVersion: '1.1',
|
|
124
|
+
syncMetadata: {
|
|
125
|
+
...status.syncMetadata,
|
|
126
|
+
githubIssueId: result.details.issueId ?? status.syncMetadata.githubIssueId,
|
|
127
|
+
githubIssueUrl: result.details.url ?? status.syncMetadata.githubIssueUrl,
|
|
128
|
+
published: {
|
|
129
|
+
...(status.syncMetadata.published ?? {}),
|
|
130
|
+
githubIssueBodySha256: bodyHash
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
138
|
+
results.push({
|
|
139
|
+
target: 'github',
|
|
140
|
+
success: false,
|
|
141
|
+
timestamp: new Date().toISOString(),
|
|
142
|
+
duration: Date.now() - started,
|
|
143
|
+
error: { message, recoverable: !config.failHard }
|
|
144
|
+
});
|
|
145
|
+
if (config.failHard)
|
|
146
|
+
throw error;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if (actualTargets.includes('obsidian') && config.obsidian?.enabled) {
|
|
150
|
+
const started = Date.now();
|
|
151
|
+
try {
|
|
152
|
+
const available = this.isObsidianAvailable ? await this.isObsidianAvailable() : await obsidian_cli_client_1.ObsidianCliClient.isAvailable();
|
|
153
|
+
if (!available)
|
|
154
|
+
throw new Error('obsidian CLI not available');
|
|
155
|
+
const syncer = this.createObsidianSyncer
|
|
156
|
+
? this.createObsidianSyncer(config.obsidian)
|
|
157
|
+
: new obsidian_sync_adapter_1.ObsidianSyncAdapter(config.obsidian, new obsidian_cli_client_1.ObsidianCliClient());
|
|
158
|
+
const result = await syncer.sync(status, reentryMarkdown);
|
|
159
|
+
results.push(result);
|
|
160
|
+
const contentHash = (0, dirty_detection_1.sha256)(syncer.renderNoteContent(status, reentryMarkdown));
|
|
161
|
+
if (result.details?.updated) {
|
|
162
|
+
await this.fileManager.writeStatusFiles(rootConfig, {
|
|
163
|
+
...status,
|
|
164
|
+
schemaVersion: '1.1',
|
|
165
|
+
syncMetadata: {
|
|
166
|
+
...status.syncMetadata,
|
|
167
|
+
obsidianNotePath: config.obsidian.notePath,
|
|
168
|
+
published: {
|
|
169
|
+
...(status.syncMetadata.published ?? {}),
|
|
170
|
+
obsidianNoteBodySha256: contentHash
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
178
|
+
results.push({
|
|
179
|
+
target: 'obsidian',
|
|
180
|
+
success: false,
|
|
181
|
+
timestamp: new Date().toISOString(),
|
|
182
|
+
duration: Date.now() - started,
|
|
183
|
+
error: { message, recoverable: !config.failHard }
|
|
184
|
+
});
|
|
185
|
+
if (config.failHard)
|
|
186
|
+
throw error;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return results;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
exports.ReentryStatusManager = ReentryStatusManager;
|
|
193
|
+
//# sourceMappingURL=reentry-status-manager.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface RoadmapMilestoneItem {
|
|
2
|
+
id: string;
|
|
3
|
+
title: string;
|
|
4
|
+
section?: string;
|
|
5
|
+
line: number;
|
|
6
|
+
}
|
|
7
|
+
export declare const ROADMAP_ITEM_REGEX: RegExp;
|
|
8
|
+
export interface ParseRoadmapResult {
|
|
9
|
+
items: RoadmapMilestoneItem[];
|
|
10
|
+
warnings: string[];
|
|
11
|
+
}
|
|
12
|
+
export declare function parseRoadmapMilestones(markdown: string): ParseRoadmapResult;
|
|
13
|
+
//# sourceMappingURL=roadmap-parser.d.ts.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ROADMAP_ITEM_REGEX = void 0;
|
|
4
|
+
exports.parseRoadmapMilestones = parseRoadmapMilestones;
|
|
5
|
+
exports.ROADMAP_ITEM_REGEX = /^\s*-\s*\[(.+?)\]\s*(.+)$/;
|
|
6
|
+
function parseRoadmapMilestones(markdown) {
|
|
7
|
+
const normalized = markdown.replace(/\r\n/g, '\n');
|
|
8
|
+
const lines = normalized.split('\n');
|
|
9
|
+
const items = [];
|
|
10
|
+
const warnings = [];
|
|
11
|
+
let currentSection;
|
|
12
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
13
|
+
const line = lines[i];
|
|
14
|
+
const h2 = /^##\s+(.+?)\s*$/.exec(line);
|
|
15
|
+
if (h2) {
|
|
16
|
+
currentSection = h2[1];
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
const match = exports.ROADMAP_ITEM_REGEX.exec(line);
|
|
20
|
+
if (!match)
|
|
21
|
+
continue;
|
|
22
|
+
const id = match[1].trim();
|
|
23
|
+
const title = match[2].trim();
|
|
24
|
+
if (!id || !title) {
|
|
25
|
+
warnings.push(`Invalid roadmap item at line ${i + 1}`);
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
items.push({ id, title, section: currentSection, line: i + 1 });
|
|
29
|
+
}
|
|
30
|
+
return { items, warnings };
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=roadmap-parser.js.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ReentryStatus } from './models';
|
|
2
|
+
export declare const ROADMAP_MANAGED_START = "<!-- roadmap:managed:start -->";
|
|
3
|
+
export declare const ROADMAP_MANAGED_END = "<!-- roadmap:managed:end -->";
|
|
4
|
+
export interface RoadmapRenderOptions {
|
|
5
|
+
projectTitle?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare class RoadmapRenderer {
|
|
8
|
+
static defaultRoadmapPath(baseDir?: string): string;
|
|
9
|
+
static renderManagedBlock(status?: Pick<ReentryStatus, 'milestone' | 'roadmapFile'>): string;
|
|
10
|
+
static renderTemplate(options?: RoadmapRenderOptions, status?: Pick<ReentryStatus, 'milestone' | 'roadmapFile'>): string;
|
|
11
|
+
/**
|
|
12
|
+
* Inserts or updates the managed header block without touching user content.
|
|
13
|
+
*/
|
|
14
|
+
static upsertManagedBlock(existing: string, status?: Pick<ReentryStatus, 'milestone' | 'roadmapFile'>): {
|
|
15
|
+
content: string;
|
|
16
|
+
changed: boolean;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=roadmap-renderer.d.ts.map
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RoadmapRenderer = exports.ROADMAP_MANAGED_END = exports.ROADMAP_MANAGED_START = void 0;
|
|
4
|
+
const constants_1 = require("./constants");
|
|
5
|
+
exports.ROADMAP_MANAGED_START = '<!-- roadmap:managed:start -->';
|
|
6
|
+
exports.ROADMAP_MANAGED_END = '<!-- roadmap:managed:end -->';
|
|
7
|
+
class RoadmapRenderer {
|
|
8
|
+
static defaultRoadmapPath(baseDir = constants_1.REENTRY_STATUS_DIRNAME) {
|
|
9
|
+
return `${baseDir}/${constants_1.ROADMAP_MD_FILENAME}`;
|
|
10
|
+
}
|
|
11
|
+
static renderManagedBlock(status) {
|
|
12
|
+
const milestoneText = status?.milestone
|
|
13
|
+
? `${status.milestone.title} (id: ${status.milestone.id})`
|
|
14
|
+
: '—';
|
|
15
|
+
const roadmapFile = status?.roadmapFile ?? RoadmapRenderer.defaultRoadmapPath();
|
|
16
|
+
// Keep this block stable: no timestamps.
|
|
17
|
+
return [
|
|
18
|
+
exports.ROADMAP_MANAGED_START,
|
|
19
|
+
'> Managed by `@edcalderon/versioning` reentry-status-extension.',
|
|
20
|
+
`> 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');
|
|
27
|
+
}
|
|
28
|
+
static renderTemplate(options = {}, status) {
|
|
29
|
+
const title = options.projectTitle?.trim() ? options.projectTitle.trim() : 'Untitled';
|
|
30
|
+
return [
|
|
31
|
+
`# Project Roadmap – ${title}`,
|
|
32
|
+
'',
|
|
33
|
+
RoadmapRenderer.renderManagedBlock(status),
|
|
34
|
+
'## North Star',
|
|
35
|
+
'',
|
|
36
|
+
'- Describe the long-term outcome this project is aiming for.',
|
|
37
|
+
'',
|
|
38
|
+
'## Now (1–2 weeks)',
|
|
39
|
+
'',
|
|
40
|
+
'- [now-01] Example: Ship X',
|
|
41
|
+
'',
|
|
42
|
+
'## Next (4–8 weeks)',
|
|
43
|
+
'',
|
|
44
|
+
'- [next-01] Example: Improve Y',
|
|
45
|
+
'',
|
|
46
|
+
'## Later',
|
|
47
|
+
'',
|
|
48
|
+
'- [later-01] Example: Explore Z',
|
|
49
|
+
''
|
|
50
|
+
].join('\n');
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Inserts or updates the managed header block without touching user content.
|
|
54
|
+
*/
|
|
55
|
+
static upsertManagedBlock(existing, status) {
|
|
56
|
+
const managed = RoadmapRenderer.renderManagedBlock(status);
|
|
57
|
+
const input = existing.replace(/\r\n/g, '\n');
|
|
58
|
+
const startIdx = input.indexOf(exports.ROADMAP_MANAGED_START);
|
|
59
|
+
const endIdx = input.indexOf(exports.ROADMAP_MANAGED_END);
|
|
60
|
+
if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
|
|
61
|
+
const endAfter = endIdx + exports.ROADMAP_MANAGED_END.length;
|
|
62
|
+
const before = input.slice(0, startIdx);
|
|
63
|
+
const after = input.slice(endAfter);
|
|
64
|
+
let afterNormalized = after;
|
|
65
|
+
if (managed.endsWith('\n') && afterNormalized.startsWith('\n')) {
|
|
66
|
+
// Prevent accidental extra blank line when swapping the managed block.
|
|
67
|
+
afterNormalized = afterNormalized.slice(1);
|
|
68
|
+
}
|
|
69
|
+
const next = `${before}${managed}${afterNormalized}`;
|
|
70
|
+
return { content: next, changed: next !== input };
|
|
71
|
+
}
|
|
72
|
+
// No managed block yet: insert right after the first H1 if present, otherwise at top.
|
|
73
|
+
const lines = input.split('\n');
|
|
74
|
+
const h1Index = lines.findIndex((l) => /^#\s+/.test(l));
|
|
75
|
+
if (h1Index !== -1) {
|
|
76
|
+
const insertAt = h1Index + 1;
|
|
77
|
+
const nextLines = [...lines];
|
|
78
|
+
// Ensure a blank line after H1.
|
|
79
|
+
if (nextLines[insertAt] !== '')
|
|
80
|
+
nextLines.splice(insertAt, 0, '');
|
|
81
|
+
// Insert managed block after that blank line.
|
|
82
|
+
const afterBlank = insertAt + 1;
|
|
83
|
+
nextLines.splice(afterBlank, 0, managed.trimEnd());
|
|
84
|
+
const next = `${nextLines.join('\n').replace(/\n{3,}/g, '\n\n')}\n`;
|
|
85
|
+
return { content: next, changed: next !== `${input}${input.endsWith('\n') ? '' : '\n'}` };
|
|
86
|
+
}
|
|
87
|
+
const next = `${managed}${input.replace(/^\n+/, '')}`;
|
|
88
|
+
return { content: next, changed: next !== input };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
exports.RoadmapRenderer = RoadmapRenderer;
|
|
92
|
+
//# sourceMappingURL=roadmap-renderer.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ReentryStatus } from './models';
|
|
2
|
+
export declare class StatusRenderer {
|
|
3
|
+
static defaultReentryMarkdownPath(): string;
|
|
4
|
+
static defaultRoadmapPath(): string;
|
|
5
|
+
static parseJson(json: string): ReentryStatus;
|
|
6
|
+
static renderJson(status: ReentryStatus): string;
|
|
7
|
+
static renderMarkdown(status: ReentryStatus): string;
|
|
8
|
+
static parseMarkdown(_markdown: string): Partial<ReentryStatus>;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=status-renderer.d.ts.map
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.StatusRenderer = void 0;
|
|
37
|
+
const path = __importStar(require("path"));
|
|
38
|
+
const constants_1 = require("./constants");
|
|
39
|
+
const roadmap_renderer_1 = require("./roadmap-renderer");
|
|
40
|
+
function sortKeysDeep(value) {
|
|
41
|
+
if (Array.isArray(value))
|
|
42
|
+
return value.map(sortKeysDeep);
|
|
43
|
+
if (value && typeof value === 'object') {
|
|
44
|
+
const obj = value;
|
|
45
|
+
const sorted = {};
|
|
46
|
+
for (const key of Object.keys(obj).sort()) {
|
|
47
|
+
const child = obj[key];
|
|
48
|
+
if (child === undefined)
|
|
49
|
+
continue;
|
|
50
|
+
sorted[key] = sortKeysDeep(child);
|
|
51
|
+
}
|
|
52
|
+
return sorted;
|
|
53
|
+
}
|
|
54
|
+
return value;
|
|
55
|
+
}
|
|
56
|
+
function nonEmptyStringOr(value, fallback) {
|
|
57
|
+
return typeof value === 'string' && value.trim().length > 0 ? value : fallback;
|
|
58
|
+
}
|
|
59
|
+
class StatusRenderer {
|
|
60
|
+
static defaultReentryMarkdownPath() {
|
|
61
|
+
return path.join(constants_1.REENTRY_STATUS_DIRNAME, constants_1.REENTRY_STATUS_MD_FILENAME);
|
|
62
|
+
}
|
|
63
|
+
static defaultRoadmapPath() {
|
|
64
|
+
return roadmap_renderer_1.RoadmapRenderer.defaultRoadmapPath();
|
|
65
|
+
}
|
|
66
|
+
static parseJson(json) {
|
|
67
|
+
const raw = JSON.parse(json);
|
|
68
|
+
const schemaVersion = raw.schemaVersion === '1.1' ? '1.1' : '1.0';
|
|
69
|
+
const milestone = raw.milestone && typeof raw.milestone === 'object' && typeof raw.milestone.id === 'string'
|
|
70
|
+
? { id: String(raw.milestone.id), title: nonEmptyStringOr(raw.milestone.title, String(raw.milestone.id)) }
|
|
71
|
+
: null;
|
|
72
|
+
const roadmapFile = nonEmptyStringOr(raw.roadmapFile, StatusRenderer.defaultRoadmapPath());
|
|
73
|
+
// Normalize missing structures to safe defaults (v1.0 compatibility).
|
|
74
|
+
const normalized = {
|
|
75
|
+
schemaVersion,
|
|
76
|
+
version: nonEmptyStringOr(raw.version, '0.0.0'),
|
|
77
|
+
lastUpdated: nonEmptyStringOr(raw.lastUpdated, new Date(0).toISOString()),
|
|
78
|
+
updatedBy: nonEmptyStringOr(raw.updatedBy, 'unknown'),
|
|
79
|
+
context: raw.context ?? {
|
|
80
|
+
trigger: 'manual',
|
|
81
|
+
gitInfo: { branch: '', commit: '', author: '', timestamp: new Date(0).toISOString() },
|
|
82
|
+
versioningInfo: {}
|
|
83
|
+
},
|
|
84
|
+
milestone,
|
|
85
|
+
roadmapFile,
|
|
86
|
+
currentPhase: raw.currentPhase ?? 'planning',
|
|
87
|
+
milestones: Array.isArray(raw.milestones) ? raw.milestones : [],
|
|
88
|
+
blockers: Array.isArray(raw.blockers) ? raw.blockers : [],
|
|
89
|
+
nextSteps: Array.isArray(raw.nextSteps) ? raw.nextSteps : [],
|
|
90
|
+
risks: Array.isArray(raw.risks) ? raw.risks : [],
|
|
91
|
+
dependencies: Array.isArray(raw.dependencies) ? raw.dependencies : [],
|
|
92
|
+
versioning: raw.versioning ?? {
|
|
93
|
+
currentVersion: '',
|
|
94
|
+
previousVersion: '',
|
|
95
|
+
versionType: 'patch'
|
|
96
|
+
},
|
|
97
|
+
syncMetadata: raw.syncMetadata ?? {
|
|
98
|
+
lastSyncAttempt: new Date(0).toISOString(),
|
|
99
|
+
lastSuccessfulSync: new Date(0).toISOString()
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
// Ensure new nested optional objects exist only when needed; do not force migration writes.
|
|
103
|
+
return normalized;
|
|
104
|
+
}
|
|
105
|
+
static renderJson(status) {
|
|
106
|
+
const stable = sortKeysDeep(status);
|
|
107
|
+
return `${JSON.stringify(stable, null, 2)}\n`;
|
|
108
|
+
}
|
|
109
|
+
static renderMarkdown(status) {
|
|
110
|
+
const milestoneText = status.milestone
|
|
111
|
+
? `${status.milestone.title} (id: ${status.milestone.id})`
|
|
112
|
+
: '—';
|
|
113
|
+
const nextMicroStep = status.nextSteps?.[0]?.description ?? '—';
|
|
114
|
+
return [
|
|
115
|
+
'# Re-entry Status',
|
|
116
|
+
'',
|
|
117
|
+
`Schema: ${status.schemaVersion}`,
|
|
118
|
+
`Version: ${status.version}`,
|
|
119
|
+
`Phase: ${status.currentPhase}`,
|
|
120
|
+
'',
|
|
121
|
+
`Next micro-step: ${nextMicroStep}`,
|
|
122
|
+
'',
|
|
123
|
+
`Milestone: ${milestoneText}`,
|
|
124
|
+
`Roadmap: ${status.roadmapFile || StatusRenderer.defaultRoadmapPath()}`,
|
|
125
|
+
'',
|
|
126
|
+
'## Notes',
|
|
127
|
+
'',
|
|
128
|
+
'- This file is generated for stable diffs. Edit ROADMAP.md for long-term planning.',
|
|
129
|
+
''
|
|
130
|
+
].join('\n');
|
|
131
|
+
}
|
|
132
|
+
static parseMarkdown(_markdown) {
|
|
133
|
+
const markdown = _markdown.replace(/\r\n/g, '\n');
|
|
134
|
+
const out = {};
|
|
135
|
+
const schema = /^Schema:[ \t]*([^\n\r]*)$/m.exec(markdown);
|
|
136
|
+
if (schema)
|
|
137
|
+
out.schemaVersion = schema[1].trim();
|
|
138
|
+
const version = /^Version:[ \t]*([^\n\r]*)$/m.exec(markdown);
|
|
139
|
+
if (version)
|
|
140
|
+
out.version = version[1].trim();
|
|
141
|
+
const phase = /^Phase:[ \t]*([^\n\r]*)$/m.exec(markdown);
|
|
142
|
+
if (phase)
|
|
143
|
+
out.currentPhase = phase[1].trim();
|
|
144
|
+
const next = /^Next micro-step:[ \t]*([^\n\r]*)$/m.exec(markdown);
|
|
145
|
+
if (next) {
|
|
146
|
+
const desc = next[1].trim();
|
|
147
|
+
if (desc && desc !== '—')
|
|
148
|
+
out.nextSteps = [{ id: 'next', description: desc, priority: 1 }];
|
|
149
|
+
}
|
|
150
|
+
const roadmap = /^Roadmap:[ \t]*([^\n\r]*)$/m.exec(markdown);
|
|
151
|
+
if (roadmap)
|
|
152
|
+
out.roadmapFile = roadmap[1].trim();
|
|
153
|
+
const milestone = /^Milestone:[ \t]*([^\n\r]*)$/m.exec(markdown);
|
|
154
|
+
if (milestone) {
|
|
155
|
+
const text = milestone[1].trim();
|
|
156
|
+
if (!text || text === '—') {
|
|
157
|
+
out.milestone = null;
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
const markers = [' (id: ', '(id: '];
|
|
161
|
+
const marker = markers.find((m) => text.includes(m));
|
|
162
|
+
if (!marker)
|
|
163
|
+
return out;
|
|
164
|
+
const markerIndex = text.lastIndexOf(marker);
|
|
165
|
+
if (markerIndex !== -1 && text.endsWith(')')) {
|
|
166
|
+
const title = text.slice(0, markerIndex).trim();
|
|
167
|
+
const id = text.slice(markerIndex + marker.length, -1).trim();
|
|
168
|
+
out.milestone = { title, id };
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return out;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
exports.StatusRenderer = StatusRenderer;
|
|
176
|
+
//# sourceMappingURL=status-renderer.js.map
|