sdx-cli 0.2.1

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 (61) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +266 -0
  3. package/bin/dev.js +11 -0
  4. package/bin/run.js +3 -0
  5. package/dist/commands/bootstrap/consumer.js +75 -0
  6. package/dist/commands/bootstrap/org.js +29 -0
  7. package/dist/commands/bootstrap/quick.js +82 -0
  8. package/dist/commands/codex/run.js +36 -0
  9. package/dist/commands/contracts/extract.js +22 -0
  10. package/dist/commands/docs/generate.js +22 -0
  11. package/dist/commands/handoff/draft.js +41 -0
  12. package/dist/commands/init.js +14 -0
  13. package/dist/commands/map/build.js +44 -0
  14. package/dist/commands/map/create.js +40 -0
  15. package/dist/commands/map/exclude.js +25 -0
  16. package/dist/commands/map/include.js +25 -0
  17. package/dist/commands/map/remove-override.js +25 -0
  18. package/dist/commands/map/status.js +30 -0
  19. package/dist/commands/migrate/artifacts.js +68 -0
  20. package/dist/commands/plan/review.js +60 -0
  21. package/dist/commands/prompt.js +62 -0
  22. package/dist/commands/publish/notices.js +98 -0
  23. package/dist/commands/publish/sync.js +67 -0
  24. package/dist/commands/publish/wiki.js +39 -0
  25. package/dist/commands/repo/add.js +29 -0
  26. package/dist/commands/repo/sync.js +30 -0
  27. package/dist/commands/service/propose.js +40 -0
  28. package/dist/commands/status.js +37 -0
  29. package/dist/commands/version.js +16 -0
  30. package/dist/index.js +10 -0
  31. package/dist/lib/artifactMigration.js +29 -0
  32. package/dist/lib/bootstrap.js +43 -0
  33. package/dist/lib/bootstrapConsumer.js +187 -0
  34. package/dist/lib/bootstrapQuick.js +27 -0
  35. package/dist/lib/codex.js +138 -0
  36. package/dist/lib/config.js +40 -0
  37. package/dist/lib/constants.js +26 -0
  38. package/dist/lib/contractChanges.js +347 -0
  39. package/dist/lib/contracts.js +93 -0
  40. package/dist/lib/db.js +41 -0
  41. package/dist/lib/docs.js +46 -0
  42. package/dist/lib/fileScan.js +34 -0
  43. package/dist/lib/fs.js +36 -0
  44. package/dist/lib/github.js +52 -0
  45. package/dist/lib/githubPublish.js +161 -0
  46. package/dist/lib/handoff.js +62 -0
  47. package/dist/lib/mapBuilder.js +182 -0
  48. package/dist/lib/paths.js +39 -0
  49. package/dist/lib/planReview.js +88 -0
  50. package/dist/lib/project.js +65 -0
  51. package/dist/lib/promptParser.js +88 -0
  52. package/dist/lib/publishContracts.js +876 -0
  53. package/dist/lib/repoRegistry.js +92 -0
  54. package/dist/lib/scope.js +110 -0
  55. package/dist/lib/serviceNoticePlan.js +130 -0
  56. package/dist/lib/serviceProposal.js +82 -0
  57. package/dist/lib/status.js +34 -0
  58. package/dist/lib/types.js +2 -0
  59. package/dist/lib/version.js +17 -0
  60. package/dist/lib/workflows.js +70 -0
  61. package/package.json +50 -0
@@ -0,0 +1,347 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.hasContractChangeIndexShape = hasContractChangeIndexShape;
7
+ exports.resolveContractDocRepoPath = resolveContractDocRepoPath;
8
+ exports.resolveContractDocAbsolutePath = resolveContractDocAbsolutePath;
9
+ exports.readContractChangeIndex = readContractChangeIndex;
10
+ exports.parseContractChangeIndexText = parseContractChangeIndexText;
11
+ exports.nextContractChangeId = nextContractChangeId;
12
+ exports.renderContractChangeIndex = renderContractChangeIndex;
13
+ exports.writeContractChangeIndex = writeContractChangeIndex;
14
+ exports.readContractChangeDoc = readContractChangeDoc;
15
+ exports.renderContractChangeDoc = renderContractChangeDoc;
16
+ exports.isNotifiableContractStatus = isNotifiableContractStatus;
17
+ exports.hasRequiredTargetContext = hasRequiredTargetContext;
18
+ exports.computeContractChangeStatus = computeContractChangeStatus;
19
+ const node_fs_1 = __importDefault(require("node:fs"));
20
+ const node_path_1 = __importDefault(require("node:path"));
21
+ const CONTRACT_TABLE_HEADER = '| ID | Name | Status | Change Type | Owner | Path | Aliases |';
22
+ const CONTRACT_TABLE_RULE = '|----|------|--------|-------------|-------|------|---------|';
23
+ const TARGET_TABLE_HEADER = '| repo | owner | context | pr_url | state |';
24
+ const TARGET_TABLE_RULE = '|------|-------|---------|--------|-------|';
25
+ function today() {
26
+ return new Date().toISOString().slice(0, 10);
27
+ }
28
+ function normalizeCell(value) {
29
+ const trimmed = value.trim();
30
+ if ((trimmed.startsWith('"') && trimmed.endsWith('"')) || (trimmed.startsWith("'") && trimmed.endsWith("'"))) {
31
+ return trimmed.slice(1, -1).trim();
32
+ }
33
+ return trimmed;
34
+ }
35
+ function splitMarkdownTableRow(value) {
36
+ return value
37
+ .trim()
38
+ .replace(/^\|/, '')
39
+ .replace(/\|$/, '')
40
+ .split('|')
41
+ .map((part) => normalizeCell(part));
42
+ }
43
+ function escapeCell(value) {
44
+ return value.replaceAll('|', '\\|');
45
+ }
46
+ function normalizeStatus(value) {
47
+ const normalized = value.trim().toLowerCase();
48
+ if (normalized === 'draft' || normalized === 'approved' || normalized === 'published' || normalized === 'closed') {
49
+ return normalized;
50
+ }
51
+ return 'draft';
52
+ }
53
+ function normalizeTargetState(value) {
54
+ const normalized = value.trim().toLowerCase();
55
+ if (normalized === 'pending' || normalized === 'opened' || normalized === 'merged' || normalized === 'blocked') {
56
+ return normalized;
57
+ }
58
+ return 'pending';
59
+ }
60
+ function parseFrontmatter(text) {
61
+ if (!text.startsWith('---\n')) {
62
+ return { frontmatter: {}, body: text };
63
+ }
64
+ const end = text.indexOf('\n---\n', 4);
65
+ if (end === -1) {
66
+ return { frontmatter: {}, body: text };
67
+ }
68
+ const block = text.slice(4, end);
69
+ const body = text.slice(end + 5);
70
+ const frontmatter = {};
71
+ for (const line of block.split(/\r?\n/)) {
72
+ const idx = line.indexOf(':');
73
+ if (idx === -1) {
74
+ continue;
75
+ }
76
+ const key = line.slice(0, idx).trim();
77
+ const value = normalizeCell(line.slice(idx + 1).trim());
78
+ if (key) {
79
+ frontmatter[key] = value;
80
+ }
81
+ }
82
+ return { frontmatter, body };
83
+ }
84
+ function hasContractChangeIndexShape(text) {
85
+ return (text.includes('doc_type: contract_change_index') &&
86
+ text.includes(CONTRACT_TABLE_HEADER) &&
87
+ text.includes(CONTRACT_TABLE_RULE));
88
+ }
89
+ function parseSections(text) {
90
+ const lines = text.split(/\r?\n/);
91
+ const sections = {};
92
+ let current = '';
93
+ let buffer = [];
94
+ const flush = () => {
95
+ if (!current) {
96
+ return;
97
+ }
98
+ sections[current] = buffer.join('\n').trim();
99
+ buffer = [];
100
+ };
101
+ for (const line of lines) {
102
+ const heading = line.match(/^##\s+(.+)$/);
103
+ if (heading) {
104
+ flush();
105
+ current = heading[1].trim();
106
+ continue;
107
+ }
108
+ if (current) {
109
+ buffer.push(line);
110
+ }
111
+ }
112
+ flush();
113
+ return sections;
114
+ }
115
+ function parseTargetsFromSection(sectionBody) {
116
+ const tableLines = sectionBody
117
+ .split(/\r?\n/)
118
+ .map((line) => line.trim())
119
+ .filter((line) => line.startsWith('|'));
120
+ if (tableLines.length < 2) {
121
+ return [];
122
+ }
123
+ const header = splitMarkdownTableRow(tableLines[0]).map((cell) => cell.toLowerCase());
124
+ const required = ['repo', 'owner', 'context', 'pr_url', 'state'];
125
+ if (required.some((col) => !header.includes(col))) {
126
+ return [];
127
+ }
128
+ const targets = [];
129
+ for (const line of tableLines.slice(2)) {
130
+ const cells = splitMarkdownTableRow(line);
131
+ const row = {};
132
+ for (let i = 0; i < header.length; i += 1) {
133
+ row[header[i]] = (cells[i] ?? '').trim();
134
+ }
135
+ const target = {
136
+ repo: row.repo ?? '',
137
+ owner: row.owner ?? '',
138
+ context: row.context ?? '',
139
+ prUrl: row.pr_url ?? '',
140
+ state: normalizeTargetState(row.state ?? ''),
141
+ };
142
+ const hasData = Object.values(target).some((value) => value.trim().length > 0);
143
+ if (hasData) {
144
+ targets.push(target);
145
+ }
146
+ }
147
+ return targets;
148
+ }
149
+ function renderTargetTable(targets) {
150
+ const lines = [TARGET_TABLE_HEADER, TARGET_TABLE_RULE];
151
+ for (const target of targets) {
152
+ lines.push(`| ${escapeCell(target.repo)} | ${escapeCell(target.owner)} | ${escapeCell(target.context)} | ${escapeCell(target.prUrl)} | ${escapeCell(target.state)} |`);
153
+ }
154
+ lines.push('');
155
+ return lines.join('\n');
156
+ }
157
+ function toPosix(value) {
158
+ return value.replaceAll('\\\\', '/').replaceAll('\\', '/');
159
+ }
160
+ function unique(values) {
161
+ return [...new Set(values.filter((value) => value.trim().length > 0))];
162
+ }
163
+ function buildContractDocCandidates(contractChangeId, rowPath) {
164
+ const normalizedPath = toPosix(rowPath.trim());
165
+ const basename = node_path_1.default.posix.basename(normalizedPath || `${contractChangeId}.md`);
166
+ return unique([
167
+ normalizedPath,
168
+ normalizedPath.startsWith('docs/') ? normalizedPath : `docs/${normalizedPath}`,
169
+ `docs/contracts/${basename}`,
170
+ `docs/contracts/${contractChangeId}.md`,
171
+ ]);
172
+ }
173
+ function resolveContractDocRepoPath(contractChangeId, rowPath) {
174
+ const candidates = buildContractDocCandidates(contractChangeId, rowPath);
175
+ for (const candidate of candidates) {
176
+ if (candidate && !candidate.startsWith('/') && candidate.startsWith('docs/')) {
177
+ return candidate;
178
+ }
179
+ }
180
+ for (const candidate of candidates) {
181
+ if (candidate && !candidate.startsWith('/')) {
182
+ return candidate;
183
+ }
184
+ }
185
+ return `docs/contracts/${contractChangeId}.md`;
186
+ }
187
+ function resolveContractDocAbsolutePath(sourceRepoPath, contractChangeId, rowPath) {
188
+ const candidates = buildContractDocCandidates(contractChangeId, rowPath);
189
+ for (const repoPath of candidates) {
190
+ const absolutePath = node_path_1.default.join(sourceRepoPath, repoPath);
191
+ if (node_fs_1.default.existsSync(absolutePath)) {
192
+ return absolutePath;
193
+ }
194
+ }
195
+ return node_path_1.default.join(sourceRepoPath, resolveContractDocRepoPath(contractChangeId, rowPath));
196
+ }
197
+ function readContractChangeIndex(indexPath) {
198
+ if (!node_fs_1.default.existsSync(indexPath)) {
199
+ return { meta: {}, rows: [] };
200
+ }
201
+ const text = node_fs_1.default.readFileSync(indexPath, 'utf8');
202
+ return parseContractChangeIndexText(text);
203
+ }
204
+ function parseContractChangeIndexText(text) {
205
+ const { frontmatter, body } = parseFrontmatter(text);
206
+ const rows = [];
207
+ for (const line of body.split(/\r?\n/)) {
208
+ const trimmed = line.trim();
209
+ if (!trimmed.startsWith('|')) {
210
+ continue;
211
+ }
212
+ if (trimmed === CONTRACT_TABLE_HEADER || trimmed === CONTRACT_TABLE_RULE) {
213
+ continue;
214
+ }
215
+ const parts = splitMarkdownTableRow(trimmed);
216
+ if (parts.length < 7 || !parts[0].startsWith('CC-')) {
217
+ continue;
218
+ }
219
+ rows.push({
220
+ contractChangeId: parts[0],
221
+ name: parts[1],
222
+ status: normalizeStatus(parts[2]),
223
+ changeType: parts[3],
224
+ owner: parts[4],
225
+ path: parts[5],
226
+ aliases: parts[6],
227
+ });
228
+ }
229
+ return {
230
+ meta: {
231
+ docType: frontmatter.doc_type,
232
+ version: frontmatter.version,
233
+ },
234
+ rows,
235
+ };
236
+ }
237
+ function nextContractChangeId(rows) {
238
+ let max = 0;
239
+ for (const row of rows) {
240
+ const match = row.contractChangeId.match(/^CC-(\d{3})$/);
241
+ if (!match) {
242
+ continue;
243
+ }
244
+ const value = Number(match[1]);
245
+ if (value > max) {
246
+ max = value;
247
+ }
248
+ }
249
+ const next = max + 1;
250
+ return `CC-${String(next).padStart(3, '0')}`;
251
+ }
252
+ function renderContractChangeIndex(rows, meta) {
253
+ const sorted = [...rows].sort((a, b) => a.contractChangeId.localeCompare(b.contractChangeId));
254
+ const lines = [
255
+ '---',
256
+ `doc_type: ${meta.docType ?? 'contract_change_index'}`,
257
+ `version: ${meta.version ?? '2.4.0'}`,
258
+ `last_synced: ${today()}`,
259
+ '---',
260
+ '# Contract Changes Index',
261
+ '',
262
+ CONTRACT_TABLE_HEADER,
263
+ CONTRACT_TABLE_RULE,
264
+ ];
265
+ for (const row of sorted) {
266
+ lines.push(`| ${escapeCell(row.contractChangeId)} | ${escapeCell(row.name)} | ${escapeCell(row.status)} | ${escapeCell(row.changeType)} | ${escapeCell(row.owner)} | ${escapeCell(row.path)} | ${escapeCell(row.aliases)} |`);
267
+ }
268
+ lines.push('');
269
+ return `${lines.join('\n')}\n`;
270
+ }
271
+ function writeContractChangeIndex(indexPath, rows, meta) {
272
+ node_fs_1.default.writeFileSync(indexPath, renderContractChangeIndex(rows, meta), 'utf8');
273
+ }
274
+ function readContractChangeDoc(sourceRepoPath, row) {
275
+ const absolutePath = resolveContractDocAbsolutePath(sourceRepoPath, row.contractChangeId, row.path);
276
+ const text = node_fs_1.default.readFileSync(absolutePath, 'utf8');
277
+ const { frontmatter, body } = parseFrontmatter(text);
278
+ const sections = parseSections(body);
279
+ const targets = parseTargetsFromSection(sections['Downstream Notification Context'] ?? '');
280
+ const relativeFromRoot = toPosix(node_path_1.default.relative(sourceRepoPath, absolutePath));
281
+ return {
282
+ contractChangeId: frontmatter.contract_change_id ?? row.contractChangeId,
283
+ name: frontmatter.name ?? row.name,
284
+ status: normalizeStatus(frontmatter.status ?? row.status),
285
+ changeType: frontmatter.change_type ?? row.changeType,
286
+ owner: frontmatter.owner ?? row.owner,
287
+ lastUpdated: frontmatter.last_updated ?? today(),
288
+ absolutePath,
289
+ relativePath: relativeFromRoot || resolveContractDocRepoPath(row.contractChangeId, row.path),
290
+ sections: {
291
+ summary: sections.Summary ?? '',
292
+ contractSurface: sections['Contract Surface'] ?? '',
293
+ changeDetails: sections['Change Details'] ?? '',
294
+ compatibilityAndMigrationGuidance: sections['Compatibility and Migration Guidance'] ?? '',
295
+ },
296
+ targets,
297
+ };
298
+ }
299
+ function renderContractChangeDoc(doc) {
300
+ const blocks = [
301
+ '---',
302
+ 'doc_type: contract_change',
303
+ `contract_change_id: ${doc.contractChangeId}`,
304
+ `name: ${doc.name}`,
305
+ `status: ${doc.status}`,
306
+ `change_type: ${doc.changeType}`,
307
+ `owner: ${doc.owner}`,
308
+ `last_updated: ${doc.lastUpdated || today()}`,
309
+ '---',
310
+ `# ${doc.name}`,
311
+ '',
312
+ '## Summary',
313
+ doc.sections.summary.trim(),
314
+ '',
315
+ '## Contract Surface',
316
+ doc.sections.contractSurface.trim(),
317
+ '',
318
+ '## Change Details',
319
+ doc.sections.changeDetails.trim(),
320
+ '',
321
+ '## Compatibility and Migration Guidance',
322
+ doc.sections.compatibilityAndMigrationGuidance.trim(),
323
+ '',
324
+ '## Downstream Notification Context',
325
+ renderTargetTable(doc.targets).trimEnd(),
326
+ '',
327
+ ];
328
+ return `${blocks.join('\n')}\n`;
329
+ }
330
+ function isNotifiableContractStatus(status) {
331
+ return status === 'approved' || status === 'published';
332
+ }
333
+ function hasRequiredTargetContext(target) {
334
+ return target.repo.trim().length > 0 && target.owner.trim().length > 0 && target.context.trim().length > 0;
335
+ }
336
+ function computeContractChangeStatus(current, targets) {
337
+ if (targets.length === 0) {
338
+ return current;
339
+ }
340
+ if (targets.every((target) => target.state === 'merged')) {
341
+ return 'closed';
342
+ }
343
+ if (targets.every((target) => target.state === 'opened' || target.state === 'merged')) {
344
+ return 'published';
345
+ }
346
+ return current;
347
+ }
@@ -0,0 +1,93 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.extractContracts = extractContracts;
7
+ exports.renderContractsMarkdown = renderContractsMarkdown;
8
+ const node_fs_1 = __importDefault(require("node:fs"));
9
+ const node_path_1 = __importDefault(require("node:path"));
10
+ const yaml_1 = __importDefault(require("yaml"));
11
+ const constants_1 = require("./constants");
12
+ const fileScan_1 = require("./fileScan");
13
+ const PATTERNS = [
14
+ { type: 'openapi', regex: /openapi.*\.(ya?ml|json)$/i },
15
+ { type: 'openapi', regex: /swagger\.(ya?ml|json)$/i },
16
+ { type: 'graphql', regex: /\.(graphql|gql)$/i },
17
+ { type: 'proto', regex: /\.proto$/i },
18
+ { type: 'asyncapi', regex: /asyncapi.*\.(ya?ml|json)$/i },
19
+ ];
20
+ function detectType(filePath) {
21
+ for (const pattern of PATTERNS) {
22
+ if (pattern.regex.test(filePath)) {
23
+ return pattern.type;
24
+ }
25
+ }
26
+ return null;
27
+ }
28
+ function tryExtractVersion(filePath) {
29
+ const lower = filePath.toLowerCase();
30
+ if (!(lower.endsWith('.json') || lower.endsWith('.yaml') || lower.endsWith('.yml'))) {
31
+ return undefined;
32
+ }
33
+ try {
34
+ const text = node_fs_1.default.readFileSync(filePath, 'utf8');
35
+ const data = lower.endsWith('.json') ? JSON.parse(text) : yaml_1.default.parse(text);
36
+ if (typeof data?.info?.version === 'string') {
37
+ return data.info.version;
38
+ }
39
+ if (typeof data?.version === 'string') {
40
+ return data.version;
41
+ }
42
+ }
43
+ catch {
44
+ return undefined;
45
+ }
46
+ return undefined;
47
+ }
48
+ function extractContracts(mapId, scope, reposByName) {
49
+ const out = [];
50
+ for (const repoName of scope.effective) {
51
+ const repo = reposByName.get(repoName);
52
+ if (!repo?.localPath || !node_fs_1.default.existsSync(repo.localPath)) {
53
+ continue;
54
+ }
55
+ const files = (0, fileScan_1.listFilesRecursive)(repo.localPath);
56
+ for (const filePath of files) {
57
+ const type = detectType(filePath);
58
+ if (!type) {
59
+ continue;
60
+ }
61
+ const relPath = node_path_1.default.relative(repo.localPath, filePath);
62
+ out.push({
63
+ schemaVersion: constants_1.SCHEMA_VERSION,
64
+ generatedAt: new Date().toISOString(),
65
+ mapId,
66
+ repo: repoName,
67
+ type,
68
+ path: relPath,
69
+ version: tryExtractVersion(filePath),
70
+ producers: [repoName],
71
+ consumers: [],
72
+ compatibilityStatus: 'unknown',
73
+ sourcePointer: `${repo.fullName}:${relPath}`,
74
+ });
75
+ }
76
+ }
77
+ out.sort((a, b) => `${a.repo}:${a.path}`.localeCompare(`${b.repo}:${b.path}`));
78
+ return out;
79
+ }
80
+ function renderContractsMarkdown(records, mapId) {
81
+ const lines = [
82
+ `# Contracts: ${mapId}`,
83
+ '',
84
+ `- Generated: ${new Date().toISOString()}`,
85
+ `- Total contracts: ${records.length}`,
86
+ '',
87
+ '| Repo | Type | Path | Version | Compatibility |',
88
+ '|---|---|---|---|---|',
89
+ ...records.map((record) => `| ${record.repo} | ${record.type} | ${record.path} | ${record.version ?? '-'} | ${record.compatibilityStatus} |`),
90
+ '',
91
+ ];
92
+ return `${lines.join('\n')}\n`;
93
+ }
package/dist/lib/db.js ADDED
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.openDb = openDb;
7
+ const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
8
+ const fs_1 = require("./fs");
9
+ const paths_1 = require("./paths");
10
+ function openDb(cwd = process.cwd()) {
11
+ (0, fs_1.ensureDir)((0, paths_1.getAppDir)(cwd));
12
+ const db = new better_sqlite3_1.default((0, paths_1.getDbPath)(cwd));
13
+ db.pragma('journal_mode = WAL');
14
+ migrate(db);
15
+ return db;
16
+ }
17
+ function migrate(db) {
18
+ db.exec(`
19
+ CREATE TABLE IF NOT EXISTS repo_registry (
20
+ name TEXT PRIMARY KEY,
21
+ full_name TEXT NOT NULL,
22
+ org TEXT NOT NULL,
23
+ default_branch TEXT,
24
+ archived INTEGER NOT NULL DEFAULT 0,
25
+ fork INTEGER NOT NULL DEFAULT 0,
26
+ html_url TEXT,
27
+ local_path TEXT,
28
+ source TEXT NOT NULL,
29
+ last_synced_at TEXT
30
+ );
31
+
32
+ CREATE TABLE IF NOT EXISTS run_log (
33
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
34
+ run_type TEXT NOT NULL,
35
+ map_id TEXT,
36
+ status TEXT NOT NULL,
37
+ created_at TEXT NOT NULL,
38
+ metadata_json TEXT
39
+ );
40
+ `);
41
+ }
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.renderArchitectureDoc = renderArchitectureDoc;
4
+ function renderArchitectureDoc(mapId, scope, serviceMap, contracts) {
5
+ const missingLocal = serviceMap.nodes
6
+ .filter((node) => node.type === 'repo')
7
+ .filter((node) => !node.metadata?.['localPath'])
8
+ .map((node) => node.label);
9
+ const lines = [
10
+ `# Architecture Overview: ${mapId}`,
11
+ '',
12
+ `- Generated: ${new Date().toISOString()}`,
13
+ `- Org: ${scope.org}`,
14
+ `- Effective repositories: ${scope.effective.length}`,
15
+ `- Contracts discovered: ${contracts.length}`,
16
+ `- Services mapped: ${serviceMap.nodes.filter((node) => node.type === 'service').length}`,
17
+ '',
18
+ '## System Design Coverage',
19
+ '',
20
+ ...serviceMap.coverageTags.map((tag) => `- ${tag}`),
21
+ '',
22
+ '## Repository Scope',
23
+ '',
24
+ ...scope.effective.map((repo) => `- ${repo}`),
25
+ '',
26
+ '## Integration Signals',
27
+ '',
28
+ ...serviceMap.edges.slice(0, 50).map((edge) => `- ${edge.from} ${edge.relation} ${edge.to}`),
29
+ '',
30
+ '## Contract Surface',
31
+ '',
32
+ ...contracts.slice(0, 100).map((contract) => `- ${contract.repo}: ${contract.type} ${contract.path}`),
33
+ '',
34
+ ];
35
+ if (missingLocal.length > 0) {
36
+ lines.push('## Partial Visibility');
37
+ lines.push('');
38
+ lines.push('The following repositories are in scope but do not have registered local paths:');
39
+ lines.push('');
40
+ for (const repo of missingLocal) {
41
+ lines.push(`- ${repo}`);
42
+ }
43
+ lines.push('');
44
+ }
45
+ return `${lines.join('\n')}\n`;
46
+ }
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.listFilesRecursive = listFilesRecursive;
7
+ exports.filterByPatterns = filterByPatterns;
8
+ const node_fs_1 = __importDefault(require("node:fs"));
9
+ const node_path_1 = __importDefault(require("node:path"));
10
+ const DEFAULT_IGNORE = new Set(['.git', 'node_modules', 'dist', 'build', '.next', '.turbo']);
11
+ function listFilesRecursive(rootDir) {
12
+ const files = [];
13
+ function walk(current) {
14
+ const entries = node_fs_1.default.readdirSync(current, { withFileTypes: true });
15
+ for (const entry of entries) {
16
+ if (entry.isDirectory()) {
17
+ if (DEFAULT_IGNORE.has(entry.name)) {
18
+ continue;
19
+ }
20
+ walk(node_path_1.default.join(current, entry.name));
21
+ continue;
22
+ }
23
+ files.push(node_path_1.default.join(current, entry.name));
24
+ }
25
+ }
26
+ if (!node_fs_1.default.existsSync(rootDir)) {
27
+ return files;
28
+ }
29
+ walk(rootDir);
30
+ return files;
31
+ }
32
+ function filterByPatterns(paths, patterns) {
33
+ return paths.filter((candidate) => patterns.some((pattern) => pattern.test(candidate)));
34
+ }
package/dist/lib/fs.js ADDED
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ensureDir = ensureDir;
7
+ exports.readJsonFile = readJsonFile;
8
+ exports.writeJsonFile = writeJsonFile;
9
+ exports.writeTextFile = writeTextFile;
10
+ exports.fileExists = fileExists;
11
+ exports.safeReadText = safeReadText;
12
+ const node_fs_1 = __importDefault(require("node:fs"));
13
+ const node_path_1 = __importDefault(require("node:path"));
14
+ function ensureDir(dirPath) {
15
+ node_fs_1.default.mkdirSync(dirPath, { recursive: true });
16
+ }
17
+ function readJsonFile(filePath) {
18
+ return JSON.parse(node_fs_1.default.readFileSync(filePath, 'utf8'));
19
+ }
20
+ function writeJsonFile(filePath, value) {
21
+ ensureDir(node_path_1.default.dirname(filePath));
22
+ node_fs_1.default.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
23
+ }
24
+ function writeTextFile(filePath, value) {
25
+ ensureDir(node_path_1.default.dirname(filePath));
26
+ node_fs_1.default.writeFileSync(filePath, value, 'utf8');
27
+ }
28
+ function fileExists(filePath) {
29
+ return node_fs_1.default.existsSync(filePath);
30
+ }
31
+ function safeReadText(filePath) {
32
+ if (!fileExists(filePath)) {
33
+ return '';
34
+ }
35
+ return node_fs_1.default.readFileSync(filePath, 'utf8');
36
+ }
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.fetchOrgRepos = fetchOrgRepos;
4
+ exports.ensureOrgRepo = ensureOrgRepo;
5
+ async function fetchOrgRepos(org, token) {
6
+ const { Octokit } = await import('@octokit/rest');
7
+ const octokit = new Octokit({ auth: token });
8
+ const now = new Date().toISOString();
9
+ const repos = await octokit.paginate(octokit.rest.repos.listForOrg, {
10
+ org,
11
+ per_page: 100,
12
+ type: 'all',
13
+ });
14
+ return repos.map((repo) => ({
15
+ name: repo.name,
16
+ fullName: repo.full_name,
17
+ org,
18
+ defaultBranch: repo.default_branch,
19
+ archived: Boolean(repo.archived),
20
+ fork: Boolean(repo.fork),
21
+ htmlUrl: repo.html_url,
22
+ source: 'github',
23
+ lastSyncedAt: now,
24
+ }));
25
+ }
26
+ async function ensureOrgRepo(org, repoName, token) {
27
+ const { Octokit } = await import('@octokit/rest');
28
+ const octokit = new Octokit({ auth: token });
29
+ try {
30
+ const existing = await octokit.rest.repos.get({ owner: org, repo: repoName });
31
+ return {
32
+ created: false,
33
+ htmlUrl: existing.data.html_url ?? undefined,
34
+ };
35
+ }
36
+ catch (error) {
37
+ const status = Number(error.status);
38
+ if (status !== 404) {
39
+ throw error;
40
+ }
41
+ }
42
+ const created = await octokit.rest.repos.createInOrg({
43
+ org,
44
+ name: repoName,
45
+ private: true,
46
+ auto_init: false,
47
+ });
48
+ return {
49
+ created: true,
50
+ htmlUrl: created.data.html_url ?? undefined,
51
+ };
52
+ }