@cristiancorreau/forge 2.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 (153) hide show
  1. package/CHANGELOG.md +228 -0
  2. package/LICENSE +191 -0
  3. package/README.md +156 -0
  4. package/assets/adapters/claude-code/commands/deploy-check.md +12 -0
  5. package/assets/adapters/claude-code/commands/new-feature.md +11 -0
  6. package/assets/adapters/claude-code/commands/plan.md +116 -0
  7. package/assets/adapters/claude-code/commands/review.md +219 -0
  8. package/assets/adapters/claude-code/commands/session-close.md +109 -0
  9. package/assets/adapters/claude-code/commands/session-start.md +59 -0
  10. package/assets/adapters/claude-code/commands/ship.md +133 -0
  11. package/assets/adapters/claude-code/commands/wiki-ingest.md +7 -0
  12. package/assets/adapters/claude-code/commands/wiki-lint.md +5 -0
  13. package/assets/adapters/claude-code/commands/wiki-query.md +7 -0
  14. package/assets/adapters/claude-code/commands/work.md +101 -0
  15. package/assets/adapters/claude-code/generate-claude-md.py +304 -0
  16. package/assets/adapters/codex/commands/plan.md +63 -0
  17. package/assets/adapters/codex/commands/review.md +53 -0
  18. package/assets/adapters/codex/commands/session-close.md +53 -0
  19. package/assets/adapters/codex/commands/session-start.md +49 -0
  20. package/assets/adapters/codex/commands/ship.md +53 -0
  21. package/assets/adapters/codex/commands/work.md +53 -0
  22. package/assets/adapters/codex/generate-codex-config.py +269 -0
  23. package/assets/adapters/codex/hooks/codex.yaml.tpl +43 -0
  24. package/assets/adapters/codex/hooks/forge-codex-finish.sh +158 -0
  25. package/assets/adapters/codex/hooks/forge-codex-start.sh +186 -0
  26. package/assets/adapters/kiro/generate-steering.py +367 -0
  27. package/assets/adapters/opencode/HOOKS.md +123 -0
  28. package/assets/adapters/opencode/commands/plan.md +119 -0
  29. package/assets/adapters/opencode/commands/review.md +164 -0
  30. package/assets/adapters/opencode/commands/session-close.md +111 -0
  31. package/assets/adapters/opencode/commands/session-start.md +62 -0
  32. package/assets/adapters/opencode/commands/ship.md +135 -0
  33. package/assets/adapters/opencode/commands/work.md +82 -0
  34. package/assets/adapters/opencode/generate-agents-md.py +262 -0
  35. package/assets/core/agents/backend-engineer.md +61 -0
  36. package/assets/core/agents/compliance-reviewer.md +83 -0
  37. package/assets/core/agents/docs-writer.md +77 -0
  38. package/assets/core/agents/frontend-engineer.md +70 -0
  39. package/assets/core/agents/orchestrator.md +104 -0
  40. package/assets/core/agents/security-auditor.md +54 -0
  41. package/assets/core/agents/test-engineer.md +57 -0
  42. package/assets/core/hooks/hooks-registry.yaml +48 -0
  43. package/assets/core/hooks/post-turn-check.sh +139 -0
  44. package/assets/core/hooks/pre-bash-check.py +202 -0
  45. package/assets/core/hooks/pre-edit-check.py +317 -0
  46. package/assets/core/hooks/session-start.sh +184 -0
  47. package/assets/core/schemas/project.schema.json +503 -0
  48. package/assets/core/skills/README.md +88 -0
  49. package/assets/core/skills/aitmpl-search/SKILL.md +74 -0
  50. package/assets/core/skills/browser-test/SKILL.md +177 -0
  51. package/assets/core/skills/db-migrate/SKILL.md +163 -0
  52. package/assets/core/skills/local2prod/SKILL.md +147 -0
  53. package/assets/core/skills/new-feature/SKILL.md +155 -0
  54. package/assets/core/skills/obsidian-sync/SKILL.md +152 -0
  55. package/assets/core/skills/phase-kickoff/SKILL.md +69 -0
  56. package/assets/core/skills/security-audit/SKILL.md +125 -0
  57. package/assets/core/skills/spec/SKILL.md +72 -0
  58. package/assets/core/skills/wiki-ingest/SKILL.md +183 -0
  59. package/assets/core/skills/wiki-lint/SKILL.md +109 -0
  60. package/assets/core/skills/wiki-query/SKILL.md +100 -0
  61. package/assets/core/templates/claude-md/architecture.rules +20 -0
  62. package/assets/core/templates/claude-md/global.md +30 -0
  63. package/assets/core/templates/claude-md/project.md +36 -0
  64. package/assets/core/templates/daily-note.md +38 -0
  65. package/assets/core/templates/spec-template.md +43 -0
  66. package/assets/core/workflows/sdd.md +69 -0
  67. package/assets/core/workflows/sprint.md +59 -0
  68. package/assets/forge.py +1265 -0
  69. package/assets/hooks/pre-commit +43 -0
  70. package/assets/manifest.json +274 -0
  71. package/assets/profiles/astro/README.md +24 -0
  72. package/assets/profiles/astro/agents/frontend-engineer.md +74 -0
  73. package/assets/profiles/django/agents/api-engineer.md +83 -0
  74. package/assets/profiles/expo/README.md +24 -0
  75. package/assets/profiles/expo/agents/mobile-engineer.md +69 -0
  76. package/assets/profiles/express/agents/api-engineer.md +60 -0
  77. package/assets/profiles/fastapi/README.md +32 -0
  78. package/assets/profiles/fastapi/agents/api-engineer.md +87 -0
  79. package/assets/profiles/go-gin/agents/api-engineer.md +98 -0
  80. package/assets/profiles/hono-drizzle/README.md +31 -0
  81. package/assets/profiles/hono-drizzle/agents/api-engineer.md +82 -0
  82. package/assets/profiles/laravel/README.md +32 -0
  83. package/assets/profiles/laravel/agents/api-engineer.md +114 -0
  84. package/assets/profiles/laravel/agents/fullstack-engineer.md +67 -0
  85. package/assets/profiles/laravel/agents/migration-specialist.md +420 -0
  86. package/assets/profiles/nestjs/agents/api-engineer.md +79 -0
  87. package/assets/profiles/nextjs-admin/README.md +32 -0
  88. package/assets/profiles/nextjs-admin/agents/admin-engineer.md +78 -0
  89. package/assets/profiles/playwright-crawler/agents/scanner-engineer.md +51 -0
  90. package/assets/profiles/rails/agents/fullstack-engineer.md +61 -0
  91. package/assets/profiles/sveltekit/agents/frontend-engineer.md +96 -0
  92. package/assets/profiles/vuenuxt/agents/frontend-engineer.md +82 -0
  93. package/assets/profiles/wordpress/README.md +30 -0
  94. package/assets/profiles/wordpress/agents/divi-engineer.md +273 -0
  95. package/assets/profiles/wordpress/agents/elementor-engineer.md +310 -0
  96. package/assets/profiles/wordpress/agents/wp-engineer.md +216 -0
  97. package/assets/requirements.txt +2 -0
  98. package/assets/scripts/aitmpl-search.py +808 -0
  99. package/assets/scripts/forge-add-opportunities.py +92 -0
  100. package/assets/scripts/forge-audit.py +1061 -0
  101. package/assets/scripts/forge-generate-all.py +283 -0
  102. package/assets/scripts/forge-init.py +900 -0
  103. package/assets/scripts/forge-migrate-project-yaml.py +397 -0
  104. package/assets/scripts/forge-scaffold-profile.py +181 -0
  105. package/assets/scripts/forge-teardown.py +193 -0
  106. package/assets/scripts/forge-validate-project-yaml.py +457 -0
  107. package/assets/scripts/forge-wizard.py +1003 -0
  108. package/assets/scripts/setup-codex.sh +229 -0
  109. package/assets/scripts/team-install.sh +147 -0
  110. package/assets/scripts/token-stats.py +201 -0
  111. package/assets/templates/modes/enterprise.yaml.tpl +114 -0
  112. package/assets/templates/modes/multi-runtime.yaml.tpl +89 -0
  113. package/assets/templates/modes/new-stack.yaml.tpl +101 -0
  114. package/assets/templates/modes/startup.yaml.tpl +74 -0
  115. package/assets/templates/project.yaml.tpl +185 -0
  116. package/assets/templates/wiki/concepts/_template.md +22 -0
  117. package/assets/templates/wiki/entities/_template.md +19 -0
  118. package/assets/templates/wiki/index.md +32 -0
  119. package/assets/templates/wiki/log.md +6 -0
  120. package/assets/templates/wiki/sources/_template.md +25 -0
  121. package/dist/cli.d.ts +3 -0
  122. package/dist/cli.d.ts.map +1 -0
  123. package/dist/cli.js +64 -0
  124. package/dist/cli.js.map +1 -0
  125. package/dist/commands/audit.d.ts +2 -0
  126. package/dist/commands/audit.d.ts.map +1 -0
  127. package/dist/commands/audit.js +21 -0
  128. package/dist/commands/audit.js.map +1 -0
  129. package/dist/commands/doctor.d.ts +2 -0
  130. package/dist/commands/doctor.d.ts.map +1 -0
  131. package/dist/commands/doctor.js +58 -0
  132. package/dist/commands/doctor.js.map +1 -0
  133. package/dist/commands/generate.d.ts +2 -0
  134. package/dist/commands/generate.d.ts.map +1 -0
  135. package/dist/commands/generate.js +27 -0
  136. package/dist/commands/generate.js.map +1 -0
  137. package/dist/commands/init.d.ts +2 -0
  138. package/dist/commands/init.d.ts.map +1 -0
  139. package/dist/commands/init.js +22 -0
  140. package/dist/commands/init.js.map +1 -0
  141. package/dist/commands/validate.d.ts +2 -0
  142. package/dist/commands/validate.d.ts.map +1 -0
  143. package/dist/commands/validate.js +20 -0
  144. package/dist/commands/validate.js.map +1 -0
  145. package/dist/lib/paths.d.ts +10 -0
  146. package/dist/lib/paths.d.ts.map +1 -0
  147. package/dist/lib/paths.js +49 -0
  148. package/dist/lib/paths.js.map +1 -0
  149. package/dist/lib/python.d.ts +4 -0
  150. package/dist/lib/python.d.ts.map +1 -0
  151. package/dist/lib/python.js +46 -0
  152. package/dist/lib/python.js.map +1 -0
  153. package/package.json +46 -0
@@ -0,0 +1,229 @@
1
+ #!/usr/bin/env bash
2
+ # setup-codex.sh — Inicializar soporte de Forge v2 para Codex CLI
3
+ #
4
+ # Uso:
5
+ # bash scripts/setup-codex.sh
6
+ #
7
+ # Qué hace:
8
+ # 1. Verifica que git y python3 están disponibles
9
+ # 2. Inicializa el submódulo de forge si es necesario
10
+ # 3. Crea .codex/codex.yaml desde la plantilla (si no existe)
11
+ # 4. Copia los scripts de hooks a .codex/
12
+ # 5. Ejecuta generate-codex-config.py para generar AGENTS.md
13
+ # 6. Imprime resumen de lo que se instaló
14
+
15
+ set -euo pipefail
16
+
17
+ # ---------------------------------------------------------------------------
18
+ # Colores para output (solo si el terminal los soporta)
19
+ # ---------------------------------------------------------------------------
20
+ if [ -t 1 ] && command -v tput &>/dev/null && tput colors &>/dev/null 2>&1; then
21
+ GREEN="$(tput setaf 2)"
22
+ YELLOW="$(tput setaf 3)"
23
+ RED="$(tput setaf 1)"
24
+ RESET="$(tput sgr0)"
25
+ else
26
+ GREEN=""
27
+ YELLOW=""
28
+ RED=""
29
+ RESET=""
30
+ fi
31
+
32
+ ok() { printf " %s[OK]%s %s\n" "$GREEN" "$RESET" "$1"; }
33
+ warn() { printf " %s[WARN]%s %s\n" "$YELLOW" "$RESET" "$1"; }
34
+ fail() { printf " %s[FAIL]%s %s\n" "$RED" "$RESET" "$1"; }
35
+
36
+ echo ""
37
+ echo "forge — Setup Codex CLI adapter"
38
+ echo "================================"
39
+ echo ""
40
+
41
+ # ---------------------------------------------------------------------------
42
+ # Step 1 — Verificar herramientas requeridas
43
+ # ---------------------------------------------------------------------------
44
+ echo "Verificando herramientas..."
45
+
46
+ if ! command -v git &>/dev/null; then
47
+ fail "git no está instalado o no está en PATH"
48
+ echo ""
49
+ echo "Instalar git: https://git-scm.com/downloads"
50
+ exit 1
51
+ fi
52
+ ok "git $(git --version | awk '{print $3}')"
53
+
54
+ if ! command -v python3 &>/dev/null; then
55
+ fail "python3 no está instalado o no está en PATH"
56
+ echo ""
57
+ echo "Instalar python3: https://www.python.org/downloads/"
58
+ exit 1
59
+ fi
60
+ ok "python3 $(python3 --version | awk '{print $2}')"
61
+
62
+ if ! python3 -c "import yaml" &>/dev/null 2>&1; then
63
+ warn "pyyaml no está instalado — instalando..."
64
+ if python3 -m pip install pyyaml --quiet; then
65
+ ok "pyyaml instalado"
66
+ else
67
+ fail "No se pudo instalar pyyaml. Instalar manualmente: pip install pyyaml"
68
+ exit 1
69
+ fi
70
+ else
71
+ ok "pyyaml disponible"
72
+ fi
73
+
74
+ echo ""
75
+
76
+ # ---------------------------------------------------------------------------
77
+ # Step 2 — Verificar que estamos en la raíz del repositorio con project.yaml
78
+ # ---------------------------------------------------------------------------
79
+ echo "Verificando proyecto..."
80
+
81
+ REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || echo "")"
82
+ if [[ -z "$REPO_ROOT" ]]; then
83
+ fail "No se encontró repositorio git. Ejecutar desde dentro del repositorio."
84
+ exit 1
85
+ fi
86
+
87
+ if [[ ! -f "$REPO_ROOT/project.yaml" ]]; then
88
+ warn "project.yaml no encontrado en $REPO_ROOT"
89
+ warn "Ejecutar primero: python3 scripts/forge-wizard.py"
90
+ warn "Continuando de todas formas — AGENTS.md puede quedar incompleto."
91
+ else
92
+ ok "project.yaml encontrado"
93
+ fi
94
+
95
+ echo ""
96
+
97
+ # ---------------------------------------------------------------------------
98
+ # Step 3 — Encontrar el directorio de forge
99
+ # ---------------------------------------------------------------------------
100
+ echo "Localizando forge..."
101
+
102
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
103
+ FORGE_DIR="$(dirname "$SCRIPT_DIR")"
104
+
105
+ if [[ ! -d "$FORGE_DIR/core" ]]; then
106
+ # Intentar inicializar submódulo si hay .gitmodules
107
+ if [[ -f "$REPO_ROOT/.gitmodules" ]] && grep -q "forge\|agentic" "$REPO_ROOT/.gitmodules" 2>/dev/null; then
108
+ warn "Directorio core/ no encontrado — intentando inicializar submódulo..."
109
+ if git -C "$REPO_ROOT" submodule update --init --recursive 2>/dev/null; then
110
+ ok "Submódulo inicializado"
111
+ else
112
+ fail "No se pudo inicializar el submódulo. Verificar .gitmodules."
113
+ exit 1
114
+ fi
115
+ else
116
+ fail "No se encontró el directorio core/ en $FORGE_DIR"
117
+ fail "Asegurarse de que forge está instalado como submódulo o en el PATH correcto."
118
+ exit 1
119
+ fi
120
+ else
121
+ ok "forge encontrado en $FORGE_DIR"
122
+ fi
123
+
124
+ CODEX_ADAPTER_DIR="$FORGE_DIR/adapters/codex"
125
+ if [[ ! -d "$CODEX_ADAPTER_DIR" ]]; then
126
+ fail "No se encontró $CODEX_ADAPTER_DIR"
127
+ exit 1
128
+ fi
129
+
130
+ echo ""
131
+
132
+ # ---------------------------------------------------------------------------
133
+ # Step 4 — Crear .codex/ y copiar codex.yaml desde plantilla
134
+ # ---------------------------------------------------------------------------
135
+ echo "Configurando .codex/..."
136
+
137
+ DOTCODEX_DIR="$REPO_ROOT/.codex"
138
+ mkdir -p "$DOTCODEX_DIR"
139
+ ok "Directorio .codex/ listo"
140
+
141
+ YAML_TPL="$CODEX_ADAPTER_DIR/hooks/codex.yaml.tpl"
142
+ YAML_DST="$DOTCODEX_DIR/codex.yaml"
143
+
144
+ if [[ -f "$YAML_DST" ]]; then
145
+ warn "codex.yaml ya existe — no sobreescribiendo. Revisar manualmente si hay cambios en la plantilla."
146
+ else
147
+ if [[ -f "$YAML_TPL" ]]; then
148
+ cp "$YAML_TPL" "$YAML_DST"
149
+ ok "codex.yaml copiado desde plantilla"
150
+ else
151
+ fail "Plantilla no encontrada: $YAML_TPL"
152
+ exit 1
153
+ fi
154
+ fi
155
+
156
+ # ---------------------------------------------------------------------------
157
+ # Step 5 — Copiar scripts de hooks a .codex/
158
+ # ---------------------------------------------------------------------------
159
+ echo ""
160
+ echo "Instalando hooks..."
161
+
162
+ HOOKS_SRC="$CODEX_ADAPTER_DIR/hooks"
163
+
164
+ for hook_script in forge-codex-start.sh forge-codex-finish.sh; do
165
+ SRC="$HOOKS_SRC/$hook_script"
166
+ DST="$DOTCODEX_DIR/$hook_script"
167
+ if [[ ! -f "$SRC" ]]; then
168
+ fail "Script de hook no encontrado: $SRC"
169
+ exit 1
170
+ fi
171
+ cp "$SRC" "$DST"
172
+ chmod +x "$DST"
173
+ ok "$hook_script instalado en .codex/"
174
+ done
175
+
176
+ echo ""
177
+
178
+ # ---------------------------------------------------------------------------
179
+ # Step 6 — Generar AGENTS.md
180
+ # ---------------------------------------------------------------------------
181
+ echo "Generando AGENTS.md..."
182
+
183
+ GENERATOR="$CODEX_ADAPTER_DIR/generate-codex-config.py"
184
+ if [[ ! -f "$GENERATOR" ]]; then
185
+ fail "generate-codex-config.py no encontrado en $CODEX_ADAPTER_DIR"
186
+ exit 1
187
+ fi
188
+
189
+ if python3 "$GENERATOR"; then
190
+ ok "AGENTS.md generado"
191
+ else
192
+ fail "Error al generar AGENTS.md"
193
+ exit 1
194
+ fi
195
+
196
+ echo ""
197
+
198
+ # ---------------------------------------------------------------------------
199
+ # Step 7 — Diff summary
200
+ # ---------------------------------------------------------------------------
201
+ echo "Resumen de cambios:"
202
+ echo ""
203
+
204
+ AGENTS_MD="$REPO_ROOT/AGENTS.md"
205
+ if git -C "$REPO_ROOT" diff --name-only HEAD -- AGENTS.md 2>/dev/null | grep -q "AGENTS.md"; then
206
+ LINES=$(wc -l < "$AGENTS_MD" | tr -d ' ')
207
+ ok "AGENTS.md actualizado ($LINES líneas)"
208
+ elif [[ -f "$AGENTS_MD" ]]; then
209
+ LINES=$(wc -l < "$AGENTS_MD" | tr -d ' ')
210
+ ok "AGENTS.md generado ($LINES líneas) — sin cambios vs HEAD"
211
+ fi
212
+
213
+ echo ""
214
+ echo "Archivos instalados en .codex/:"
215
+ for f in "$DOTCODEX_DIR"/*; do
216
+ printf " - .codex/%s\n" "$(basename "$f")"
217
+ done
218
+
219
+ echo ""
220
+ echo "================================"
221
+ ok "Setup completado. Codex CLI está listo para usar con Forge v2."
222
+ echo ""
223
+ echo "Próximos pasos:"
224
+ echo " 1. Revisar .codex/codex.yaml y ajustar el modelo si es necesario."
225
+ echo " 2. Commitear los archivos generados:"
226
+ echo " git add AGENTS.md .codex/"
227
+ echo " git commit -m 'chore(codex): initialize Forge v2 Codex adapter'"
228
+ echo " 3. Consultar adapters/codex/commands/ para los prompt templates de cada comando."
229
+ echo ""
@@ -0,0 +1,147 @@
1
+ #!/usr/bin/env bash
2
+ # team-install.sh — Automates forge setup for a new developer joining a project.
3
+ #
4
+ # Usage:
5
+ # bash .agentic/scripts/team-install.sh
6
+ # bash scripts/team-install.sh (if running from repo root and forge is at .agentic/)
7
+ #
8
+ # What it does:
9
+ # 1. Checks git is available
10
+ # 2. Ensures the forge submodule is initialized
11
+ # 3. Installs Python dependencies (pyyaml)
12
+ # 4. Runs forge-init in non-interactive mode
13
+ # 5. Prints next steps
14
+
15
+ set -euo pipefail
16
+
17
+ # ---------------------------------------------------------------------------
18
+ # Colors
19
+ # ---------------------------------------------------------------------------
20
+ RED='\033[0;31m'
21
+ GREEN='\033[0;32m'
22
+ YELLOW='\033[1;33m'
23
+ BOLD='\033[1m'
24
+ RESET='\033[0m'
25
+
26
+ info() { echo -e "${BOLD}[forge]${RESET} $*"; }
27
+ success() { echo -e "${GREEN}[forge]${RESET} $*"; }
28
+ warn() { echo -e "${YELLOW}[forge] WARNING:${RESET} $*"; }
29
+ error() { echo -e "${RED}[forge] ERROR:${RESET} $*" >&2; }
30
+
31
+ # ---------------------------------------------------------------------------
32
+ # 1. Check git
33
+ # ---------------------------------------------------------------------------
34
+ info "Checking requirements..."
35
+
36
+ if ! command -v git &>/dev/null; then
37
+ error "git is not installed or not in PATH."
38
+ error "Install git from https://git-scm.com and retry."
39
+ exit 1
40
+ fi
41
+
42
+ if ! command -v python3 &>/dev/null; then
43
+ error "python3 is not installed or not in PATH."
44
+ error "Install Python 3.9+ from https://www.python.org/downloads/ and retry."
45
+ exit 1
46
+ fi
47
+
48
+ PYTHON_VERSION=$(python3 -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")')
49
+ PYTHON_MAJOR=$(echo "$PYTHON_VERSION" | cut -d. -f1)
50
+ PYTHON_MINOR=$(echo "$PYTHON_VERSION" | cut -d. -f2)
51
+
52
+ if [ "$PYTHON_MAJOR" -lt 3 ] || { [ "$PYTHON_MAJOR" -eq 3 ] && [ "$PYTHON_MINOR" -lt 9 ]; }; then
53
+ error "Python 3.9+ is required. Found: $PYTHON_VERSION"
54
+ exit 1
55
+ fi
56
+
57
+ success "git and python3 ($PYTHON_VERSION) are available."
58
+
59
+ # ---------------------------------------------------------------------------
60
+ # 2. Locate repo root
61
+ # ---------------------------------------------------------------------------
62
+ if ! ROOT=$(git rev-parse --show-toplevel 2>/dev/null); then
63
+ error "Not inside a git repository. Run this script from within the project directory."
64
+ exit 1
65
+ fi
66
+
67
+ info "Repository root: $ROOT"
68
+
69
+ # ---------------------------------------------------------------------------
70
+ # 3. Initialize submodule if needed
71
+ # ---------------------------------------------------------------------------
72
+ FORGE_PY=""
73
+
74
+ for candidate in "$ROOT/.agentic" "$ROOT/forge" "$ROOT/.forge"; do
75
+ if [ -f "$candidate/forge.py" ]; then
76
+ FORGE_PY="$candidate/forge.py"
77
+ FORGE_DIR="$candidate"
78
+ break
79
+ fi
80
+ done
81
+
82
+ if [ -z "$FORGE_PY" ]; then
83
+ info "forge not found — initializing git submodules..."
84
+ git -C "$ROOT" submodule update --init --recursive
85
+
86
+ # Try again after submodule init
87
+ for candidate in "$ROOT/.agentic" "$ROOT/forge" "$ROOT/.forge"; do
88
+ if [ -f "$candidate/forge.py" ]; then
89
+ FORGE_PY="$candidate/forge.py"
90
+ FORGE_DIR="$candidate"
91
+ break
92
+ fi
93
+ done
94
+ fi
95
+
96
+ if [ -z "$FORGE_PY" ]; then
97
+ error "forge.py not found after submodule initialization."
98
+ error "Checked: .agentic/, forge/, .forge/"
99
+ error "Ask a team lead for the correct forge submodule path."
100
+ exit 1
101
+ fi
102
+
103
+ success "forge found at: $FORGE_DIR"
104
+
105
+ # ---------------------------------------------------------------------------
106
+ # 4. Install Python dependencies
107
+ # ---------------------------------------------------------------------------
108
+ REQUIREMENTS="$FORGE_DIR/requirements.txt"
109
+
110
+ if [ -f "$REQUIREMENTS" ]; then
111
+ info "Installing Python dependencies..."
112
+ python3 -m pip install -r "$REQUIREMENTS" --quiet
113
+ success "Dependencies installed."
114
+ else
115
+ warn "requirements.txt not found at $REQUIREMENTS — skipping pip install."
116
+ warn "If forge fails, run: pip3 install pyyaml"
117
+ fi
118
+
119
+ # ---------------------------------------------------------------------------
120
+ # 5. Run forge-init (non-interactive)
121
+ # ---------------------------------------------------------------------------
122
+ INIT_SCRIPT="$FORGE_DIR/scripts/forge-init.py"
123
+
124
+ if [ -f "$INIT_SCRIPT" ]; then
125
+ info "Running forge-init (non-interactive)..."
126
+ python3 "$INIT_SCRIPT" --tool claude-code
127
+ else
128
+ warn "forge-init.py not found at $INIT_SCRIPT"
129
+ warn "Run the interactive CLI manually: python3 $FORGE_PY"
130
+ warn "Then select 'Inicializar agentes' from the menu."
131
+ exit 0
132
+ fi
133
+
134
+ # ---------------------------------------------------------------------------
135
+ # 6. Done
136
+ # ---------------------------------------------------------------------------
137
+ echo ""
138
+ success "forge is ready."
139
+ echo ""
140
+ echo -e " ${BOLD}Next steps:${RESET}"
141
+ echo -e " 1. Open Claude Code in this project directory"
142
+ echo -e " 2. Run ${BOLD}/session-start${RESET} to begin your first session"
143
+ echo -e " 3. The orchestrator will brief you on the current sprint"
144
+ echo ""
145
+ echo -e " ${BOLD}Agents installed:${RESET} $(ls "$ROOT/.claude/agents/" 2>/dev/null | wc -l | tr -d ' ') agents in .claude/agents/"
146
+ echo -e " ${BOLD}Commands installed:${RESET} $(ls "$ROOT/.claude/commands/" 2>/dev/null | wc -l | tr -d ' ') commands in .claude/commands/"
147
+ echo ""
@@ -0,0 +1,201 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Token usage stats per agent/team — forge framework.
4
+
5
+ Usage:
6
+ python3 scripts/token-stats.py # tabla en terminal
7
+ python3 scripts/token-stats.py --json # JSON para embeber en progress.html
8
+ python3 scripts/token-stats.py --patch-html PATH # actualiza el HTML directamente
9
+ python3 scripts/token-stats.py --project-dir PATH # usar directorio de sesiones específico
10
+
11
+ PROJECT_DIR se detecta automáticamente desde el directorio de trabajo actual,
12
+ o se puede pasar explícitamente con --project-dir.
13
+
14
+ Identifica agentes lanzados con el protocolo teammate-message.
15
+ Las sesiones sin ese marcador se agrupan como "orquestador".
16
+ """
17
+ import json, glob, os, re, sys
18
+ from collections import defaultdict
19
+ from datetime import datetime, timezone
20
+
21
+
22
+ def detect_project_dir() -> str:
23
+ """Detecta el directorio de sesiones Claude Code para el proyecto actual.
24
+
25
+ Claude Code usa el path completo con cada '/' reemplazado por '-',
26
+ manteniendo el '-' inicial (e.g. /Users/foo/proj → -Users-foo-proj).
27
+ """
28
+ cwd = os.getcwd()
29
+ project_slug = cwd.replace("/", "-") # /Users/foo → -Users-foo (mantiene el '-' inicial)
30
+ return os.path.expanduser(f"~/.claude/projects/{project_slug}")
31
+
32
+
33
+ # Respetar --project-dir si se pasa, sino detectar automáticamente
34
+ if "--project-dir" in sys.argv:
35
+ idx = sys.argv.index("--project-dir")
36
+ PROJECT_DIR = sys.argv[idx + 1] if idx + 1 < len(sys.argv) else detect_project_dir()
37
+ else:
38
+ PROJECT_DIR = detect_project_dir()
39
+
40
+ # NOTA: precios actualizados a 2026-05. Verificar en https://www.anthropic.com/pricing
41
+ # si los precios cambiaron antes de confiar en estos reportes.
42
+ _PRICING_DATE = "2026-05"
43
+
44
+ # Precios por millón de tokens (USD) — actualizar si cambian
45
+ PRICING = {
46
+ "claude-sonnet-4-6": {"input": 3.00, "output": 15.00, "cache_write": 3.75, "cache_read": 0.30},
47
+ "claude-opus-4-7": {"input": 15.00, "output": 75.00, "cache_write": 18.75, "cache_read": 1.50},
48
+ "claude-haiku-4-5": {"input": 0.80, "output": 4.00, "cache_write": 1.00, "cache_read": 0.08},
49
+ }
50
+ DEFAULT_PRICE = PRICING["claude-sonnet-4-6"]
51
+
52
+
53
+ def parse_sessions():
54
+ files = sorted(glob.glob(f"{PROJECT_DIR}/*.jsonl"))
55
+ results = []
56
+
57
+ for f in files:
58
+ session_id = os.path.basename(f).replace(".jsonl", "")[:8]
59
+ agent_name = None
60
+ team_name = None
61
+ per_model = defaultdict(lambda: {"input": 0, "output": 0, "cache_read": 0, "cache_write": 0, "msgs": 0})
62
+ first_msgs = 0
63
+
64
+ with open(f) as fh:
65
+ for line in fh:
66
+ try:
67
+ obj = json.loads(line)
68
+ except Exception:
69
+ continue
70
+
71
+ # Detect agent identity from first teammate-messages
72
+ if first_msgs < 15 and obj.get("type") == "user":
73
+ content = obj.get("message", {}).get("content", "")
74
+ if isinstance(content, list):
75
+ for block in content:
76
+ if isinstance(block, dict) and block.get("type") == "text":
77
+ content = block.get("text", "")
78
+ break
79
+ if isinstance(content, str):
80
+ m = re.search(r'Eres el teammate "([^"]+)" en el equipo "([^"]+)"', content)
81
+ if m and agent_name is None:
82
+ agent_name = m.group(1)
83
+ team_name = m.group(2)
84
+ first_msgs += 1
85
+
86
+ if obj.get("type") == "assistant":
87
+ msg = obj.get("message", {})
88
+ usage = msg.get("usage")
89
+ model = msg.get("model", "claude-sonnet-4-6")
90
+ if usage:
91
+ pm = per_model[model]
92
+ pm["input"] += usage.get("input_tokens", 0)
93
+ pm["output"] += usage.get("output_tokens", 0)
94
+ pm["cache_read"] += usage.get("cache_read_input_tokens", 0)
95
+ pm["cache_write"] += usage.get("cache_creation_input_tokens", 0)
96
+ pm["msgs"] += 1
97
+
98
+ total_cost = 0.0
99
+ totals = {"input": 0, "output": 0, "cache_read": 0, "cache_write": 0, "msgs": 0}
100
+ models_used = sorted(per_model.keys())
101
+
102
+ for model, v in per_model.items():
103
+ p = PRICING.get(model, DEFAULT_PRICE)
104
+ cost = (
105
+ v["input"] * p["input"] +
106
+ v["output"] * p["output"] +
107
+ v["cache_write"] * p["cache_write"] +
108
+ v["cache_read"] * p["cache_read"]
109
+ ) / 1_000_000
110
+ total_cost += cost
111
+ for k in totals:
112
+ totals[k] += v[k]
113
+
114
+ label = f"{agent_name} [{team_name}]" if agent_name else f"orquestador/{session_id}"
115
+ results.append({
116
+ "label": label,
117
+ "session_id": session_id,
118
+ "agent": agent_name,
119
+ "team": team_name,
120
+ "models": models_used,
121
+ "msgs": totals["msgs"],
122
+ "input": totals["input"],
123
+ "output": totals["output"],
124
+ "cache_read": totals["cache_read"],
125
+ "cache_write":totals["cache_write"],
126
+ "cost_usd": round(total_cost, 2),
127
+ })
128
+
129
+ results.sort(key=lambda x: x["cost_usd"], reverse=True)
130
+ return results
131
+
132
+
133
+ def fmt(n):
134
+ return f"{n:,}"
135
+
136
+
137
+ def main():
138
+ rows = parse_sessions()
139
+
140
+ grand = {"input": 0, "output": 0, "cache_read": 0, "cache_write": 0, "msgs": 0, "cost_usd": 0.0}
141
+ for r in rows:
142
+ for k in grand:
143
+ grand[k] += r[k]
144
+ grand["cost_usd"] = round(grand["cost_usd"], 2)
145
+
146
+ output = {
147
+ "rows": rows,
148
+ "totals": grand,
149
+ "generated_at": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
150
+ "pricing_note": "USD estimates: Sonnet $3/$15 · Opus $15/$75 per M input/output tokens",
151
+ }
152
+
153
+ if "--json" in sys.argv:
154
+ print(json.dumps(output, indent=2))
155
+ return
156
+
157
+ if "--patch-html" in sys.argv:
158
+ idx = sys.argv.index("--patch-html")
159
+ html_path = sys.argv[idx + 1] if idx + 1 < len(sys.argv) else "docs/progress.html"
160
+ patch_html(html_path, output)
161
+ return
162
+
163
+ # Terminal table
164
+ print(f"\n{'Agente':<32} {'Modelo(s)':<22} {'Output':>9} {'CacheR':>12} {'CacheW':>10} {'Msgs':>5} {'USD est.':>9}")
165
+ print("─" * 105)
166
+ for r in rows:
167
+ m = ", ".join(r["models"])
168
+ print(f"{r['label']:<32} {m:<22} {fmt(r['output']):>9} {fmt(r['cache_read']):>12} {fmt(r['cache_write']):>10} {r['msgs']:>5,} ${r['cost_usd']:>8.2f}")
169
+
170
+ print("─" * 105)
171
+ print(f"{'TOTAL':<56} {fmt(grand['output']):>9} {fmt(grand['cache_read']):>12} {fmt(grand['cache_write']):>10} {grand['msgs']:>5,} ${grand['cost_usd']:>8.2f}")
172
+ print(f"\nGenerado: {output['generated_at']}")
173
+ print(f"Nota: {output['pricing_note']}")
174
+ print(f"Nota: precios al {_PRICING_DATE} — verificar anthropic.com/pricing si el reporte parece incorrecto.")
175
+
176
+
177
+ def patch_html(html_path: str, data: dict) -> None:
178
+ """Inject fresh JSON data into the <script id="token-data"> block in the HTML file."""
179
+ import re as _re
180
+ try:
181
+ with open(html_path, "r") as f:
182
+ html = f.read()
183
+ except FileNotFoundError:
184
+ print(f"[token-stats] HTML not found: {html_path}", file=sys.stderr)
185
+ return
186
+
187
+ new_json = json.dumps(data, separators=(",", ":"))
188
+ pattern = r'(<script id="token-data" type="application/json">\n)([^\n]+)(\n</script>)'
189
+ new_html, n = _re.subn(pattern, lambda m: m.group(1) + new_json + m.group(3), html)
190
+
191
+ if n == 0:
192
+ print("[token-stats] token-data script block not found in HTML", file=sys.stderr)
193
+ return
194
+
195
+ with open(html_path, "w") as f:
196
+ f.write(new_html)
197
+ print(f"[token-stats] Patched {html_path} ({data['generated_at']})")
198
+
199
+
200
+ if __name__ == "__main__":
201
+ main()
@@ -0,0 +1,114 @@
1
+ # project.yaml — Modo enterprise (9+ personas)
2
+ # Roster completo, compliance activo, fases de sprint con specs, CI habilitado.
3
+ # Renombrar a project.yaml en la raíz del repositorio.
4
+ #
5
+ # CI: ejecutar forge-audit con --json para integrar en pipelines:
6
+ # python3 .agentic/scripts/forge-audit.py --json | jq '.summary'
7
+ # Retorna código de salida 1 si hay errores de severidad "error" o "critical".
8
+
9
+ project:
10
+ name: "Mi Proyecto Enterprise"
11
+ slug: "mi-proyecto-enterprise"
12
+ description: "Descripción del sistema"
13
+ language: "typescript" # typescript | python | ruby | go | php | mixed
14
+ status: "active"
15
+ mode: "enterprise" # Activa validaciones estrictas de compliance y auditoría
16
+
17
+ team:
18
+ name: "Equipo Principal"
19
+ members:
20
+ - name: "Tech Lead"
21
+ role: "lead"
22
+ email: "lead@empresa.com"
23
+ - name: "Backend Senior"
24
+ role: "backend"
25
+ email: "backend@empresa.com"
26
+ - name: "Frontend Senior"
27
+ role: "frontend"
28
+ email: "frontend@empresa.com"
29
+ - name: "QA Engineer"
30
+ role: "qa"
31
+ email: "qa@empresa.com"
32
+ - name: "Security Engineer"
33
+ role: "backend"
34
+ email: "security@empresa.com"
35
+
36
+ stack:
37
+ backend: null # "hono" | "fastapi" | "rails" | "express" | "laravel" | null
38
+ frontend: null # "nextjs" | "nuxt" | "remix" | "rails-views" | null
39
+ database: null # "postgresql" | "mysql" | "sqlite" | null
40
+ cache: null # "redis" | "memcached" | null
41
+ testing: ["vitest", "playwright"]
42
+
43
+ agents:
44
+ active:
45
+ - orchestrator
46
+ - backend-engineer
47
+ - frontend-engineer
48
+ - test-engineer
49
+ - docs-writer
50
+ # Compliance y seguridad obligatorios en enterprise
51
+ compliance:
52
+ - compliance-reviewer # GDPR, LGPD, Ley 21.719, CCPA
53
+ - security-auditor # Auditoría de vulnerabilidades — obligatorio pre-merge
54
+ profiles: [] # Ejemplo: [hono-drizzle, nextjs-admin]
55
+
56
+ sprint:
57
+ current: 1
58
+ length_days: 14
59
+ phases:
60
+ - id: "A"
61
+ name: "Core"
62
+ specs:
63
+ - "docs/specs/A1-autenticacion.md"
64
+ - "docs/specs/A2-modelo-datos.md"
65
+ - id: "B"
66
+ name: "Features"
67
+ specs:
68
+ - "docs/specs/B1-feature-principal.md"
69
+ - "docs/specs/B2-integraciones.md"
70
+ - id: "C"
71
+ name: "Compliance & Security"
72
+ specs:
73
+ - "docs/specs/C1-audit-trail.md"
74
+ - "docs/specs/C2-dsar-flow.md"
75
+
76
+ skills:
77
+ active:
78
+ - security-audit
79
+ - db-migrate
80
+ - local2prod
81
+ - new-feature
82
+ integrations: []
83
+
84
+ deploy:
85
+ provider: null # "vercel" | "railway" | "fly" | "github-actions" | "custom"
86
+ branch: "main"
87
+
88
+ # Compliance enterprise: múltiples marcos regulatorios
89
+ # forge-audit verifica automáticamente los frameworks listados aquí.
90
+ # Usar --json en CI: python3 .agentic/scripts/forge-audit.py --json
91
+ compliance:
92
+ frameworks:
93
+ - gdpr # Unión Europea
94
+ - lgpd # Brasil
95
+ - ley-21719 # Chile
96
+ - ccpa # California, EE.UU.
97
+ pii_handling: true # El sistema procesa datos personales
98
+ audit_logs: true # Logs inmutables requeridos (append-only, sin UPDATE/DELETE)
99
+
100
+ paths:
101
+ specs: "docs/specs"
102
+ progress: "docs/progress.html"
103
+ migrations: null # "packages/api/migrations" | "db/migrate" | null
104
+ tests: null # "tests/" | "__tests__/" | null
105
+
106
+ integrations:
107
+ obsidian:
108
+ vault_path: null
109
+ map:
110
+ api: null
111
+ database: null
112
+ frontend: null
113
+ deploy: null
114
+ decisions: null