@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.
- package/README.md +227 -0
- package/bin/collab +10 -0
- package/dist/cli.js +34 -0
- package/dist/commands/canon/index.js +16 -0
- package/dist/commands/canon/rebuild.js +95 -0
- package/dist/commands/compose/generate.js +63 -0
- package/dist/commands/compose/index.js +18 -0
- package/dist/commands/compose/validate.js +53 -0
- package/dist/commands/doctor.js +153 -0
- package/dist/commands/index.js +27 -0
- package/dist/commands/infra/down.js +23 -0
- package/dist/commands/infra/index.js +20 -0
- package/dist/commands/infra/shared.js +59 -0
- package/dist/commands/infra/status.js +64 -0
- package/dist/commands/infra/up.js +29 -0
- package/dist/commands/init.js +830 -0
- package/dist/commands/mcp/index.js +20 -0
- package/dist/commands/mcp/shared.js +57 -0
- package/dist/commands/mcp/start.js +45 -0
- package/dist/commands/mcp/status.js +62 -0
- package/dist/commands/mcp/stop.js +23 -0
- package/dist/commands/seed.js +55 -0
- package/dist/commands/uninstall.js +36 -0
- package/dist/commands/up.js +78 -0
- package/dist/commands/update-canons.js +48 -0
- package/dist/commands/upgrade.js +54 -0
- package/dist/index.js +14 -0
- package/dist/lib/ai-client.js +317 -0
- package/dist/lib/ansi.js +58 -0
- package/dist/lib/canon-index-generator.js +64 -0
- package/dist/lib/canon-index-targets.js +68 -0
- package/dist/lib/canon-resolver.js +262 -0
- package/dist/lib/canon-scaffold.js +57 -0
- package/dist/lib/cli-detection.js +149 -0
- package/dist/lib/command-context.js +23 -0
- package/dist/lib/compose-defaults.js +47 -0
- package/dist/lib/compose-env.js +24 -0
- package/dist/lib/compose-paths.js +36 -0
- package/dist/lib/compose-renderer.js +134 -0
- package/dist/lib/compose-validator.js +56 -0
- package/dist/lib/config.js +195 -0
- package/dist/lib/credentials.js +63 -0
- package/dist/lib/docker-checks.js +73 -0
- package/dist/lib/docker-compose.js +15 -0
- package/dist/lib/docker-status.js +151 -0
- package/dist/lib/domain-gen.js +376 -0
- package/dist/lib/ecosystem.js +150 -0
- package/dist/lib/env-file.js +77 -0
- package/dist/lib/errors.js +30 -0
- package/dist/lib/executor.js +85 -0
- package/dist/lib/github-auth.js +204 -0
- package/dist/lib/hash.js +7 -0
- package/dist/lib/health-checker.js +140 -0
- package/dist/lib/logger.js +87 -0
- package/dist/lib/mcp-client.js +88 -0
- package/dist/lib/mode.js +36 -0
- package/dist/lib/model-listing.js +102 -0
- package/dist/lib/model-registry.js +55 -0
- package/dist/lib/npm-operations.js +69 -0
- package/dist/lib/orchestrator.js +170 -0
- package/dist/lib/parsers.js +42 -0
- package/dist/lib/port-resolver.js +57 -0
- package/dist/lib/preconditions.js +35 -0
- package/dist/lib/preflight.js +88 -0
- package/dist/lib/process.js +6 -0
- package/dist/lib/prompt.js +125 -0
- package/dist/lib/providers.js +117 -0
- package/dist/lib/repo-analysis-helpers.js +379 -0
- package/dist/lib/repo-scanner.js +195 -0
- package/dist/lib/service-health.js +79 -0
- package/dist/lib/shell.js +49 -0
- package/dist/lib/state.js +38 -0
- package/dist/lib/update-checker.js +130 -0
- package/dist/lib/version.js +27 -0
- package/dist/stages/agent-skills-setup.js +301 -0
- package/dist/stages/assistant-setup.js +325 -0
- package/dist/stages/canon-ingest.js +249 -0
- package/dist/stages/canon-rebuild-graph.js +33 -0
- package/dist/stages/canon-rebuild-indexes.js +40 -0
- package/dist/stages/canon-rebuild-snapshot.js +75 -0
- package/dist/stages/canon-rebuild-validate.js +57 -0
- package/dist/stages/canon-rebuild-vectors.js +30 -0
- package/dist/stages/canon-scaffold.js +15 -0
- package/dist/stages/canon-sync.js +49 -0
- package/dist/stages/ci-setup.js +56 -0
- package/dist/stages/domain-gen.js +363 -0
- package/dist/stages/graph-seed.js +26 -0
- package/dist/stages/repo-analysis-fileonly.js +111 -0
- package/dist/stages/repo-analysis.js +112 -0
- package/dist/stages/repo-scaffold.js +110 -0
- package/dist/templates/canon/contracts-readme.js +39 -0
- package/dist/templates/canon/domain-readme.js +40 -0
- package/dist/templates/canon/evolution/changelog.js +53 -0
- package/dist/templates/canon/governance/confidence-levels.js +38 -0
- package/dist/templates/canon/governance/implementation-process.js +34 -0
- package/dist/templates/canon/governance/review-process.js +29 -0
- package/dist/templates/canon/governance/schema-versioning.js +25 -0
- package/dist/templates/canon/governance/what-enters-the-canon.js +44 -0
- package/dist/templates/canon/index.js +28 -0
- package/dist/templates/canon/knowledge-readme.js +129 -0
- package/dist/templates/canon/system-prompt.js +101 -0
- package/dist/templates/ci/architecture-merge.js +29 -0
- package/dist/templates/ci/architecture-pr.js +26 -0
- package/dist/templates/ci/index.js +7 -0
- package/dist/templates/consolidated.js +114 -0
- package/dist/templates/infra.js +90 -0
- package/dist/templates/mcp.js +32 -0
- package/install.sh +455 -0
- 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
|
+
}
|