legion-cc 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 (49) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +269 -0
  3. package/VERSION +1 -0
  4. package/agents/legion-orchestrator.md +95 -0
  5. package/bin/install.js +898 -0
  6. package/bin/legion-tools.cjs +421 -0
  7. package/bin/lib/config.cjs +141 -0
  8. package/bin/lib/core.cjs +216 -0
  9. package/bin/lib/domain.cjs +107 -0
  10. package/bin/lib/init.cjs +184 -0
  11. package/bin/lib/session.cjs +140 -0
  12. package/bin/lib/state.cjs +280 -0
  13. package/commands/legion/devops/architect.md +44 -0
  14. package/commands/legion/devops/build.md +52 -0
  15. package/commands/legion/devops/cycle.md +52 -0
  16. package/commands/legion/devops/execute.md +52 -0
  17. package/commands/legion/devops/plan.md +51 -0
  18. package/commands/legion/devops/quick.md +45 -0
  19. package/commands/legion/devops/review.md +52 -0
  20. package/commands/legion/resume.md +52 -0
  21. package/commands/legion/status.md +53 -0
  22. package/hooks/legion-context-monitor.js +180 -0
  23. package/hooks/legion-statusline.js +191 -0
  24. package/package.json +48 -0
  25. package/references/agent-routing.md +64 -0
  26. package/references/devops/agent-map.md +61 -0
  27. package/references/devops/pipeline-patterns.md +87 -0
  28. package/references/domain-registry.md +63 -0
  29. package/references/ui-brand.md +102 -0
  30. package/templates/config.json +25 -0
  31. package/templates/devops/architect-output.md +28 -0
  32. package/templates/devops/execution-report.md +23 -0
  33. package/templates/devops/plan-output.md +33 -0
  34. package/templates/devops/review-checklist.md +35 -0
  35. package/templates/session.md +17 -0
  36. package/templates/state.md +17 -0
  37. package/templates/task-record.md +19 -0
  38. package/workflows/core/completion.md +70 -0
  39. package/workflows/core/context-load.md +57 -0
  40. package/workflows/core/init.md +52 -0
  41. package/workflows/devops/architect.md +91 -0
  42. package/workflows/devops/build.md +92 -0
  43. package/workflows/devops/cycle.md +237 -0
  44. package/workflows/devops/execute.md +118 -0
  45. package/workflows/devops/plan.md +108 -0
  46. package/workflows/devops/quick.md +107 -0
  47. package/workflows/devops/review.md +112 -0
  48. package/workflows/resume.md +88 -0
  49. package/workflows/status.md +72 -0
@@ -0,0 +1,421 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const path = require('path');
5
+
6
+ // ─── Library Imports ─────────────────────────────────────────────────────────
7
+
8
+ const core = require('./lib/core.cjs');
9
+ const config = require('./lib/config.cjs');
10
+ const state = require('./lib/state.cjs');
11
+ const init = require('./lib/init.cjs');
12
+ const session = require('./lib/session.cjs');
13
+ const domain = require('./lib/domain.cjs');
14
+
15
+ // ─── CLI Helpers ─────────────────────────────────────────────────────────────
16
+
17
+ function usage() {
18
+ const lines = [
19
+ '',
20
+ 'Usage: legion-tools <command> [options]',
21
+ '',
22
+ 'Commands:',
23
+ ' init <workflow-type> [args...] Initialize a workflow and return context',
24
+ ' state load|update|get Manage STATE.md',
25
+ ' status Show current Legion status',
26
+ ' generate-slug <text> Generate a URL-safe slug from text',
27
+ ' current-timestamp [format] Print current timestamp (iso|date|datetime)',
28
+ ' task-record <type> <desc> <status> Add a task record to STATE.md',
29
+ ' context-load [path] Load context from .codebase/ and .planning/',
30
+ ' domain list|info [name] Domain registry operations',
31
+ '',
32
+ 'Examples:',
33
+ ' legion-tools init devops',
34
+ ' legion-tools state load',
35
+ ' legion-tools state update currentWork.stage plan',
36
+ ' legion-tools state get project.domain',
37
+ ' legion-tools status',
38
+ ' legion-tools generate-slug "My Cool Project"',
39
+ ' legion-tools current-timestamp date',
40
+ ' legion-tools task-record architect "Initial architecture" done',
41
+ ' legion-tools context-load',
42
+ ' legion-tools domain list',
43
+ ' legion-tools domain info devops',
44
+ '',
45
+ ];
46
+ console.log(lines.join('\n'));
47
+ }
48
+
49
+ /**
50
+ * Print a JSON result to stdout.
51
+ */
52
+ function out(data) {
53
+ console.log(JSON.stringify(data, null, 2));
54
+ }
55
+
56
+ /**
57
+ * Print an error message and exit.
58
+ */
59
+ function die(msg, code) {
60
+ console.error(`${core.UI.cross} Error: ${msg}`);
61
+ process.exit(code || 1);
62
+ }
63
+
64
+ /**
65
+ * Require that a planning directory exists, or exit with an error.
66
+ */
67
+ function requirePlanningDir() {
68
+ const planningDir = core.resolvePlanningDir();
69
+ if (!planningDir) {
70
+ die('No .planning/legion/ directory found. Run "legion-tools init <domain>" first.');
71
+ }
72
+ return planningDir;
73
+ }
74
+
75
+ // ─── Command: init ───────────────────────────────────────────────────────────
76
+
77
+ function cmdInit(args) {
78
+ const workflowType = args[0];
79
+ if (!workflowType) {
80
+ die('Missing workflow type. Usage: legion-tools init <workflow-type>');
81
+ }
82
+
83
+ const restArgs = args.slice(1);
84
+ const context = init.initWorkflow(workflowType, restArgs);
85
+
86
+ core.banner(context.domain, 'init');
87
+ console.log(`${core.UI.check} Initialized ${context.domain} workflow`);
88
+ console.log(` Project root: ${context.projectRoot}`);
89
+ console.log(` Planning dir: ${context.planning.path}`);
90
+ console.log(` Config: ${context.config.existed ? 'loaded' : 'created default'}`);
91
+ console.log(` State: ${context.state.existed ? 'loaded' : 'initialized'}`);
92
+ console.log(` Codebase: ${context.codebase.exists ? context.codebase.files.length + ' files' : 'not found'}`);
93
+ console.log(` Next task: #${context.nextTaskNum}`);
94
+ console.log('');
95
+
96
+ out(context);
97
+ }
98
+
99
+ // ─── Command: state ──────────────────────────────────────────────────────────
100
+
101
+ function cmdState(args) {
102
+ const subcommand = args[0];
103
+
104
+ if (!subcommand) {
105
+ die('Missing subcommand. Usage: legion-tools state load|update|get');
106
+ }
107
+
108
+ const planningDir = requirePlanningDir();
109
+
110
+ switch (subcommand) {
111
+ case 'load': {
112
+ const stateObj = state.loadState(planningDir);
113
+ if (!stateObj) {
114
+ die('STATE.md not found. Run "legion-tools init <domain>" first.');
115
+ }
116
+ out(stateObj);
117
+ break;
118
+ }
119
+
120
+ case 'update': {
121
+ const field = args[1];
122
+ const value = args.slice(2).join(' ');
123
+ if (!field || !value) {
124
+ die('Usage: legion-tools state update <field> <value>');
125
+ }
126
+ state.updateField(planningDir, field, value);
127
+ console.log(`${core.UI.check} Updated ${field} = ${value}`);
128
+ break;
129
+ }
130
+
131
+ case 'get': {
132
+ const field = args[1];
133
+ if (!field) {
134
+ die('Usage: legion-tools state get <field>');
135
+ }
136
+ const stateObj = state.loadState(planningDir);
137
+ if (!stateObj) {
138
+ die('STATE.md not found.');
139
+ }
140
+
141
+ // Navigate dot-notation
142
+ const parts = field.split('.');
143
+ let target = stateObj;
144
+ for (const part of parts) {
145
+ if (target === undefined || target === null || typeof target !== 'object') {
146
+ die(`Field not found: ${field}`);
147
+ }
148
+ target = target[part];
149
+ }
150
+
151
+ if (typeof target === 'object') {
152
+ out(target);
153
+ } else {
154
+ console.log(target);
155
+ }
156
+ break;
157
+ }
158
+
159
+ default:
160
+ die(`Unknown state subcommand: ${subcommand}. Use load|update|get`);
161
+ }
162
+ }
163
+
164
+ // ─── Command: status ─────────────────────────────────────────────────────────
165
+
166
+ function cmdStatus() {
167
+ const planningDir = core.resolvePlanningDir();
168
+ const codebaseDir = core.resolveCodebaseDir();
169
+
170
+ const result = {
171
+ planningDir: planningDir,
172
+ codebaseDir: codebaseDir,
173
+ hasState: false,
174
+ hasConfig: false,
175
+ state: null,
176
+ config: null,
177
+ latestSession: null,
178
+ };
179
+
180
+ if (planningDir) {
181
+ const stateObj = state.loadState(planningDir);
182
+ if (stateObj) {
183
+ result.hasState = true;
184
+ result.state = stateObj;
185
+ }
186
+
187
+ const cfg = config.loadConfig(planningDir);
188
+ if (cfg) {
189
+ result.hasConfig = true;
190
+ result.config = cfg;
191
+ }
192
+
193
+ const sess = session.getLatestSession(planningDir);
194
+ if (sess) {
195
+ result.latestSession = { path: sess.path, name: sess.name };
196
+ }
197
+ }
198
+
199
+ // Pretty display
200
+ if (planningDir) {
201
+ const domainName = (result.config && result.config.domain) || 'unknown';
202
+ core.banner(domainName, 'status');
203
+ console.log(` Planning: ${planningDir}`);
204
+ console.log(` Codebase: ${codebaseDir || 'not found'}`);
205
+ console.log(` Config: ${result.hasConfig ? core.UI.check : core.UI.cross}`);
206
+ console.log(` State: ${result.hasState ? core.UI.check : core.UI.cross}`);
207
+
208
+ if (result.state) {
209
+ console.log('');
210
+ console.log(` Domain: ${result.state.project.domain}`);
211
+ console.log(` Stage: ${result.state.currentWork.stage}`);
212
+ console.log(` Next: ${result.state.currentWork.next}`);
213
+ console.log(` Tasks: ${result.state.taskHistory.length}`);
214
+ }
215
+
216
+ if (result.latestSession) {
217
+ console.log(` Session: ${result.latestSession.name}`);
218
+ }
219
+ console.log('');
220
+ } else {
221
+ console.log(`${core.UI.warn} No Legion project found in current directory tree.`);
222
+ console.log(' Run "legion-tools init <domain>" to create one.');
223
+ console.log('');
224
+ }
225
+
226
+ out(result);
227
+ }
228
+
229
+ // ─── Command: generate-slug ──────────────────────────────────────────────────
230
+
231
+ function cmdGenerateSlug(args) {
232
+ const text = args.join(' ');
233
+ if (!text) {
234
+ die('Usage: legion-tools generate-slug <text>');
235
+ }
236
+ console.log(core.generateSlug(text));
237
+ }
238
+
239
+ // ─── Command: current-timestamp ──────────────────────────────────────────────
240
+
241
+ function cmdCurrentTimestamp(args) {
242
+ const format = args[0] || 'iso';
243
+ console.log(core.currentTimestamp(format));
244
+ }
245
+
246
+ // ─── Command: task-record ────────────────────────────────────────────────────
247
+
248
+ function cmdTaskRecord(args) {
249
+ const type = args[0];
250
+ const desc = args[1];
251
+ const status = args[2];
252
+
253
+ if (!type || !desc || !status) {
254
+ die('Usage: legion-tools task-record <type> <description> <status>');
255
+ }
256
+
257
+ const planningDir = requirePlanningDir();
258
+
259
+ state.addTaskRecord(planningDir, {
260
+ type: type,
261
+ description: desc,
262
+ status: status,
263
+ });
264
+
265
+ console.log(`${core.UI.check} Task recorded: [${type}] ${desc} (${status})`);
266
+ }
267
+
268
+ // ─── Command: context-load ───────────────────────────────────────────────────
269
+
270
+ function cmdContextLoad(args) {
271
+ const startPath = args[0] || process.cwd();
272
+
273
+ const codebaseDir = core.resolveCodebaseDir(startPath);
274
+ const planningDir = core.resolvePlanningDir(startPath);
275
+
276
+ const result = {
277
+ codebase: null,
278
+ planning: null,
279
+ state: null,
280
+ config: null,
281
+ };
282
+
283
+ if (codebaseDir) {
284
+ let files = [];
285
+ try {
286
+ files = _listFiles(codebaseDir, codebaseDir, 0, []);
287
+ } catch (_err) { /* ignore */ }
288
+ result.codebase = {
289
+ path: codebaseDir,
290
+ files: files,
291
+ };
292
+ }
293
+
294
+ if (planningDir) {
295
+ result.planning = { path: planningDir };
296
+ result.state = state.loadState(planningDir);
297
+ result.config = config.loadConfig(planningDir);
298
+ }
299
+
300
+ out(result);
301
+ }
302
+
303
+ /**
304
+ * Recursively list files, limited depth and count.
305
+ */
306
+ function _listFiles(dir, base, depth, results) {
307
+ if (depth > 3 || results.length >= 100) return results;
308
+ const entries = require('fs').readdirSync(dir, { withFileTypes: true });
309
+ for (const entry of entries) {
310
+ if (results.length >= 100) break;
311
+ const full = path.join(dir, entry.name);
312
+ if (entry.isDirectory()) {
313
+ if (!entry.name.startsWith('.')) {
314
+ _listFiles(full, base, depth + 1, results);
315
+ }
316
+ } else {
317
+ results.push(path.relative(base, full));
318
+ }
319
+ }
320
+ return results;
321
+ }
322
+
323
+ // ─── Command: domain ─────────────────────────────────────────────────────────
324
+
325
+ function cmdDomain(args) {
326
+ const subcommand = args[0];
327
+
328
+ if (!subcommand) {
329
+ die('Usage: legion-tools domain list|info [name]');
330
+ }
331
+
332
+ switch (subcommand) {
333
+ case 'list': {
334
+ const domains = domain.listDomains();
335
+ console.log(`${core.UI.diamond} Installed domains:\n`);
336
+ for (const d of domains) {
337
+ const marker = d.hasConfig ? core.UI.check : core.UI.circle;
338
+ console.log(` ${marker} ${d.name} (${d.source})`);
339
+ }
340
+ console.log('');
341
+ out(domains);
342
+ break;
343
+ }
344
+
345
+ case 'info': {
346
+ const name = args[1];
347
+ if (!name) {
348
+ die('Usage: legion-tools domain info <name>');
349
+ }
350
+ const info = domain.getDomainInfo(name);
351
+ if (!info) {
352
+ die(`Domain not found: ${name}`);
353
+ }
354
+
355
+ core.banner(info.name, 'info');
356
+ console.log(` Source: ${info.source}`);
357
+ console.log(` Version: ${info.version}`);
358
+ console.log(` Pipeline: ${info.pipeline.join(' -> ')}`);
359
+ console.log('');
360
+ console.log(' Agents:');
361
+ for (const [role, agent] of Object.entries(info.agents)) {
362
+ console.log(` ${role}: ${agent} (model: ${info.models[role] || 'default'})`);
363
+ }
364
+ console.log('');
365
+ console.log(' Checkpoints:');
366
+ for (const [cp, enabled] of Object.entries(info.checkpoints)) {
367
+ console.log(` ${cp}: ${enabled ? core.UI.check : core.UI.cross}`);
368
+ }
369
+ console.log('');
370
+ out(info);
371
+ break;
372
+ }
373
+
374
+ default:
375
+ die(`Unknown domain subcommand: ${subcommand}. Use list|info`);
376
+ }
377
+ }
378
+
379
+ // ─── Main Router ─────────────────────────────────────────────────────────────
380
+
381
+ function main() {
382
+ const args = process.argv.slice(2);
383
+ const command = args[0];
384
+ const commandArgs = args.slice(1);
385
+
386
+ if (!command || command === '--help' || command === '-h') {
387
+ usage();
388
+ process.exit(0);
389
+ }
390
+
391
+ switch (command) {
392
+ case 'init':
393
+ cmdInit(commandArgs);
394
+ break;
395
+ case 'state':
396
+ cmdState(commandArgs);
397
+ break;
398
+ case 'status':
399
+ cmdStatus();
400
+ break;
401
+ case 'generate-slug':
402
+ cmdGenerateSlug(commandArgs);
403
+ break;
404
+ case 'current-timestamp':
405
+ cmdCurrentTimestamp(commandArgs);
406
+ break;
407
+ case 'task-record':
408
+ cmdTaskRecord(commandArgs);
409
+ break;
410
+ case 'context-load':
411
+ cmdContextLoad(commandArgs);
412
+ break;
413
+ case 'domain':
414
+ cmdDomain(commandArgs);
415
+ break;
416
+ default:
417
+ die(`Unknown command: ${command}. Run "legion-tools --help" for usage.`);
418
+ }
419
+ }
420
+
421
+ main();
@@ -0,0 +1,141 @@
1
+ 'use strict';
2
+
3
+ const path = require('path');
4
+ const { readJsonSafe, writeJsonSafe } = require('./core.cjs');
5
+
6
+ // ─── Default Configurations by Domain ────────────────────────────────────────
7
+
8
+ const DOMAIN_DEFAULTS = {
9
+ devops: {
10
+ version: '0.1.0',
11
+ domain: 'devops',
12
+ agents: {
13
+ architect: 'devops-architect',
14
+ planner: 'delivery-planner',
15
+ executor: 'infra-executor',
16
+ builder: 'codebase-builder',
17
+ reviewer: 'devops-architect',
18
+ },
19
+ models: {
20
+ architect: 'opus',
21
+ planner: 'sonnet',
22
+ executor: 'opus',
23
+ builder: 'opus',
24
+ reviewer: 'sonnet',
25
+ },
26
+ pipeline: ['architect', 'plan', 'execute', 'review'],
27
+ checkpoints: {
28
+ after_architect: true,
29
+ after_plan: true,
30
+ after_execute: false,
31
+ after_review: false,
32
+ },
33
+ },
34
+ backend: {
35
+ version: '0.1.0',
36
+ domain: 'backend',
37
+ agents: {
38
+ architect: 'backend-architect',
39
+ planner: 'delivery-planner',
40
+ executor: 'backend-executor',
41
+ builder: 'codebase-builder',
42
+ reviewer: 'backend-architect',
43
+ },
44
+ models: {
45
+ architect: 'opus',
46
+ planner: 'sonnet',
47
+ executor: 'opus',
48
+ builder: 'opus',
49
+ reviewer: 'sonnet',
50
+ },
51
+ pipeline: ['architect', 'plan', 'execute', 'review'],
52
+ checkpoints: {
53
+ after_architect: true,
54
+ after_plan: true,
55
+ after_execute: false,
56
+ after_review: false,
57
+ },
58
+ },
59
+ frontend: {
60
+ version: '0.1.0',
61
+ domain: 'frontend',
62
+ agents: {
63
+ architect: 'frontend-architect',
64
+ planner: 'delivery-planner',
65
+ executor: 'frontend-executor',
66
+ builder: 'codebase-builder',
67
+ reviewer: 'frontend-architect',
68
+ },
69
+ models: {
70
+ architect: 'opus',
71
+ planner: 'sonnet',
72
+ executor: 'opus',
73
+ builder: 'opus',
74
+ reviewer: 'sonnet',
75
+ },
76
+ pipeline: ['architect', 'plan', 'execute', 'review'],
77
+ checkpoints: {
78
+ after_architect: true,
79
+ after_plan: true,
80
+ after_execute: false,
81
+ after_review: false,
82
+ },
83
+ },
84
+ };
85
+
86
+ // ─── Config Functions ────────────────────────────────────────────────────────
87
+
88
+ /**
89
+ * Load config.json from the given planning directory.
90
+ *
91
+ * @param {string} planningDir - Absolute path to `.planning/legion/`
92
+ * @returns {object|null} Parsed config or null if missing/invalid
93
+ */
94
+ function loadConfig(planningDir) {
95
+ const configPath = path.join(planningDir, 'config.json');
96
+ return readJsonSafe(configPath, null);
97
+ }
98
+
99
+ /**
100
+ * Return the default configuration for a given domain.
101
+ * Falls back to 'devops' if the domain is not recognized.
102
+ *
103
+ * @param {string} domain - Domain name (e.g. 'devops', 'backend', 'frontend')
104
+ * @returns {object} Default config object (deep copy)
105
+ */
106
+ function defaultConfig(domain) {
107
+ const key = (domain || 'devops').toLowerCase();
108
+ const template = DOMAIN_DEFAULTS[key] || DOMAIN_DEFAULTS.devops;
109
+ // Return a deep copy so callers cannot mutate the template
110
+ return JSON.parse(JSON.stringify(template));
111
+ }
112
+
113
+ /**
114
+ * Save a config object to `.planning/legion/config.json`.
115
+ *
116
+ * @param {string} planningDir - Absolute path to `.planning/legion/`
117
+ * @param {object} config - Config object to persist
118
+ */
119
+ function saveConfig(planningDir, config) {
120
+ const configPath = path.join(planningDir, 'config.json');
121
+ writeJsonSafe(configPath, config);
122
+ }
123
+
124
+ /**
125
+ * Return the list of known domain names.
126
+ *
127
+ * @returns {string[]}
128
+ */
129
+ function knownDomains() {
130
+ return Object.keys(DOMAIN_DEFAULTS);
131
+ }
132
+
133
+ // ─── Exports ─────────────────────────────────────────────────────────────────
134
+
135
+ module.exports = {
136
+ loadConfig,
137
+ defaultConfig,
138
+ saveConfig,
139
+ knownDomains,
140
+ DOMAIN_DEFAULTS,
141
+ };