@uxmaltech/collab-cli 0.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 (109) hide show
  1. package/README.md +227 -0
  2. package/bin/collab +10 -0
  3. package/dist/cli.js +34 -0
  4. package/dist/commands/canon/index.js +16 -0
  5. package/dist/commands/canon/rebuild.js +95 -0
  6. package/dist/commands/compose/generate.js +63 -0
  7. package/dist/commands/compose/index.js +18 -0
  8. package/dist/commands/compose/validate.js +53 -0
  9. package/dist/commands/doctor.js +153 -0
  10. package/dist/commands/index.js +27 -0
  11. package/dist/commands/infra/down.js +23 -0
  12. package/dist/commands/infra/index.js +20 -0
  13. package/dist/commands/infra/shared.js +59 -0
  14. package/dist/commands/infra/status.js +64 -0
  15. package/dist/commands/infra/up.js +29 -0
  16. package/dist/commands/init.js +830 -0
  17. package/dist/commands/mcp/index.js +20 -0
  18. package/dist/commands/mcp/shared.js +57 -0
  19. package/dist/commands/mcp/start.js +45 -0
  20. package/dist/commands/mcp/status.js +62 -0
  21. package/dist/commands/mcp/stop.js +23 -0
  22. package/dist/commands/seed.js +55 -0
  23. package/dist/commands/uninstall.js +36 -0
  24. package/dist/commands/up.js +78 -0
  25. package/dist/commands/update-canons.js +48 -0
  26. package/dist/commands/upgrade.js +54 -0
  27. package/dist/index.js +14 -0
  28. package/dist/lib/ai-client.js +317 -0
  29. package/dist/lib/ansi.js +58 -0
  30. package/dist/lib/canon-index-generator.js +64 -0
  31. package/dist/lib/canon-index-targets.js +68 -0
  32. package/dist/lib/canon-resolver.js +262 -0
  33. package/dist/lib/canon-scaffold.js +57 -0
  34. package/dist/lib/cli-detection.js +149 -0
  35. package/dist/lib/command-context.js +23 -0
  36. package/dist/lib/compose-defaults.js +47 -0
  37. package/dist/lib/compose-env.js +24 -0
  38. package/dist/lib/compose-paths.js +36 -0
  39. package/dist/lib/compose-renderer.js +134 -0
  40. package/dist/lib/compose-validator.js +56 -0
  41. package/dist/lib/config.js +195 -0
  42. package/dist/lib/credentials.js +63 -0
  43. package/dist/lib/docker-checks.js +73 -0
  44. package/dist/lib/docker-compose.js +15 -0
  45. package/dist/lib/docker-status.js +151 -0
  46. package/dist/lib/domain-gen.js +376 -0
  47. package/dist/lib/ecosystem.js +150 -0
  48. package/dist/lib/env-file.js +77 -0
  49. package/dist/lib/errors.js +30 -0
  50. package/dist/lib/executor.js +85 -0
  51. package/dist/lib/github-auth.js +204 -0
  52. package/dist/lib/hash.js +7 -0
  53. package/dist/lib/health-checker.js +140 -0
  54. package/dist/lib/logger.js +87 -0
  55. package/dist/lib/mcp-client.js +88 -0
  56. package/dist/lib/mode.js +36 -0
  57. package/dist/lib/model-listing.js +102 -0
  58. package/dist/lib/model-registry.js +55 -0
  59. package/dist/lib/npm-operations.js +69 -0
  60. package/dist/lib/orchestrator.js +170 -0
  61. package/dist/lib/parsers.js +42 -0
  62. package/dist/lib/port-resolver.js +57 -0
  63. package/dist/lib/preconditions.js +35 -0
  64. package/dist/lib/preflight.js +88 -0
  65. package/dist/lib/process.js +6 -0
  66. package/dist/lib/prompt.js +125 -0
  67. package/dist/lib/providers.js +117 -0
  68. package/dist/lib/repo-analysis-helpers.js +379 -0
  69. package/dist/lib/repo-scanner.js +195 -0
  70. package/dist/lib/service-health.js +79 -0
  71. package/dist/lib/shell.js +49 -0
  72. package/dist/lib/state.js +38 -0
  73. package/dist/lib/update-checker.js +130 -0
  74. package/dist/lib/version.js +27 -0
  75. package/dist/stages/agent-skills-setup.js +301 -0
  76. package/dist/stages/assistant-setup.js +325 -0
  77. package/dist/stages/canon-ingest.js +249 -0
  78. package/dist/stages/canon-rebuild-graph.js +33 -0
  79. package/dist/stages/canon-rebuild-indexes.js +40 -0
  80. package/dist/stages/canon-rebuild-snapshot.js +75 -0
  81. package/dist/stages/canon-rebuild-validate.js +57 -0
  82. package/dist/stages/canon-rebuild-vectors.js +30 -0
  83. package/dist/stages/canon-scaffold.js +15 -0
  84. package/dist/stages/canon-sync.js +49 -0
  85. package/dist/stages/ci-setup.js +56 -0
  86. package/dist/stages/domain-gen.js +363 -0
  87. package/dist/stages/graph-seed.js +26 -0
  88. package/dist/stages/repo-analysis-fileonly.js +111 -0
  89. package/dist/stages/repo-analysis.js +112 -0
  90. package/dist/stages/repo-scaffold.js +110 -0
  91. package/dist/templates/canon/contracts-readme.js +39 -0
  92. package/dist/templates/canon/domain-readme.js +40 -0
  93. package/dist/templates/canon/evolution/changelog.js +53 -0
  94. package/dist/templates/canon/governance/confidence-levels.js +38 -0
  95. package/dist/templates/canon/governance/implementation-process.js +34 -0
  96. package/dist/templates/canon/governance/review-process.js +29 -0
  97. package/dist/templates/canon/governance/schema-versioning.js +25 -0
  98. package/dist/templates/canon/governance/what-enters-the-canon.js +44 -0
  99. package/dist/templates/canon/index.js +28 -0
  100. package/dist/templates/canon/knowledge-readme.js +129 -0
  101. package/dist/templates/canon/system-prompt.js +101 -0
  102. package/dist/templates/ci/architecture-merge.js +29 -0
  103. package/dist/templates/ci/architecture-pr.js +26 -0
  104. package/dist/templates/ci/index.js +7 -0
  105. package/dist/templates/consolidated.js +114 -0
  106. package/dist/templates/infra.js +90 -0
  107. package/dist/templates/mcp.js +32 -0
  108. package/install.sh +455 -0
  109. package/package.json +48 -0
@@ -0,0 +1,317 @@
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.createAiClient = createAiClient;
7
+ exports.createFirstAvailableClient = createFirstAvailableClient;
8
+ const node_child_process_1 = require("node:child_process");
9
+ const node_fs_1 = __importDefault(require("node:fs"));
10
+ const node_https_1 = __importDefault(require("node:https"));
11
+ const node_os_1 = __importDefault(require("node:os"));
12
+ const node_path_1 = __importDefault(require("node:path"));
13
+ const node_url_1 = require("node:url");
14
+ const credentials_1 = require("./credentials");
15
+ const providers_1 = require("./providers");
16
+ /**
17
+ * Makes an HTTPS POST request and returns the response body as a string.
18
+ */
19
+ function httpsPost(url, headers, body, timeoutMs = 120_000) {
20
+ return new Promise((resolve, reject) => {
21
+ const parsed = new node_url_1.URL(url);
22
+ const req = node_https_1.default.request({
23
+ hostname: parsed.hostname,
24
+ port: parsed.port || 443,
25
+ path: parsed.pathname + parsed.search,
26
+ method: 'POST',
27
+ headers: {
28
+ ...headers,
29
+ 'Content-Type': 'application/json',
30
+ 'Content-Length': Buffer.byteLength(body).toString(),
31
+ },
32
+ }, (res) => {
33
+ let data = '';
34
+ res.on('data', (chunk) => {
35
+ data += chunk.toString();
36
+ });
37
+ res.on('end', () => {
38
+ resolve({ statusCode: res.statusCode ?? 0, body: data });
39
+ });
40
+ });
41
+ req.setTimeout(timeoutMs, () => {
42
+ req.destroy(new Error('AI API request timed out'));
43
+ });
44
+ req.on('error', reject);
45
+ req.write(body);
46
+ req.end();
47
+ });
48
+ }
49
+ /**
50
+ * Resolves the API key for a provider.
51
+ *
52
+ * Resolution order:
53
+ * 1. Environment variable (e.g. OPENAI_API_KEY)
54
+ * 2. Stored credential from .collab/credentials.json
55
+ */
56
+ function resolveApiKey(provider, config) {
57
+ const defaults = providers_1.PROVIDER_DEFAULTS[provider];
58
+ // 1. Check environment variable
59
+ const envKey = process.env[defaults.envVar];
60
+ if (envKey) {
61
+ return envKey;
62
+ }
63
+ // 2. Check stored credentials
64
+ const storedKey = (0, credentials_1.loadApiKey)(config, provider);
65
+ if (storedKey) {
66
+ return storedKey;
67
+ }
68
+ return null;
69
+ }
70
+ // ---------- OpenAI ----------
71
+ function createOpenAiClient(apiKey) {
72
+ return {
73
+ provider: 'codex',
74
+ async complete(messages, options = {}) {
75
+ const model = options.model ?? providers_1.PROVIDER_DEFAULTS.codex.models[0];
76
+ const body = JSON.stringify({
77
+ model,
78
+ messages: messages.map((m) => ({ role: m.role, content: m.content })),
79
+ max_completion_tokens: options.maxTokens ?? 4096,
80
+ temperature: options.temperature ?? 0.2,
81
+ });
82
+ const res = await httpsPost('https://api.openai.com/v1/chat/completions', {
83
+ Authorization: `Bearer ${apiKey}`,
84
+ Accept: 'application/json',
85
+ }, body);
86
+ if (res.statusCode < 200 || res.statusCode >= 300) {
87
+ throw new Error(`OpenAI API error (${res.statusCode}): ${res.body}`);
88
+ }
89
+ const parsed = JSON.parse(res.body);
90
+ return parsed.choices?.[0]?.message?.content ?? '';
91
+ },
92
+ };
93
+ }
94
+ // ---------- Anthropic ----------
95
+ function createAnthropicClient(apiKey) {
96
+ return {
97
+ provider: 'claude',
98
+ async complete(messages, options = {}) {
99
+ const model = options.model ?? providers_1.PROVIDER_DEFAULTS.claude.models[0];
100
+ // Anthropic API uses a system parameter, not a system message
101
+ const systemMessage = messages.find((m) => m.role === 'system');
102
+ const userMessages = messages.filter((m) => m.role !== 'system');
103
+ const body = JSON.stringify({
104
+ model,
105
+ max_tokens: options.maxTokens ?? 4096,
106
+ ...(systemMessage ? { system: systemMessage.content } : {}),
107
+ messages: userMessages.map((m) => ({ role: m.role, content: m.content })),
108
+ });
109
+ const res = await httpsPost('https://api.anthropic.com/v1/messages', {
110
+ 'x-api-key': apiKey,
111
+ 'anthropic-version': '2023-06-01',
112
+ Accept: 'application/json',
113
+ }, body);
114
+ if (res.statusCode < 200 || res.statusCode >= 300) {
115
+ throw new Error(`Anthropic API error (${res.statusCode}): ${res.body}`);
116
+ }
117
+ const parsed = JSON.parse(res.body);
118
+ const textBlock = parsed.content?.find((b) => b.type === 'text');
119
+ return textBlock?.text ?? '';
120
+ },
121
+ };
122
+ }
123
+ // ---------- Gemini ----------
124
+ function createGeminiClient(apiKey) {
125
+ return {
126
+ provider: 'gemini',
127
+ async complete(messages, options = {}) {
128
+ const model = options.model ?? providers_1.PROVIDER_DEFAULTS.gemini.models[0];
129
+ // Gemini uses a different message format
130
+ const systemInstruction = messages.find((m) => m.role === 'system');
131
+ const contents = messages
132
+ .filter((m) => m.role !== 'system')
133
+ .map((m) => ({
134
+ role: m.role === 'assistant' ? 'model' : 'user',
135
+ parts: [{ text: m.content }],
136
+ }));
137
+ const body = JSON.stringify({
138
+ ...(systemInstruction
139
+ ? { system_instruction: { parts: [{ text: systemInstruction.content }] } }
140
+ : {}),
141
+ contents,
142
+ generationConfig: {
143
+ maxOutputTokens: options.maxTokens ?? 4096,
144
+ temperature: options.temperature ?? 0.2,
145
+ },
146
+ });
147
+ const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${apiKey}`;
148
+ const res = await httpsPost(url, { Accept: 'application/json' }, body);
149
+ if (res.statusCode < 200 || res.statusCode >= 300) {
150
+ throw new Error(`Gemini API error (${res.statusCode}): ${res.body}`);
151
+ }
152
+ const parsed = JSON.parse(res.body);
153
+ return parsed.candidates?.[0]?.content?.parts?.[0]?.text ?? '';
154
+ },
155
+ };
156
+ }
157
+ // ---------- CLI-based clients ----------
158
+ /**
159
+ * Combines system and user messages into a single prompt string
160
+ * for CLI tools that don't support separate message roles.
161
+ */
162
+ function buildCombinedPrompt(messages) {
163
+ const parts = [];
164
+ for (const msg of messages) {
165
+ if (msg.role === 'system') {
166
+ parts.push(msg.content);
167
+ }
168
+ else if (msg.role === 'user') {
169
+ parts.push('---\n\n' + msg.content);
170
+ }
171
+ else if (msg.role === 'assistant') {
172
+ parts.push('Assistant: ' + msg.content);
173
+ }
174
+ }
175
+ return parts.join('\n\n');
176
+ }
177
+ /**
178
+ * Executes codex CLI for completion using `codex exec`.
179
+ */
180
+ function execCodexCli(prompt, model) {
181
+ const tmpFile = node_path_1.default.join(node_os_1.default.tmpdir(), `collab-analysis-${Date.now()}.txt`);
182
+ try {
183
+ const args = [
184
+ 'exec',
185
+ '--ephemeral',
186
+ '--sandbox', 'read-only',
187
+ '--skip-git-repo-check',
188
+ '-o', tmpFile,
189
+ ];
190
+ if (model) {
191
+ args.push('-m', model);
192
+ }
193
+ args.push(prompt);
194
+ (0, node_child_process_1.execFileSync)('codex', args, {
195
+ encoding: 'utf8',
196
+ timeout: 300_000,
197
+ stdio: ['ignore', 'inherit', 'inherit'],
198
+ });
199
+ if (node_fs_1.default.existsSync(tmpFile)) {
200
+ return node_fs_1.default.readFileSync(tmpFile, 'utf8').trim();
201
+ }
202
+ return '';
203
+ }
204
+ finally {
205
+ try {
206
+ if (node_fs_1.default.existsSync(tmpFile)) {
207
+ node_fs_1.default.unlinkSync(tmpFile);
208
+ }
209
+ }
210
+ catch {
211
+ // Ignore cleanup errors
212
+ }
213
+ }
214
+ }
215
+ /**
216
+ * Executes claude CLI for completion using `claude -p`.
217
+ */
218
+ function execClaudeCli(prompt, model) {
219
+ const args = ['-p'];
220
+ if (model) {
221
+ args.push('--model', model);
222
+ }
223
+ args.push(prompt);
224
+ const output = (0, node_child_process_1.execFileSync)('claude', args, {
225
+ encoding: 'utf8',
226
+ timeout: 300_000,
227
+ stdio: ['ignore', 'pipe', 'inherit'],
228
+ });
229
+ return output.trim();
230
+ }
231
+ /**
232
+ * Executes gemini CLI for completion.
233
+ */
234
+ function execGeminiCli(prompt, model) {
235
+ const args = [];
236
+ if (model) {
237
+ args.push('--model', model);
238
+ }
239
+ args.push(prompt);
240
+ const output = (0, node_child_process_1.execFileSync)('gemini', args, {
241
+ encoding: 'utf8',
242
+ timeout: 300_000,
243
+ stdio: ['ignore', 'pipe', 'inherit'],
244
+ });
245
+ return output.trim();
246
+ }
247
+ const CLI_EXECUTORS = {
248
+ codex: execCodexCli,
249
+ claude: execClaudeCli,
250
+ gemini: execGeminiCli,
251
+ };
252
+ /**
253
+ * Creates an AI client that uses the provider's official CLI for completion.
254
+ * This allows repo analysis to work without API keys.
255
+ */
256
+ function createCliClient(provider, model) {
257
+ return {
258
+ provider,
259
+ complete(messages, options = {}) {
260
+ const effectiveModel = options.model ?? model;
261
+ const prompt = buildCombinedPrompt(messages);
262
+ const executor = CLI_EXECUTORS[provider];
263
+ if (!executor) {
264
+ throw new Error(`No CLI executor available for provider '${provider}'.`);
265
+ }
266
+ return Promise.resolve(executor(prompt, effectiveModel));
267
+ },
268
+ };
269
+ }
270
+ // ---------- Factory ----------
271
+ const CLIENT_FACTORIES = {
272
+ codex: createOpenAiClient,
273
+ claude: createAnthropicClient,
274
+ gemini: createGeminiClient,
275
+ };
276
+ /**
277
+ * Creates an AI client for the given provider, resolving auth automatically.
278
+ *
279
+ * Resolution order:
280
+ * 1. API key (env var or stored credentials) → use HTTP API
281
+ * 2. CLI auth configured with CLI available → use CLI executable
282
+ * 3. null if neither available
283
+ */
284
+ function createAiClient(provider, config, logger) {
285
+ // Copilot doesn't support AI completion — it works via GitHub issues
286
+ if (provider === 'copilot') {
287
+ return null;
288
+ }
289
+ // 1. Try API key first
290
+ const factory = CLIENT_FACTORIES[provider];
291
+ const apiKey = resolveApiKey(provider, config);
292
+ if (apiKey && factory) {
293
+ return factory(apiKey);
294
+ }
295
+ // 2. Try CLI auth if configured
296
+ const providerConfig = config.assistants?.providers?.[provider];
297
+ if (providerConfig?.auth?.method === 'cli' && providerConfig?.cli?.available) {
298
+ const model = providerConfig.model ?? providerConfig.cli.configuredModel;
299
+ logger.debug(`Using ${providerConfig.cli.command} CLI for ${providers_1.PROVIDER_DEFAULTS[provider].label} completion.`);
300
+ return createCliClient(provider, model);
301
+ }
302
+ return null;
303
+ }
304
+ /**
305
+ * Creates an AI client for the first available provider.
306
+ * Tries providers in order: codex, claude, gemini.
307
+ */
308
+ function createFirstAvailableClient(providers, config, logger) {
309
+ for (const provider of providers) {
310
+ const client = createAiClient(provider, config, logger);
311
+ if (client) {
312
+ logger.info(`Using ${providers_1.PROVIDER_DEFAULTS[provider].label} for repository analysis.`);
313
+ return client;
314
+ }
315
+ }
316
+ return null;
317
+ }
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ /**
3
+ * ANSI escape code utilities for terminal output.
4
+ * Zero external dependencies — uses raw escape sequences.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.CLEAR_SCREEN = exports.BULLET = exports.CROSS = exports.CHECK = void 0;
8
+ exports.bold = bold;
9
+ exports.dim = dim;
10
+ exports.green = green;
11
+ exports.red = red;
12
+ exports.yellow = yellow;
13
+ exports.cyan = cyan;
14
+ exports.gray = gray;
15
+ const ESC = '\x1b[';
16
+ const RESET = `${ESC}0m`;
17
+ function supportsColor() {
18
+ if (process.env['NO_COLOR'] !== undefined) {
19
+ return false;
20
+ }
21
+ if (process.env['FORCE_COLOR'] !== undefined) {
22
+ return true;
23
+ }
24
+ return Boolean(process.stdout.isTTY);
25
+ }
26
+ const colorEnabled = supportsColor();
27
+ function wrap(code, text) {
28
+ if (!colorEnabled) {
29
+ return text;
30
+ }
31
+ return `${ESC}${code}m${text}${RESET}`;
32
+ }
33
+ function bold(text) {
34
+ return wrap('1', text);
35
+ }
36
+ function dim(text) {
37
+ return wrap('2', text);
38
+ }
39
+ function green(text) {
40
+ return wrap('32', text);
41
+ }
42
+ function red(text) {
43
+ return wrap('31', text);
44
+ }
45
+ function yellow(text) {
46
+ return wrap('33', text);
47
+ }
48
+ function cyan(text) {
49
+ return wrap('36', text);
50
+ }
51
+ function gray(text) {
52
+ return wrap('90', text);
53
+ }
54
+ exports.CHECK = '\u2713';
55
+ exports.CROSS = '\u2717';
56
+ exports.BULLET = '\u2022';
57
+ /** ANSI: clear entire screen and move cursor to top-left. */
58
+ exports.CLEAR_SCREEN = '\x1b[2J\x1b[H';
@@ -0,0 +1,64 @@
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.scanCanonEntries = scanCanonEntries;
7
+ exports.generateIndexReadme = generateIndexReadme;
8
+ const node_fs_1 = __importDefault(require("node:fs"));
9
+ const node_path_1 = __importDefault(require("node:path"));
10
+ /**
11
+ * Scans .md files in a directory and extracts canon entry metadata.
12
+ * Parses the heading (# ID Title) and plain-text Status/Confidence fields.
13
+ * Skips README.md files.
14
+ */
15
+ function scanCanonEntries(dir) {
16
+ if (!node_fs_1.default.existsSync(dir))
17
+ return [];
18
+ const entries = [];
19
+ const files = node_fs_1.default.readdirSync(dir).filter((f) => f.endsWith('.md') && f.toLowerCase() !== 'readme.md');
20
+ for (const file of files) {
21
+ const content = node_fs_1.default.readFileSync(node_path_1.default.join(dir, file), 'utf8');
22
+ // Parse heading: "# AX-001 Authoritative Canon" or "# ADR-006 Collab: ..."
23
+ const heading = content.match(/^#\s+(.+)/m);
24
+ if (!heading)
25
+ continue;
26
+ const headingText = heading[1].trim();
27
+ // Match "ID Title" where ID is like AX-001, ADR-006, CN-002, AP-003, UIC-001, etc.
28
+ const parts = headingText.match(/^([A-Z]+-\d+)\s+(.+)$/);
29
+ const id = parts?.[1] ?? file.replace(/\.md$/, '');
30
+ const title = parts?.[2] ?? headingText;
31
+ // Parse plain-text fields (not bold)
32
+ const confidence = content.match(/^Confidence:\s*(\w+)/mi)?.[1] ?? 'unknown';
33
+ const status = content.match(/^Status:\s*(\w+)/mi)?.[1] ?? 'active';
34
+ entries.push({ id, title, confidence, status, fileName: file });
35
+ }
36
+ return entries.sort((a, b) => a.id.localeCompare(b.id));
37
+ }
38
+ /**
39
+ * Generates a README.md index table from scanned canon entries.
40
+ */
41
+ function generateIndexReadme(sectionTitle, description, entries) {
42
+ const lines = [
43
+ `# ${sectionTitle}`,
44
+ '',
45
+ `> ${description}`,
46
+ '',
47
+ ];
48
+ if (entries.length === 0) {
49
+ lines.push('_No entries yet._');
50
+ lines.push('');
51
+ lines.push('<!-- GENERATED: INDEX -->');
52
+ return lines.join('\n');
53
+ }
54
+ lines.push('| ID | Title | Confidence | Status |');
55
+ lines.push('|----|-------|------------|--------|');
56
+ for (const e of entries) {
57
+ lines.push(`| ${e.id} | [${e.title}](./${e.fileName}) | ${e.confidence} | ${e.status} |`);
58
+ }
59
+ lines.push('');
60
+ lines.push(`_${entries.length} entries indexed._`);
61
+ lines.push('');
62
+ lines.push('<!-- GENERATED: INDEX -->');
63
+ return lines.join('\n');
64
+ }
@@ -0,0 +1,68 @@
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.getCanonIndexTargets = getCanonIndexTargets;
7
+ exports.getSnapshotIndexPaths = getSnapshotIndexPaths;
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ /**
10
+ * Returns the canonical index targets for a given architecture directory.
11
+ * This is the single source of truth for which index files are
12
+ * generated/snapshotted/validated across canon rebuild stages.
13
+ */
14
+ function getCanonIndexTargets(archDir) {
15
+ return [
16
+ {
17
+ scanDir: node_path_1.default.join(archDir, 'knowledge', 'axioms'),
18
+ outputFile: node_path_1.default.join(archDir, 'knowledge', 'axioms', 'README.md'),
19
+ sectionTitle: 'Axioms',
20
+ description: 'Fundamental architectural invariants that MUST always hold.',
21
+ },
22
+ {
23
+ scanDir: node_path_1.default.join(archDir, 'knowledge', 'decisions'),
24
+ outputFile: node_path_1.default.join(archDir, 'knowledge', 'decisions', 'README.md'),
25
+ sectionTitle: 'Architectural Decisions',
26
+ description: 'ADRs documenting key architectural choices and their rationale.',
27
+ },
28
+ {
29
+ scanDir: node_path_1.default.join(archDir, 'knowledge', 'conventions'),
30
+ outputFile: node_path_1.default.join(archDir, 'knowledge', 'conventions', 'README.md'),
31
+ sectionTitle: 'Conventions',
32
+ description: 'Coding and design conventions followed across this codebase.',
33
+ },
34
+ {
35
+ scanDir: node_path_1.default.join(archDir, 'knowledge', 'anti-patterns'),
36
+ outputFile: node_path_1.default.join(archDir, 'knowledge', 'anti-patterns', 'README.md'),
37
+ sectionTitle: 'Anti-Patterns',
38
+ description: 'Known pitfalls and patterns that MUST be avoided.',
39
+ },
40
+ {
41
+ scanDir: node_path_1.default.join(archDir, 'domains'),
42
+ outputFile: node_path_1.default.join(archDir, 'domains', 'README.md'),
43
+ sectionTitle: 'Domains',
44
+ description: 'Bounded contexts and domain boundaries identified in the architecture.',
45
+ },
46
+ {
47
+ scanDir: node_path_1.default.join(archDir, 'contracts'),
48
+ outputFile: node_path_1.default.join(archDir, 'contracts', 'README.md'),
49
+ sectionTitle: 'Contracts',
50
+ description: 'Interface contracts between domains (UI-backend shapes, command outcomes).',
51
+ },
52
+ ];
53
+ }
54
+ /**
55
+ * Returns relative paths of all README files to snapshot during rebuild.
56
+ * Includes top-level and parent READMEs that are not auto-generated
57
+ * but should be preserved for rollback.
58
+ */
59
+ function getSnapshotIndexPaths(archDir) {
60
+ // Top-level hand-crafted READMEs (not regenerable, but worth snapshotting)
61
+ const topLevel = [
62
+ 'README.md',
63
+ 'knowledge/README.md',
64
+ ];
65
+ // Auto-generated sub-category READMEs (from getCanonIndexTargets)
66
+ const generated = getCanonIndexTargets(archDir).map((t) => node_path_1.default.relative(archDir, t.outputFile));
67
+ return [...topLevel, ...generated];
68
+ }