vibe-forge 0.8.1 → 0.8.2

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 (51) hide show
  1. package/.claude/commands/configure-vcs.md +102 -102
  2. package/.claude/commands/forge.md +218 -218
  3. package/.claude/hooks/worker-loop.js +220 -217
  4. package/.claude/settings.json +89 -89
  5. package/README.md +149 -191
  6. package/agents/aegis/personality.md +303 -303
  7. package/agents/anvil/personality.md +278 -278
  8. package/agents/architect/personality.md +260 -260
  9. package/agents/crucible/personality.md +362 -362
  10. package/agents/crucible-x/personality.md +210 -210
  11. package/agents/ember/personality.md +293 -293
  12. package/agents/flux/personality.md +248 -248
  13. package/agents/furnace/personality.md +342 -342
  14. package/agents/herald/personality.md +249 -249
  15. package/agents/oracle/personality.md +284 -284
  16. package/agents/pixel/personality.md +140 -140
  17. package/agents/planning-hub/personality.md +473 -473
  18. package/agents/scribe/personality.md +253 -253
  19. package/agents/slag/personality.md +268 -268
  20. package/agents/temper/personality.md +270 -270
  21. package/bin/cli.js +372 -372
  22. package/bin/forge-daemon.sh +477 -477
  23. package/bin/forge-setup.sh +662 -661
  24. package/bin/forge-spawn.sh +164 -164
  25. package/bin/forge.sh +566 -566
  26. package/docs/commands.md +8 -8
  27. package/package.json +77 -77
  28. package/{bin → src}/lib/agents.sh +177 -177
  29. package/{bin → src}/lib/check-aliases.js +50 -50
  30. package/{bin → src}/lib/colors.sh +45 -44
  31. package/{bin → src}/lib/config.sh +347 -347
  32. package/{bin → src}/lib/constants.sh +241 -241
  33. package/{bin → src}/lib/daemon/budgets.sh +107 -107
  34. package/{bin → src}/lib/daemon/dependencies.sh +146 -146
  35. package/{bin → src}/lib/daemon/display.sh +128 -128
  36. package/{bin → src}/lib/daemon/notifications.sh +273 -273
  37. package/{bin → src}/lib/daemon/routing.sh +93 -93
  38. package/{bin → src}/lib/daemon/state.sh +163 -163
  39. package/{bin → src}/lib/daemon/sync.sh +103 -103
  40. package/{bin → src}/lib/database.sh +357 -357
  41. package/{bin → src}/lib/frontmatter.js +106 -106
  42. package/{bin → src}/lib/heimdall-setup.js +113 -113
  43. package/{bin → src}/lib/heimdall.js +265 -265
  44. package/src/lib/index.sh +25 -0
  45. package/{bin → src}/lib/json.sh +264 -264
  46. package/{bin → src}/lib/terminal.js +452 -452
  47. package/{bin → src}/lib/util.sh +126 -126
  48. package/{bin → src}/lib/vcs.js +349 -349
  49. package/{context → templates}/project-context-template.md +122 -122
  50. package/config/task-template.md +0 -159
  51. package/config/templates/handoff-template.md +0 -40
package/docs/commands.md CHANGED
@@ -11,7 +11,7 @@ These commands are run from your terminal.
11
11
  Initialize Vibe Forge in your project.
12
12
 
13
13
  ```bash
14
- npx @sugar-crash-studios/vibe-forge init
14
+ npx vibe-forge init
15
15
  ```
16
16
 
17
17
  **What it does:**
@@ -32,7 +32,7 @@ npx @sugar-crash-studios/vibe-forge init
32
32
  Update Vibe Forge to the latest version.
33
33
 
34
34
  ```bash
35
- npx @sugar-crash-studios/vibe-forge update
35
+ npx vibe-forge update
36
36
  ```
37
37
 
38
38
  **What it does:**
@@ -45,9 +45,9 @@ npx @sugar-crash-studios/vibe-forge update
45
45
  Show the installed version.
46
46
 
47
47
  ```bash
48
- npx @sugar-crash-studios/vibe-forge version
49
- npx @sugar-crash-studios/vibe-forge --version
50
- npx @sugar-crash-studios/vibe-forge -v
48
+ npx vibe-forge version
49
+ npx vibe-forge --version
50
+ npx vibe-forge -v
51
51
  ```
52
52
 
53
53
  ### vibe-forge help
@@ -55,9 +55,9 @@ npx @sugar-crash-studios/vibe-forge -v
55
55
  Show help information.
56
56
 
57
57
  ```bash
58
- npx @sugar-crash-studios/vibe-forge help
59
- npx @sugar-crash-studios/vibe-forge --help
60
- npx @sugar-crash-studios/vibe-forge -h
58
+ npx vibe-forge help
59
+ npx vibe-forge --help
60
+ npx vibe-forge -h
61
61
  ```
62
62
 
63
63
  ---
package/package.json CHANGED
@@ -1,77 +1,77 @@
1
- {
2
- "name": "vibe-forge",
3
- "version": "0.8.1",
4
- "description": "Multi-agent development orchestration system for terminal-native vibe coding",
5
- "keywords": [
6
- "vibe-coding",
7
- "claude",
8
- "ai",
9
- "agents",
10
- "multi-agent",
11
- "development",
12
- "orchestration",
13
- "terminal",
14
- "cli"
15
- ],
16
- "author": "sugar-crash-studios",
17
- "license": "MIT",
18
- "repository": {
19
- "type": "git",
20
- "url": "git+https://github.com/sugar-crash-studios/vibe-forge.git"
21
- },
22
- "homepage": "https://github.com/sugar-crash-studios/vibe-forge#readme",
23
- "bugs": {
24
- "url": "https://github.com/sugar-crash-studios/vibe-forge/issues"
25
- },
26
- "publishConfig": {
27
- "access": "public"
28
- },
29
- "bin": {
30
- "vibe-forge": "bin/cli.js"
31
- },
32
- "files": [
33
- "bin/cli.js",
34
- "bin/lib/",
35
- "bin/forge.cmd",
36
- "bin/forge.sh",
37
- "bin/forge-daemon.sh",
38
- "bin/forge-setup.sh",
39
- "bin/forge-spawn.sh",
40
- "bin/dashboard/server.js",
41
- "bin/dashboard/api/",
42
- "bin/dashboard/public/",
43
- "agents/",
44
- "config/",
45
- "context/project-context-template.md",
46
- "context/architecture.md",
47
- "context/modern-conventions.md",
48
- "context/agent-overrides/README.md",
49
- ".claude/commands/",
50
- ".claude/hooks/",
51
- ".claude/scripts/",
52
- ".claude/settings.json",
53
- "docs/security.md",
54
- "docs/architecture.md",
55
- "docs/agents.md",
56
- "docs/commands.md"
57
- ],
58
- "scripts": {
59
- "prepare": "husky",
60
- "test": "node --no-warnings node_modules/jest/bin/jest.js tests/unit/",
61
- "test:unit": "node --no-warnings node_modules/jest/bin/jest.js tests/unit/",
62
- "test:integration": "node --no-warnings node_modules/jest/bin/jest.js tests/integration/",
63
- "test:all": "node --no-warnings node_modules/jest/bin/jest.js tests/"
64
- },
65
- "devDependencies": {
66
- "husky": "^9.1.7",
67
- "jest": "^30.0.0",
68
- "ws": "^8.18.0"
69
- },
70
- "engines": {
71
- "node": ">=16.0.0"
72
- },
73
- "dependencies": {
74
- "js-yaml": "^4.1.1",
75
- "msedge-tts": "^2.0.4"
76
- }
77
- }
1
+ {
2
+ "name": "vibe-forge",
3
+ "version": "0.8.2",
4
+ "description": "Multi-agent development orchestration system for terminal-native vibe coding",
5
+ "keywords": [
6
+ "vibe-coding",
7
+ "claude",
8
+ "ai",
9
+ "agents",
10
+ "multi-agent",
11
+ "development",
12
+ "orchestration",
13
+ "terminal",
14
+ "cli"
15
+ ],
16
+ "author": "sugar-crash-studios",
17
+ "license": "MIT",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/sugar-crash-studios/vibe-forge.git"
21
+ },
22
+ "homepage": "https://github.com/sugar-crash-studios/vibe-forge#readme",
23
+ "bugs": {
24
+ "url": "https://github.com/sugar-crash-studios/vibe-forge/issues"
25
+ },
26
+ "publishConfig": {
27
+ "access": "public"
28
+ },
29
+ "bin": {
30
+ "vibe-forge": "bin/cli.js"
31
+ },
32
+ "files": [
33
+ "bin/cli.js",
34
+ "src/lib/",
35
+ "bin/forge.cmd",
36
+ "bin/forge.sh",
37
+ "bin/forge-daemon.sh",
38
+ "bin/forge-setup.sh",
39
+ "bin/forge-spawn.sh",
40
+ "bin/dashboard/server.js",
41
+ "bin/dashboard/api/",
42
+ "bin/dashboard/public/",
43
+ "agents/",
44
+ "config/",
45
+ "templates/project-context-template.md",
46
+ "context/architecture.md",
47
+ "context/modern-conventions.md",
48
+ "context/agent-overrides/README.md",
49
+ ".claude/commands/",
50
+ ".claude/hooks/",
51
+ ".claude/scripts/",
52
+ ".claude/settings.json",
53
+ "docs/security.md",
54
+ "docs/architecture.md",
55
+ "docs/agents.md",
56
+ "docs/commands.md"
57
+ ],
58
+ "scripts": {
59
+ "prepare": "husky",
60
+ "test": "node --no-warnings node_modules/jest/bin/jest.js tests/unit/",
61
+ "test:unit": "node --no-warnings node_modules/jest/bin/jest.js tests/unit/",
62
+ "test:integration": "node --no-warnings node_modules/jest/bin/jest.js tests/integration/",
63
+ "test:all": "node --no-warnings node_modules/jest/bin/jest.js tests/"
64
+ },
65
+ "devDependencies": {
66
+ "husky": "^9.1.7",
67
+ "jest": "^30.0.0",
68
+ "ws": "^8.18.0"
69
+ },
70
+ "engines": {
71
+ "node": ">=16.0.0"
72
+ },
73
+ "dependencies": {
74
+ "js-yaml": "^4.1.1",
75
+ "msedge-tts": "^2.0.4"
76
+ }
77
+ }
@@ -1,177 +1,177 @@
1
- #!/usr/bin/env bash
2
- #
3
- # Vibe Forge - Agent Resolution and Validation
4
- # Source this file in other scripts: source "$SCRIPT_DIR/lib/agents.sh"
5
- #
6
- # SECURITY: This module provides safe agent name resolution with whitelist validation.
7
- # Never pass user input directly to shell commands - always resolve through these functions.
8
- #
9
-
10
- # Ensure constants are loaded
11
- if [[ -z "${VALID_AGENTS+x}" ]]; then
12
- echo "Error: constants.sh must be sourced before agents.sh" >&2
13
- exit 1
14
- fi
15
-
16
- # resolve_agent ALIAS
17
- # Converts an agent alias to its canonical name.
18
- # Returns: canonical name on stdout, empty string if not found
19
- # Exit code: 0 if found, 1 if not found
20
- #
21
- # SECURITY: This function validates against a whitelist.
22
- # The returned value is safe to use in file paths and commands.
23
- resolve_agent() {
24
- local input="$1"
25
-
26
- # Normalize to lowercase for matching
27
- local normalized
28
- normalized=$(echo "$input" | tr '[:upper:]' '[:lower:]')
29
-
30
- # Look up in alias map
31
- local canonical="${AGENT_ALIASES[$normalized]:-}"
32
-
33
- if [[ -n "$canonical" ]]; then
34
- echo "$canonical"
35
- return 0
36
- fi
37
-
38
- return 1
39
- }
40
-
41
- # is_valid_agent AGENT
42
- # Checks if an agent name is valid (either canonical or alias)
43
- # Returns: 0 if valid, 1 if invalid
44
- is_valid_agent() {
45
- local agent="$1"
46
- resolve_agent "$agent" >/dev/null 2>&1
47
- }
48
-
49
- # get_agent_personality_path FORGE_ROOT AGENT
50
- # Returns the path to an agent's personality file.
51
- # Uses AGENT_PERSONALITY_FILES from constants.sh (or loaded from agents.json)
52
- # SECURITY: Validates agent name before constructing path.
53
- # Returns: full path on stdout
54
- # Exit code: 0 on success, 1 if agent invalid or file missing
55
- get_agent_personality_path() {
56
- local forge_root="$1"
57
- local agent="$2"
58
-
59
- # Resolve and validate agent
60
- local canonical
61
- canonical=$(resolve_agent "$agent") || {
62
- return 1
63
- }
64
-
65
- # Get personality file path from configuration
66
- local relative_path="${AGENT_PERSONALITY_FILES[$canonical]:-}"
67
- if [[ -z "$relative_path" ]]; then
68
- # Fallback: construct path if not in config
69
- relative_path="agents/$canonical/personality.md"
70
- fi
71
-
72
- local personality_path="$forge_root/$relative_path"
73
-
74
- # Validate file exists
75
- if [[ ! -f "$personality_path" ]]; then
76
- return 1
77
- fi
78
-
79
- # SECURITY: Verify the resolved path is within agents directory
80
- local real_path
81
- real_path=$(cd "$(dirname "$personality_path")" 2>/dev/null && pwd)/$(basename "$personality_path")
82
- local agents_dir
83
- agents_dir=$(cd "$forge_root/agents" 2>/dev/null && pwd)
84
-
85
- if [[ "$real_path" != "$agents_dir"/* ]]; then
86
- echo "Security error: Path traversal detected" >&2
87
- return 1
88
- fi
89
-
90
- echo "$personality_path"
91
- return 0
92
- }
93
-
94
- # show_available_agents
95
- # Prints a formatted list of available agents with aliases
96
- # Uses data from AGENT_DISPLAY_NAMES, AGENT_ROLES, and AGENT_ALIASES
97
- show_available_agents() {
98
- echo "Available agents:"
99
-
100
- # Iterate over valid agents (excluding hub for user display)
101
- for agent in "${VALID_AGENTS[@]}"; do
102
- if [[ "$agent" == "hub" ]]; then
103
- continue
104
- fi
105
-
106
- # Get display info
107
- local role="${AGENT_ROLES[$agent]:-}"
108
-
109
- # Collect aliases for this agent
110
- local aliases=()
111
- for alias in "${!AGENT_ALIASES[@]}"; do
112
- if [[ "${AGENT_ALIASES[$alias]}" == "$agent" && "$alias" != "$agent" ]]; then
113
- aliases+=("$alias")
114
- fi
115
- done
116
-
117
- # Format aliases (take first 3)
118
- local alias_str=""
119
- if [[ ${#aliases[@]} -gt 0 ]]; then
120
- # Sort and take first 3
121
- IFS=$'\n' sorted=($(printf '%s\n' "${aliases[@]}" | sort)); unset IFS
122
- local display_aliases=("${sorted[@]:0:3}")
123
- alias_str="($(IFS=", "; echo "${display_aliases[*]}"))"
124
- fi
125
-
126
- # Print formatted line
127
- printf " %-9s %-25s - %s\n" "$agent" "$alias_str" "$role"
128
- done
129
- }
130
-
131
- # get_agent_display_name AGENT
132
- # Returns the display name for an agent (e.g., "Anvil" for "anvil")
133
- # Uses AGENT_DISPLAY_NAMES from constants.sh (or loaded from agents.json)
134
- get_agent_display_name() {
135
- local agent="$1"
136
- local canonical
137
- canonical=$(resolve_agent "$agent") || return 1
138
-
139
- # Look up in AGENT_DISPLAY_NAMES array
140
- local display_name="${AGENT_DISPLAY_NAMES[$canonical]:-}"
141
- if [[ -n "$display_name" ]]; then
142
- echo "$display_name"
143
- else
144
- # Fallback to canonical name with first letter capitalized
145
- echo "${canonical^}"
146
- fi
147
- }
148
-
149
- # get_agent_role AGENT
150
- # Returns the role description for an agent
151
- get_agent_role() {
152
- local agent="$1"
153
- local canonical
154
- canonical=$(resolve_agent "$agent") || return 1
155
-
156
- echo "${AGENT_ROLES[$canonical]:-}"
157
- }
158
-
159
- # get_agent_icon AGENT
160
- # Returns the icon/emoji for an agent
161
- get_agent_icon() {
162
- local agent="$1"
163
- local canonical
164
- canonical=$(resolve_agent "$agent") || return 1
165
-
166
- echo "${AGENT_ICONS[$canonical]:-}"
167
- }
168
-
169
- # get_agent_tab_color AGENT
170
- # Returns the Windows Terminal tab color for an agent (hex format)
171
- get_agent_tab_color() {
172
- local agent="$1"
173
- local canonical
174
- canonical=$(resolve_agent "$agent") || return 1
175
-
176
- echo "${AGENT_TAB_COLORS[$canonical]:-}"
177
- }
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Vibe Forge - Agent Resolution and Validation
4
+ # Source this file in other scripts: source "$SCRIPT_DIR/lib/agents.sh"
5
+ #
6
+ # SECURITY: This module provides safe agent name resolution with whitelist validation.
7
+ # Never pass user input directly to shell commands - always resolve through these functions.
8
+ #
9
+
10
+ # Ensure constants are loaded
11
+ if [[ -z "${VALID_AGENTS+x}" ]]; then
12
+ echo "Error: constants.sh must be sourced before agents.sh" >&2
13
+ exit 1
14
+ fi
15
+
16
+ # resolve_agent ALIAS
17
+ # Converts an agent alias to its canonical name.
18
+ # Returns: canonical name on stdout, empty string if not found
19
+ # Exit code: 0 if found, 1 if not found
20
+ #
21
+ # SECURITY: This function validates against a whitelist.
22
+ # The returned value is safe to use in file paths and commands.
23
+ resolve_agent() {
24
+ local input="$1"
25
+
26
+ # Normalize to lowercase for matching
27
+ local normalized
28
+ normalized=$(echo "$input" | tr '[:upper:]' '[:lower:]')
29
+
30
+ # Look up in alias map
31
+ local canonical="${AGENT_ALIASES[$normalized]:-}"
32
+
33
+ if [[ -n "$canonical" ]]; then
34
+ echo "$canonical"
35
+ return 0
36
+ fi
37
+
38
+ return 1
39
+ }
40
+
41
+ # is_valid_agent AGENT
42
+ # Checks if an agent name is valid (either canonical or alias)
43
+ # Returns: 0 if valid, 1 if invalid
44
+ is_valid_agent() {
45
+ local agent="$1"
46
+ resolve_agent "$agent" >/dev/null 2>&1
47
+ }
48
+
49
+ # get_agent_personality_path FORGE_ROOT AGENT
50
+ # Returns the path to an agent's personality file.
51
+ # Uses AGENT_PERSONALITY_FILES from constants.sh (or loaded from agents.json)
52
+ # SECURITY: Validates agent name before constructing path.
53
+ # Returns: full path on stdout
54
+ # Exit code: 0 on success, 1 if agent invalid or file missing
55
+ get_agent_personality_path() {
56
+ local forge_root="$1"
57
+ local agent="$2"
58
+
59
+ # Resolve and validate agent
60
+ local canonical
61
+ canonical=$(resolve_agent "$agent") || {
62
+ return 1
63
+ }
64
+
65
+ # Get personality file path from configuration
66
+ local relative_path="${AGENT_PERSONALITY_FILES[$canonical]:-}"
67
+ if [[ -z "$relative_path" ]]; then
68
+ # Fallback: construct path if not in config
69
+ relative_path="agents/$canonical/personality.md"
70
+ fi
71
+
72
+ local personality_path="$forge_root/$relative_path"
73
+
74
+ # Validate file exists
75
+ if [[ ! -f "$personality_path" ]]; then
76
+ return 1
77
+ fi
78
+
79
+ # SECURITY: Verify the resolved path is within agents directory
80
+ local real_path
81
+ real_path=$(cd "$(dirname "$personality_path")" 2>/dev/null && pwd)/$(basename "$personality_path")
82
+ local agents_dir
83
+ agents_dir=$(cd "$forge_root/agents" 2>/dev/null && pwd)
84
+
85
+ if [[ "$real_path" != "$agents_dir"/* ]]; then
86
+ echo "Security error: Path traversal detected" >&2
87
+ return 1
88
+ fi
89
+
90
+ echo "$personality_path"
91
+ return 0
92
+ }
93
+
94
+ # show_available_agents
95
+ # Prints a formatted list of available agents with aliases
96
+ # Uses data from AGENT_DISPLAY_NAMES, AGENT_ROLES, and AGENT_ALIASES
97
+ show_available_agents() {
98
+ echo "Available agents:"
99
+
100
+ # Iterate over valid agents (excluding hub for user display)
101
+ for agent in "${VALID_AGENTS[@]}"; do
102
+ if [[ "$agent" == "hub" ]]; then
103
+ continue
104
+ fi
105
+
106
+ # Get display info
107
+ local role="${AGENT_ROLES[$agent]:-}"
108
+
109
+ # Collect aliases for this agent
110
+ local aliases=()
111
+ for alias in "${!AGENT_ALIASES[@]}"; do
112
+ if [[ "${AGENT_ALIASES[$alias]}" == "$agent" && "$alias" != "$agent" ]]; then
113
+ aliases+=("$alias")
114
+ fi
115
+ done
116
+
117
+ # Format aliases (take first 3)
118
+ local alias_str=""
119
+ if [[ ${#aliases[@]} -gt 0 ]]; then
120
+ # Sort and take first 3
121
+ IFS=$'\n' sorted=($(printf '%s\n' "${aliases[@]}" | sort)); unset IFS
122
+ local display_aliases=("${sorted[@]:0:3}")
123
+ alias_str="($(IFS=", "; echo "${display_aliases[*]}"))"
124
+ fi
125
+
126
+ # Print formatted line
127
+ printf " %-9s %-25s - %s\n" "$agent" "$alias_str" "$role"
128
+ done
129
+ }
130
+
131
+ # get_agent_display_name AGENT
132
+ # Returns the display name for an agent (e.g., "Anvil" for "anvil")
133
+ # Uses AGENT_DISPLAY_NAMES from constants.sh (or loaded from agents.json)
134
+ get_agent_display_name() {
135
+ local agent="$1"
136
+ local canonical
137
+ canonical=$(resolve_agent "$agent") || return 1
138
+
139
+ # Look up in AGENT_DISPLAY_NAMES array
140
+ local display_name="${AGENT_DISPLAY_NAMES[$canonical]:-}"
141
+ if [[ -n "$display_name" ]]; then
142
+ echo "$display_name"
143
+ else
144
+ # Fallback to canonical name with first letter capitalized
145
+ echo "${canonical^}"
146
+ fi
147
+ }
148
+
149
+ # get_agent_role AGENT
150
+ # Returns the role description for an agent
151
+ get_agent_role() {
152
+ local agent="$1"
153
+ local canonical
154
+ canonical=$(resolve_agent "$agent") || return 1
155
+
156
+ echo "${AGENT_ROLES[$canonical]:-}"
157
+ }
158
+
159
+ # get_agent_icon AGENT
160
+ # Returns the icon/emoji for an agent
161
+ get_agent_icon() {
162
+ local agent="$1"
163
+ local canonical
164
+ canonical=$(resolve_agent "$agent") || return 1
165
+
166
+ echo "${AGENT_ICONS[$canonical]:-}"
167
+ }
168
+
169
+ # get_agent_tab_color AGENT
170
+ # Returns the Windows Terminal tab color for an agent (hex format)
171
+ get_agent_tab_color() {
172
+ local agent="$1"
173
+ local canonical
174
+ canonical=$(resolve_agent "$agent") || return 1
175
+
176
+ echo "${AGENT_TAB_COLORS[$canonical]:-}"
177
+ }
@@ -1,50 +1,50 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Agent alias collision detector.
4
- *
5
- * Used by:
6
- * - .husky/pre-commit (pre-commit hook)
7
- * - .github/workflows/ci.yml (CI lint job)
8
- * - bin/cli.js validateAgentsConfig() (forge init)
9
- *
10
- * Exit code 0: no collisions
11
- * Exit code 1: collisions found (printed to stderr)
12
- */
13
-
14
- const path = require('path');
15
- const fs = require('fs');
16
-
17
- const configPath = process.argv[2] || path.join(__dirname, '..', '..', 'config', 'agents.json');
18
-
19
- if (!fs.existsSync(configPath)) {
20
- // No agents.json is fine (e.g., fresh clone before setup)
21
- process.exit(0);
22
- }
23
-
24
- let config;
25
- try {
26
- config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
27
- } catch (err) {
28
- console.error(`ERROR: Failed to parse ${configPath}: ${err.message}`);
29
- process.exit(1);
30
- }
31
-
32
- const agents = config.agents || {};
33
- const seen = {};
34
- const dupes = [];
35
-
36
- for (const [name, info] of Object.entries(agents)) {
37
- for (const alias of [name, ...(info.aliases || [])]) {
38
- if (seen[alias] && seen[alias] !== name) {
39
- dupes.push(`${alias} (${seen[alias]} vs ${name})`);
40
- }
41
- seen[alias] = name;
42
- }
43
- }
44
-
45
- if (dupes.length) {
46
- console.error(`ERROR: Alias collision in agents.json: ${dupes.join(', ')}`);
47
- process.exit(1);
48
- }
49
-
50
- console.log(`No alias collisions found (${Object.keys(agents).length} agents checked)`);
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Agent alias collision detector.
4
+ *
5
+ * Used by:
6
+ * - .husky/pre-commit (pre-commit hook)
7
+ * - .github/workflows/ci.yml (CI lint job)
8
+ * - bin/cli.js validateAgentsConfig() (forge init)
9
+ *
10
+ * Exit code 0: no collisions
11
+ * Exit code 1: collisions found (printed to stderr)
12
+ */
13
+
14
+ const path = require('path');
15
+ const fs = require('fs');
16
+
17
+ const configPath = process.argv[2] || path.join(__dirname, '..', '..', 'config', 'agents.json');
18
+
19
+ if (!fs.existsSync(configPath)) {
20
+ // No agents.json is fine (e.g., fresh clone before setup)
21
+ process.exit(0);
22
+ }
23
+
24
+ let config;
25
+ try {
26
+ config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
27
+ } catch (err) {
28
+ console.error(`ERROR: Failed to parse ${configPath}: ${err.message}`);
29
+ process.exit(1);
30
+ }
31
+
32
+ const agents = config.agents || {};
33
+ const seen = {};
34
+ const dupes = [];
35
+
36
+ for (const [name, info] of Object.entries(agents)) {
37
+ for (const alias of [name, ...(info.aliases || [])]) {
38
+ if (seen[alias] && seen[alias] !== name) {
39
+ dupes.push(`${alias} (${seen[alias]} vs ${name})`);
40
+ }
41
+ seen[alias] = name;
42
+ }
43
+ }
44
+
45
+ if (dupes.length) {
46
+ console.error(`ERROR: Alias collision in agents.json: ${dupes.join(', ')}`);
47
+ process.exit(1);
48
+ }
49
+
50
+ console.log(`No alias collisions found (${Object.keys(agents).length} agents checked)`);