@edcalderon/versioning 1.0.10 → 1.1.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.
Files changed (39) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/README.md +27 -0
  3. package/dist/cli.js +3 -1
  4. package/dist/extensions/reentry-status/config-manager.d.ts +12 -0
  5. package/dist/extensions/reentry-status/config-manager.js +120 -0
  6. package/dist/extensions/reentry-status/constants.d.ts +6 -0
  7. package/dist/extensions/reentry-status/constants.js +9 -0
  8. package/dist/extensions/reentry-status/dirty-detection.d.ts +4 -0
  9. package/dist/extensions/reentry-status/dirty-detection.js +18 -0
  10. package/dist/extensions/reentry-status/file-manager.d.ts +29 -0
  11. package/dist/extensions/reentry-status/file-manager.js +144 -0
  12. package/dist/extensions/reentry-status/github-rest-client.d.ts +29 -0
  13. package/dist/extensions/reentry-status/github-rest-client.js +72 -0
  14. package/dist/extensions/reentry-status/github-sync-adapter.d.ts +39 -0
  15. package/dist/extensions/reentry-status/github-sync-adapter.js +80 -0
  16. package/dist/extensions/reentry-status/index.d.ts +14 -0
  17. package/dist/extensions/reentry-status/index.js +30 -0
  18. package/dist/extensions/reentry-status/models.d.ts +159 -0
  19. package/dist/extensions/reentry-status/models.js +3 -0
  20. package/dist/extensions/reentry-status/obsidian-cli-client.d.ts +15 -0
  21. package/dist/extensions/reentry-status/obsidian-cli-client.js +96 -0
  22. package/dist/extensions/reentry-status/obsidian-sync-adapter.d.ts +24 -0
  23. package/dist/extensions/reentry-status/obsidian-sync-adapter.js +80 -0
  24. package/dist/extensions/reentry-status/reentry-status-manager.d.ts +30 -0
  25. package/dist/extensions/reentry-status/reentry-status-manager.js +193 -0
  26. package/dist/extensions/reentry-status/roadmap-parser.d.ts +13 -0
  27. package/dist/extensions/reentry-status/roadmap-parser.js +32 -0
  28. package/dist/extensions/reentry-status/roadmap-renderer.d.ts +19 -0
  29. package/dist/extensions/reentry-status/roadmap-renderer.js +92 -0
  30. package/dist/extensions/reentry-status/status-renderer.d.ts +10 -0
  31. package/dist/extensions/reentry-status/status-renderer.js +176 -0
  32. package/dist/extensions/reentry-status-extension.d.ts +4 -0
  33. package/dist/extensions/reentry-status-extension.js +400 -0
  34. package/examples/reentry-status/README.md +18 -0
  35. package/examples/reentry-status/REENTRY.md.example +14 -0
  36. package/examples/reentry-status/ROADMAP.md.example +25 -0
  37. package/examples/reentry-status/reentry.status.v1.1.example.json +42 -0
  38. package/examples/reentry-status/versioning.config.reentryStatus.example.json +44 -0
  39. package/package.json +2 -1
package/CHANGELOG.md CHANGED
@@ -1,3 +1,55 @@
1
+ # [1.1.0](https://github.com/edcalderon/my-second-brain/compare/v1.1.4...v1.1.0) (2026-02-12)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * add force-static export to API routes for static export compatibility ([6333e2d](https://github.com/edcalderon/my-second-brain/commit/6333e2d2b3ae149b5b893520271e7a0e171bace8))
7
+ * add search method to supermemory wrapper ([75f646a](https://github.com/edcalderon/my-second-brain/commit/75f646a377ee96a2ef66e9225b003ef19e25277e))
8
+ * change Next.js output to export for static GitHub Pages deployment ([c94fded](https://github.com/edcalderon/my-second-brain/commit/c94fdedda9b311b1aae27ddce4b2c9bb6083b325))
9
+ * lazy-load supermemory to allow builds without API key ([6892009](https://github.com/edcalderon/my-second-brain/commit/68920097e9aeabe76d8a08e45d96837ba009f32d))
10
+ * make environment variable check optional in deploy-web workflow ([e6cb9b0](https://github.com/edcalderon/my-second-brain/commit/e6cb9b0873fc9244fbc104f2bb832056e1ffd44c))
11
+ * pin pnpm version to 8.15.9 in GitHub Actions workflows for lockfile compatibility ([3a8538e](https://github.com/edcalderon/my-second-brain/commit/3a8538e3e2f84f21b4ad98d2c878eac8e6c065f7))
12
+ * relax pnpm version requirement to >=8.0.0 for CI compatibility ([9aa90ac](https://github.com/edcalderon/my-second-brain/commit/9aa90acc92b62debd0e0e840a493d30e71821b38))
13
+
14
+
15
+ ### Features
16
+
17
+ * **gcp-functions:** Implement automated GitHub daily screenshot and tweet ([b3366d4](https://github.com/edcalderon/my-second-brain/commit/b3366d4a13858ae2702f9f4cd16216a41f650a83))
18
+ * **versioning:** reentry status roadmap layer (v1.1.0) ([f6e94e5](https://github.com/edcalderon/my-second-brain/commit/f6e94e5dfc1fdd386d4ae9612c083155a2c803e8))
19
+
20
+
21
+
22
+
23
+
24
+ # [1.1.0](https://github.com/edcalderon/my-second-brain/compare/v1.1.4...v1.1.0) (2026-02-12)
25
+
26
+
27
+ ### Bug Fixes
28
+
29
+ * add force-static export to API routes for static export compatibility ([6333e2d](https://github.com/edcalderon/my-second-brain/commit/6333e2d2b3ae149b5b893520271e7a0e171bace8))
30
+ * add search method to supermemory wrapper ([75f646a](https://github.com/edcalderon/my-second-brain/commit/75f646a377ee96a2ef66e9225b003ef19e25277e))
31
+ * change Next.js output to export for static GitHub Pages deployment ([c94fded](https://github.com/edcalderon/my-second-brain/commit/c94fdedda9b311b1aae27ddce4b2c9bb6083b325))
32
+ * lazy-load supermemory to allow builds without API key ([6892009](https://github.com/edcalderon/my-second-brain/commit/68920097e9aeabe76d8a08e45d96837ba009f32d))
33
+ * make environment variable check optional in deploy-web workflow ([e6cb9b0](https://github.com/edcalderon/my-second-brain/commit/e6cb9b0873fc9244fbc104f2bb832056e1ffd44c))
34
+ * pin pnpm version to 8.15.9 in GitHub Actions workflows for lockfile compatibility ([3a8538e](https://github.com/edcalderon/my-second-brain/commit/3a8538e3e2f84f21b4ad98d2c878eac8e6c065f7))
35
+ * relax pnpm version requirement to >=8.0.0 for CI compatibility ([9aa90ac](https://github.com/edcalderon/my-second-brain/commit/9aa90acc92b62debd0e0e840a493d30e71821b38))
36
+
37
+
38
+ ### Features
39
+
40
+ * **gcp-functions:** Implement automated GitHub daily screenshot and tweet ([b3366d4](https://github.com/edcalderon/my-second-brain/commit/b3366d4a13858ae2702f9f4cd16216a41f650a83))
41
+ * **versioning:** reentry status roadmap layer (v1.1.0) ([f6e94e5](https://github.com/edcalderon/my-second-brain/commit/f6e94e5dfc1fdd386d4ae9612c083155a2c803e8))
42
+
43
+
44
+
45
+
46
+
47
+ ## [1.0.11](https://github.com/edcalderon/my-second-brain/compare/v1.0.10...v1.0.11) (2026-01-02)
48
+
49
+
50
+
51
+
52
+
1
53
  ## [1.0.10](https://github.com/edcalderon/my-second-brain/compare/v1.0.9...v1.0.10) (2026-01-02)
2
54
 
3
55
 
package/README.md CHANGED
@@ -113,6 +113,33 @@ Features:
113
113
  - Local registry support
114
114
  - Dry-run mode
115
115
 
116
+ #### Re-entry Status + Roadmap Extension
117
+
118
+ Maintains a fast **re-entry** layer (current state + next micro-step) and a slow **roadmap/backlog** layer (long-term plan).
119
+
120
+ Canonical files:
121
+ - `.versioning/reentry.status.json` (machine)
122
+ - `.versioning/REENTRY.md` (generated, minimal diffs)
123
+ - `.versioning/ROADMAP.md` (human-first; only a small managed header block is auto-updated)
124
+
125
+ Commands:
126
+
127
+ ```bash
128
+ # Fast layer
129
+ versioning reentry init
130
+ versioning reentry sync
131
+
132
+ # Slow layer
133
+ versioning roadmap init --title "My Project"
134
+ versioning roadmap list
135
+ 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"
137
+ ```
138
+
139
+ Backward compatibility:
140
+ - v1.0 status files load safely.
141
+ - Schema migrates to v1.1 only when you actually modify status, or explicitly via `versioning reentry sync --migrate`.
142
+
116
143
  ### External Extensions
117
144
 
118
145
  To use external extensions, add them to your `versioning.config.json`:
package/dist/cli.js CHANGED
@@ -41,11 +41,13 @@ const changelog_1 = require("./changelog");
41
41
  const sync_1 = require("./sync");
42
42
  const release_1 = require("./release");
43
43
  const extensions_1 = require("./extensions");
44
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
45
+ const pkg = require('../package.json');
44
46
  const program = new commander_1.Command();
45
47
  program
46
48
  .name('versioning')
47
49
  .description('Comprehensive versioning and changelog management for monorepos')
48
- .version('1.0.4');
50
+ .version(pkg.version || '0.0.0');
49
51
  program
50
52
  .command('bump <type>')
51
53
  .description('Bump version (patch, minor, major, prerelease)')
@@ -0,0 +1,12 @@
1
+ import { ReentryStatusConfig, SyncTarget } from './models';
2
+ export interface ValidationResult {
3
+ valid: boolean;
4
+ errors: string[];
5
+ }
6
+ export declare class ConfigManager {
7
+ static loadConfig(rootConfig: any): ReentryStatusConfig;
8
+ static mergeWithDefaults(partial: Partial<ReentryStatusConfig>): ReentryStatusConfig;
9
+ static validateConfig(config: ReentryStatusConfig): ValidationResult;
10
+ static getSyncTargets(config: ReentryStatusConfig): SyncTarget[];
11
+ }
12
+ //# sourceMappingURL=config-manager.d.ts.map
@@ -0,0 +1,120 @@
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.ConfigManager = void 0;
37
+ const path = __importStar(require("path"));
38
+ const constants_1 = require("./constants");
39
+ function isNonEmptyString(value) {
40
+ return typeof value === 'string' && value.trim().length > 0;
41
+ }
42
+ function defaultFilesConfig() {
43
+ return {
44
+ jsonPath: path.join(constants_1.REENTRY_STATUS_DIRNAME, constants_1.REENTRY_STATUS_JSON_FILENAME),
45
+ markdownPath: path.join(constants_1.REENTRY_STATUS_DIRNAME, constants_1.REENTRY_STATUS_MD_FILENAME)
46
+ };
47
+ }
48
+ class ConfigManager {
49
+ static loadConfig(rootConfig) {
50
+ const partial = (rootConfig && typeof rootConfig === 'object' ? rootConfig.reentryStatus : undefined);
51
+ return ConfigManager.mergeWithDefaults(partial ?? {});
52
+ }
53
+ static mergeWithDefaults(partial) {
54
+ const merged = {
55
+ enabled: partial.enabled ?? true,
56
+ autoSync: partial.autoSync ?? true,
57
+ failHard: partial.failHard ?? false,
58
+ hooks: {
59
+ postVersion: partial.hooks?.postVersion ?? true,
60
+ postRelease: partial.hooks?.postRelease ?? false
61
+ },
62
+ files: {
63
+ jsonPath: partial.files?.jsonPath ?? defaultFilesConfig().jsonPath,
64
+ markdownPath: partial.files?.markdownPath ?? defaultFilesConfig().markdownPath
65
+ },
66
+ github: partial.github,
67
+ obsidian: partial.obsidian,
68
+ template: partial.template
69
+ ? {
70
+ includeSections: partial.template.includeSections ?? [],
71
+ excludeSections: partial.template.excludeSections ?? [],
72
+ customSections: partial.template.customSections
73
+ }
74
+ : undefined
75
+ };
76
+ return merged;
77
+ }
78
+ static validateConfig(config) {
79
+ const errors = [];
80
+ if (!config.files || !isNonEmptyString(config.files.jsonPath) || !isNonEmptyString(config.files.markdownPath)) {
81
+ errors.push('files.jsonPath and files.markdownPath must be non-empty strings');
82
+ }
83
+ if (config.github?.enabled) {
84
+ if (!isNonEmptyString(config.github.owner))
85
+ errors.push('github.owner is required when github.enabled is true');
86
+ if (!isNonEmptyString(config.github.repo))
87
+ errors.push('github.repo is required when github.enabled is true');
88
+ if (!config.github.issue || !isNonEmptyString(config.github.issue.title)) {
89
+ errors.push('github.issue.title is required when github.enabled is true');
90
+ }
91
+ if (!config.github.issue || !Array.isArray(config.github.issue.labels)) {
92
+ errors.push('github.issue.labels must be an array when github.enabled is true');
93
+ }
94
+ if (!config.github.auth || !isNonEmptyString(config.github.auth.token)) {
95
+ errors.push('github.auth.token is required when github.enabled is true');
96
+ }
97
+ }
98
+ if (config.obsidian?.enabled) {
99
+ if (!isNonEmptyString(config.obsidian.vaultPath)) {
100
+ errors.push('obsidian.vaultPath is required when obsidian.enabled is true');
101
+ }
102
+ if (!isNonEmptyString(config.obsidian.notePath)) {
103
+ errors.push('obsidian.notePath is required when obsidian.enabled is true');
104
+ }
105
+ }
106
+ return { valid: errors.length === 0, errors };
107
+ }
108
+ static getSyncTargets(config) {
109
+ if (!config.enabled)
110
+ return [];
111
+ const targets = ['files'];
112
+ if (config.github?.enabled)
113
+ targets.push('github');
114
+ if (config.obsidian?.enabled)
115
+ targets.push('obsidian');
116
+ return targets;
117
+ }
118
+ }
119
+ exports.ConfigManager = ConfigManager;
120
+ //# sourceMappingURL=config-manager.js.map
@@ -0,0 +1,6 @@
1
+ export declare const REENTRY_EXTENSION_NAME = "reentry-status-extension";
2
+ export declare const REENTRY_STATUS_DIRNAME = ".versioning";
3
+ export declare const REENTRY_STATUS_JSON_FILENAME = "reentry.status.json";
4
+ export declare const REENTRY_STATUS_MD_FILENAME = "REENTRY.md";
5
+ export declare const ROADMAP_MD_FILENAME = "ROADMAP.md";
6
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ROADMAP_MD_FILENAME = exports.REENTRY_STATUS_MD_FILENAME = exports.REENTRY_STATUS_JSON_FILENAME = exports.REENTRY_STATUS_DIRNAME = exports.REENTRY_EXTENSION_NAME = void 0;
4
+ exports.REENTRY_EXTENSION_NAME = 'reentry-status-extension';
5
+ exports.REENTRY_STATUS_DIRNAME = '.versioning';
6
+ exports.REENTRY_STATUS_JSON_FILENAME = 'reentry.status.json';
7
+ exports.REENTRY_STATUS_MD_FILENAME = 'REENTRY.md';
8
+ exports.ROADMAP_MD_FILENAME = 'ROADMAP.md';
9
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1,4 @@
1
+ export declare function normalizeBody(body: string): string;
2
+ export declare function bodiesEqual(a: string, b: string): boolean;
3
+ export declare function sha256(text: string): string;
4
+ //# sourceMappingURL=dirty-detection.d.ts.map
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.normalizeBody = normalizeBody;
4
+ exports.bodiesEqual = bodiesEqual;
5
+ exports.sha256 = sha256;
6
+ const crypto_1 = require("crypto");
7
+ function normalizeBody(body) {
8
+ // Normalize EOL and ensure a trailing newline for stable comparisons.
9
+ const normalized = body.replace(/\r\n/g, '\n');
10
+ return normalized.endsWith('\n') ? normalized : `${normalized}\n`;
11
+ }
12
+ function bodiesEqual(a, b) {
13
+ return normalizeBody(a) === normalizeBody(b);
14
+ }
15
+ function sha256(text) {
16
+ return (0, crypto_1.createHash)('sha256').update(normalizeBody(text), 'utf8').digest('hex');
17
+ }
18
+ //# sourceMappingURL=dirty-detection.js.map
@@ -0,0 +1,29 @@
1
+ import { ReentryStatus } from './models';
2
+ export interface FileSystem {
3
+ pathExists(filePath: string): Promise<boolean>;
4
+ ensureDir(dirPath: string): Promise<void>;
5
+ readFile(filePath: string, encoding: 'utf8'): Promise<string>;
6
+ writeFile(filePath: string, content: string, encoding: 'utf8'): Promise<void>;
7
+ move(src: string, dest: string, options: {
8
+ overwrite: boolean;
9
+ }): Promise<void>;
10
+ remove(filePath: string): Promise<void>;
11
+ }
12
+ export declare class FileManager {
13
+ private readonly fs;
14
+ constructor(fs?: FileSystem);
15
+ ensureDirForFile(filePath: string): Promise<void>;
16
+ readFileIfExists(filePath: string): Promise<string | null>;
17
+ writeFileIfChanged(filePath: string, content: string): Promise<boolean>;
18
+ /**
19
+ * Writes both JSON and Markdown status files as a single operation.
20
+ * Best-effort atomicity: either both are updated, or any partial update is rolled back.
21
+ */
22
+ writeStatusFiles(config: any, status: ReentryStatus): Promise<{
23
+ changed: boolean;
24
+ }>;
25
+ loadStatus(config: any): Promise<ReentryStatus | null>;
26
+ writeStatusJson(config: any, status: ReentryStatus): Promise<boolean>;
27
+ writeReentryMarkdown(config: any, status: ReentryStatus): Promise<boolean>;
28
+ }
29
+ //# sourceMappingURL=file-manager.d.ts.map
@@ -0,0 +1,144 @@
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.FileManager = void 0;
37
+ const fsExtra = __importStar(require("fs-extra"));
38
+ const path = __importStar(require("path"));
39
+ const config_manager_1 = require("./config-manager");
40
+ const status_renderer_1 = require("./status-renderer");
41
+ class FileManager {
42
+ constructor(fs = fsExtra) {
43
+ this.fs = fs;
44
+ }
45
+ async ensureDirForFile(filePath) {
46
+ await this.fs.ensureDir(path.dirname(filePath));
47
+ }
48
+ async readFileIfExists(filePath) {
49
+ if (!(await this.fs.pathExists(filePath)))
50
+ return null;
51
+ return await this.fs.readFile(filePath, 'utf8');
52
+ }
53
+ async writeFileIfChanged(filePath, content) {
54
+ const prev = await this.readFileIfExists(filePath);
55
+ if (prev !== null && prev.replace(/\r\n/g, '\n') === content.replace(/\r\n/g, '\n')) {
56
+ return false;
57
+ }
58
+ await this.ensureDirForFile(filePath);
59
+ const tmpPath = `${filePath}.tmp`;
60
+ await this.fs.writeFile(tmpPath, content, 'utf8');
61
+ await this.fs.move(tmpPath, filePath, { overwrite: true });
62
+ return true;
63
+ }
64
+ /**
65
+ * Writes both JSON and Markdown status files as a single operation.
66
+ * Best-effort atomicity: either both are updated, or any partial update is rolled back.
67
+ */
68
+ async writeStatusFiles(config, status) {
69
+ const reentryConfig = config_manager_1.ConfigManager.loadConfig(config);
70
+ const jsonPath = reentryConfig.files.jsonPath;
71
+ const markdownPath = reentryConfig.files.markdownPath;
72
+ const nextJson = status_renderer_1.StatusRenderer.renderJson(status);
73
+ const nextMarkdown = status_renderer_1.StatusRenderer.renderMarkdown(status);
74
+ const prevJson = await this.readFileIfExists(jsonPath);
75
+ const prevMarkdown = await this.readFileIfExists(markdownPath);
76
+ const jsonSame = prevJson !== null && prevJson.replace(/\r\n/g, '\n') === nextJson.replace(/\r\n/g, '\n');
77
+ const mdSame = prevMarkdown !== null && prevMarkdown.replace(/\r\n/g, '\n') === nextMarkdown.replace(/\r\n/g, '\n');
78
+ if (jsonSame && mdSame)
79
+ return { changed: false };
80
+ await this.ensureDirForFile(jsonPath);
81
+ await this.ensureDirForFile(markdownPath);
82
+ const jsonTmp = `${jsonPath}.tmp`;
83
+ const mdTmp = `${markdownPath}.tmp`;
84
+ // Stage temp files first.
85
+ await this.fs.writeFile(jsonTmp, nextJson, 'utf8');
86
+ await this.fs.writeFile(mdTmp, nextMarkdown, 'utf8');
87
+ let jsonMoved = false;
88
+ try {
89
+ await this.fs.move(jsonTmp, jsonPath, { overwrite: true });
90
+ jsonMoved = true;
91
+ await this.fs.move(mdTmp, markdownPath, { overwrite: true });
92
+ return { changed: true };
93
+ }
94
+ catch (error) {
95
+ // Clean up temp files (ignore errors).
96
+ try {
97
+ if (await this.fs.pathExists(jsonTmp))
98
+ await this.fs.remove(jsonTmp);
99
+ }
100
+ catch { }
101
+ try {
102
+ if (await this.fs.pathExists(mdTmp))
103
+ await this.fs.remove(mdTmp);
104
+ }
105
+ catch { }
106
+ // Roll back JSON if it was moved but Markdown wasn't.
107
+ if (jsonMoved) {
108
+ try {
109
+ if (prevJson === null) {
110
+ await this.fs.remove(jsonPath);
111
+ }
112
+ else {
113
+ const rollbackTmp = `${jsonPath}.rollback.tmp`;
114
+ await this.fs.writeFile(rollbackTmp, prevJson, 'utf8');
115
+ await this.fs.move(rollbackTmp, jsonPath, { overwrite: true });
116
+ }
117
+ }
118
+ catch {
119
+ // If rollback fails, rethrow original error.
120
+ }
121
+ }
122
+ throw error;
123
+ }
124
+ }
125
+ async loadStatus(config) {
126
+ const reentryConfig = config_manager_1.ConfigManager.loadConfig(config);
127
+ const jsonContent = await this.readFileIfExists(reentryConfig.files.jsonPath);
128
+ if (!jsonContent)
129
+ return null;
130
+ return status_renderer_1.StatusRenderer.parseJson(jsonContent);
131
+ }
132
+ async writeStatusJson(config, status) {
133
+ const reentryConfig = config_manager_1.ConfigManager.loadConfig(config);
134
+ const json = status_renderer_1.StatusRenderer.renderJson(status);
135
+ return await this.writeFileIfChanged(reentryConfig.files.jsonPath, json);
136
+ }
137
+ async writeReentryMarkdown(config, status) {
138
+ const reentryConfig = config_manager_1.ConfigManager.loadConfig(config);
139
+ const md = status_renderer_1.StatusRenderer.renderMarkdown(status);
140
+ return await this.writeFileIfChanged(reentryConfig.files.markdownPath, md);
141
+ }
142
+ }
143
+ exports.FileManager = FileManager;
144
+ //# sourceMappingURL=file-manager.js.map
@@ -0,0 +1,29 @@
1
+ import { GitHubClient, GitHubIssue } from './github-sync-adapter';
2
+ export declare class GitHubRestClient implements GitHubClient {
3
+ private readonly token;
4
+ constructor(token: string);
5
+ private request;
6
+ findIssueByTitle(params: {
7
+ owner: string;
8
+ repo: string;
9
+ title: string;
10
+ }): Promise<GitHubIssue | null>;
11
+ createIssue(params: {
12
+ owner: string;
13
+ repo: string;
14
+ title: string;
15
+ body: string;
16
+ labels: string[];
17
+ assignees?: string[];
18
+ }): Promise<GitHubIssue>;
19
+ updateIssue(params: {
20
+ owner: string;
21
+ repo: string;
22
+ issueId: number;
23
+ title?: string;
24
+ body: string;
25
+ labels?: string[];
26
+ assignees?: string[];
27
+ }): Promise<GitHubIssue>;
28
+ }
29
+ //# sourceMappingURL=github-rest-client.d.ts.map
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GitHubRestClient = void 0;
4
+ class GitHubRestClient {
5
+ constructor(token) {
6
+ this.token = token;
7
+ }
8
+ async request(method, url, body) {
9
+ const res = await fetch(url, {
10
+ method,
11
+ headers: {
12
+ Accept: 'application/vnd.github+json',
13
+ Authorization: `Bearer ${this.token}`,
14
+ 'X-GitHub-Api-Version': '2022-11-28',
15
+ 'Content-Type': 'application/json'
16
+ },
17
+ body: body ? JSON.stringify(body) : undefined
18
+ });
19
+ if (!res.ok) {
20
+ const text = await res.text();
21
+ throw new Error(`GitHub API error ${res.status} ${res.statusText}: ${text}`);
22
+ }
23
+ return (await res.json());
24
+ }
25
+ async findIssueByTitle(params) {
26
+ // Minimal approach: list open issues and match exact title.
27
+ // (Avoid Search API auth/perms complexity; keep behavior deterministic.)
28
+ const url = `https://api.github.com/repos/${params.owner}/${params.repo}/issues?state=open&per_page=100`;
29
+ const issues = await this.request('GET', url);
30
+ const match = issues.find((i) => typeof i?.title === 'string' && i.title === params.title);
31
+ if (!match)
32
+ return null;
33
+ return {
34
+ id: Number(match.number),
35
+ url: String(match.html_url ?? ''),
36
+ title: String(match.title),
37
+ body: String(match.body ?? '')
38
+ };
39
+ }
40
+ async createIssue(params) {
41
+ const url = `https://api.github.com/repos/${params.owner}/${params.repo}/issues`;
42
+ const created = await this.request('POST', url, {
43
+ title: params.title,
44
+ body: params.body,
45
+ labels: params.labels,
46
+ assignees: params.assignees
47
+ });
48
+ return {
49
+ id: Number(created.number),
50
+ url: String(created.html_url ?? ''),
51
+ title: String(created.title),
52
+ body: String(created.body ?? '')
53
+ };
54
+ }
55
+ async updateIssue(params) {
56
+ const url = `https://api.github.com/repos/${params.owner}/${params.repo}/issues/${params.issueId}`;
57
+ const updated = await this.request('PATCH', url, {
58
+ title: params.title,
59
+ body: params.body,
60
+ labels: params.labels,
61
+ assignees: params.assignees
62
+ });
63
+ return {
64
+ id: Number(updated.number),
65
+ url: String(updated.html_url ?? ''),
66
+ title: String(updated.title),
67
+ body: String(updated.body ?? '')
68
+ };
69
+ }
70
+ }
71
+ exports.GitHubRestClient = GitHubRestClient;
72
+ //# sourceMappingURL=github-rest-client.js.map
@@ -0,0 +1,39 @@
1
+ import { GitHubConfig, ReentryStatus, SyncResult } from './models';
2
+ export interface GitHubIssue {
3
+ id: number;
4
+ url: string;
5
+ title: string;
6
+ body: string;
7
+ }
8
+ export interface GitHubClient {
9
+ findIssueByTitle(params: {
10
+ owner: string;
11
+ repo: string;
12
+ title: string;
13
+ }): Promise<GitHubIssue | null>;
14
+ createIssue(params: {
15
+ owner: string;
16
+ repo: string;
17
+ title: string;
18
+ body: string;
19
+ labels: string[];
20
+ assignees?: string[];
21
+ }): Promise<GitHubIssue>;
22
+ updateIssue(params: {
23
+ owner: string;
24
+ repo: string;
25
+ issueId: number;
26
+ title?: string;
27
+ body: string;
28
+ labels?: string[];
29
+ assignees?: string[];
30
+ }): Promise<GitHubIssue>;
31
+ }
32
+ export declare class GitHubSyncAdapter {
33
+ private readonly config;
34
+ private readonly client;
35
+ constructor(config: GitHubConfig, client: GitHubClient);
36
+ renderIssueBody(status: ReentryStatus, reentryMarkdown: string): string;
37
+ sync(status: ReentryStatus, reentryMarkdown: string): Promise<SyncResult>;
38
+ }
39
+ //# sourceMappingURL=github-sync-adapter.d.ts.map