legion-cc 0.1.0 → 0.2.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 (37) hide show
  1. package/README.md +17 -26
  2. package/VERSION +1 -1
  3. package/agents/legion-orchestrator.md +4 -4
  4. package/bin/install.js +1 -1
  5. package/bin/legion-tools.cjs +34 -2
  6. package/bin/lib/config.cjs +3 -3
  7. package/bin/lib/core.cjs +16 -6
  8. package/bin/lib/init.cjs +20 -27
  9. package/bin/lib/session.cjs +4 -4
  10. package/bin/lib/state.cjs +2 -2
  11. package/bin/lib/version-check.cjs +183 -0
  12. package/commands/legion/devops/architect.md +1 -1
  13. package/commands/legion/devops/{build.md → init.md} +7 -7
  14. package/commands/legion/devops/quick.md +2 -2
  15. package/commands/legion/resume.md +2 -2
  16. package/commands/legion/status.md +2 -2
  17. package/hooks/legion-context-monitor.js +21 -0
  18. package/hooks/legion-statusline.js +32 -7
  19. package/package.json +1 -1
  20. package/references/agent-routing.md +3 -3
  21. package/references/devops/agent-map.md +3 -3
  22. package/references/devops/pipeline-patterns.md +3 -3
  23. package/references/domain-registry.md +2 -2
  24. package/references/ui-brand.md +4 -4
  25. package/templates/task-record.md +1 -1
  26. package/workflows/core/completion.md +4 -4
  27. package/workflows/core/context-load.md +21 -25
  28. package/workflows/core/init.md +3 -5
  29. package/workflows/devops/architect.md +3 -3
  30. package/workflows/devops/cycle.md +7 -7
  31. package/workflows/devops/execute.md +4 -4
  32. package/workflows/devops/{build.md → init.md} +14 -14
  33. package/workflows/devops/plan.md +5 -5
  34. package/workflows/devops/quick.md +9 -9
  35. package/workflows/devops/review.md +2 -2
  36. package/workflows/resume.md +3 -3
  37. package/workflows/status.md +4 -4
package/README.md CHANGED
@@ -12,10 +12,10 @@ The first domain ships with Legion: **devops** — infrastructure design, planni
12
12
  Each domain defines a sequential pipeline of specialized agents. The devops domain runs: `architect → plan → execute → review`. Every stage produces a numbered artifact that feeds the next.
13
13
 
14
14
  **Fast context-aware execution**
15
- `/legion:devops:quick` reads `.codebase/` and `.planning/codebase/` before dispatching to classify and route the task to the correct agent automatically.
15
+ `/legion:devops:quick` reads `.legion/codebase/` before dispatching to classify and route the task to the correct agent automatically.
16
16
 
17
17
  **State tracking across sessions**
18
- All pipeline state is written to `.planning/legion/STATE.md` — current domain, stage, task history, and artifact chain. State persists between Claude Code sessions.
18
+ All pipeline state is written to `.legion/planning/STATE.md` — current domain, stage, task history, and artifact chain. State persists between Claude Code sessions.
19
19
 
20
20
  **Context window monitoring**
21
21
  A `PostToolUse` hook reads context metrics after each tool call. When remaining context drops below 35%, the agent receives an in-context warning to wrap up. At 25%, it receives a critical stop directive. Warnings are debounced across tool calls and coexist safely with GSD.
@@ -24,7 +24,7 @@ A `PostToolUse` hook reads context metrics after each tool call. When remaining
24
24
  `/legion:devops:cycle` pauses after the architect and plan stages to show the output and ask for approval before continuing. The user can approve, request a revision, or stop and save state for later.
25
25
 
26
26
  **Sequential artifact management**
27
- Each pipeline stage writes a numbered artifact to `.planning/legion/devops/`:
27
+ Each pipeline stage writes a numbered artifact to `.legion/planning/devops/`:
28
28
  ```
29
29
  001-architect-eks-migration.md
30
30
  002-plan-eks-migration.md
@@ -40,10 +40,7 @@ Each pipeline stage writes a numbered artifact to `.planning/legion/devops/`:
40
40
  ## Quick Start
41
41
 
42
42
  ```bash
43
- git clone git@github.com:vladyslavblackmoore/legion.git
44
- cd legion
45
- chmod +x install.sh
46
- ./install.sh
43
+ npx legion-cc@latest
47
44
  ```
48
45
 
49
46
  The installer copies commands, workflows, templates, references, and hooks into `~/.claude/`, registers the `PostToolUse` context monitor in Claude Code settings, and writes a file manifest for clean uninstalls.
@@ -51,14 +48,9 @@ The installer copies commands, workflows, templates, references, and hooks into
51
48
  **Options**
52
49
 
53
50
  ```bash
54
- ./install.sh --dry-run # preview what would be installed
55
- ./install.sh --force # overwrite existing agent files
56
- ```
57
-
58
- **Uninstall**
59
-
60
- ```bash
61
- ./uninstall.sh
51
+ npx legion-cc@latest --force # overwrite existing agent files
52
+ npx legion-cc@latest --dry-run # preview what would be installed
53
+ npx legion-cc@latest --uninstall # remove Legion
62
54
  ```
63
55
 
64
56
  ---
@@ -69,11 +61,11 @@ The installer copies commands, workflows, templates, references, and hooks into
69
61
 
70
62
  | Command | Description |
71
63
  |---------|-------------|
72
- | `/legion:devops:quick <task>` | Classify the task and dispatch to the right agent. Reads `.codebase/` first. |
64
+ | `/legion:devops:quick <task>` | Classify the task and dispatch to the right agent. Reads `.legion/codebase/` first. |
73
65
  | `/legion:devops:architect <task>` | Design infrastructure architecture with the `devops-architect` agent (opus). |
74
66
  | `/legion:devops:plan [path]` | Decompose architecture into an implementation plan with `delivery-planner` (sonnet). Uses latest architect artifact if no path given. |
75
67
  | `/legion:devops:execute [path]` | Implement the plan with `infra-executor` (opus). Supports `--task T1` to run a single task. |
76
- | `/legion:devops:build [path]` | Scaffold project structure and create `.codebase/` documentation with `codebase-builder` (opus). |
68
+ | `/legion:devops:init [path]` | Scaffold project structure and create `.legion/codebase/` documentation with `codebase-builder` (opus). |
77
69
  | `/legion:devops:review [path]` | Review changes against architectural intent with `devops-architect` in review mode (sonnet). |
78
70
  | `/legion:devops:cycle <task>` | Run the full pipeline (architect → plan → execute → review) with checkpoints after architect and plan. |
79
71
 
@@ -113,8 +105,6 @@ legion/
113
105
  ├── workflows/ # Step-by-step execution logic referenced by commands
114
106
  │ ├── core/ # init, context-load, completion (shared across domains)
115
107
  │ └── devops/ # One workflow per command
116
- ├── install.sh
117
- ├── uninstall.sh
118
108
  ├── package.json
119
109
  └── VERSION
120
110
  ```
@@ -128,7 +118,7 @@ legion/
128
118
  | `devops-architect` | Senior/Lead DevOps Architect — infrastructure design, security assessment, migration planning, HA/DR | opus (architect) / sonnet (review) | `architect`, `review`, `cycle` |
129
119
  | `delivery-planner` | Senior Delivery Planner — work decomposition, phased planning, dependency mapping, risk assessment | sonnet | `plan`, `cycle` |
130
120
  | `infra-executor` | Elite Infrastructure Execution Specialist — Terraform, CI/CD, Kubernetes, AWS resource management | opus | `execute`, `cycle` |
131
- | `codebase-builder` | Elite Codebase Builder — project scaffolding, `.codebase/` documentation creation, existing project analysis | opus | `build` |
121
+ | `codebase-builder` | Elite Codebase Builder — project scaffolding, `.legion/codebase/` documentation creation, existing project analysis | opus | `init` |
132
122
 
133
123
  The `devops-architect` agent doubles as reviewer: the same agent definition is invoked with a review prompt at sonnet model for the review stage.
134
124
 
@@ -166,7 +156,7 @@ The `devops-architect` agent doubles as reviewer: the same agent definition is i
166
156
  done
167
157
  ```
168
158
 
169
- Checkpoints default to ON after architect and plan, OFF after execute and review. This is configurable per project in `.planning/legion/config.json`.
159
+ Checkpoints default to ON after architect and plan, OFF after execute and review. This is configurable per project in `.legion/planning/config.json`.
170
160
 
171
161
  ---
172
162
 
@@ -187,9 +177,9 @@ Checkpoints default to ON after architect and plan, OFF after execute and review
187
177
  /legion:devops:quick "Add ingress rule for port 8443 to eks-node SG"
188
178
  ```
189
179
 
190
- **Documentation first** — existing project without `.codebase/` docs:
180
+ **Documentation first** — existing project without `.legion/codebase/` docs:
191
181
  ```
192
- /legion:devops:build
182
+ /legion:devops:init
193
183
  /legion:devops:architect "migration plan"
194
184
  ```
195
185
 
@@ -210,7 +200,7 @@ Legion is designed to host multiple domains. To add a new domain (e.g., `backend
210
200
  4. Create references at `references/{domain}/` — `agent-map.md` and `pipeline-patterns.md`
211
201
  5. Define agents in `agents/` and register them in `references/{domain}/agent-map.md`
212
202
  6. Add the domain entry to `references/domain-registry.md`
213
- 7. Run `./install.sh` to deploy
203
+ 7. Run `npx legion-cc --force` to deploy
214
204
 
215
205
  Each domain must define: pipeline stages (ordered), agent mapping (stage to agent), model assignment, and classification rules for its `/quick` command.
216
206
 
@@ -247,8 +237,9 @@ Per-project state is written alongside your code:
247
237
 
248
238
  ```
249
239
  {project}/
250
- └── .planning/
251
- └── legion/
240
+ └── .legion/
241
+ ├── codebase/ # Project documentation
242
+ └── planning/
252
243
  ├── STATE.md
253
244
  ├── config.json
254
245
  ├── sessions/
package/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 0.2.0
@@ -14,7 +14,7 @@ You are the Legion Orchestrator — a lightweight routing agent that analyzes in
14
14
 
15
15
  You do NOT execute tasks yourself. You:
16
16
  1. Analyze the task description
17
- 2. Load project context (.codebase/, .planning/)
17
+ 2. Load project context (.legion/)
18
18
  3. Classify the task type
19
19
  4. Recommend the appropriate agent and provide context for spawning
20
20
 
@@ -37,10 +37,10 @@ Analyze the task and classify into one of these categories:
37
37
  **Triggers**: Writing Terraform code, modifying infrastructure, fixing configs, updating pipelines, creating AWS resources, changing security groups
38
38
  **Keywords**: implement, create, add, write, fix, update, change, terraform, pipeline, module, resource, deploy, configure
39
39
 
40
- ### build
40
+ ### init
41
41
  **Agent**: codebase-builder (opus)
42
- **Triggers**: New project setup, scaffolding, creating .codebase/ documentation, initializing project structure
43
- **Keywords**: scaffold, init, setup, create project, new project, .codebase/, document structure, bootstrap
42
+ **Triggers**: New project setup, scaffolding, creating .legion/codebase/ documentation, initializing project structure
43
+ **Keywords**: scaffold, init, setup, create project, new project, .legion/codebase/, document structure, bootstrap
44
44
 
45
45
  ### review
46
46
  **Agent**: devops-architect (sonnet, review mode)
package/bin/install.js CHANGED
@@ -784,7 +784,7 @@ function doUninstall() {
784
784
  console.log(` ${c(yellow, 'Failed:')} ${failedCount} files`);
785
785
  }
786
786
  console.log('');
787
- console.log(` ${c(dim, 'Note: Agent files (agents/) and project data (.planning/legion/) were NOT removed.')}`);
787
+ console.log(` ${c(dim, 'Note: Agent files (agents/) and project data (.legion/) were NOT removed.')}`);
788
788
  console.log('');
789
789
  }
790
790
 
@@ -26,8 +26,9 @@ function usage() {
26
26
  ' generate-slug <text> Generate a URL-safe slug from text',
27
27
  ' current-timestamp [format] Print current timestamp (iso|date|datetime)',
28
28
  ' task-record <type> <desc> <status> Add a task record to STATE.md',
29
- ' context-load [path] Load context from .codebase/ and .planning/',
29
+ ' context-load [path] Load context from .legion/codebase/ and .legion/planning/',
30
30
  ' domain list|info [name] Domain registry operations',
31
+ ' update-check Check for newer version on npm',
31
32
  '',
32
33
  'Examples:',
33
34
  ' legion-tools init devops',
@@ -67,7 +68,7 @@ function die(msg, code) {
67
68
  function requirePlanningDir() {
68
69
  const planningDir = core.resolvePlanningDir();
69
70
  if (!planningDir) {
70
- die('No .planning/legion/ directory found. Run "legion-tools init <domain>" first.');
71
+ die('No .legion/planning/ directory found. Run "legion-tools init <domain>" first.');
71
72
  }
72
73
  return planningDir;
73
74
  }
@@ -376,6 +377,34 @@ function cmdDomain(args) {
376
377
  }
377
378
  }
378
379
 
380
+ // ─── Command: update-check ───────────────────────────────────────────────
381
+
382
+ function cmdUpdateCheck() {
383
+ const vc = require('./lib/version-check.cjs');
384
+ const current = vc.getInstalledVersion();
385
+
386
+ console.log(` Installed: v${current}`);
387
+ console.log(' Checking npm registry...');
388
+
389
+ const result = vc.checkForUpdate();
390
+
391
+ if (!result) {
392
+ console.log(` ${core.UI.warn} Could not reach npm registry.`);
393
+ return;
394
+ }
395
+
396
+ if (result.available) {
397
+ console.log(` ${core.UI.diamond} Update available: v${result.current} \u2192 v${result.latest}`);
398
+ console.log('');
399
+ console.log(' Run: npx legion-cc@latest');
400
+ } else {
401
+ console.log(` ${core.UI.check} Already on latest version (v${result.current})`);
402
+ }
403
+ console.log('');
404
+
405
+ out(result);
406
+ }
407
+
379
408
  // ─── Main Router ─────────────────────────────────────────────────────────────
380
409
 
381
410
  function main() {
@@ -413,6 +442,9 @@ function main() {
413
442
  case 'domain':
414
443
  cmdDomain(commandArgs);
415
444
  break;
445
+ case 'update-check':
446
+ cmdUpdateCheck();
447
+ break;
416
448
  default:
417
449
  die(`Unknown command: ${command}. Run "legion-tools --help" for usage.`);
418
450
  }
@@ -88,7 +88,7 @@ const DOMAIN_DEFAULTS = {
88
88
  /**
89
89
  * Load config.json from the given planning directory.
90
90
  *
91
- * @param {string} planningDir - Absolute path to `.planning/legion/`
91
+ * @param {string} planningDir - Absolute path to `.legion/planning/`
92
92
  * @returns {object|null} Parsed config or null if missing/invalid
93
93
  */
94
94
  function loadConfig(planningDir) {
@@ -111,9 +111,9 @@ function defaultConfig(domain) {
111
111
  }
112
112
 
113
113
  /**
114
- * Save a config object to `.planning/legion/config.json`.
114
+ * Save a config object to `.legion/planning/config.json`.
115
115
  *
116
- * @param {string} planningDir - Absolute path to `.planning/legion/`
116
+ * @param {string} planningDir - Absolute path to `.legion/planning/`
117
117
  * @param {object} config - Config object to persist
118
118
  */
119
119
  function saveConfig(planningDir, config) {
package/bin/lib/core.cjs CHANGED
@@ -70,7 +70,7 @@ function currentTimestamp(format) {
70
70
  * Walk up from `startDir` looking for a directory that matches `target`.
71
71
  * Returns the absolute path to the found directory, or null.
72
72
  *
73
- * @param {string} target - Relative directory name to locate (e.g. '.planning/legion')
73
+ * @param {string} target - Relative directory name to locate (e.g. '.legion/planning')
74
74
  * @param {string} [startDir=process.cwd()] - Where to start searching
75
75
  * @returns {string|null}
76
76
  */
@@ -89,21 +89,30 @@ function _walkUp(target, startDir) {
89
89
  }
90
90
 
91
91
  /**
92
- * Find `.planning/legion/` by walking up from cwd.
92
+ * Find `.legion/planning/` by walking up from cwd.
93
93
  * @param {string} [startDir]
94
94
  * @returns {string|null}
95
95
  */
96
96
  function resolvePlanningDir(startDir) {
97
- return _walkUp(path.join('.planning', 'legion'), startDir);
97
+ return _walkUp(path.join('.legion', 'planning'), startDir);
98
98
  }
99
99
 
100
100
  /**
101
- * Find `.codebase/` by walking up from cwd.
101
+ * Find `.legion/codebase/` by walking up from cwd.
102
102
  * @param {string} [startDir]
103
103
  * @returns {string|null}
104
104
  */
105
105
  function resolveCodebaseDir(startDir) {
106
- return _walkUp('.codebase', startDir);
106
+ return _walkUp(path.join('.legion', 'codebase'), startDir);
107
+ }
108
+
109
+ /**
110
+ * Find `.legion/` root by walking up from cwd.
111
+ * @param {string} [startDir]
112
+ * @returns {string|null}
113
+ */
114
+ function resolveLegionDir(startDir) {
115
+ return _walkUp('.legion', startDir);
107
116
  }
108
117
 
109
118
  // ─── Task Numbering ──────────────────────────────────────────────────────────
@@ -112,7 +121,7 @@ function resolveCodebaseDir(startDir) {
112
121
  * Scan existing artifact files in `planningDir/artifacts/` and return the next
113
122
  * zero-padded three-digit number (e.g. "002").
114
123
  *
115
- * @param {string} planningDir - Absolute path to `.planning/legion/`
124
+ * @param {string} planningDir - Absolute path to `.legion/planning/`
116
125
  * @returns {string} Next task number, zero-padded to 3 digits
117
126
  */
118
127
  function nextTaskNum(planningDir) {
@@ -209,6 +218,7 @@ module.exports = {
209
218
  currentTimestamp,
210
219
  resolvePlanningDir,
211
220
  resolveCodebaseDir,
221
+ resolveLegionDir,
212
222
  nextTaskNum,
213
223
  readJsonSafe,
214
224
  writeJsonSafe,
package/bin/lib/init.cjs CHANGED
@@ -5,9 +5,9 @@ const path = require('path');
5
5
  const {
6
6
  resolvePlanningDir,
7
7
  resolveCodebaseDir,
8
+ resolveLegionDir,
8
9
  currentTimestamp,
9
10
  nextTaskNum,
10
- banner,
11
11
  } = require('./core.cjs');
12
12
  const { loadConfig, defaultConfig, saveConfig } = require('./config.cjs');
13
13
  const { loadState, initState } = require('./state.cjs');
@@ -59,9 +59,8 @@ function _scanDir(dir, base, depth, results) {
59
59
  /**
60
60
  * Initialize a workflow and return a full context JSON blob.
61
61
  *
62
- * - Detects `.codebase/` and its contents
63
- * - Detects `.planning/codebase/`
64
- * - Ensures `.planning/legion/` exists
62
+ * - Detects `.legion/codebase/` and its contents
63
+ * - Ensures `.legion/planning/` exists
65
64
  * - Loads or creates STATE.md
66
65
  * - Loads or creates config.json
67
66
  *
@@ -73,7 +72,20 @@ function initWorkflow(type, args) {
73
72
  const domain = (type || 'devops').toLowerCase();
74
73
  const cwd = process.cwd();
75
74
 
76
- // ── Detect .codebase/ ──────────────────────────────────────────────────
75
+ // ── Detect project root (parent of .legion/) ───────────────────────────
76
+ let projectRoot = cwd;
77
+ const legionDir = resolveLegionDir(cwd);
78
+ if (legionDir) {
79
+ projectRoot = path.dirname(legionDir);
80
+ }
81
+
82
+ // ── Ensure .legion/ structure ──────────────────────────────────────────
83
+ const legionRoot = legionDir || path.join(projectRoot, '.legion');
84
+ if (!fs.existsSync(legionRoot)) {
85
+ fs.mkdirSync(legionRoot, { recursive: true });
86
+ }
87
+
88
+ // ── Detect .legion/codebase/ ───────────────────────────────────────────
77
89
  const codebaseDir = resolveCodebaseDir(cwd);
78
90
  const codebaseExists = codebaseDir !== null;
79
91
  let codebaseFiles = [];
@@ -81,27 +93,12 @@ function initWorkflow(type, args) {
81
93
  codebaseFiles = _scanDir(codebaseDir, codebaseDir);
82
94
  }
83
95
 
84
- // ── Detect project root (the parent of .codebase or .planning) ─────────
85
- let projectRoot = cwd;
86
- if (codebaseDir) {
87
- projectRoot = path.dirname(codebaseDir);
88
- }
89
-
90
- // ── Detect .planning/codebase/ ─────────────────────────────────────────
91
- const planningCodebaseDir = path.join(projectRoot, '.planning', 'codebase');
92
- const planningCodebaseExists = fs.existsSync(planningCodebaseDir) &&
93
- fs.statSync(planningCodebaseDir).isDirectory();
94
- let planningCodebaseFiles = [];
95
- if (planningCodebaseExists) {
96
- planningCodebaseFiles = _scanDir(planningCodebaseDir, planningCodebaseDir);
97
- }
98
-
99
- // ── Ensure .planning/legion/ ───────────────────────────────────────────
96
+ // ── Ensure .legion/planning/ ───────────────────────────────────────────
100
97
  let planningDir = resolvePlanningDir(cwd);
101
98
  const planningDirExisted = planningDir !== null;
102
99
 
103
100
  if (!planningDir) {
104
- planningDir = path.join(projectRoot, '.planning', 'legion');
101
+ planningDir = path.join(legionRoot, 'planning');
105
102
  fs.mkdirSync(planningDir, { recursive: true });
106
103
  }
107
104
 
@@ -147,11 +144,7 @@ function initWorkflow(type, args) {
147
144
  files: codebaseFiles,
148
145
  },
149
146
 
150
- planningCodebase: {
151
- exists: planningCodebaseExists,
152
- path: planningCodebaseExists ? planningCodebaseDir : null,
153
- files: planningCodebaseFiles,
154
- },
147
+ legionRoot: legionRoot,
155
148
 
156
149
  planning: {
157
150
  path: planningDir,
@@ -7,12 +7,12 @@ const { currentTimestamp } = require('./core.cjs');
7
7
  // ─── Session Management ─────────────────────────────────────────────────────
8
8
 
9
9
  /**
10
- * Create a new session record in `.planning/legion/sessions/`.
10
+ * Create a new session record in `.legion/planning/sessions/`.
11
11
  *
12
12
  * Session file: `{YYYY-MM-DD}-{NNN}.md`
13
13
  * NNN is a zero-padded counter within the same day.
14
14
  *
15
- * @param {string} planningDir - Absolute path to `.planning/legion/`
15
+ * @param {string} planningDir - Absolute path to `.legion/planning/`
16
16
  * @param {object} data - Session data
17
17
  * @param {string} [data.domain] - Domain name
18
18
  * @param {string} [data.stage] - Pipeline stage
@@ -93,9 +93,9 @@ function createSession(planningDir, data) {
93
93
  }
94
94
 
95
95
  /**
96
- * Get the most recent session file from `.planning/legion/sessions/`.
96
+ * Get the most recent session file from `.legion/planning/sessions/`.
97
97
  *
98
- * @param {string} planningDir - Absolute path to `.planning/legion/`
98
+ * @param {string} planningDir - Absolute path to `.legion/planning/`
99
99
  * @returns {object|null} Session info: { path, name, content } or null
100
100
  */
101
101
  function getLatestSession(planningDir) {
package/bin/lib/state.cjs CHANGED
@@ -9,7 +9,7 @@ const { currentTimestamp } = require('./core.cjs');
9
9
  /**
10
10
  * Parse STATE.md into a structured object.
11
11
  *
12
- * @param {string} planningDir - Absolute path to `.planning/legion/`
12
+ * @param {string} planningDir - Absolute path to `.legion/planning/`
13
13
  * @returns {object|null} Parsed state, or null if STATE.md doesn't exist
14
14
  */
15
15
  function loadState(planningDir) {
@@ -101,7 +101,7 @@ function loadState(planningDir) {
101
101
  /**
102
102
  * Write a structured state object back to STATE.md.
103
103
  *
104
- * @param {string} planningDir - Absolute path to `.planning/legion/`
104
+ * @param {string} planningDir - Absolute path to `.legion/planning/`
105
105
  * @param {object} stateObj - State object (same shape as loadState output)
106
106
  */
107
107
  function saveState(planningDir, stateObj) {
@@ -0,0 +1,183 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const os = require('os');
6
+ const { spawnSync } = require('child_process');
7
+
8
+ const PACKAGE_NAME = 'legion-cc';
9
+ const CACHE_TTL_SECONDS = 3600; // 1 hour
10
+
11
+ // ─── Version Utilities ──────────────────────────────────────────────────────
12
+
13
+ /**
14
+ * Read installed version from ~/.claude/legion/VERSION.
15
+ * Falls back to package.json, then '0.0.0'.
16
+ *
17
+ * @param {string} [legionHome] - Path to ~/.claude/legion/
18
+ * @returns {string}
19
+ */
20
+ function getInstalledVersion(legionHome) {
21
+ const home = legionHome || path.join(os.homedir(), '.claude', 'legion');
22
+
23
+ // Try VERSION file
24
+ const versionFile = path.join(home, 'VERSION');
25
+ try {
26
+ const v = fs.readFileSync(versionFile, 'utf8').trim();
27
+ if (v) return v;
28
+ } catch (_e) { /* fallthrough */ }
29
+
30
+ // Try package.json
31
+ const pkgFile = path.join(home, 'package.json');
32
+ try {
33
+ const pkg = JSON.parse(fs.readFileSync(pkgFile, 'utf8'));
34
+ if (pkg.version) return pkg.version;
35
+ } catch (_e) { /* fallthrough */ }
36
+
37
+ return '0.0.0';
38
+ }
39
+
40
+ /**
41
+ * Compare two semver strings. Returns true if `latest` is newer than `current`.
42
+ *
43
+ * @param {string} current
44
+ * @param {string} latest
45
+ * @returns {boolean}
46
+ */
47
+ function isNewer(current, latest) {
48
+ const curr = current.split('.').map(Number);
49
+ const next = latest.split('.').map(Number);
50
+
51
+ for (let i = 0; i < 3; i++) {
52
+ const c = curr[i] || 0;
53
+ const n = next[i] || 0;
54
+ if (n > c) return true;
55
+ if (n < c) return false;
56
+ }
57
+ return false;
58
+ }
59
+
60
+ // ─── npm Registry Query ─────────────────────────────────────────────────────
61
+
62
+ /**
63
+ * Query npm registry for the latest version of legion-cc.
64
+ * Uses `npm view` with a 5-second timeout.
65
+ *
66
+ * @returns {string|null} Latest version string, or null on failure
67
+ */
68
+ function queryLatestVersion() {
69
+ try {
70
+ const result = spawnSync('npm', ['view', `${PACKAGE_NAME}@latest`, 'version'], {
71
+ timeout: 5000,
72
+ encoding: 'utf8',
73
+ stdio: ['pipe', 'pipe', 'pipe'],
74
+ });
75
+
76
+ if (result.status === 0 && result.stdout) {
77
+ return result.stdout.trim();
78
+ }
79
+ } catch (_e) { /* silent */ }
80
+
81
+ return null;
82
+ }
83
+
84
+ // ─── Cache ──────────────────────────────────────────────────────────────────
85
+
86
+ /**
87
+ * Get the cache file path.
88
+ *
89
+ * @param {string} [legionHome]
90
+ * @returns {string}
91
+ */
92
+ function cachePath(legionHome) {
93
+ const home = legionHome || path.join(os.homedir(), '.claude', 'legion');
94
+ return path.join(home, '.update-cache.json');
95
+ }
96
+
97
+ /**
98
+ * Read cached update check result. Returns null if stale or missing.
99
+ *
100
+ * @param {string} [legionHome]
101
+ * @returns {object|null} { current, latest, available, checked_at }
102
+ */
103
+ function readCache(legionHome) {
104
+ try {
105
+ const raw = fs.readFileSync(cachePath(legionHome), 'utf8');
106
+ const data = JSON.parse(raw);
107
+
108
+ const now = Math.floor(Date.now() / 1000);
109
+ if (data.checked_at && (now - data.checked_at) < CACHE_TTL_SECONDS) {
110
+ return data;
111
+ }
112
+ } catch (_e) { /* miss */ }
113
+
114
+ return null;
115
+ }
116
+
117
+ /**
118
+ * Write update check result to cache.
119
+ *
120
+ * @param {object} data
121
+ * @param {string} [legionHome]
122
+ */
123
+ function writeCache(data, legionHome) {
124
+ try {
125
+ const p = cachePath(legionHome);
126
+ const dir = path.dirname(p);
127
+ if (!fs.existsSync(dir)) {
128
+ fs.mkdirSync(dir, { recursive: true });
129
+ }
130
+ fs.writeFileSync(p, JSON.stringify(data, null, 2) + '\n', 'utf8');
131
+ } catch (_e) { /* silent */ }
132
+ }
133
+
134
+ // ─── Main Check ─────────────────────────────────────────────────────────────
135
+
136
+ /**
137
+ * Check if an update is available. Uses cache to avoid frequent npm queries.
138
+ *
139
+ * @param {string} [legionHome] - Path to ~/.claude/legion/
140
+ * @returns {{ available: boolean, current: string, latest: string }|null}
141
+ */
142
+ function checkForUpdate(legionHome) {
143
+ // Check cache first
144
+ const cached = readCache(legionHome);
145
+ if (cached) {
146
+ return {
147
+ available: cached.available,
148
+ current: cached.current,
149
+ latest: cached.latest,
150
+ };
151
+ }
152
+
153
+ // Query npm
154
+ const current = getInstalledVersion(legionHome);
155
+ const latest = queryLatestVersion();
156
+
157
+ if (!latest) return null;
158
+
159
+ const available = isNewer(current, latest);
160
+
161
+ const result = {
162
+ available,
163
+ current,
164
+ latest,
165
+ checked_at: Math.floor(Date.now() / 1000),
166
+ };
167
+
168
+ writeCache(result, legionHome);
169
+
170
+ return { available, current, latest };
171
+ }
172
+
173
+ // ─── Exports ────────────────────────────────────────────────────────────────
174
+
175
+ module.exports = {
176
+ getInstalledVersion,
177
+ isNewer,
178
+ queryLatestVersion,
179
+ checkForUpdate,
180
+ readCache,
181
+ writeCache,
182
+ CACHE_TTL_SECONDS,
183
+ };
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: legion:devops:architect
3
- description: "Design infrastructure architecture with devops-architect agent — creates architecture documents in .planning/legion/devops/"
3
+ description: "Design infrastructure architecture with devops-architect agent — creates architecture documents in .legion/planning/devops/"
4
4
  argument-hint: "<architecture task description>"
5
5
  allowed-tools:
6
6
  - Read