okam 0.2.0__py3-none-any.whl
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.
- okam/__init__.py +1 -0
- okam/cli.py +392 -0
- okam/hooks/commit-msg +58 -0
- okam/hooks/pre-commit +114 -0
- okam/hooks/pre-push +36 -0
- okam/hooks.py +213 -0
- okam/manager.py +155 -0
- okam/skill_creator.py +166 -0
- okam-0.2.0.dist-info/METADATA +134 -0
- okam-0.2.0.dist-info/RECORD +13 -0
- okam-0.2.0.dist-info/WHEEL +4 -0
- okam-0.2.0.dist-info/entry_points.txt +2 -0
- okam-0.2.0.dist-info/licenses/LICENSE +21 -0
okam/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.0"
|
okam/cli.py
ADDED
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import argparse
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
|
|
6
|
+
from okam import __version__
|
|
7
|
+
from okam.manager import (
|
|
8
|
+
get_wiki_files,
|
|
9
|
+
validate_file,
|
|
10
|
+
load_markdown_file,
|
|
11
|
+
save_markdown_file,
|
|
12
|
+
)
|
|
13
|
+
from okam.skill_creator import (
|
|
14
|
+
create_new_skill,
|
|
15
|
+
print_colored,
|
|
16
|
+
ask_question,
|
|
17
|
+
COLOR_GREEN,
|
|
18
|
+
COLOR_YELLOW,
|
|
19
|
+
COLOR_RED,
|
|
20
|
+
COLOR_BLUE,
|
|
21
|
+
COLOR_BOLD,
|
|
22
|
+
)
|
|
23
|
+
from okam.hooks import (
|
|
24
|
+
install_hooks,
|
|
25
|
+
uninstall_hooks,
|
|
26
|
+
get_hooks_status,
|
|
27
|
+
HOOK_NAMES,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def get_default_wiki_dir():
|
|
32
|
+
"""Retorna o diretório default do wiki."""
|
|
33
|
+
# Se ./knowledge/wiki existir no diretório atual, usa ele
|
|
34
|
+
if os.path.exists(os.path.join("knowledge", "wiki")):
|
|
35
|
+
return os.path.abspath(os.path.join("knowledge", "wiki"))
|
|
36
|
+
# Caso contrário, tenta ./wiki
|
|
37
|
+
if os.path.exists("wiki"):
|
|
38
|
+
return os.path.abspath("wiki")
|
|
39
|
+
# Fallback para o valor default
|
|
40
|
+
return os.path.abspath(os.path.join("knowledge", "wiki"))
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def cmd_init(wiki_dir):
|
|
44
|
+
"""Gera seed pages com frontmatter OKF válido."""
|
|
45
|
+
print_colored(f"Inicializando base de conhecimento OKF em: {wiki_dir}...", COLOR_BLUE)
|
|
46
|
+
os.makedirs(wiki_dir, exist_ok=True)
|
|
47
|
+
today = datetime.now().strftime("%Y-%m-%d")
|
|
48
|
+
|
|
49
|
+
seeds = {
|
|
50
|
+
"index.md": {
|
|
51
|
+
"frontmatter": {
|
|
52
|
+
"title": "Índice do Wiki",
|
|
53
|
+
"description": "Catálogo central de conhecimento persistente.",
|
|
54
|
+
"type": "index",
|
|
55
|
+
"resource": "workspace",
|
|
56
|
+
"timestamp": today,
|
|
57
|
+
"tags": ["hub/wiki", "tipo/indice"],
|
|
58
|
+
"parent": "root"
|
|
59
|
+
},
|
|
60
|
+
"body": """
|
|
61
|
+
# Índice do Wiki
|
|
62
|
+
|
|
63
|
+
Bem-vindo ao catálogo central de conhecimento persistente.
|
|
64
|
+
|
|
65
|
+
## 00. Visão Geral
|
|
66
|
+
|
|
67
|
+
- [[getting-started]]: Guia de primeiros passos.
|
|
68
|
+
- [[architecture]]: Visão geral da arquitetura.
|
|
69
|
+
|
|
70
|
+
## 01. Conceitos
|
|
71
|
+
|
|
72
|
+
*(Adicione links para páginas de conceitos aqui)*
|
|
73
|
+
|
|
74
|
+
## 02. Entidades
|
|
75
|
+
|
|
76
|
+
*(Adicione links para páginas de entidades aqui)*
|
|
77
|
+
"""
|
|
78
|
+
},
|
|
79
|
+
"getting-started.md": {
|
|
80
|
+
"frontmatter": {
|
|
81
|
+
"title": "Primeiros Passos",
|
|
82
|
+
"description": "Guia rápido para começar a usar o Wiki de Conhecimento.",
|
|
83
|
+
"type": "runbook",
|
|
84
|
+
"resource": "workspace",
|
|
85
|
+
"timestamp": today,
|
|
86
|
+
"tags": ["tipo/guia", "onboarding"],
|
|
87
|
+
"parent": "[[index]]"
|
|
88
|
+
},
|
|
89
|
+
"body": """
|
|
90
|
+
# Primeiros Passos
|
|
91
|
+
|
|
92
|
+
Este guia ajuda você a configurar e começar a usar o Wiki de Conhecimento.
|
|
93
|
+
|
|
94
|
+
## 1. Estrutura
|
|
95
|
+
|
|
96
|
+
- `wiki/`: Páginas de síntese (você está aqui).
|
|
97
|
+
- `raw-sources/`: Fontes brutas e imutáveis.
|
|
98
|
+
- `scripts/`: Ferramentas de validação.
|
|
99
|
+
|
|
100
|
+
## 2. Criar uma Página
|
|
101
|
+
|
|
102
|
+
Cada página do wiki deve ter frontmatter YAML no formato OKF:
|
|
103
|
+
|
|
104
|
+
```yaml
|
|
105
|
+
---
|
|
106
|
+
title: "Título da Página"
|
|
107
|
+
description: "Descrição breve do conteúdo."
|
|
108
|
+
type: concept
|
|
109
|
+
resource: workspace
|
|
110
|
+
timestamp: 2026-01-01
|
|
111
|
+
tags:
|
|
112
|
+
- conceito/exemplo
|
|
113
|
+
parent: "[[index]]"
|
|
114
|
+
---
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## 3. Validar
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
okam validate
|
|
121
|
+
```
|
|
122
|
+
"""
|
|
123
|
+
},
|
|
124
|
+
"architecture.md": {
|
|
125
|
+
"frontmatter": {
|
|
126
|
+
"title": "Arquitetura do Conhecimento",
|
|
127
|
+
"description": "Visão geral da arquitetura de 3 camadas: Raw Sources, Wiki e Schema.",
|
|
128
|
+
"type": "architecture",
|
|
129
|
+
"resource": "workspace",
|
|
130
|
+
"timestamp": today,
|
|
131
|
+
"tags": ["conceito/arquitetura", "conceito/okf"],
|
|
132
|
+
"parent": "[[index]]"
|
|
133
|
+
},
|
|
134
|
+
"body": """
|
|
135
|
+
# Arquitetura do Conhecimento
|
|
136
|
+
|
|
137
|
+
O sistema de conhecimento opera em 3 camadas:
|
|
138
|
+
|
|
139
|
+
## 1. Raw Sources (Fontes Brutas)
|
|
140
|
+
|
|
141
|
+
Documentos imutáveis: PDFs, transcrições, logs, clippings.
|
|
142
|
+
Servem como fonte de verdade original.
|
|
143
|
+
|
|
144
|
+
## 2. Wiki (Síntese)
|
|
145
|
+
|
|
146
|
+
Páginas de síntese geradas e mantidas por IA.
|
|
147
|
+
Seguem o formato OKF para garantir interoperabilidade.
|
|
148
|
+
|
|
149
|
+
## 3. Schema (Governança)
|
|
150
|
+
|
|
151
|
+
Regras definidas em `AGENTS.md` e `.agents/rules/`.
|
|
152
|
+
Controlam como o conhecimento é criado, validado e mantido.
|
|
153
|
+
"""
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
created = 0
|
|
158
|
+
for filename, content in seeds.items():
|
|
159
|
+
filepath = os.path.join(wiki_dir, filename)
|
|
160
|
+
if os.path.exists(filepath):
|
|
161
|
+
print_colored(f" [EXISTE] {filename} — pulando", COLOR_YELLOW)
|
|
162
|
+
continue
|
|
163
|
+
|
|
164
|
+
frontmatter = content["frontmatter"]
|
|
165
|
+
body = content["body"]
|
|
166
|
+
save_markdown_file(filepath, frontmatter, body)
|
|
167
|
+
print_colored(f" [CRIADO] {filename}", COLOR_GREEN)
|
|
168
|
+
created += 1
|
|
169
|
+
|
|
170
|
+
print_colored(f"\n✅ {created} seed page(s) criada(s) em {wiki_dir}", COLOR_GREEN + COLOR_BOLD)
|
|
171
|
+
|
|
172
|
+
# Oferecer instalação de hooks
|
|
173
|
+
try:
|
|
174
|
+
resp = ask_question(
|
|
175
|
+
"Deseja instalar os Git hooks de governança?",
|
|
176
|
+
default="sim"
|
|
177
|
+
).lower()
|
|
178
|
+
if resp in ['s', 'sim', 'y', 'yes']:
|
|
179
|
+
print_colored("\nInstalando hooks de governança...", COLOR_BLUE)
|
|
180
|
+
installed, skipped, errors = install_hooks()
|
|
181
|
+
if errors == 0:
|
|
182
|
+
print_colored(
|
|
183
|
+
f"✅ {installed} hook(s) instalado(s).",
|
|
184
|
+
COLOR_GREEN + COLOR_BOLD
|
|
185
|
+
)
|
|
186
|
+
else:
|
|
187
|
+
print_colored(
|
|
188
|
+
f"⚠ {installed} instalado(s), {errors} erro(s).",
|
|
189
|
+
COLOR_YELLOW
|
|
190
|
+
)
|
|
191
|
+
except (KeyboardInterrupt, EOFError):
|
|
192
|
+
pass # Non-interactive ou cancelado — pular silenciosamente
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def cmd_validate(wiki_dir):
|
|
196
|
+
"""Valida todos os arquivos .md no wiki_dir contra o padrão OKF."""
|
|
197
|
+
files = get_wiki_files(wiki_dir)
|
|
198
|
+
|
|
199
|
+
if not files:
|
|
200
|
+
print_colored(f"Nenhum arquivo .md encontrado em: {wiki_dir}", COLOR_YELLOW)
|
|
201
|
+
return True
|
|
202
|
+
|
|
203
|
+
all_ok = True
|
|
204
|
+
print_colored(f"Validando conformidade OKF em: {wiki_dir}...\n", COLOR_BLUE)
|
|
205
|
+
|
|
206
|
+
for filepath in files:
|
|
207
|
+
filename = os.path.basename(filepath)
|
|
208
|
+
is_ok, errors = validate_file(filepath)
|
|
209
|
+
if is_ok:
|
|
210
|
+
print_colored(f" [OK] {filename}", COLOR_GREEN)
|
|
211
|
+
else:
|
|
212
|
+
all_ok = False
|
|
213
|
+
print_colored(f" [FALHA] {filename}", COLOR_RED)
|
|
214
|
+
for err in errors:
|
|
215
|
+
print(f" - {err}")
|
|
216
|
+
|
|
217
|
+
if all_ok:
|
|
218
|
+
print_colored("\n✅ Todos os arquivos estão em conformidade com o OKF!", COLOR_GREEN + COLOR_BOLD)
|
|
219
|
+
return True
|
|
220
|
+
else:
|
|
221
|
+
print_colored("\n❌ Validação falhou com erros.", COLOR_RED + COLOR_BOLD)
|
|
222
|
+
return False
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def cmd_index(wiki_dir):
|
|
226
|
+
"""Gera índice markdown da base OKF e exibe no stdout."""
|
|
227
|
+
files = get_wiki_files(wiki_dir)
|
|
228
|
+
|
|
229
|
+
print("# Índice de Conhecimento OKF\n")
|
|
230
|
+
print("| Arquivo | Título | Tipo | Recurso | Data | Descrição |")
|
|
231
|
+
print("| --- | --- | --- | --- | --- | --- |")
|
|
232
|
+
|
|
233
|
+
for filepath in files:
|
|
234
|
+
filename = os.path.basename(filepath)
|
|
235
|
+
try:
|
|
236
|
+
frontmatter, _ = load_markdown_file(filepath)
|
|
237
|
+
if not frontmatter:
|
|
238
|
+
continue
|
|
239
|
+
except Exception:
|
|
240
|
+
continue
|
|
241
|
+
|
|
242
|
+
title = frontmatter.get('title', filename)
|
|
243
|
+
ftype = frontmatter.get('type', '-')
|
|
244
|
+
resource = frontmatter.get('resource', '-')
|
|
245
|
+
timestamp = frontmatter.get('timestamp', '-')
|
|
246
|
+
description = frontmatter.get('description', '-')
|
|
247
|
+
|
|
248
|
+
print(f"| {filename} | {title} | {ftype} | {resource} | {timestamp} | {description} |")
|
|
249
|
+
|
|
250
|
+
def cmd_hooks(args):
|
|
251
|
+
"""Gerencia Git hooks de governança do Okam."""
|
|
252
|
+
action = getattr(args, "hooks_action", None)
|
|
253
|
+
|
|
254
|
+
if action is None:
|
|
255
|
+
print_colored("Uso: okam hooks {install|uninstall|status}", COLOR_YELLOW)
|
|
256
|
+
print_colored(" install — Instala hooks de governança no repositório", COLOR_BLUE)
|
|
257
|
+
print_colored(" uninstall — Remove hooks do Okam e restaura backups", COLOR_BLUE)
|
|
258
|
+
print_colored(" status — Mostra o status de cada hook", COLOR_BLUE)
|
|
259
|
+
sys.exit(0)
|
|
260
|
+
|
|
261
|
+
if action == "install":
|
|
262
|
+
print_colored("⬡ Instalando hooks de governança...\n", COLOR_BLUE + COLOR_BOLD)
|
|
263
|
+
skip = []
|
|
264
|
+
if getattr(args, "skip_commit_msg", False):
|
|
265
|
+
skip.append("commit-msg")
|
|
266
|
+
installed, skipped, errors = install_hooks(skip_hooks=skip)
|
|
267
|
+
print()
|
|
268
|
+
if errors == 0:
|
|
269
|
+
print_colored(
|
|
270
|
+
f"✅ {installed} hook(s) instalado(s), {skipped} pulado(s).",
|
|
271
|
+
COLOR_GREEN + COLOR_BOLD,
|
|
272
|
+
)
|
|
273
|
+
else:
|
|
274
|
+
print_colored(
|
|
275
|
+
f"⚠ {installed} instalado(s), {errors} erro(s).",
|
|
276
|
+
COLOR_RED + COLOR_BOLD,
|
|
277
|
+
)
|
|
278
|
+
sys.exit(1)
|
|
279
|
+
|
|
280
|
+
elif action == "uninstall":
|
|
281
|
+
print_colored("⬡ Removendo hooks de governança...\n", COLOR_BLUE + COLOR_BOLD)
|
|
282
|
+
removed, restored, not_found = uninstall_hooks()
|
|
283
|
+
print()
|
|
284
|
+
total = removed + restored
|
|
285
|
+
if total > 0:
|
|
286
|
+
print_colored(
|
|
287
|
+
f"✅ {total} hook(s) removido(s) ({restored} backup(s) restaurado(s)).",
|
|
288
|
+
COLOR_GREEN + COLOR_BOLD,
|
|
289
|
+
)
|
|
290
|
+
else:
|
|
291
|
+
print_colored("Nenhum hook do Okam encontrado para remover.", COLOR_YELLOW)
|
|
292
|
+
|
|
293
|
+
elif action == "status":
|
|
294
|
+
print_colored("⬡ Status dos hooks de governança:\n", COLOR_BLUE + COLOR_BOLD)
|
|
295
|
+
status = get_hooks_status()
|
|
296
|
+
for hook_name, state in status.items():
|
|
297
|
+
if "Okam" in state:
|
|
298
|
+
color = COLOR_GREEN
|
|
299
|
+
icon = "✓"
|
|
300
|
+
elif "externo" in state:
|
|
301
|
+
color = COLOR_YELLOW
|
|
302
|
+
icon = "~"
|
|
303
|
+
else:
|
|
304
|
+
color = COLOR_RED
|
|
305
|
+
icon = "✗"
|
|
306
|
+
print_colored(f" {icon} {hook_name}: {state}", color)
|
|
307
|
+
print()
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def main():
|
|
311
|
+
# Configura encoding UTF-8 para Windows se executado diretamente
|
|
312
|
+
if sys.platform.startswith('win'):
|
|
313
|
+
import io
|
|
314
|
+
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
|
|
315
|
+
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')
|
|
316
|
+
|
|
317
|
+
parser = argparse.ArgumentParser(
|
|
318
|
+
description="⬡ Okam CLI — Framework de governança de IA e memória persistente",
|
|
319
|
+
formatter_class=argparse.RawDescriptionHelpFormatter
|
|
320
|
+
)
|
|
321
|
+
parser.add_argument(
|
|
322
|
+
'--version', '-v',
|
|
323
|
+
action='version',
|
|
324
|
+
version=f"okam version {__version__}"
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
subparsers = parser.add_subparsers(dest="command", help="Comando a ser executado")
|
|
328
|
+
|
|
329
|
+
# Comando init
|
|
330
|
+
p_init = subparsers.add_parser("init", help="Inicializa wiki com seed pages OKF")
|
|
331
|
+
p_init.add_argument("--wiki-dir", help="Diretório destino do wiki (default: autodetect ./knowledge/wiki)")
|
|
332
|
+
|
|
333
|
+
# Comando validate
|
|
334
|
+
p_val = subparsers.add_parser("validate", help="Valida conformidade OKF de arquivos Markdown")
|
|
335
|
+
p_val.add_argument("--wiki-dir", help="Diretório do wiki a validar (default: autodetect ./knowledge/wiki)")
|
|
336
|
+
|
|
337
|
+
# Comando index
|
|
338
|
+
p_idx = subparsers.add_parser("index", help="Gera tabela Markdown do índice de conhecimento")
|
|
339
|
+
p_idx.add_argument("--wiki-dir", help="Diretório do wiki (default: autodetect ./knowledge/wiki)")
|
|
340
|
+
|
|
341
|
+
# Adiciona compatibilidade para dump-index diretamente
|
|
342
|
+
p_dump = subparsers.add_parser("dump-index", help="Alias para o comando 'index'")
|
|
343
|
+
p_dump.add_argument("--wiki-dir", help="Diretório do wiki (default: autodetect ./knowledge/wiki)")
|
|
344
|
+
|
|
345
|
+
# Comando new-skill
|
|
346
|
+
subparsers.add_parser("new-skill", help="Cria interativamente uma nova skill na pasta .agents/skills")
|
|
347
|
+
|
|
348
|
+
# Comando hooks
|
|
349
|
+
p_hooks = subparsers.add_parser("hooks", help="Gerencia Git hooks de governança")
|
|
350
|
+
p_hooks_sub = p_hooks.add_subparsers(dest="hooks_action", help="Ação dos hooks")
|
|
351
|
+
|
|
352
|
+
p_hooks_install = p_hooks_sub.add_parser("install", help="Instala hooks de governança no repositório")
|
|
353
|
+
p_hooks_install.add_argument(
|
|
354
|
+
"--skip-commit-msg",
|
|
355
|
+
action="store_true",
|
|
356
|
+
help="Não instalar o hook de Conventional Commits"
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
p_hooks_sub.add_parser("uninstall", help="Remove hooks do Okam e restaura backups")
|
|
360
|
+
p_hooks_sub.add_parser("status", help="Mostra o status de cada hook de governança")
|
|
361
|
+
|
|
362
|
+
args = parser.parse_args()
|
|
363
|
+
|
|
364
|
+
if not args.command:
|
|
365
|
+
parser.print_help()
|
|
366
|
+
sys.exit(0)
|
|
367
|
+
|
|
368
|
+
# Roteamento dos comandos
|
|
369
|
+
wiki_dir = None
|
|
370
|
+
if hasattr(args, "wiki_dir") and args.wiki_dir:
|
|
371
|
+
wiki_dir = os.path.abspath(args.wiki_dir)
|
|
372
|
+
else:
|
|
373
|
+
wiki_dir = get_default_wiki_dir()
|
|
374
|
+
|
|
375
|
+
if args.command == "init":
|
|
376
|
+
cmd_init(wiki_dir)
|
|
377
|
+
elif args.command == "validate":
|
|
378
|
+
success = cmd_validate(wiki_dir)
|
|
379
|
+
sys.exit(0 if success else 1)
|
|
380
|
+
elif args.command in ["index", "dump-index"]:
|
|
381
|
+
if not os.path.exists(wiki_dir):
|
|
382
|
+
print_colored(f"Erro: Diretório do wiki não encontrado em {wiki_dir}", COLOR_RED)
|
|
383
|
+
sys.exit(1)
|
|
384
|
+
cmd_index(wiki_dir)
|
|
385
|
+
elif args.command == "new-skill":
|
|
386
|
+
create_new_skill()
|
|
387
|
+
elif args.command == "hooks":
|
|
388
|
+
cmd_hooks(args)
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
if __name__ == "__main__":
|
|
392
|
+
main()
|
okam/hooks/commit-msg
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# ⬡ Okam — Commit-msg Hook
|
|
3
|
+
# Enforces Conventional Commits format on commit messages.
|
|
4
|
+
# Part of the Okam governance framework. Install via: okam hooks install
|
|
5
|
+
|
|
6
|
+
# ── Colors ──────────────────────────────────────────────────────────────────
|
|
7
|
+
RED='\033[0;31m'
|
|
8
|
+
GREEN='\033[0;32m'
|
|
9
|
+
YELLOW='\033[0;33m'
|
|
10
|
+
BOLD='\033[1m'
|
|
11
|
+
RESET='\033[0m'
|
|
12
|
+
|
|
13
|
+
COMMIT_MSG_FILE="$1"
|
|
14
|
+
|
|
15
|
+
if [ ! -f "$COMMIT_MSG_FILE" ]; then
|
|
16
|
+
printf "${RED}⬡ Okam commit-msg: Arquivo de mensagem não encontrado.${RESET}\n"
|
|
17
|
+
exit 1
|
|
18
|
+
fi
|
|
19
|
+
|
|
20
|
+
# Read the first line (subject) of the commit message
|
|
21
|
+
FIRST_LINE=$(head -n1 "$COMMIT_MSG_FILE")
|
|
22
|
+
|
|
23
|
+
# Skip merge commits and fixup/squash commits
|
|
24
|
+
case "$FIRST_LINE" in
|
|
25
|
+
Merge*|Revert*|fixup!*|squash!*|amend!*)
|
|
26
|
+
exit 0
|
|
27
|
+
;;
|
|
28
|
+
esac
|
|
29
|
+
|
|
30
|
+
# Conventional Commits regex:
|
|
31
|
+
# <type>[optional scope][optional !]: <description>
|
|
32
|
+
# Types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert
|
|
33
|
+
PATTERN='^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\([a-zA-Z0-9._-]+\))?(!)?: .{1,}'
|
|
34
|
+
|
|
35
|
+
if ! echo "$FIRST_LINE" | grep -qE "$PATTERN"; then
|
|
36
|
+
printf "\n${RED}${BOLD}⬡ Okam commit-msg: Mensagem fora do padrão Conventional Commits.${RESET}\n"
|
|
37
|
+
printf "${RED} Recebido: ${FIRST_LINE}${RESET}\n"
|
|
38
|
+
printf "\n${YELLOW} Formato esperado: <tipo>[escopo opcional]: <descrição>${RESET}\n"
|
|
39
|
+
printf "${YELLOW} Tipos válidos: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert${RESET}\n"
|
|
40
|
+
printf "\n${YELLOW} Exemplos:${RESET}\n"
|
|
41
|
+
printf "${GREEN} feat: adiciona autenticação OAuth${RESET}\n"
|
|
42
|
+
printf "${GREEN} fix(api): corrige timeout no endpoint de login${RESET}\n"
|
|
43
|
+
printf "${GREEN} docs: atualiza README com instruções de deploy${RESET}\n"
|
|
44
|
+
printf "${GREEN} feat!: breaking change na API de pagamentos${RESET}\n"
|
|
45
|
+
printf "\n${YELLOW} Para bypass emergencial: git commit --no-verify${RESET}\n"
|
|
46
|
+
exit 1
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
# Validate subject line length (soft warning at 72, hard fail at 100)
|
|
50
|
+
SUBJECT_LEN=$(printf '%s' "$FIRST_LINE" | wc -c)
|
|
51
|
+
if [ "$SUBJECT_LEN" -gt 100 ]; then
|
|
52
|
+
printf "${RED}${BOLD}⬡ Okam commit-msg: Linha de assunto muito longa (${SUBJECT_LEN} chars, max 100).${RESET}\n"
|
|
53
|
+
exit 1
|
|
54
|
+
elif [ "$SUBJECT_LEN" -gt 72 ]; then
|
|
55
|
+
printf "${YELLOW}⬡ Okam commit-msg: Linha de assunto longa (${SUBJECT_LEN} chars, recomendado ≤72).${RESET}\n"
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
exit 0
|
okam/hooks/pre-commit
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# ⬡ Okam — Pre-commit Hook
|
|
3
|
+
# Validates OKF compliance for staged wiki files and detects leaked secrets.
|
|
4
|
+
# Part of the Okam governance framework. Install via: okam hooks install
|
|
5
|
+
|
|
6
|
+
set -e
|
|
7
|
+
|
|
8
|
+
# ── Colors ──────────────────────────────────────────────────────────────────
|
|
9
|
+
RED='\033[0;31m'
|
|
10
|
+
GREEN='\033[0;32m'
|
|
11
|
+
YELLOW='\033[0;33m'
|
|
12
|
+
BLUE='\033[0;34m'
|
|
13
|
+
BOLD='\033[1m'
|
|
14
|
+
RESET='\033[0m'
|
|
15
|
+
|
|
16
|
+
ERRORS=0
|
|
17
|
+
|
|
18
|
+
# ── 1. OKF Validation ──────────────────────────────────────────────────────
|
|
19
|
+
# Find staged .md files in wiki directories
|
|
20
|
+
WIKI_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '(knowledge/wiki|wiki)/.*\.md$' || true)
|
|
21
|
+
|
|
22
|
+
if [ -n "$WIKI_FILES" ]; then
|
|
23
|
+
printf "${BLUE}${BOLD}⬡ Okam pre-commit: Validando conformidade OKF...${RESET}\n"
|
|
24
|
+
|
|
25
|
+
# Check if okam CLI is available
|
|
26
|
+
if command -v okam >/dev/null 2>&1; then
|
|
27
|
+
for file in $WIKI_FILES; do
|
|
28
|
+
# Validate the staged version by using the working copy
|
|
29
|
+
# (okam validate works on files in the filesystem)
|
|
30
|
+
WIKI_DIR=$(dirname "$file")
|
|
31
|
+
RESULT=$(okam validate --wiki-dir "$WIKI_DIR" 2>&1) || true
|
|
32
|
+
|
|
33
|
+
BASENAME=$(basename "$file")
|
|
34
|
+
if echo "$RESULT" | grep -q "\[FALHA\] $BASENAME"; then
|
|
35
|
+
printf "${RED} ✗ ${BASENAME}${RESET}\n"
|
|
36
|
+
echo "$RESULT" | grep -A5 "\[FALHA\] $BASENAME" | grep "^ -" || true
|
|
37
|
+
ERRORS=$((ERRORS + 1))
|
|
38
|
+
else
|
|
39
|
+
printf "${GREEN} ✓ ${BASENAME}${RESET}\n"
|
|
40
|
+
fi
|
|
41
|
+
done
|
|
42
|
+
else
|
|
43
|
+
printf "${YELLOW} ⚠ okam CLI não encontrado. Pulando validação OKF.${RESET}\n"
|
|
44
|
+
printf "${YELLOW} Instale com: pip install -e . (na raiz do projeto Okam)${RESET}\n"
|
|
45
|
+
fi
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
# ── 2. Secret Detection ────────────────────────────────────────────────────
|
|
49
|
+
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM || true)
|
|
50
|
+
|
|
51
|
+
if [ -n "$STAGED_FILES" ]; then
|
|
52
|
+
# Check for .env files being committed
|
|
53
|
+
ENV_FILES=$(echo "$STAGED_FILES" | grep -E '\.env($|\.)' | grep -v '\.env\.example$' || true)
|
|
54
|
+
if [ -n "$ENV_FILES" ]; then
|
|
55
|
+
printf "\n${RED}${BOLD}⬡ Okam pre-commit: Arquivos .env detectados!${RESET}\n"
|
|
56
|
+
for f in $ENV_FILES; do
|
|
57
|
+
printf "${RED} ✗ ${f}${RESET}\n"
|
|
58
|
+
done
|
|
59
|
+
printf "${YELLOW} → Adicione ao .gitignore ou use .env.example como template.${RESET}\n"
|
|
60
|
+
ERRORS=$((ERRORS + 1))
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
# Scan staged content for secret patterns
|
|
64
|
+
SECRET_FOUND=0
|
|
65
|
+
for file in $STAGED_FILES; do
|
|
66
|
+
# Skip binary files and known safe extensions
|
|
67
|
+
case "$file" in
|
|
68
|
+
*.png|*.jpg|*.jpeg|*.gif|*.ico|*.woff|*.woff2|*.ttf|*.eot|*.svg|*.mp4|*.zip|*.tar|*.gz)
|
|
69
|
+
continue
|
|
70
|
+
;;
|
|
71
|
+
esac
|
|
72
|
+
|
|
73
|
+
# Get staged content (not working copy)
|
|
74
|
+
CONTENT=$(git show ":$file" 2>/dev/null) || continue
|
|
75
|
+
|
|
76
|
+
# AWS Access Key IDs
|
|
77
|
+
if echo "$CONTENT" | grep -qE 'AKIA[0-9A-Z]{16}'; then
|
|
78
|
+
printf "${RED} ✗ Possível AWS Key em: ${file}${RESET}\n"
|
|
79
|
+
SECRET_FOUND=1
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
# OpenAI / Anthropic / Generic API keys
|
|
83
|
+
if echo "$CONTENT" | grep -qE '(sk-[a-zA-Z0-9]{20,}|ghp_[a-zA-Z0-9]{36}|glpat-[a-zA-Z0-9\-]{20,}|xoxb-[0-9]{10,})'; then
|
|
84
|
+
printf "${RED} ✗ Possível API key/token em: ${file}${RESET}\n"
|
|
85
|
+
SECRET_FOUND=1
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
# Generic secret assignments (password, secret, token with long values)
|
|
89
|
+
if echo "$CONTENT" | grep -qiE '(password|secret|token|api_key|apikey|private_key)\s*[=:]\s*["\x27][^\s"'\'']{12,}'; then
|
|
90
|
+
printf "${RED} ✗ Possível segredo hardcoded em: ${file}${RESET}\n"
|
|
91
|
+
SECRET_FOUND=1
|
|
92
|
+
fi
|
|
93
|
+
done
|
|
94
|
+
|
|
95
|
+
if [ "$SECRET_FOUND" -eq 1 ]; then
|
|
96
|
+
printf "\n${RED}${BOLD}⬡ Okam pre-commit: Segredos detectados!${RESET}\n"
|
|
97
|
+
printf "${YELLOW} → Remova os segredos e use variáveis de ambiente.${RESET}\n"
|
|
98
|
+
printf "${YELLOW} → Para scan mais robusto, considere: https://github.com/gitleaks/gitleaks${RESET}\n"
|
|
99
|
+
ERRORS=$((ERRORS + 1))
|
|
100
|
+
fi
|
|
101
|
+
fi
|
|
102
|
+
|
|
103
|
+
# ── Result ──────────────────────────────────────────────────────────────────
|
|
104
|
+
if [ "$ERRORS" -gt 0 ]; then
|
|
105
|
+
printf "\n${RED}${BOLD}⬡ Okam: Commit bloqueado — corrija os erros acima.${RESET}\n"
|
|
106
|
+
printf "${YELLOW} Para bypass emergencial: git commit --no-verify${RESET}\n"
|
|
107
|
+
exit 1
|
|
108
|
+
fi
|
|
109
|
+
|
|
110
|
+
if [ -n "$WIKI_FILES" ]; then
|
|
111
|
+
printf "${GREEN}${BOLD}⬡ Okam: Todas as verificações passaram.${RESET}\n"
|
|
112
|
+
fi
|
|
113
|
+
|
|
114
|
+
exit 0
|
okam/hooks/pre-push
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# ⬡ Okam — Pre-push Hook
|
|
3
|
+
# Runs full OKF validation before pushing to remote.
|
|
4
|
+
# Part of the Okam governance framework. Install via: okam hooks install
|
|
5
|
+
|
|
6
|
+
set -e
|
|
7
|
+
|
|
8
|
+
# ── Colors ──────────────────────────────────────────────────────────────────
|
|
9
|
+
RED='\033[0;31m'
|
|
10
|
+
GREEN='\033[0;32m'
|
|
11
|
+
YELLOW='\033[0;33m'
|
|
12
|
+
BLUE='\033[0;34m'
|
|
13
|
+
BOLD='\033[1m'
|
|
14
|
+
RESET='\033[0m'
|
|
15
|
+
|
|
16
|
+
printf "${BLUE}${BOLD}⬡ Okam pre-push: Executando validação OKF completa...${RESET}\n"
|
|
17
|
+
|
|
18
|
+
# Check if okam CLI is available
|
|
19
|
+
if ! command -v okam >/dev/null 2>&1; then
|
|
20
|
+
printf "${YELLOW} ⚠ okam CLI não encontrado. Pulando validação OKF.${RESET}\n"
|
|
21
|
+
printf "${YELLOW} Instale com: pip install -e . (na raiz do projeto Okam)${RESET}\n"
|
|
22
|
+
exit 0
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
# Run full validation
|
|
26
|
+
if okam validate >/dev/null 2>&1; then
|
|
27
|
+
printf "${GREEN}${BOLD}⬡ Okam pre-push: Validação OKF completa — tudo OK.${RESET}\n"
|
|
28
|
+
exit 0
|
|
29
|
+
else
|
|
30
|
+
printf "\n${RED}${BOLD}⬡ Okam pre-push: Validação OKF falhou!${RESET}\n"
|
|
31
|
+
printf "${YELLOW} Detalhes:${RESET}\n"
|
|
32
|
+
okam validate 2>&1 | grep -E '\[(FALHA|OK)\]' || okam validate 2>&1
|
|
33
|
+
printf "\n${YELLOW} Corrija os erros antes de enviar ao remote.${RESET}\n"
|
|
34
|
+
printf "${YELLOW} Para bypass emergencial: git push --no-verify${RESET}\n"
|
|
35
|
+
exit 1
|
|
36
|
+
fi
|
okam/hooks.py
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Okam Git Hooks Manager — Instala, desinstala e verifica hooks de governança.
|
|
3
|
+
|
|
4
|
+
Hooks portáveis (POSIX shell) que rodam validação OKF, detecção de segredos
|
|
5
|
+
e Conventional Commits antes de commits e pushes.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import shutil
|
|
10
|
+
import stat
|
|
11
|
+
import sys
|
|
12
|
+
|
|
13
|
+
from okam.skill_creator import (
|
|
14
|
+
print_colored,
|
|
15
|
+
find_workspace_root,
|
|
16
|
+
COLOR_GREEN,
|
|
17
|
+
COLOR_YELLOW,
|
|
18
|
+
COLOR_RED,
|
|
19
|
+
COLOR_BLUE,
|
|
20
|
+
COLOR_BOLD,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
# Hooks que o Okam gerencia
|
|
24
|
+
HOOK_NAMES = ["pre-commit", "commit-msg", "pre-push"]
|
|
25
|
+
|
|
26
|
+
# Marcador para identificar hooks instalados pelo Okam
|
|
27
|
+
OKAM_MARKER = "# ⬡ Okam"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _get_hooks_source_dir():
|
|
31
|
+
"""Retorna o diretório onde os hook scripts do Okam estão armazenados."""
|
|
32
|
+
# Tenta encontrar a pasta hooks/ relativa ao pacote instalado
|
|
33
|
+
# Primeiro: relativo ao workspace root (dev mode / pip install -e .)
|
|
34
|
+
workspace = find_workspace_root()
|
|
35
|
+
hooks_dir = os.path.join(workspace, "hooks")
|
|
36
|
+
if os.path.isdir(hooks_dir):
|
|
37
|
+
return hooks_dir
|
|
38
|
+
|
|
39
|
+
# Fallback: relativo ao pacote Python instalado
|
|
40
|
+
package_dir = os.path.dirname(os.path.abspath(__file__))
|
|
41
|
+
hooks_dir = os.path.join(package_dir, "hooks")
|
|
42
|
+
if os.path.isdir(hooks_dir):
|
|
43
|
+
return hooks_dir
|
|
44
|
+
|
|
45
|
+
return None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _get_git_hooks_dir(repo_root=None):
|
|
49
|
+
"""Retorna o caminho para .git/hooks/ do repositório."""
|
|
50
|
+
if repo_root is None:
|
|
51
|
+
repo_root = find_workspace_root()
|
|
52
|
+
|
|
53
|
+
git_dir = os.path.join(repo_root, ".git")
|
|
54
|
+
|
|
55
|
+
# Suporta git worktrees onde .git é um arquivo apontando para o gitdir
|
|
56
|
+
if os.path.isfile(git_dir):
|
|
57
|
+
with open(git_dir, "r", encoding="utf-8") as f:
|
|
58
|
+
content = f.read().strip()
|
|
59
|
+
if content.startswith("gitdir:"):
|
|
60
|
+
git_dir = content[7:].strip()
|
|
61
|
+
if not os.path.isabs(git_dir):
|
|
62
|
+
git_dir = os.path.normpath(os.path.join(repo_root, git_dir))
|
|
63
|
+
|
|
64
|
+
hooks_dir = os.path.join(git_dir, "hooks")
|
|
65
|
+
return hooks_dir
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _is_okam_hook(filepath):
|
|
69
|
+
"""Verifica se um hook foi instalado pelo Okam (contém o marcador)."""
|
|
70
|
+
if not os.path.exists(filepath):
|
|
71
|
+
return False
|
|
72
|
+
try:
|
|
73
|
+
with open(filepath, "r", encoding="utf-8") as f:
|
|
74
|
+
content = f.read(512) # Ler só o início
|
|
75
|
+
return OKAM_MARKER in content
|
|
76
|
+
except (OSError, UnicodeDecodeError):
|
|
77
|
+
return False
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def install_hooks(repo_root=None, skip_hooks=None):
|
|
81
|
+
"""
|
|
82
|
+
Instala hooks de governança do Okam no repositório.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
repo_root: Raiz do repositório Git. Se None, autodetecta.
|
|
86
|
+
skip_hooks: Lista de nomes de hooks para pular (ex: ['commit-msg']).
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
Tuple (installed, skipped, errors) com contagens.
|
|
90
|
+
"""
|
|
91
|
+
if skip_hooks is None:
|
|
92
|
+
skip_hooks = []
|
|
93
|
+
|
|
94
|
+
source_dir = _get_hooks_source_dir()
|
|
95
|
+
if source_dir is None:
|
|
96
|
+
print_colored(
|
|
97
|
+
"Erro: Pasta 'hooks/' não encontrada. Verifique a instalação do Okam.",
|
|
98
|
+
COLOR_RED,
|
|
99
|
+
)
|
|
100
|
+
return 0, 0, 1
|
|
101
|
+
|
|
102
|
+
git_hooks_dir = _get_git_hooks_dir(repo_root)
|
|
103
|
+
os.makedirs(git_hooks_dir, exist_ok=True)
|
|
104
|
+
|
|
105
|
+
installed = 0
|
|
106
|
+
skipped = 0
|
|
107
|
+
errors = 0
|
|
108
|
+
|
|
109
|
+
for hook_name in HOOK_NAMES:
|
|
110
|
+
if hook_name in skip_hooks:
|
|
111
|
+
print_colored(f" [PULADO] {hook_name} (--skip-{hook_name})", COLOR_YELLOW)
|
|
112
|
+
skipped += 1
|
|
113
|
+
continue
|
|
114
|
+
|
|
115
|
+
source_path = os.path.join(source_dir, hook_name)
|
|
116
|
+
target_path = os.path.join(git_hooks_dir, hook_name)
|
|
117
|
+
|
|
118
|
+
if not os.path.exists(source_path):
|
|
119
|
+
print_colored(
|
|
120
|
+
f" [ERRO] Script fonte não encontrado: {source_path}", COLOR_RED
|
|
121
|
+
)
|
|
122
|
+
errors += 1
|
|
123
|
+
continue
|
|
124
|
+
|
|
125
|
+
# Se já existe um hook que NÃO é do Okam, fazer backup
|
|
126
|
+
if os.path.exists(target_path) and not _is_okam_hook(target_path):
|
|
127
|
+
backup_path = target_path + ".bak"
|
|
128
|
+
shutil.copy2(target_path, backup_path)
|
|
129
|
+
print_colored(
|
|
130
|
+
f" [BACKUP] {hook_name} existente salvo em {hook_name}.bak",
|
|
131
|
+
COLOR_YELLOW,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# Copiar o hook
|
|
135
|
+
shutil.copy2(source_path, target_path)
|
|
136
|
+
|
|
137
|
+
# Setar permissão de execução (no-op funcional no Windows)
|
|
138
|
+
try:
|
|
139
|
+
current = os.stat(target_path).st_mode
|
|
140
|
+
os.chmod(target_path, current | stat.S_IEXEC | stat.S_IXGRP | stat.S_IXOTH)
|
|
141
|
+
except OSError:
|
|
142
|
+
pass # Windows pode não suportar chmod, mas Git for Windows não precisa
|
|
143
|
+
|
|
144
|
+
print_colored(f" [INSTALADO] {hook_name}", COLOR_GREEN)
|
|
145
|
+
installed += 1
|
|
146
|
+
|
|
147
|
+
return installed, skipped, errors
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def uninstall_hooks(repo_root=None):
|
|
151
|
+
"""
|
|
152
|
+
Remove hooks do Okam e restaura backups se existirem.
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
Tuple (removed, restored, not_found) com contagens.
|
|
156
|
+
"""
|
|
157
|
+
git_hooks_dir = _get_git_hooks_dir(repo_root)
|
|
158
|
+
|
|
159
|
+
removed = 0
|
|
160
|
+
restored = 0
|
|
161
|
+
not_found = 0
|
|
162
|
+
|
|
163
|
+
for hook_name in HOOK_NAMES:
|
|
164
|
+
target_path = os.path.join(git_hooks_dir, hook_name)
|
|
165
|
+
backup_path = target_path + ".bak"
|
|
166
|
+
|
|
167
|
+
if not os.path.exists(target_path):
|
|
168
|
+
print_colored(f" [NÃO ENCONTRADO] {hook_name}", COLOR_YELLOW)
|
|
169
|
+
not_found += 1
|
|
170
|
+
continue
|
|
171
|
+
|
|
172
|
+
if not _is_okam_hook(target_path):
|
|
173
|
+
print_colored(
|
|
174
|
+
f" [PULADO] {hook_name} — não foi instalado pelo Okam", COLOR_YELLOW
|
|
175
|
+
)
|
|
176
|
+
continue
|
|
177
|
+
|
|
178
|
+
os.remove(target_path)
|
|
179
|
+
|
|
180
|
+
if os.path.exists(backup_path):
|
|
181
|
+
shutil.move(backup_path, target_path)
|
|
182
|
+
print_colored(
|
|
183
|
+
f" [RESTAURADO] {hook_name} (backup restaurado)", COLOR_GREEN
|
|
184
|
+
)
|
|
185
|
+
restored += 1
|
|
186
|
+
else:
|
|
187
|
+
print_colored(f" [REMOVIDO] {hook_name}", COLOR_GREEN)
|
|
188
|
+
removed += 1
|
|
189
|
+
|
|
190
|
+
return removed, restored, not_found
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def get_hooks_status(repo_root=None):
|
|
194
|
+
"""
|
|
195
|
+
Verifica o status de cada hook de governança.
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
Dict {hook_name: status_string}
|
|
199
|
+
"""
|
|
200
|
+
git_hooks_dir = _get_git_hooks_dir(repo_root)
|
|
201
|
+
status = {}
|
|
202
|
+
|
|
203
|
+
for hook_name in HOOK_NAMES:
|
|
204
|
+
target_path = os.path.join(git_hooks_dir, hook_name)
|
|
205
|
+
|
|
206
|
+
if not os.path.exists(target_path):
|
|
207
|
+
status[hook_name] = "não instalado"
|
|
208
|
+
elif _is_okam_hook(target_path):
|
|
209
|
+
status[hook_name] = "instalado (Okam)"
|
|
210
|
+
else:
|
|
211
|
+
status[hook_name] = "instalado (externo)"
|
|
212
|
+
|
|
213
|
+
return status
|
okam/manager.py
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
import sys
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
|
|
6
|
+
VALID_TYPES = {"index", "concept", "architecture", "runbook", "entity", "benchmark"}
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def parse_simple_yaml(yaml_str):
|
|
10
|
+
"""Parser minimalista de YAML para frontmatter. Sem dependências externas."""
|
|
11
|
+
lines = yaml_str.splitlines()
|
|
12
|
+
data = {}
|
|
13
|
+
current_key = None
|
|
14
|
+
for line in lines:
|
|
15
|
+
stripped = line.strip()
|
|
16
|
+
if not stripped or stripped.startswith('#'):
|
|
17
|
+
continue
|
|
18
|
+
|
|
19
|
+
match = re.match(r'^([a-zA-Z0-9_\-]+)\s*:\s*(.*)$', line)
|
|
20
|
+
if match:
|
|
21
|
+
current_key = match.group(1)
|
|
22
|
+
val = match.group(2).strip()
|
|
23
|
+
if val.startswith('[') and val.endswith(']'):
|
|
24
|
+
items = [x.strip() for x in val[1:-1].split(',')]
|
|
25
|
+
cleaned_items = []
|
|
26
|
+
for x in items:
|
|
27
|
+
if (x.startswith('"') and x.endswith('"')) or (x.startswith("'") and x.endswith("'")):
|
|
28
|
+
x = x[1:-1]
|
|
29
|
+
cleaned_items.append(x)
|
|
30
|
+
data[current_key] = cleaned_items
|
|
31
|
+
elif val == "":
|
|
32
|
+
data[current_key] = None
|
|
33
|
+
else:
|
|
34
|
+
if (val.startswith('"') and val.endswith('"')) or (val.startswith("'") and val.endswith("'")):
|
|
35
|
+
val = val[1:-1]
|
|
36
|
+
data[current_key] = val
|
|
37
|
+
elif line.startswith(' - ') and current_key:
|
|
38
|
+
val = line[4:].strip()
|
|
39
|
+
if (val.startswith('"') and val.endswith('"')) or (val.startswith("'") and val.endswith("'")):
|
|
40
|
+
val = val[1:-1]
|
|
41
|
+
if not isinstance(data.get(current_key), list):
|
|
42
|
+
data[current_key] = []
|
|
43
|
+
data[current_key].append(val)
|
|
44
|
+
return data
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def serialize_simple_yaml(data):
|
|
48
|
+
"""Serializa dicionário para YAML simples."""
|
|
49
|
+
lines = []
|
|
50
|
+
for key, val in data.items():
|
|
51
|
+
if val is None:
|
|
52
|
+
lines.append(f"{key}:")
|
|
53
|
+
elif isinstance(val, list):
|
|
54
|
+
if len(val) == 0:
|
|
55
|
+
lines.append(f"{key}: []")
|
|
56
|
+
else:
|
|
57
|
+
lines.append(f"{key}:")
|
|
58
|
+
for item in val:
|
|
59
|
+
lines.append(f" - {item}")
|
|
60
|
+
elif isinstance(val, str):
|
|
61
|
+
if any(c in val for c in [':', '#', '[', ']', '{', '}', ',', '"', "'"]) or val.strip() != val:
|
|
62
|
+
escaped = val.replace('"', '\\"')
|
|
63
|
+
lines.append(f'{key}: "{escaped}"')
|
|
64
|
+
else:
|
|
65
|
+
lines.append(f"{key}: {val}")
|
|
66
|
+
else:
|
|
67
|
+
lines.append(f"{key}: {val}")
|
|
68
|
+
return "\n".join(lines)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def load_markdown_file(filepath):
|
|
72
|
+
"""Carrega um arquivo Markdown e separa frontmatter do corpo."""
|
|
73
|
+
with open(filepath, 'r', encoding='utf-8') as f:
|
|
74
|
+
content = f.read()
|
|
75
|
+
|
|
76
|
+
frontmatter = {}
|
|
77
|
+
body = content
|
|
78
|
+
match = re.match(r'^---\s*\n(.*?)\n---\s*\n', content, re.DOTALL)
|
|
79
|
+
if match:
|
|
80
|
+
yaml_content = match.group(1)
|
|
81
|
+
try:
|
|
82
|
+
frontmatter = parse_simple_yaml(yaml_content)
|
|
83
|
+
body = content[match.end():]
|
|
84
|
+
except Exception as e:
|
|
85
|
+
raise ValueError(f"Erro ao parsear YAML em {os.path.basename(filepath)}: {e}")
|
|
86
|
+
|
|
87
|
+
return frontmatter, body
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def save_markdown_file(filepath, frontmatter, body):
|
|
91
|
+
"""Salva arquivo Markdown com frontmatter YAML ordenado."""
|
|
92
|
+
ordered_keys = ['title', 'description', 'type', 'resource', 'timestamp', 'tags', 'parent']
|
|
93
|
+
ordered_fm = {}
|
|
94
|
+
|
|
95
|
+
for key in ordered_keys:
|
|
96
|
+
if key in frontmatter:
|
|
97
|
+
ordered_fm[key] = frontmatter[key]
|
|
98
|
+
|
|
99
|
+
for key, val in frontmatter.items():
|
|
100
|
+
if key not in ordered_keys:
|
|
101
|
+
ordered_fm[key] = val
|
|
102
|
+
|
|
103
|
+
yaml_str = serialize_simple_yaml(ordered_fm).strip()
|
|
104
|
+
|
|
105
|
+
with open(filepath, 'w', encoding='utf-8') as f:
|
|
106
|
+
f.write(f"---\n{yaml_str}\n---\n{body}")
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def extract_h1(body):
|
|
110
|
+
"""Extrai o primeiro heading H1 do corpo do documento."""
|
|
111
|
+
match = re.search(r'^#\s+(.+)$', body, re.MULTILINE)
|
|
112
|
+
return match.group(1).strip() if match else ""
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def validate_file(filepath):
|
|
116
|
+
"""Valida um arquivo contra o padrão OKF. Retorna (is_valid, errors)."""
|
|
117
|
+
try:
|
|
118
|
+
frontmatter, body = load_markdown_file(filepath)
|
|
119
|
+
except Exception as e:
|
|
120
|
+
return False, [str(e)]
|
|
121
|
+
|
|
122
|
+
if frontmatter is None:
|
|
123
|
+
return False, ["Erro de parse no YAML"]
|
|
124
|
+
|
|
125
|
+
errors = []
|
|
126
|
+
|
|
127
|
+
required_keys = ['title', 'description', 'type', 'resource', 'timestamp', 'tags', 'parent']
|
|
128
|
+
for key in required_keys:
|
|
129
|
+
if key not in frontmatter or frontmatter[key] is None:
|
|
130
|
+
errors.append(f"Chave obrigatória ausente: '{key}'")
|
|
131
|
+
|
|
132
|
+
if 'type' in frontmatter and frontmatter['type'] not in VALID_TYPES:
|
|
133
|
+
errors.append(f"Tipo inválido '{frontmatter['type']}'. Deve ser: {', '.join(VALID_TYPES)}")
|
|
134
|
+
|
|
135
|
+
if 'timestamp' in frontmatter:
|
|
136
|
+
ts = str(frontmatter['timestamp'])
|
|
137
|
+
if not re.match(r'^\d{4}-\d{2}-\d{2}$', ts):
|
|
138
|
+
errors.append(f"Formato de timestamp inválido '{ts}'. Deve ser YYYY-MM-DD")
|
|
139
|
+
|
|
140
|
+
if 'title' in frontmatter and body:
|
|
141
|
+
expected_title = extract_h1(body)
|
|
142
|
+
if expected_title and frontmatter['title'] != expected_title:
|
|
143
|
+
errors.append(f"Título no frontmatter ('{frontmatter['title']}') difere do H1 ('{expected_title}')")
|
|
144
|
+
|
|
145
|
+
return len(errors) == 0, errors
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def get_wiki_files(wiki_dir):
|
|
149
|
+
"""Retorna todos os arquivos .md de um wiki_dir."""
|
|
150
|
+
files = []
|
|
151
|
+
for root, _, filenames in os.walk(wiki_dir):
|
|
152
|
+
for f in filenames:
|
|
153
|
+
if f.endswith('.md'):
|
|
154
|
+
files.append(os.path.join(root, f))
|
|
155
|
+
return sorted(files)
|
okam/skill_creator.py
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
# Cores ANSI
|
|
6
|
+
COLOR_GREEN = "\033[92m"
|
|
7
|
+
COLOR_BLUE = "\033[94m"
|
|
8
|
+
COLOR_YELLOW = "\033[93m"
|
|
9
|
+
COLOR_RED = "\033[91m"
|
|
10
|
+
COLOR_BOLD = "\033[1m"
|
|
11
|
+
COLOR_RESET = "\033[0m"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def print_colored(text, color):
|
|
15
|
+
"""Imprime texto colorido se o terminal suportar."""
|
|
16
|
+
# Ativa suporte a ANSI no Windows 10+
|
|
17
|
+
if sys.platform.startswith('win'):
|
|
18
|
+
os.system('')
|
|
19
|
+
sys.stdout.write(f"{color}{text}{COLOR_RESET}\n")
|
|
20
|
+
sys.stdout.flush()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def find_workspace_root():
|
|
24
|
+
"""Sobe a árvore de diretórios a partir do atual para achar a raiz do workspace."""
|
|
25
|
+
current = os.path.abspath(os.getcwd())
|
|
26
|
+
while True:
|
|
27
|
+
# Indicadores de raiz do workspace okam/Juliano
|
|
28
|
+
indicators = ['.git', '.agents', 'AGENTS.md', 'Dashboard_Central.md']
|
|
29
|
+
for ind in indicators:
|
|
30
|
+
if os.path.exists(os.path.join(current, ind)):
|
|
31
|
+
return current
|
|
32
|
+
|
|
33
|
+
parent = os.path.dirname(current)
|
|
34
|
+
if parent == current:
|
|
35
|
+
# Chegou na raiz do sistema de arquivos
|
|
36
|
+
break
|
|
37
|
+
current = parent
|
|
38
|
+
|
|
39
|
+
return os.path.abspath(os.getcwd())
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def ask_question(prompt_text, default=None, validator=None):
|
|
43
|
+
"""Faz uma pergunta ao usuário no terminal e retorna a resposta validada."""
|
|
44
|
+
while True:
|
|
45
|
+
display = f"{COLOR_BOLD}{prompt_text}{COLOR_RESET}"
|
|
46
|
+
if default is not None:
|
|
47
|
+
display += f" [{COLOR_BLUE}{default}{COLOR_RESET}]"
|
|
48
|
+
display += ": "
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
resp = input(display).strip()
|
|
52
|
+
except (KeyboardInterrupt, EOFError):
|
|
53
|
+
print_colored("\nOperação cancelada pelo usuário.", COLOR_RED)
|
|
54
|
+
sys.exit(1)
|
|
55
|
+
|
|
56
|
+
if not resp and default is not None:
|
|
57
|
+
resp = default
|
|
58
|
+
|
|
59
|
+
if not resp:
|
|
60
|
+
print_colored("A resposta não pode ser vazia.", COLOR_YELLOW)
|
|
61
|
+
continue
|
|
62
|
+
|
|
63
|
+
if validator:
|
|
64
|
+
is_valid, err_msg = validator(resp)
|
|
65
|
+
if not is_valid:
|
|
66
|
+
print_colored(err_msg, COLOR_RED)
|
|
67
|
+
continue
|
|
68
|
+
|
|
69
|
+
return resp
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def validate_skill_name(name):
|
|
73
|
+
"""Valida se o nome da skill é seguro e segue o padrão kebab-case."""
|
|
74
|
+
if not re.match(r'^[a-z0-9\-]+$', name):
|
|
75
|
+
return False, "O nome da skill deve conter apenas letras minúsculas, números e hifens (ex: minha-nova-skill)."
|
|
76
|
+
return True, ""
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def create_new_skill():
|
|
80
|
+
"""Fluxo interativo de criação de uma nova skill."""
|
|
81
|
+
print_colored("\n⬡ Criando Nova Skill para o Agente ⬡\n", COLOR_BLUE + COLOR_BOLD)
|
|
82
|
+
|
|
83
|
+
workspace_root = find_workspace_root()
|
|
84
|
+
agents_dir = os.path.join(workspace_root, ".agents")
|
|
85
|
+
skills_dir = os.path.join(agents_dir, "skills")
|
|
86
|
+
|
|
87
|
+
if not os.path.exists(agents_dir):
|
|
88
|
+
print_colored(f"Aviso: Diretório de governança '.agents' não encontrado na raiz: {workspace_root}", COLOR_YELLOW)
|
|
89
|
+
# Pergunta se quer criar a pasta .agents
|
|
90
|
+
confirm = ask_question("Deseja criar a pasta '.agents' na raiz deste diretório?", "sim").lower()
|
|
91
|
+
if confirm in ['s', 'sim', 'y', 'yes']:
|
|
92
|
+
os.makedirs(skills_dir, exist_ok=True)
|
|
93
|
+
else:
|
|
94
|
+
print_colored("Cancelado. Não é possível prosseguir sem a pasta de destino.", COLOR_RED)
|
|
95
|
+
sys.exit(1)
|
|
96
|
+
else:
|
|
97
|
+
os.makedirs(skills_dir, exist_ok=True)
|
|
98
|
+
|
|
99
|
+
skill_name = ask_question("Nome da Skill (kebab-case)", validator=validate_skill_name)
|
|
100
|
+
|
|
101
|
+
target_skill_dir = os.path.join(skills_dir, skill_name)
|
|
102
|
+
if os.path.exists(target_skill_dir):
|
|
103
|
+
print_colored(f"Erro: A skill '{skill_name}' já existe em: {target_skill_dir}", COLOR_RED)
|
|
104
|
+
sys.exit(1)
|
|
105
|
+
|
|
106
|
+
description = ask_question("Descrição da Skill (uma frase clara do que ela faz)")
|
|
107
|
+
version = ask_question("Versão inicial", default="1.0")
|
|
108
|
+
|
|
109
|
+
priority = ask_question(
|
|
110
|
+
"Prioridade (CRITICAL, HIGH, NORMAL, LOW)",
|
|
111
|
+
default="NORMAL",
|
|
112
|
+
validator=lambda x: (x.upper() in ["CRITICAL", "HIGH", "NORMAL", "LOW"], "Prioridade inválida. Escolha entre: CRITICAL, HIGH, NORMAL, LOW.")
|
|
113
|
+
).upper()
|
|
114
|
+
|
|
115
|
+
create_dirs_prompt = ask_question("Criar subpastas de apoio (scripts, examples, resources, references)? (sim/não)", default="não").lower()
|
|
116
|
+
create_subdirs = create_dirs_prompt in ['s', 'sim', 'y', 'yes']
|
|
117
|
+
|
|
118
|
+
# Criando diretórios e arquivos
|
|
119
|
+
os.makedirs(target_skill_dir, exist_ok=True)
|
|
120
|
+
|
|
121
|
+
skill_md_path = os.path.join(target_skill_dir, "SKILL.md")
|
|
122
|
+
|
|
123
|
+
# Capitaliza as palavras do nome da skill para o título
|
|
124
|
+
title_display = " ".join(word.capitalize() for word in skill_name.split("-"))
|
|
125
|
+
|
|
126
|
+
skill_content = f"""---
|
|
127
|
+
name: {skill_name}
|
|
128
|
+
description: {description}
|
|
129
|
+
version: "{version}"
|
|
130
|
+
priority: {priority}
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
# {title_display}
|
|
134
|
+
|
|
135
|
+
## Objetivo
|
|
136
|
+
|
|
137
|
+
Descreva detalhadamente o objetivo e o escopo operacional desta skill.
|
|
138
|
+
|
|
139
|
+
## Diretrizes de Uso
|
|
140
|
+
|
|
141
|
+
- Regra 1: Defina como o agente deve agir ao executar esta skill.
|
|
142
|
+
- Regra 2: Seja cirúrgico e prefira simplicidade.
|
|
143
|
+
|
|
144
|
+
## Exemplos / Referências
|
|
145
|
+
|
|
146
|
+
Adicione exemplos de prompt ou referências úteis de código aqui.
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
with open(skill_md_path, 'w', encoding='utf-8') as f:
|
|
150
|
+
f.write(skill_content)
|
|
151
|
+
|
|
152
|
+
print_colored(f"\n[CRIADO] {os.path.relpath(skill_md_path, workspace_root)}", COLOR_GREEN)
|
|
153
|
+
|
|
154
|
+
if create_subdirs:
|
|
155
|
+
subdirs = ["scripts", "examples", "resources", "references"]
|
|
156
|
+
for sd in subdirs:
|
|
157
|
+
sd_path = os.path.join(target_skill_dir, sd)
|
|
158
|
+
os.makedirs(sd_path, exist_ok=True)
|
|
159
|
+
# Cria um gitkeep para que as pastas vazias possam ser commitadas
|
|
160
|
+
gitkeep_path = os.path.join(sd_path, ".gitkeep")
|
|
161
|
+
with open(gitkeep_path, 'w') as f:
|
|
162
|
+
pass
|
|
163
|
+
print_colored(f"[CRIADO] {os.path.relpath(sd_path, workspace_root)}/", COLOR_GREEN)
|
|
164
|
+
|
|
165
|
+
print_colored(f"\n✅ Skill '{skill_name}' criada com sucesso na governança do workspace!", COLOR_GREEN + COLOR_BOLD)
|
|
166
|
+
print_colored(f"Caminho: {target_skill_dir}\n", COLOR_BLUE)
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: okam
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Framework de governança de IA, memória persistente e gestão de conhecimento
|
|
5
|
+
Author: Juliano Ceconi
|
|
6
|
+
License: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Requires-Python: >=3.8
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
<div align="center">
|
|
15
|
+
|
|
16
|
+
# ⬡ Okam
|
|
17
|
+
|
|
18
|
+
**Pare de Redescobrir. Comece a Governar.**
|
|
19
|
+
|
|
20
|
+
Framework open-source para governança de IA, memória persistente e gestão de conhecimento.
|
|
21
|
+
|
|
22
|
+
[](./LICENSE)
|
|
23
|
+
[](https://python.org)
|
|
24
|
+
[](https://github.com/juliano-ceconi/okam/pulls)
|
|
25
|
+
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## O Que É Isso?
|
|
31
|
+
|
|
32
|
+
**Okam** é um framework que resolve um problema simples: **seus agentes de IA não têm memória.**
|
|
33
|
+
|
|
34
|
+
Toda vez que você abre uma sessão com seu copilot, ele começa do zero. Sem contexto das decisões passadas, sem governança, sem reutilização de conhecimento.
|
|
35
|
+
|
|
36
|
+
Okam resolve isso com:
|
|
37
|
+
|
|
38
|
+
- 🏛️ **Governança** — Regras claras para seus agentes (AGENTS.md + pipeline de 4 fases)
|
|
39
|
+
- 🧩 **Skills** — Capacidades modulares reutilizáveis entre projetos
|
|
40
|
+
- 📚 **Wiki de Conhecimento** — Memória persistente no formato OKF (não é RAG)
|
|
41
|
+
- 🔍 **Pipeline de Metadados** — Extrai a "alma" dos seus projetos automaticamente
|
|
42
|
+
|
|
43
|
+
## Quick Start (5 minutos)
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# 1. Clone e instale localmente em modo de desenvolvimento
|
|
47
|
+
git clone https://github.com/juliano-ceconi/okam.git
|
|
48
|
+
cd okam
|
|
49
|
+
pip install -e .
|
|
50
|
+
|
|
51
|
+
# 2. Inicialize o wiki com seed pages
|
|
52
|
+
okam init
|
|
53
|
+
|
|
54
|
+
# 3. Customize o AGENTS.md com as regras do seu projeto
|
|
55
|
+
# (edite AGENTS.md com suas preferências)
|
|
56
|
+
|
|
57
|
+
# 4. Valide a conformidade
|
|
58
|
+
okam validate
|
|
59
|
+
|
|
60
|
+
# 5. Crie uma nova skill interativamente
|
|
61
|
+
okam new-skill
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
*Nota: O script legado `python knowledge/scripts/okf_manager.py` continua funcionando como um wrapper de compatibilidade.*
|
|
65
|
+
|
|
66
|
+
Para o guia completo, veja o [QUICKSTART.md](./QUICKSTART.md).
|
|
67
|
+
|
|
68
|
+
## Conceitos Core
|
|
69
|
+
|
|
70
|
+
| Conceito | O Que É | Doc |
|
|
71
|
+
|:---|:---|:---|
|
|
72
|
+
| **LLM Wiki** | Memória persistente vs RAG tradicional | [docs/concepts/llm-wiki.md](./docs/concepts/llm-wiki.md) |
|
|
73
|
+
| **OKF** | Formato padronizado de metadados para conhecimento | [docs/concepts/okf-format.md](./docs/concepts/okf-format.md) |
|
|
74
|
+
| **Deep Metadata** | Pipeline de 4 fases para extrair contexto | [docs/concepts/deep-metadata.md](./docs/concepts/deep-metadata.md) |
|
|
75
|
+
| **Guided Tours** | Padrão TOUR.md para onboarding de agentes | [docs/concepts/guided-tours.md](./docs/concepts/guided-tours.md) |
|
|
76
|
+
| **Git Hooks** | Governança automatizada via hooks pre-commit/push | [docs/concepts/git-hooks.md](./docs/concepts/git-hooks.md) |
|
|
77
|
+
|
|
78
|
+
## Estrutura do Projeto
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
okam/
|
|
82
|
+
├── .agents/
|
|
83
|
+
│ ├── rules/ # Padrões de governança
|
|
84
|
+
│ ├── skills/ # Capacidades modulares
|
|
85
|
+
│ │ ├── deep-metadata-analysis/
|
|
86
|
+
│ │ ├── knowledge-wiki/
|
|
87
|
+
│ │ └── memory-maintenance/
|
|
88
|
+
│ └── workflows/ # Pipelines de execução
|
|
89
|
+
├── hooks/ # Git hooks portáveis (POSIX sh)
|
|
90
|
+
│ ├── pre-commit # Validação OKF + detecção de segredos
|
|
91
|
+
│ ├── commit-msg # Conventional Commits
|
|
92
|
+
│ └── pre-push # Validação OKF completa
|
|
93
|
+
├── knowledge/
|
|
94
|
+
│ ├── wiki/ # Páginas de síntese (OKF)
|
|
95
|
+
│ ├── raw-sources/ # Fontes brutas
|
|
96
|
+
│ └── scripts/ # Validador OKF
|
|
97
|
+
├── templates/ # Templates reutilizáveis
|
|
98
|
+
├── docs/
|
|
99
|
+
│ ├── concepts/ # Documentação de conceitos
|
|
100
|
+
│ └── diagrams/ # Diagramas Mermaid
|
|
101
|
+
├── landing/ # Landing page
|
|
102
|
+
├── AGENTS.md # Governança central
|
|
103
|
+
├── QUICKSTART.md # Guia rápido
|
|
104
|
+
└── LICENSE # MIT
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Construído Com
|
|
108
|
+
|
|
109
|
+
- **Python** — Validador OKF (zero dependências, stdlib only)
|
|
110
|
+
- **Markdown** — Documentação e Wiki
|
|
111
|
+
- **YAML** — Metadados estruturados
|
|
112
|
+
|
|
113
|
+
## Contribuindo
|
|
114
|
+
|
|
115
|
+
1. Fork o repositório
|
|
116
|
+
2. Crie uma branch para sua feature (`git checkout -b feat/minha-feature`)
|
|
117
|
+
3. Instale os hooks de governança (`okam hooks install`)
|
|
118
|
+
4. Commit suas mudanças (`git commit -m 'feat: adiciona minha feature'`)
|
|
119
|
+
5. Push para a branch (`git push origin feat/minha-feature`)
|
|
120
|
+
6. Abra um Pull Request
|
|
121
|
+
|
|
122
|
+
## Licença
|
|
123
|
+
|
|
124
|
+
Este projeto está sob a licença MIT. Veja [LICENSE](./LICENSE) para mais detalhes.
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
<div align="center">
|
|
129
|
+
|
|
130
|
+
**⬡ Feito por devs, para devs.**
|
|
131
|
+
|
|
132
|
+
[Landing Page](./landing/index.html) · [Quick Start](./QUICKSTART.md) · [Documentação](./docs/)
|
|
133
|
+
|
|
134
|
+
</div>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
okam/__init__.py,sha256=Zn1KFblwuFHiDRdRAiRnDBRkbPttWh44jKa5zG2ov0E,22
|
|
2
|
+
okam/cli.py,sha256=5-bRTTUKqVr8TQCod2zmpFPE0VSV13bTi3h978SE5o8,12922
|
|
3
|
+
okam/hooks.py,sha256=0WFBJvaWnydLNOd-Y1UCbWzkxuKTFHWaJNz2o6DvC-k,6366
|
|
4
|
+
okam/manager.py,sha256=FPf4SwBPn4TFriHUfWYiHAOVC7bp_R3JO2zbgT0qamI,5549
|
|
5
|
+
okam/skill_creator.py,sha256=Fg9Mbl_zL_me1bn9mHa7oUZHixFVvW1Xy7ByqiF9x1Y,6028
|
|
6
|
+
okam/hooks/commit-msg,sha256=K6JPe8SpywxD-JprJO_GiwZRxyMOKu-dVlg60liwIYw,2490
|
|
7
|
+
okam/hooks/pre-commit,sha256=ga_MH6QrcyPE-IU-abElVs6xfazmcq64_gq-HqtNj18,4967
|
|
8
|
+
okam/hooks/pre-push,sha256=2VFHSF1gMdAHI2V52EXwM-X68oSTobJ5Co7tGz-CtbA,1402
|
|
9
|
+
okam-0.2.0.dist-info/METADATA,sha256=oXeRW9CREduCo9AYTjvh9UHfP0_orGvyQZK5ME8AabI,4801
|
|
10
|
+
okam-0.2.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
11
|
+
okam-0.2.0.dist-info/entry_points.txt,sha256=J464ar3O_pbCuxaKrFOUL1H2Z0OqmxwQiq20L63nDI8,39
|
|
12
|
+
okam-0.2.0.dist-info/licenses/LICENSE,sha256=X_SoMbZ6BaV_Mq5UcrmXbHumMxVqOPpF0-kXZ_DRqbs,1074
|
|
13
|
+
okam-0.2.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Okam Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|