@jaguilar87/gaia-ops 1.0.1 ā 1.0.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.
package/bin/gaia-init.js
CHANGED
|
@@ -445,6 +445,58 @@ async function generateAgentsMd() {
|
|
|
445
445
|
}
|
|
446
446
|
}
|
|
447
447
|
|
|
448
|
+
/**
|
|
449
|
+
* Validate and setup project paths (gitops, terraform, app-services)
|
|
450
|
+
* Creates directories if they don't exist (with user confirmation in interactive mode)
|
|
451
|
+
*/
|
|
452
|
+
async function validateAndSetupProjectPaths(config, nonInteractive) {
|
|
453
|
+
console.log(chalk.cyan('\nš Setting up project directories...\n'));
|
|
454
|
+
|
|
455
|
+
const paths = {
|
|
456
|
+
gitops: { path: config.gitops, name: 'GitOps' },
|
|
457
|
+
terraform: { path: config.terraform, name: 'Terraform' },
|
|
458
|
+
appServices: { path: config.appServices, name: 'App Services' }
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
for (const [, { path: userPath, name }] of Object.entries(paths)) {
|
|
462
|
+
const absPath = resolve(CWD, userPath);
|
|
463
|
+
|
|
464
|
+
// Check if path exists
|
|
465
|
+
if (existsSync(absPath)) {
|
|
466
|
+
console.log(chalk.gray(` ā ${name}: ${userPath} (exists)`));
|
|
467
|
+
continue;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Path doesn't exist - decide what to do
|
|
471
|
+
let shouldCreate = nonInteractive; // Auto-create in non-interactive mode
|
|
472
|
+
|
|
473
|
+
if (!nonInteractive) {
|
|
474
|
+
// Ask user in interactive mode
|
|
475
|
+
const response = await prompts({
|
|
476
|
+
type: 'confirm',
|
|
477
|
+
name: 'create',
|
|
478
|
+
message: `Directory ${chalk.yellow(userPath)} doesn't exist. Create it?`,
|
|
479
|
+
initial: true
|
|
480
|
+
});
|
|
481
|
+
shouldCreate = response.create;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
if (shouldCreate) {
|
|
485
|
+
await fs.mkdir(absPath, { recursive: true });
|
|
486
|
+
console.log(chalk.green(` ā ${name}: ${userPath} (created)`));
|
|
487
|
+
} else {
|
|
488
|
+
console.log(chalk.yellow(` ā ${name}: ${userPath} (skipped - agents may create it later if needed)`));
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// Warn about absolute paths (portability concern)
|
|
492
|
+
if (isAbsolute(userPath)) {
|
|
493
|
+
console.log(chalk.yellow(` ā Warning: Absolute path "${userPath}" may not work on other machines`));
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
console.log('');
|
|
498
|
+
}
|
|
499
|
+
|
|
448
500
|
/**
|
|
449
501
|
* Generate project-context.json
|
|
450
502
|
*/
|
|
@@ -453,8 +505,17 @@ async function generateProjectContext(config) {
|
|
|
453
505
|
|
|
454
506
|
try {
|
|
455
507
|
const projectContext = {
|
|
456
|
-
|
|
457
|
-
|
|
508
|
+
metadata: {
|
|
509
|
+
version: '1.0',
|
|
510
|
+
last_updated: new Date().toISOString(),
|
|
511
|
+
project_root: '.', // Reference point: where CLAUDE.md is located
|
|
512
|
+
created_by: 'gaia-init'
|
|
513
|
+
},
|
|
514
|
+
paths: {
|
|
515
|
+
gitops: config.gitops,
|
|
516
|
+
terraform: config.terraform,
|
|
517
|
+
app_services: config.appServices
|
|
518
|
+
},
|
|
458
519
|
project_details: {
|
|
459
520
|
id: config.projectId,
|
|
460
521
|
region: config.region,
|
|
@@ -590,6 +651,9 @@ async function main() {
|
|
|
590
651
|
// Step 5: Install @jaguilar87/gaia-ops package
|
|
591
652
|
await installClaudeAgentsPackage();
|
|
592
653
|
|
|
654
|
+
// Step 5.5: Validate and setup project paths (gitops, terraform, app-services)
|
|
655
|
+
await validateAndSetupProjectPaths(config, args.nonInteractive);
|
|
656
|
+
|
|
593
657
|
// Step 6: Create .claude/ directory with symlinks
|
|
594
658
|
await createClaudeDirectory();
|
|
595
659
|
|
package/package.json
CHANGED
|
Binary file
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import argparse
|
|
3
3
|
import sys
|
|
4
|
+
import os
|
|
4
5
|
from pathlib import Path
|
|
5
|
-
from typing import Dict, List, Any
|
|
6
|
+
from typing import Dict, List, Any, Optional
|
|
6
7
|
|
|
7
8
|
# This script is expected to be run from the root of the repository.
|
|
8
9
|
# The project context file is located at `.claude/project-context.json`.
|
|
@@ -57,6 +58,108 @@ def load_project_context(context_path: Path) -> Dict[str, Any]:
|
|
|
57
58
|
with open(context_path, 'r', encoding='utf-8') as f:
|
|
58
59
|
return json.load(f)
|
|
59
60
|
|
|
61
|
+
|
|
62
|
+
def get_project_root() -> Path:
|
|
63
|
+
"""
|
|
64
|
+
Finds the project root by looking for CLAUDE.md.
|
|
65
|
+
This is the reference point for resolving relative paths.
|
|
66
|
+
Similar to JAVA_HOME or GAIA_HOME concept.
|
|
67
|
+
"""
|
|
68
|
+
current = Path.cwd()
|
|
69
|
+
|
|
70
|
+
# Walk up the directory tree looking for CLAUDE.md
|
|
71
|
+
while current != current.parent:
|
|
72
|
+
claude_md = current / "CLAUDE.md"
|
|
73
|
+
if claude_md.is_file():
|
|
74
|
+
return current
|
|
75
|
+
current = current.parent
|
|
76
|
+
|
|
77
|
+
# If not found, assume current directory is project root
|
|
78
|
+
print("Warning: CLAUDE.md not found, using current directory as project root", file=sys.stderr)
|
|
79
|
+
return Path.cwd()
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def resolve_path(relative_path: str, project_root: Optional[Path] = None) -> Path:
|
|
83
|
+
"""
|
|
84
|
+
Resolves a path to absolute, handling both relative and absolute paths.
|
|
85
|
+
|
|
86
|
+
Examples:
|
|
87
|
+
./gitops ā /home/jaguilar/project/gitops
|
|
88
|
+
../shared-infra ā /home/jaguilar/shared-infra
|
|
89
|
+
/abs/path ā /abs/path (unchanged)
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
relative_path: Path from project-context.json
|
|
93
|
+
project_root: Project root (where CLAUDE.md is). If None, auto-detected.
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
Absolute Path object
|
|
97
|
+
"""
|
|
98
|
+
if project_root is None:
|
|
99
|
+
project_root = get_project_root()
|
|
100
|
+
|
|
101
|
+
path = Path(relative_path)
|
|
102
|
+
|
|
103
|
+
# If already absolute, return as-is
|
|
104
|
+
if path.is_absolute():
|
|
105
|
+
return path
|
|
106
|
+
|
|
107
|
+
# Resolve relative to project root
|
|
108
|
+
return (project_root / path).resolve()
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def validate_project_paths(project_context: Dict[str, Any], auto_create: bool = True) -> List[str]:
|
|
112
|
+
"""
|
|
113
|
+
Validates that all critical paths in project-context.json exist.
|
|
114
|
+
Optionally creates missing directories with a warning.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
project_context: The loaded project context
|
|
118
|
+
auto_create: If True, creates missing directories (default: True for agents)
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
List of warning messages (empty if all OK)
|
|
122
|
+
"""
|
|
123
|
+
warnings = []
|
|
124
|
+
project_root = get_project_root()
|
|
125
|
+
|
|
126
|
+
# Get paths section
|
|
127
|
+
paths = project_context.get("paths", {})
|
|
128
|
+
if not paths:
|
|
129
|
+
# Fallback to old format (backward compatibility)
|
|
130
|
+
paths = {
|
|
131
|
+
"gitops": project_context.get("gitops_configuration", {}).get("repository", {}).get("path"),
|
|
132
|
+
"terraform": project_context.get("terraform_infrastructure", {}).get("layout", {}).get("base_path"),
|
|
133
|
+
"app_services": project_context.get("application_services", {}).get("base_path")
|
|
134
|
+
}
|
|
135
|
+
# Filter out None values
|
|
136
|
+
paths = {k: v for k, v in paths.items() if v}
|
|
137
|
+
|
|
138
|
+
for path_name, path_value in paths.items():
|
|
139
|
+
if not path_value:
|
|
140
|
+
continue
|
|
141
|
+
|
|
142
|
+
abs_path = resolve_path(path_value, project_root)
|
|
143
|
+
|
|
144
|
+
if not abs_path.exists():
|
|
145
|
+
if auto_create:
|
|
146
|
+
try:
|
|
147
|
+
abs_path.mkdir(parents=True, exist_ok=True)
|
|
148
|
+
msg = f"Created missing directory: {path_name} at {abs_path}"
|
|
149
|
+
print(f"ā ļø {msg}", file=sys.stderr)
|
|
150
|
+
warnings.append(msg)
|
|
151
|
+
except Exception as e:
|
|
152
|
+
msg = f"Failed to create {path_name} at {abs_path}: {e}"
|
|
153
|
+
print(f"ā {msg}", file=sys.stderr)
|
|
154
|
+
warnings.append(msg)
|
|
155
|
+
else:
|
|
156
|
+
msg = f"Path does not exist: {path_name} = {path_value} (resolved to {abs_path})"
|
|
157
|
+
print(f"ā ļø {msg}", file=sys.stderr)
|
|
158
|
+
warnings.append(msg)
|
|
159
|
+
|
|
160
|
+
return warnings
|
|
161
|
+
|
|
162
|
+
|
|
60
163
|
def get_contract_context(project_context: Dict[str, Any], agent_name: str) -> Dict[str, Any]:
|
|
61
164
|
"""Extracts the contract-defined context for a given agent."""
|
|
62
165
|
contract_keys = AGENT_CONTRACTS.get(agent_name)
|
|
@@ -161,7 +264,10 @@ def main():
|
|
|
161
264
|
args = parser.parse_args()
|
|
162
265
|
|
|
163
266
|
project_context = load_project_context(args.context_file)
|
|
164
|
-
|
|
267
|
+
|
|
268
|
+
# Validate project paths and auto-create missing directories
|
|
269
|
+
validate_project_paths(project_context, auto_create=True)
|
|
270
|
+
|
|
165
271
|
contract_context = get_contract_context(project_context, args.agent_name)
|
|
166
272
|
|
|
167
273
|
enrichment_context = get_semantic_enrichment(
|