@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
package/README.md ADDED
@@ -0,0 +1,227 @@
1
+ # collab-cli
2
+
3
+ Orchestration CLI for collaborative workflows with canonical architecture. Manages the complete lifecycle: from initial repository setup to Docker infrastructure, AI provider configuration, and architectural canon synchronization.
4
+
5
+ ## Collab Ecosystem
6
+
7
+ ```mermaid
8
+ graph TD
9
+ DEV[Developer] --> CLI[collab-cli<br><i>infrastructure</i>]
10
+
11
+ subgraph APP["collab-laravel-app"]
12
+ CHAT[collab-chat-ai-pkg<br><i>Chat UI</i>]
13
+ CORE[collab-core-pkg<br><i>Discovery Agent</i>]
14
+ PM[collab-project-manager-pkg]
15
+ CHAT -- interface --> CORE
16
+ end
17
+
18
+ DEV -- "chat · GOV-R-001" --> CHAT
19
+
20
+ CLI -- canon-sync --> CA[collab-architecture<br><i>framework canon</i>]
21
+ CLI -- docker compose up --> MCP[collab-architecture-mcp<br><i>NebulaGraph · Qdrant</i>]
22
+ CA -- seed + ingest --> MCP
23
+
24
+ CORE -- research --> MCP
25
+ CORE -- "Epic + Stories" --> GH[GitHub Issues]
26
+
27
+ GH -- "GOV-R-002" --> REPOS[Target Repos]
28
+ REPOS -- "merge → GOV-R-003" --> BCA[collab-app-architecture<br><i>application canon</i>]
29
+ BCA -- ingest --> MCP
30
+
31
+ style CLI fill:#4a9eff,stroke:#2b7de9,color:#fff
32
+ style APP fill:#e8f4f8,stroke:#4a9eff
33
+ ```
34
+
35
+ | Repository | Role | Relation to this repo |
36
+ |------------|------|----------------------|
37
+ | [`collab-architecture`](https://github.com/uxmaltech/collab-architecture) | Framework canon | Framework-level rules, patterns, and governance |
38
+ | [`collab-app-architecture`](https://github.com/uxmaltech/collab-app-architecture) | Application canon | Application-specific rules, patterns, and decisions |
39
+ | **`collab-cli`** | **Orchestrator CLI** | **This repo — infrastructure CLI — canon sync, init, domain generation** |
40
+ | [`collab-architecture-mcp`](https://github.com/uxmaltech/collab-architecture-mcp) | MCP server | NebulaGraph + Qdrant — indexes canon for AI agents |
41
+ | [`collab-laravel-app`](https://github.com/uxmaltech/collab-laravel-app) | Host application | Laravel app that installs the ecosystem packages |
42
+ | [`collab-chat-ai-pkg`](https://github.com/uxmaltech/collab-chat-ai-pkg) | AI Chat package | Chat UI and prompt admin |
43
+ | [`collab-core-pkg`](https://github.com/uxmaltech/collab-core-pkg) | Issue orchestration | AI agent pipeline for issue creation |
44
+ | [`collab-project-manager-pkg`](https://github.com/uxmaltech/collab-project-manager-pkg) | Project manager | Project management functionality |
45
+
46
+ ## Prerequisites
47
+
48
+ | Requirement | Version | Notes |
49
+ |-------------|---------|-------|
50
+ | Node.js | >= 20 | Required |
51
+ | npm | >= 10 | Required |
52
+ | git | any | Required for install script |
53
+ | Docker | any | Indexed mode only |
54
+
55
+ ## Installation
56
+
57
+ **npm (global):**
58
+ ```bash
59
+ npm install -g @uxmaltech/collab-cli
60
+ collab --version
61
+ ```
62
+
63
+ **npx (ephemeral):**
64
+ ```bash
65
+ npx @uxmaltech/collab-cli --help
66
+ ```
67
+
68
+ **Installer script (latest-main):**
69
+ ```bash
70
+ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/uxmaltech/collab-cli/main/install.sh)"
71
+ ```
72
+
73
+ **Local development:**
74
+ ```bash
75
+ npm install && npm run build
76
+ bin/collab --help
77
+ ```
78
+
79
+ **Uninstall:**
80
+ ```bash
81
+ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/uxmaltech/collab-cli/main/uninstall.sh)"
82
+ ```
83
+
84
+ ## Quick start
85
+
86
+ ```bash
87
+ collab init # interactive wizard
88
+ collab init --yes # automatic mode (file-only, defaults)
89
+ collab init --yes --mode indexed # automatic with Docker infrastructure
90
+ collab init --resume # resume from last failed stage
91
+ ```
92
+
93
+ ## Operation modes
94
+
95
+ | Aspect | File-only | Indexed |
96
+ |--------|-----------|---------|
97
+ | **Description** | Agents read `.md` files directly | Agents query NebulaGraph + Qdrant via MCP |
98
+ | **Docker** | Not required | Required (Qdrant, NebulaGraph, MCP server) |
99
+ | **MCP** | No | Yes — endpoint `http://127.0.0.1:7337/mcp` |
100
+ | **Wizard stages** | 8 | 14 |
101
+ | **Use case** | Small projects, no Docker, quick start | Multi-repo ecosystems, large canons |
102
+
103
+ **Transition heuristic:** Consider indexed mode when the canon exceeds ~50,000 tokens (~375 files).
104
+
105
+ ## Commands
106
+
107
+ | Command | Description |
108
+ |---------|-------------|
109
+ | `collab init` | Onboarding wizard (complete setup) |
110
+ | `collab compose generate` | Generate docker-compose files (consolidated \| split) |
111
+ | `collab compose validate` | Validate compose files via `docker compose config` |
112
+ | `collab infra up\|down\|status` | Manage infrastructure services (Qdrant + NebulaGraph) |
113
+ | `collab mcp start\|stop\|status` | Manage MCP runtime service |
114
+ | `collab up` | Full startup pipeline (infra → MCP) |
115
+ | `collab seed` | Preflight check for infrastructure before seeding |
116
+ | `collab doctor` | System diagnostics: config, health, and versions |
117
+ | `collab update-canons` | Download/update canon from GitHub |
118
+
119
+ ## Global options
120
+
121
+ | Option | Description |
122
+ |--------|-------------|
123
+ | `--cwd <path>` | Working directory for collab operations |
124
+ | `--dry-run` | Preview actions without side effects |
125
+ | `--verbose` | Detailed command logging |
126
+ | `--quiet` | Reduce output to results and errors only |
127
+ | `-v, --version` | Show CLI version |
128
+
129
+ ## AI providers
130
+
131
+ | Provider | Env var | CLI detection | Default models |
132
+ |----------|---------|---------------|----------------|
133
+ | Codex (OpenAI) | `OPENAI_API_KEY` | `codex` | o3-pro, gpt-4.1, o4-mini |
134
+ | Claude (Anthropic) | `ANTHROPIC_API_KEY` | `claude` | claude-sonnet-4, claude-opus-4 |
135
+ | Gemini (Google) | `GOOGLE_AI_API_KEY` | `gemini` | gemini-2.5-pro, gemini-2.5-flash |
136
+ | Copilot (GitHub) | — | `gh` | GitHub Copilot backend |
137
+
138
+ **Auto-detection:** Providers are detected automatically if their env var is set or their CLI is in PATH.
139
+
140
+ **MCP snippets:** During `collab init`, MCP configuration files are generated per provider (`claude-mcp-config.json`, `gemini-mcp-config.json`) to connect agents to the MCP server.
141
+
142
+ ## Wizard pipeline (`collab init`)
143
+
144
+ ### File-only (8 stages)
145
+
146
+ 1. Preflight checks (docker, node, npm, git)
147
+ 2. Environment setup (`.collab/config.json`)
148
+ 3. Assistant setup (AI provider configuration)
149
+ 4. Canon sync (download collab-architecture from GitHub)
150
+ 5. Repo scaffold (`docs/architecture` and `docs/ai` structure)
151
+ 6. Repo analysis (basic structure and dependency analysis)
152
+ 7. CI setup (GitHub Actions templates)
153
+ 8. Agent skills setup (skills and prompts registration)
154
+
155
+ ### Indexed (14 stages)
156
+
157
+ **Phase A — Local setup (stages 1-8):** Same as file-only, but repo analysis uses AI.
158
+
159
+ **Phase B — Infrastructure (stages 9-11):**
160
+
161
+ 9. Compose generation (docker-compose.yml or split files)
162
+ 10. Infra startup (Qdrant + NebulaGraph via Docker)
163
+ 11. MCP startup (MCP service + health checks)
164
+
165
+ **Phase C — Ingestion (stages 12-14):**
166
+
167
+ 12. MCP client config (provider snippets)
168
+ 13. Graph seeding (initialize graph with architecture data)
169
+ 14. Canon ingest (ingest collab-architecture into Qdrant/Nebula)
170
+
171
+ **Useful flags:**
172
+ - `--resume` — resume from last incomplete stage
173
+ - `--force` — overwrite existing config
174
+ - `--skip-analysis` — skip code analysis
175
+ - `--skip-ci` — skip CI generation
176
+ - `--providers codex,claude` — specify providers
177
+
178
+ ## Workspace mode
179
+
180
+ For multi-repo ecosystems, collab-cli automatically detects the workspace root and allows repository selection:
181
+
182
+ ```bash
183
+ collab init --repos repo-a,repo-b,repo-c
184
+ ```
185
+
186
+ When run from a directory containing multiple repos, the wizard presents repository selection interactively.
187
+
188
+ ## Local development
189
+
190
+ | Script | Description |
191
+ |--------|-------------|
192
+ | `npm run build` | Compile TypeScript to `dist/` |
193
+ | `npm run lint` | ESLint on `src/**/*.ts` |
194
+ | `npm run format` | Prettier (check) |
195
+ | `npm run format:write` | Prettier (write) |
196
+ | `npm test` | Build + run tests |
197
+ | `npm run test:e2e` | E2E with Docker (`collab init --mode indexed` → MCP tool call) |
198
+ | `npm run typecheck` | TypeScript without emit |
199
+ | `npm run pack:dry-run` | Verify npm package contents |
200
+
201
+ ## Project structure
202
+
203
+ ```
204
+ bin/ # executable entrypoint (bin/collab)
205
+ src/
206
+ cli.ts # main entry point, registers commands
207
+ commands/ # command hierarchy (init, compose, infra, mcp, up, seed, doctor)
208
+ lib/ # shared utilities (config, orchestrator, health, providers, executor)
209
+ stages/ # pipeline stages (preflight, canon-sync, repo-analysis, graph-seed...)
210
+ templates/ # compose and CI templates
211
+ tests/ # integration and orchestration tests
212
+ scripts/ # auxiliary scripts (test runner)
213
+ docs/
214
+ release.md # distribution and versioning strategy
215
+ ai/ # AI agent context (brief, domain map, module map)
216
+ architecture/ # architectural knowledge
217
+ ecosystem.manifest.json # cross-repo compatibility ranges
218
+ ```
219
+
220
+ ## Governance and releases
221
+
222
+ - [CONTRIBUTING](CONTRIBUTING.md) — contribution rules and language policy
223
+ - [Release strategy](docs/release.md) — distribution, SemVer, CI pinning, rollback
224
+
225
+ ## License
226
+
227
+ MIT
package/bin/collab ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+
6
+ const scriptPath = fs.realpathSync(process.argv[1]);
7
+ const projectRoot = path.resolve(path.dirname(scriptPath), '..');
8
+ const entrypoint = path.join(projectRoot, 'dist', 'index.js');
9
+
10
+ require(entrypoint);
package/dist/cli.js ADDED
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createCli = createCli;
4
+ exports.run = run;
5
+ const commander_1 = require("commander");
6
+ const commands_1 = require("./commands");
7
+ const update_checker_1 = require("./lib/update-checker");
8
+ const version_1 = require("./lib/version");
9
+ function createCli() {
10
+ const program = new commander_1.Command();
11
+ program
12
+ .name('collab')
13
+ .description('CLI for collaborative architecture and delivery workflows')
14
+ .version((0, version_1.readCliVersion)(), '-v, --version', 'Show CLI version')
15
+ .option('--cwd <path>', 'Working directory for collab operations')
16
+ .option('--dry-run', 'Preview actions without side effects')
17
+ .option('--verbose', 'Enable verbose command logging')
18
+ .option('--quiet', 'Reduce output to results and errors')
19
+ .showHelpAfterError(true)
20
+ .addHelpCommand(true);
21
+ // Daily update check — shows a non-blocking banner if a new version is available.
22
+ // Skips the upgrade command itself (it does its own check).
23
+ program.hook('preAction', async (_thisCommand, actionCommand) => {
24
+ if (actionCommand.name() === 'upgrade')
25
+ return;
26
+ await (0, update_checker_1.maybeNotifyUpdate)();
27
+ });
28
+ (0, commands_1.registerCommands)(program);
29
+ return program;
30
+ }
31
+ async function run(argv = process.argv) {
32
+ const program = createCli();
33
+ await program.parseAsync(argv);
34
+ }
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerCanonCommand = registerCanonCommand;
4
+ const rebuild_1 = require("./rebuild");
5
+ function registerCanonCommand(program) {
6
+ const canon = program
7
+ .command('canon')
8
+ .description('Manage canonical architecture artifacts')
9
+ .addHelpText('after', `
10
+ Examples:
11
+ collab canon rebuild --confirm
12
+ collab canon rebuild --confirm --indexes
13
+ collab canon rebuild --confirm --graph --dry-run
14
+ `);
15
+ (0, rebuild_1.registerCanonRebuildCommand)(canon);
16
+ }
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildRebuildPipeline = buildRebuildPipeline;
4
+ exports.registerCanonRebuildCommand = registerCanonRebuildCommand;
5
+ const command_context_1 = require("../../lib/command-context");
6
+ const errors_1 = require("../../lib/errors");
7
+ const orchestrator_1 = require("../../lib/orchestrator");
8
+ const canon_rebuild_snapshot_1 = require("../../stages/canon-rebuild-snapshot");
9
+ const canon_rebuild_indexes_1 = require("../../stages/canon-rebuild-indexes");
10
+ const canon_rebuild_graph_1 = require("../../stages/canon-rebuild-graph");
11
+ const canon_rebuild_vectors_1 = require("../../stages/canon-rebuild-vectors");
12
+ const canon_rebuild_validate_1 = require("../../stages/canon-rebuild-validate");
13
+ /**
14
+ * Builds the ordered list of orchestration stages for a canon rebuild.
15
+ *
16
+ * The pipeline always starts with a snapshot and ends with validation.
17
+ * Middle stages depend on the workspace mode and the selective flags
18
+ * provided by the user.
19
+ */
20
+ function buildRebuildPipeline(isFileOnly, flags) {
21
+ const selective = flags.graph || flags.vectors || flags.indexes;
22
+ const stages = [];
23
+ // 1. Snapshot always runs first
24
+ stages.push(canon_rebuild_snapshot_1.canonRebuildSnapshotStage);
25
+ // 2. Selective or full rebuild
26
+ if (!selective) {
27
+ // Full rebuild: all applicable stages for the mode
28
+ stages.push(canon_rebuild_indexes_1.canonRebuildIndexesStage);
29
+ if (!isFileOnly) {
30
+ stages.push(canon_rebuild_graph_1.canonRebuildGraphStage);
31
+ stages.push(canon_rebuild_vectors_1.canonRebuildVectorsStage);
32
+ }
33
+ }
34
+ else {
35
+ if (flags.indexes)
36
+ stages.push(canon_rebuild_indexes_1.canonRebuildIndexesStage);
37
+ if (flags.graph)
38
+ stages.push(canon_rebuild_graph_1.canonRebuildGraphStage);
39
+ if (flags.vectors)
40
+ stages.push(canon_rebuild_vectors_1.canonRebuildVectorsStage);
41
+ }
42
+ // 3. Validation always runs last
43
+ stages.push(canon_rebuild_validate_1.canonRebuildValidateStage);
44
+ return stages;
45
+ }
46
+ function registerCanonRebuildCommand(parent) {
47
+ parent
48
+ .command('rebuild')
49
+ .description('Destroy and recreate all derived canon artifacts for the current workspace')
50
+ .option('--confirm', 'Required flag to confirm destructive rebuild')
51
+ .option('--graph', 'Only rebuild graph seeds via MCP')
52
+ .option('--vectors', 'Only rebuild vector embeddings')
53
+ .option('--indexes', 'Only rebuild README/index files')
54
+ .addHelpText('after', `
55
+ Examples:
56
+ collab canon rebuild --confirm
57
+ collab canon rebuild --confirm --indexes
58
+ collab canon rebuild --confirm --graph --vectors
59
+ collab canon rebuild --dry-run
60
+ `)
61
+ .action(async (options, command) => {
62
+ const context = (0, command_context_1.createCommandContext)(command);
63
+ // Safety: --confirm is mandatory unless in dry-run
64
+ if (!options.confirm && !context.dryRun) {
65
+ throw new errors_1.CliError('Canon rebuild is destructive. Pass --confirm to proceed, or --dry-run to preview.');
66
+ }
67
+ const isFileOnly = context.config.mode === 'file-only';
68
+ const selectiveMode = options.graph || options.vectors || options.indexes;
69
+ // Mode-aware validation
70
+ if (isFileOnly && (options.graph || options.vectors)) {
71
+ throw new errors_1.CliError('Flags --graph and --vectors require indexed mode. ' +
72
+ 'Current workspace is file-only. Only --indexes is available.');
73
+ }
74
+ const stages = buildRebuildPipeline(isFileOnly, options);
75
+ const modeLabel = isFileOnly ? 'file-only' : 'indexed';
76
+ const scopeLabel = selectiveMode
77
+ ? [options.graph && 'graph', options.vectors && 'vectors', options.indexes && 'indexes']
78
+ .filter(Boolean)
79
+ .join('+')
80
+ : 'full';
81
+ await (0, orchestrator_1.runOrchestration)({
82
+ workflowId: 'canon-rebuild',
83
+ config: context.config,
84
+ executor: context.executor,
85
+ logger: context.logger,
86
+ mode: `${modeLabel} (${scopeLabel})`,
87
+ stageOptions: {
88
+ rebuildGraph: !selectiveMode || Boolean(options.graph),
89
+ rebuildVectors: !selectiveMode || Boolean(options.vectors),
90
+ rebuildIndexes: !selectiveMode || Boolean(options.indexes),
91
+ },
92
+ }, stages);
93
+ context.logger.result('Canon rebuild complete.');
94
+ });
95
+ }
@@ -0,0 +1,63 @@
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.registerComposeGenerateCommand = registerComposeGenerateCommand;
7
+ const node_path_1 = __importDefault(require("node:path"));
8
+ const command_context_1 = require("../../lib/command-context");
9
+ const compose_validator_1 = require("../../lib/compose-validator");
10
+ const compose_renderer_1 = require("../../lib/compose-renderer");
11
+ const errors_1 = require("../../lib/errors");
12
+ function parseGenerationMode(value) {
13
+ if (!value || value === 'consolidated' || value === 'split') {
14
+ return (value ?? 'consolidated');
15
+ }
16
+ throw new errors_1.CliError(`Invalid mode '${value}'. Use 'consolidated' or 'split'.`);
17
+ }
18
+ function registerComposeGenerateCommand(program) {
19
+ program
20
+ .command('generate')
21
+ .description('Generate Docker Compose templates for collab services')
22
+ .option('--mode <mode>', 'Generation mode: consolidated|split', 'consolidated')
23
+ .option('--output <file>', 'Output path for consolidated mode')
24
+ .option('--output-dir <directory>', 'Output directory for generated compose files')
25
+ .option('--env-file <file>', 'Path to environment override file')
26
+ .option('--skip-validate', 'Skip docker compose validation after generation')
27
+ .addHelpText('after', `
28
+ Examples:
29
+ collab compose generate --mode consolidated
30
+ collab compose generate --mode consolidated --output docker-compose.local.yml
31
+ collab compose generate --mode split --output-dir ./deploy
32
+ `)
33
+ .action((options, command) => {
34
+ const context = (0, command_context_1.createCommandContext)(command);
35
+ const mode = parseGenerationMode(options.mode);
36
+ if (options.output && mode !== 'consolidated') {
37
+ throw new errors_1.CliError('--output can only be used with --mode consolidated.');
38
+ }
39
+ if (options.outputDir) {
40
+ context.executor.ensureDirectory(node_path_1.default.resolve(context.config.workspaceDir, options.outputDir));
41
+ }
42
+ const generation = (0, compose_renderer_1.generateComposeFiles)({
43
+ config: context.config,
44
+ logger: context.logger,
45
+ executor: context.executor,
46
+ mode,
47
+ outputDirectory: options.outputDir,
48
+ outputFile: options.output,
49
+ envFile: options.envFile,
50
+ });
51
+ for (const warning of generation.driftWarnings) {
52
+ context.logger.warn(warning);
53
+ }
54
+ for (const file of generation.files) {
55
+ context.logger.info(`Generated ${file.filePath}`);
56
+ }
57
+ context.logger.info(`Using environment file ${generation.envFilePath}`);
58
+ if (!options.skipValidate) {
59
+ (0, compose_validator_1.assertComposeFilesValid)(generation.files.map((file) => file.filePath), context.config.workspaceDir, context.executor);
60
+ }
61
+ context.logger.result('Compose generation completed successfully.');
62
+ });
63
+ }
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerComposeCommand = registerComposeCommand;
4
+ const generate_1 = require("./generate");
5
+ const validate_1 = require("./validate");
6
+ function registerComposeCommand(program) {
7
+ const composeCommand = program
8
+ .command('compose')
9
+ .description('Generate and validate Docker Compose files')
10
+ .addHelpText('after', `
11
+ Examples:
12
+ collab compose generate --mode consolidated
13
+ collab compose generate --mode split
14
+ collab compose validate --mode auto
15
+ `);
16
+ (0, generate_1.registerComposeGenerateCommand)(composeCommand);
17
+ (0, validate_1.registerComposeValidateCommand)(composeCommand);
18
+ }
@@ -0,0 +1,53 @@
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.registerComposeValidateCommand = registerComposeValidateCommand;
7
+ const node_fs_1 = __importDefault(require("node:fs"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const command_context_1 = require("../../lib/command-context");
10
+ const compose_validator_1 = require("../../lib/compose-validator");
11
+ const compose_paths_1 = require("../../lib/compose-paths");
12
+ const errors_1 = require("../../lib/errors");
13
+ function parseValidateMode(value) {
14
+ if (!value || value === 'auto' || value === 'consolidated' || value === 'split') {
15
+ return (value ?? 'auto');
16
+ }
17
+ throw new errors_1.CliError(`Invalid mode '${value}'. Use 'auto', 'consolidated', or 'split'.`);
18
+ }
19
+ function resolveFiles(options, config, mode) {
20
+ if (options.file && options.file.length > 0) {
21
+ return options.file.map((item) => node_path_1.default.resolve(config.workspaceDir, item));
22
+ }
23
+ const paths = (0, compose_paths_1.getComposeFilePaths)(config, options.outputDir);
24
+ if (mode === 'consolidated') {
25
+ return [paths.consolidated];
26
+ }
27
+ if (mode === 'split') {
28
+ return [paths.infra, paths.mcp];
29
+ }
30
+ const splitExists = [paths.infra, paths.mcp].every((candidate) => node_fs_1.default.existsSync(candidate));
31
+ return splitExists ? [paths.infra, paths.mcp] : [paths.consolidated];
32
+ }
33
+ function registerComposeValidateCommand(program) {
34
+ program
35
+ .command('validate')
36
+ .description('Validate generated Docker Compose files via docker compose config')
37
+ .option('--mode <mode>', 'Validation mode: auto|consolidated|split', 'auto')
38
+ .option('--file <path...>', 'Explicit compose files to validate')
39
+ .option('--output-dir <directory>', 'Compose directory when using mode selection')
40
+ .addHelpText('after', `
41
+ Examples:
42
+ collab compose validate --mode auto
43
+ collab compose validate --mode split
44
+ collab compose validate --file docker-compose.yml
45
+ `)
46
+ .action((options, command) => {
47
+ const context = (0, command_context_1.createCommandContext)(command);
48
+ const mode = parseValidateMode(options.mode);
49
+ const files = resolveFiles(options, context.config, mode);
50
+ (0, compose_validator_1.assertComposeFilesValid)(files, context.config.workspaceDir, context.executor);
51
+ context.logger.result(`Compose validation successful for ${files.length} file(s).`);
52
+ });
53
+ }
@@ -0,0 +1,153 @@
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.registerDoctorCommand = registerDoctorCommand;
7
+ const node_fs_1 = __importDefault(require("node:fs"));
8
+ const command_context_1 = require("../lib/command-context");
9
+ const compose_defaults_1 = require("../lib/compose-defaults");
10
+ const compose_paths_1 = require("../lib/compose-paths");
11
+ const compose_validator_1 = require("../lib/compose-validator");
12
+ const docker_checks_1 = require("../lib/docker-checks");
13
+ const ecosystem_1 = require("../lib/ecosystem");
14
+ const errors_1 = require("../lib/errors");
15
+ const service_health_1 = require("../lib/service-health");
16
+ const preflight_1 = require("../lib/preflight");
17
+ function printCheck(check) {
18
+ const prefix = check.ok ? '[PASS]' : '[FAIL]';
19
+ process.stdout.write(`${prefix} ${check.id}: ${check.detail}\n`);
20
+ if (!check.ok && check.fix) {
21
+ process.stdout.write(` fix: ${check.fix}\n`);
22
+ }
23
+ }
24
+ function registerDoctorCommand(program) {
25
+ program
26
+ .command('doctor')
27
+ .description('Run diagnostics across system, infra, MCP, config, and versions')
28
+ .addHelpText('after', `
29
+ Examples:
30
+ collab doctor
31
+ collab doctor --verbose
32
+ `)
33
+ .action(async (_options, command) => {
34
+ const context = (0, command_context_1.createCommandContext)(command);
35
+ const checks = [];
36
+ checks.push({
37
+ id: 'workspace',
38
+ ok: true,
39
+ detail: context.config.workspaceDir,
40
+ });
41
+ const preflight = (0, preflight_1.runPreflightChecks)(context.executor);
42
+ checks.push(...preflight.map((item) => ({
43
+ id: `system:${item.id}`,
44
+ ok: item.ok,
45
+ detail: item.detail,
46
+ fix: item.fix,
47
+ })));
48
+ // ── Docker daemon check ───────────────────────────────
49
+ const dockerBinaryOk = preflight.find((item) => item.id === 'docker')?.ok ?? false;
50
+ const daemonResult = (0, docker_checks_1.checkDockerDaemon)(context.executor);
51
+ const daemonFix = !dockerBinaryOk
52
+ ? 'Install Docker Desktop or Docker Engine.'
53
+ : 'Start Docker Desktop or run: sudo systemctl start docker';
54
+ checks.push({
55
+ id: 'docker:daemon',
56
+ ok: daemonResult.ok,
57
+ detail: daemonResult.ok
58
+ ? `Docker daemon v${daemonResult.version}`
59
+ : (daemonResult.error ?? 'Docker daemon unavailable'),
60
+ fix: daemonFix,
61
+ });
62
+ // ── Docker image checks ────────────────────────────────
63
+ const env = (0, service_health_1.loadRuntimeEnv)(context.config);
64
+ const imagesToCheck = [
65
+ env.MCP_IMAGE || compose_defaults_1.COMPOSE_ENV_DEFAULTS.MCP_IMAGE,
66
+ env.QDRANT_IMAGE || compose_defaults_1.COMPOSE_ENV_DEFAULTS.QDRANT_IMAGE,
67
+ ];
68
+ const imageResults = (0, docker_checks_1.checkDockerImages)(context.executor, imagesToCheck);
69
+ for (const img of imageResults) {
70
+ checks.push({
71
+ id: `docker:image:${img.image.split('/').pop()?.split(':')[0] ?? img.image}`,
72
+ ok: img.ok,
73
+ detail: img.ok ? `${img.image} available locally` : (img.error ?? `${img.image} not found`),
74
+ fix: img.error && !/not found locally/i.test(img.error)
75
+ ? img.error
76
+ : `Pull with: docker pull ${img.image}`,
77
+ });
78
+ }
79
+ const envFileExists = node_fs_1.default.existsSync(context.config.envFile);
80
+ checks.push({
81
+ id: 'config:env-file',
82
+ ok: envFileExists,
83
+ detail: envFileExists ? `${context.config.envFile} found` : `${context.config.envFile} missing`,
84
+ fix: 'Run collab init or collab compose generate to create .env defaults.',
85
+ });
86
+ const composePaths = (0, compose_paths_1.getComposeFilePaths)(context.config);
87
+ const composeCandidates = [composePaths.consolidated, composePaths.infra, composePaths.mcp].filter((p) => node_fs_1.default.existsSync(p));
88
+ if (composeCandidates.length === 0) {
89
+ checks.push({
90
+ id: 'config:compose-files',
91
+ ok: false,
92
+ detail: 'No compose files found in workspace.',
93
+ fix: 'Run collab compose generate first.',
94
+ });
95
+ }
96
+ else {
97
+ const errors = (0, compose_validator_1.validateComposeFiles)(composeCandidates, context.config.workspaceDir, context.executor);
98
+ checks.push({
99
+ id: 'config:compose-files',
100
+ ok: errors.length === 0,
101
+ detail: errors.length === 0
102
+ ? `${composeCandidates.length} compose file(s) validated`
103
+ : `${errors.length} compose validation error(s)`,
104
+ fix: 'Run collab compose validate to inspect exact compose errors.',
105
+ });
106
+ }
107
+ const infraHealth = await (0, service_health_1.waitForInfraHealth)(env, {
108
+ timeoutMs: 2_000,
109
+ retries: 1,
110
+ retryDelayMs: 0,
111
+ dryRun: context.executor.dryRun,
112
+ });
113
+ checks.push({
114
+ id: 'infra:qdrant-nebula',
115
+ ok: infraHealth.ok,
116
+ detail: infraHealth.ok
117
+ ? infraHealth.checks.join('; ')
118
+ : infraHealth.errors.join('; '),
119
+ fix: 'Run collab infra up and verify ports in .env.',
120
+ });
121
+ const mcpHealth = await (0, service_health_1.waitForMcpHealth)(env, {
122
+ timeoutMs: 2_000,
123
+ retries: 1,
124
+ retryDelayMs: 0,
125
+ dryRun: context.executor.dryRun,
126
+ });
127
+ checks.push({
128
+ id: 'mcp:health',
129
+ ok: mcpHealth.ok,
130
+ detail: mcpHealth.ok ? mcpHealth.checks.join('; ') : mcpHealth.errors.join('; '),
131
+ fix: 'Run collab mcp start and verify MCP_PORT/MCP_HOST in .env.',
132
+ });
133
+ const compatibility = await (0, ecosystem_1.checkEcosystemCompatibility)(context.config, {
134
+ dryRun: context.executor.dryRun,
135
+ });
136
+ checks.push(...compatibility.map((item) => ({
137
+ id: `version:${item.id}`,
138
+ ok: item.ok,
139
+ detail: item.detail,
140
+ fix: item.fix,
141
+ })));
142
+ process.stdout.write(`node: ${process.version}\n`);
143
+ process.stdout.write(`platform: ${process.platform}/${process.arch}\n`);
144
+ process.stdout.write(`docker: ${daemonResult.version ?? 'not available'}\n`);
145
+ for (const check of checks) {
146
+ printCheck(check);
147
+ }
148
+ const failed = checks.filter((item) => !item.ok);
149
+ if (failed.length > 0) {
150
+ throw new errors_1.CliError(`Doctor found ${failed.length} failing check(s).`);
151
+ }
152
+ });
153
+ }