@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.
Files changed (39) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +50 -0
  3. package/dist/cli.js +3 -1
  4. package/dist/extensions/reentry-status/config-manager.d.ts +17 -0
  5. package/dist/extensions/reentry-status/config-manager.js +187 -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 +528 -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,49 @@
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
+
1
47
  ## [1.0.11](https://github.com/edcalderon/my-second-brain/compare/v1.0.10...v1.0.11) (2026-01-02)
2
48
 
3
49
 
package/README.md CHANGED
@@ -113,6 +113,56 @@ 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
+
122
+ Single-project (default):
123
+ - `.versioning/reentry.status.json` (machine)
124
+ - `.versioning/REENTRY.md` (generated, minimal diffs)
125
+ - `.versioning/ROADMAP.md` (human-first; only a small managed header block is auto-updated)
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
+
136
+ Commands:
137
+
138
+ ```bash
139
+ # Fast layer
140
+ versioning reentry init
141
+ versioning reentry sync
142
+
143
+ # Fast layer (scoped)
144
+ versioning reentry init --project trader
145
+ versioning reentry sync --project trader
146
+
147
+ # Slow layer
148
+ versioning roadmap init --title "My Project"
149
+ versioning roadmap list
150
+ versioning roadmap set-milestone --id "now-01" --title "Ship X"
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
160
+ ```
161
+
162
+ Backward compatibility:
163
+ - v1.0 status files load safely.
164
+ - Schema migrates to v1.1 only when you actually modify status, or explicitly via `versioning reentry sync --migrate`.
165
+
116
166
  ### External Extensions
117
167
 
118
168
  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,17 @@
1
+ import { ReentryStatusConfig, SyncTarget } from './models';
2
+ export interface ValidationResult {
3
+ valid: boolean;
4
+ errors: string[];
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;
10
+ export declare class ConfigManager {
11
+ static loadConfig(rootConfig: any, project?: string): ReentryStatusConfig;
12
+ static mergeWithDefaults(partial: DeepPartial<ReentryStatusConfig>, project?: string): ReentryStatusConfig;
13
+ static validateConfig(config: ReentryStatusConfig): ValidationResult;
14
+ static getSyncTargets(config: ReentryStatusConfig): SyncTarget[];
15
+ }
16
+ export {};
17
+ //# sourceMappingURL=config-manager.d.ts.map
@@ -0,0 +1,187 @@
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
+ exports.canonicalProjectKey = canonicalProjectKey;
38
+ const path = __importStar(require("path"));
39
+ const constants_1 = require("./constants");
40
+ function isNonEmptyString(value) {
41
+ return typeof value === 'string' && value.trim().length > 0;
42
+ }
43
+ function defaultFilesConfig() {
44
+ return {
45
+ jsonPath: path.join(constants_1.REENTRY_STATUS_DIRNAME, constants_1.REENTRY_STATUS_JSON_FILENAME),
46
+ markdownPath: path.join(constants_1.REENTRY_STATUS_DIRNAME, constants_1.REENTRY_STATUS_MD_FILENAME)
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
+ }
84
+ class ConfigManager {
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);
118
+ }
119
+ static mergeWithDefaults(partial, project) {
120
+ const defaults = defaultFilesConfigForProject(project);
121
+ const merged = {
122
+ enabled: partial.enabled ?? true,
123
+ autoSync: partial.autoSync ?? true,
124
+ failHard: partial.failHard ?? false,
125
+ hooks: {
126
+ postVersion: partial.hooks?.postVersion ?? true,
127
+ postRelease: partial.hooks?.postRelease ?? false
128
+ },
129
+ files: {
130
+ jsonPath: partial.files?.jsonPath ?? defaults.jsonPath,
131
+ markdownPath: partial.files?.markdownPath ?? defaults.markdownPath
132
+ },
133
+ github: partial.github,
134
+ obsidian: partial.obsidian,
135
+ template: partial.template
136
+ ? {
137
+ includeSections: partial.template.includeSections ?? [],
138
+ excludeSections: partial.template.excludeSections ?? [],
139
+ customSections: partial.template.customSections
140
+ }
141
+ : undefined
142
+ };
143
+ return merged;
144
+ }
145
+ static validateConfig(config) {
146
+ const errors = [];
147
+ if (!config.files || !isNonEmptyString(config.files.jsonPath) || !isNonEmptyString(config.files.markdownPath)) {
148
+ errors.push('files.jsonPath and files.markdownPath must be non-empty strings');
149
+ }
150
+ if (config.github?.enabled) {
151
+ if (!isNonEmptyString(config.github.owner))
152
+ errors.push('github.owner is required when github.enabled is true');
153
+ if (!isNonEmptyString(config.github.repo))
154
+ errors.push('github.repo is required when github.enabled is true');
155
+ if (!config.github.issue || !isNonEmptyString(config.github.issue.title)) {
156
+ errors.push('github.issue.title is required when github.enabled is true');
157
+ }
158
+ if (!config.github.issue || !Array.isArray(config.github.issue.labels)) {
159
+ errors.push('github.issue.labels must be an array when github.enabled is true');
160
+ }
161
+ if (!config.github.auth || !isNonEmptyString(config.github.auth.token)) {
162
+ errors.push('github.auth.token is required when github.enabled is true');
163
+ }
164
+ }
165
+ if (config.obsidian?.enabled) {
166
+ if (!isNonEmptyString(config.obsidian.vaultPath)) {
167
+ errors.push('obsidian.vaultPath is required when obsidian.enabled is true');
168
+ }
169
+ if (!isNonEmptyString(config.obsidian.notePath)) {
170
+ errors.push('obsidian.notePath is required when obsidian.enabled is true');
171
+ }
172
+ }
173
+ return { valid: errors.length === 0, errors };
174
+ }
175
+ static getSyncTargets(config) {
176
+ if (!config.enabled)
177
+ return [];
178
+ const targets = ['files'];
179
+ if (config.github?.enabled)
180
+ targets.push('github');
181
+ if (config.obsidian?.enabled)
182
+ targets.push('obsidian');
183
+ return targets;
184
+ }
185
+ }
186
+ exports.ConfigManager = ConfigManager;
187
+ //# 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