ganbatte-os 0.2.7 → 0.2.9
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/.gos/docs/curation.md +18 -0
- package/.gos/integrations/qwen/command-map.json +20 -0
- package/.gos/integrations/registry.json +2 -1
- package/.gos/rules/demand-elegance.md +38 -0
- package/.gos/rules/plan-mode.md +4 -1
- package/.gos/scripts/hooks/claude-session-track.js +19 -1
- package/.gos/scripts/hooks/claude-stop-summary.js +16 -0
- package/.gos/scripts/hooks/post-commit-notify.js +175 -0
- package/.gos/scripts/integrations/check-ide-compat.js +2 -1
- package/.gos/scripts/integrations/setup-ide-adapters.js +3 -0
- package/.gos/scripts/tools/slack-notify.js +190 -0
- package/AGENTS.md +2 -2
- package/package.json +1 -1
package/.gos/docs/curation.md
CHANGED
|
@@ -58,3 +58,21 @@ Foram mantidos apenas os suportes exigidos pelas skills copiadas:
|
|
|
58
58
|
- memoria completa do framework
|
|
59
59
|
- CLI legada do framework anterior
|
|
60
60
|
- runtime multi-IDE integral
|
|
61
|
+
|
|
62
|
+
## Decisoes de Curadoria
|
|
63
|
+
|
|
64
|
+
### Lessons Loop / `tasks/lessons.md`
|
|
65
|
+
|
|
66
|
+
O `G-OS` NAO ativa o loop de `tasks/lessons.md` e NAO replica o `Livro de Ouro` do framework principal.
|
|
67
|
+
|
|
68
|
+
Motivos:
|
|
69
|
+
|
|
70
|
+
- esta distribuicao nao inclui o subsistema completo de memoria do `.a8z-framework`
|
|
71
|
+
- o custo de manter memoria graduada localmente aqui seria desproporcional ao escopo enxuto do `G-OS`
|
|
72
|
+
- o ciclo canonico desta distribuicao permanece: `implementation_plan.md` -> `task.md` -> `walkthrough.md`
|
|
73
|
+
|
|
74
|
+
Decisao operacional:
|
|
75
|
+
|
|
76
|
+
- nao criar `tasks/lessons.md` neste repo
|
|
77
|
+
- nao adicionar hooks de captura automatica de lessons
|
|
78
|
+
- registrar aprendizados duraveis por curadoria manual em `docs/curation.md` ou promover direto para `rules/` quando houver padrao estavel
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"ide": "qwen-code",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"status": "active",
|
|
5
|
+
"notes": {
|
|
6
|
+
"settingsFile": ".qwen/settings.json",
|
|
7
|
+
"skillsDir": ".qwen/skills/gos-{slug}/SKILL.md",
|
|
8
|
+
"agentsDir": ".qwen/agents/{id}.md",
|
|
9
|
+
"commandsDir": ".qwen/commands/",
|
|
10
|
+
"mcpConfig": "mcpServers object in .qwen/settings.json",
|
|
11
|
+
"ignoreFile": ".qwenignore"
|
|
12
|
+
},
|
|
13
|
+
"mappings": [
|
|
14
|
+
{ "canonicalCommand": "gos.search", "coreArtifact": ".gos/prompts/01-search.md" },
|
|
15
|
+
{ "canonicalCommand": "gos.spec", "coreArtifact": ".gos/prompts/02-spec.md" },
|
|
16
|
+
{ "canonicalCommand": "gos.tasks", "coreArtifact": ".gos/prompts/03-tasks.md" },
|
|
17
|
+
{ "canonicalCommand": "gos.code", "coreArtifact": ".gos/prompts/04-code.md" },
|
|
18
|
+
{ "canonicalCommand": "gos.review", "coreArtifact": ".gos/prompts/05-reviews.md" }
|
|
19
|
+
]
|
|
20
|
+
}
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"cursor": { "workspaceDir": ".cursor", "ruleFile": ".cursor/rules/g-os.mdc", "status": "active" },
|
|
16
16
|
"codex": { "workspaceDir": ".codex", "adapter": "integrations/codex", "status": "active" },
|
|
17
17
|
"opencode": { "workspaceDir": ".opencode", "adapter": "integrations/opencode", "status": "active" },
|
|
18
|
-
"kilo-code": { "workspaceDir": ".kilocode", "ruleFile": ".kilocode/rules/g-os.md", "status": "active" }
|
|
18
|
+
"kilo-code": { "workspaceDir": ".kilocode", "ruleFile": ".kilocode/rules/g-os.md", "status": "active" },
|
|
19
|
+
"qwen-code": { "workspaceDir": ".qwen", "adapter": "integrations/qwen", "status": "active" }
|
|
19
20
|
}
|
|
20
21
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths: "**"
|
|
3
|
+
severity: INFO
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Demand Elegance
|
|
7
|
+
|
|
8
|
+
## Resumo
|
|
9
|
+
|
|
10
|
+
Agentes de IA devem buscar a solução mais elegante para problemas não-triviais, evitando soluções "hacky" ou excessivamente complexas. No entanto, deve haver um equilíbrio: para correções simples e óbvias, a velocidade e a simplicidade técnica prevalecem sobre o over-engineering.
|
|
11
|
+
|
|
12
|
+
## Princípios
|
|
13
|
+
|
|
14
|
+
1. **The Elegance Pause**: Para qualquer mudança que envolva decisões arquiteturais ou lógica complexa, o agente deve pausar internamente e perguntar: "Existe uma forma mais elegante de resolver isso?".
|
|
15
|
+
2. **Hacky vs. Elegant**: Se um fix parece "remendado" (hacky), o agente deve propor a implementação da solução robusta e elegante, mesmo que exija um pouco mais de esforço inicial.
|
|
16
|
+
3. **Over-engineering Guardrail**: Não crie abstrações desnecessárias para problemas simples. Se a solução óbvia é direta, use-a.
|
|
17
|
+
4. **Refactoring Mindset**: Trate cada tarefa como uma oportunidade de deixar o código ligeiramente mais limpo do que o encontrou (Regra do Escoteiro).
|
|
18
|
+
|
|
19
|
+
## Quando aplicar (The Elegance Gate)
|
|
20
|
+
|
|
21
|
+
| Contexto | Ação |
|
|
22
|
+
|----------|------|
|
|
23
|
+
| **Bug Fix Crítico** | Direto ao ponto, correção robusta mas sem abstrações complexas. |
|
|
24
|
+
| **Nova Feature** | Design elegante, modular e extensível por padrão. |
|
|
25
|
+
| **Refatoração** | Máxima elegância, seguindo padrões de projeto e princípios SOLID. |
|
|
26
|
+
| **Scripts Rápidos** | Funcionalidade sobre estética de código, desde que seguro. |
|
|
27
|
+
|
|
28
|
+
## Exemplos
|
|
29
|
+
|
|
30
|
+
### ❌ Inelegante (Hacky)
|
|
31
|
+
Usar `setTimeout` arbitrário para esperar uma condição de rede sem retry-policy ou tratamento de erro adequado.
|
|
32
|
+
|
|
33
|
+
### ✅ Elegante
|
|
34
|
+
Implementar um padrão de `backoff exponencial` ou um `subscription mechanism` que reaja ao estado real do recurso.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
*Baseado nos Core Principles do .a8z-OS.*
|
package/.gos/rules/plan-mode.md
CHANGED
|
@@ -20,7 +20,10 @@ Ativar **PLAN MODE** automaticamente sempre que o input do usuário envolver **q
|
|
|
20
20
|
- Git operations: `commit`, `push`, `pull`, `branch`, `merge`, `status`, `log`
|
|
21
21
|
- Leitura e explicação: "o que é", "como funciona", "onde está", "mostre", "explique"
|
|
22
22
|
- Edições simples: 1 arquivo, 1 mudança isolada (ex: corrigir typo, ajustar 1 linha)
|
|
23
|
+
- Correções de lint, typo, formatação, ortografia
|
|
23
24
|
- Quando o usuário inclui: `"só faça"`, `"execute direto"`, `"sem plano"`, `"direto ao ponto"`
|
|
25
|
+
- Comandos de retomada ou continuação: `"continue"`, `"continuar"`, `"retomar"`, `"resume"`, `"prosseguir"`, `"segue"`
|
|
26
|
+
- Aprovações após um plano já apresentado: `"ok"`, `"aprovado"`, `"go"`, `"execute"`, `"pode ir"`, `"sim"`, `"proceed"`
|
|
24
27
|
- Quando plan mode já foi ativado ou aprovado na sessão atual
|
|
25
28
|
|
|
26
29
|
---
|
|
@@ -37,7 +40,7 @@ Ativar **PLAN MODE** automaticamente sempre que o input do usuário envolver **q
|
|
|
37
40
|
- Mudanças propostas agrupadas por componente
|
|
38
41
|
- Arquivos a criar `[NEW]`, modificar `[MODIFY]`, deletar `[DELETE]`
|
|
39
42
|
- Plano de verificação
|
|
40
|
-
- **Apresentar o plano ao usuário e
|
|
43
|
+
- **Apresentar o plano ao usuário e aguardar aprovação na resposta principal, sem bloquear via hook**
|
|
41
44
|
|
|
42
45
|
### Fase 3 — AWAIT APPROVAL
|
|
43
46
|
- Aguardar aprovação explícita do usuário (`"ok"`, `"aprovado"`, `"go"`, etc.)
|
|
@@ -48,6 +48,9 @@ function readState(statePath, sessionId) {
|
|
|
48
48
|
sessionId,
|
|
49
49
|
touchedFiles: [],
|
|
50
50
|
commands: [],
|
|
51
|
+
gitCommit: false,
|
|
52
|
+
lastCommitMessage: "",
|
|
53
|
+
taskRefs: [],
|
|
51
54
|
significantAction: false,
|
|
52
55
|
updatedAt: new Date().toISOString(),
|
|
53
56
|
};
|
|
@@ -60,6 +63,9 @@ function readState(statePath, sessionId) {
|
|
|
60
63
|
sessionId,
|
|
61
64
|
touchedFiles: [],
|
|
62
65
|
commands: [],
|
|
66
|
+
gitCommit: false,
|
|
67
|
+
lastCommitMessage: "",
|
|
68
|
+
taskRefs: [],
|
|
63
69
|
significantAction: false,
|
|
64
70
|
updatedAt: new Date().toISOString(),
|
|
65
71
|
};
|
|
@@ -125,7 +131,19 @@ function main() {
|
|
|
125
131
|
const toolName = String(payload.tool || payload.tool_name || payload.matcher || "");
|
|
126
132
|
if (/Bash/i.test(toolName)) {
|
|
127
133
|
const command = extractCommand(payload);
|
|
128
|
-
if (command)
|
|
134
|
+
if (command) {
|
|
135
|
+
uniquePush(state.commands, command);
|
|
136
|
+
if (/\bgit\s+commit\b/i.test(command)) {
|
|
137
|
+
state.gitCommit = true;
|
|
138
|
+
const msgMatch = command.match(/-m\s+["']([^"']+)["']/);
|
|
139
|
+
if (msgMatch) {
|
|
140
|
+
const message = msgMatch[1].trim();
|
|
141
|
+
state.lastCommitMessage = message;
|
|
142
|
+
const refs = message.match(/\bT-\d{3}\b/g);
|
|
143
|
+
if (refs) state.taskRefs = [...new Set(refs)];
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
129
147
|
}
|
|
130
148
|
|
|
131
149
|
if (state.touchedFiles.length > 0 || state.commands.length > 0) {
|
|
@@ -122,6 +122,22 @@ function main() {
|
|
|
122
122
|
const summary = [];
|
|
123
123
|
const syncMatcher = /^(\.gos|\.claude|data|README\.md|CLAUDE\.md|AGENTS\.md|GEMINI\.md)/;
|
|
124
124
|
|
|
125
|
+
// Trigger post-commit notification if git commit was made
|
|
126
|
+
if (state.gitCommit) {
|
|
127
|
+
try {
|
|
128
|
+
execFileSync('node', [path.join(ROOT, '.gos', 'scripts', 'hooks', 'post-commit-notify.js')], {
|
|
129
|
+
cwd: ROOT,
|
|
130
|
+
encoding: 'utf8',
|
|
131
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
132
|
+
timeout: 30000,
|
|
133
|
+
})
|
|
134
|
+
summary.push('post-commit-notify OK')
|
|
135
|
+
} catch (error) {
|
|
136
|
+
const message = error.stderr || error.stdout || error.message || 'falha no post-commit'
|
|
137
|
+
summary.push(`post-commit-notify falhou (${String(message).split(/\r?\n/)[0]})`)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
125
141
|
if (anyPathMatches(touchedFiles, syncMatcher)) {
|
|
126
142
|
try {
|
|
127
143
|
runNpm(["run", "sync:ides"], { timeout: 180000 });
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// post-commit-notify.js — Post-commit hook: marks tasks done in ClickUp + notifies Slack
|
|
4
|
+
// Trigger: Claude Code Stop hook or git post-commit
|
|
5
|
+
// Rule: ALWAYS exit 0 (observation hook, never blocks)
|
|
6
|
+
|
|
7
|
+
const { execFileSync } = require('node:child_process')
|
|
8
|
+
const { existsSync, readFileSync } = require('node:fs')
|
|
9
|
+
const { resolve } = require('node:path')
|
|
10
|
+
|
|
11
|
+
// Find repo root
|
|
12
|
+
function findRepoRoot() {
|
|
13
|
+
try {
|
|
14
|
+
return execFileSync('git', ['rev-parse', '--show-toplevel'], { encoding: 'utf-8' }).trim()
|
|
15
|
+
} catch {
|
|
16
|
+
return process.cwd()
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Parse last commit
|
|
21
|
+
function getLastCommit() {
|
|
22
|
+
try {
|
|
23
|
+
const log = execFileSync('git', ['log', '-1', '--format=%H|%s|%an'], { encoding: 'utf-8' }).trim()
|
|
24
|
+
const sepIdx = log.indexOf('|')
|
|
25
|
+
const hash = log.slice(0, sepIdx)
|
|
26
|
+
const rest = log.slice(sepIdx + 1)
|
|
27
|
+
const lastSep = rest.lastIndexOf('|')
|
|
28
|
+
const message = rest.slice(0, lastSep)
|
|
29
|
+
const author = rest.slice(lastSep + 1)
|
|
30
|
+
return { hash, message, author }
|
|
31
|
+
} catch {
|
|
32
|
+
return null
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Extract task refs from commit message (T-NNN pattern)
|
|
37
|
+
function extractTaskRefs(message) {
|
|
38
|
+
const matches = message.match(/\bT-(\d{3})\b/g)
|
|
39
|
+
return matches ? [...new Set(matches)] : []
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Load sprint registry to resolve local IDs -> ClickUp IDs
|
|
43
|
+
function loadRegistry(repoRoot) {
|
|
44
|
+
const paths = [
|
|
45
|
+
resolve(repoRoot, 'data/sprints/registry.json'),
|
|
46
|
+
resolve(repoRoot, '.gos/data/sprints/registry.json'),
|
|
47
|
+
]
|
|
48
|
+
for (const p of paths) {
|
|
49
|
+
if (existsSync(p)) {
|
|
50
|
+
try {
|
|
51
|
+
return { data: JSON.parse(readFileSync(p, 'utf-8')), path: p }
|
|
52
|
+
} catch { continue }
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return { data: { version: '1.0', sprints: [], taskMap: {}, taskSprintMap: {} }, path: paths[0] }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Run a CLI tool safely (no shell injection)
|
|
59
|
+
function runTool(repoRoot, tool, toolArgs) {
|
|
60
|
+
const toolPaths = [
|
|
61
|
+
resolve(repoRoot, `.gos/scripts/tools/${tool}`),
|
|
62
|
+
resolve(repoRoot, `scripts/tools/${tool}`),
|
|
63
|
+
]
|
|
64
|
+
const toolPath = toolPaths.find(p => existsSync(p))
|
|
65
|
+
if (!toolPath) return null
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
const result = execFileSync('node', [toolPath, ...toolArgs], {
|
|
69
|
+
encoding: 'utf-8',
|
|
70
|
+
timeout: 15000,
|
|
71
|
+
env: process.env,
|
|
72
|
+
})
|
|
73
|
+
return JSON.parse(result)
|
|
74
|
+
} catch (e) {
|
|
75
|
+
return { error: e.message }
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Detect if commit implies task completion
|
|
80
|
+
function isCompletionCommit(message) {
|
|
81
|
+
const completionPatterns = [
|
|
82
|
+
/^feat[:(]/i,
|
|
83
|
+
/^fix[:(]/i,
|
|
84
|
+
/\b(close[sd]?|complete[sd]?|finish|done|resolve[sd]?)\b/i,
|
|
85
|
+
]
|
|
86
|
+
return completionPatterns.some(p => p.test(message))
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Detect if commit is a start/WIP commit
|
|
90
|
+
function isStartCommit(message) {
|
|
91
|
+
const startPatterns = [
|
|
92
|
+
/^(wip|chore|progress|draft|start|refactor|test|docs|style)[:(]/i,
|
|
93
|
+
/\b(wip|in.?progress|draft|start)\b/i,
|
|
94
|
+
]
|
|
95
|
+
return startPatterns.some(p => p.test(message))
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Find sprint for a task reference
|
|
99
|
+
function findSprintForTask(registry, taskRef) {
|
|
100
|
+
if (registry.taskSprintMap?.[taskRef]) {
|
|
101
|
+
return registry.sprints.find(s => s.id === registry.taskSprintMap[taskRef]) || null
|
|
102
|
+
}
|
|
103
|
+
return registry.sprints.filter(s => !s.end || new Date(s.end) >= new Date())
|
|
104
|
+
.sort((a, b) => new Date(b.start) - new Date(a.start))[0] || null
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function main() {
|
|
108
|
+
const repoRoot = findRepoRoot()
|
|
109
|
+
const commit = getLastCommit()
|
|
110
|
+
if (!commit) return
|
|
111
|
+
|
|
112
|
+
const taskRefs = extractTaskRefs(commit.message)
|
|
113
|
+
if (taskRefs.length === 0) return
|
|
114
|
+
|
|
115
|
+
const hasClickUp = !!process.env.CLICKUP_API_KEY
|
|
116
|
+
const hasSlack = !!process.env.SLACK_WEBHOOK_URL
|
|
117
|
+
if (!hasClickUp && !hasSlack) return
|
|
118
|
+
|
|
119
|
+
const { data: registry } = loadRegistry(repoRoot)
|
|
120
|
+
const shouldComplete = isCompletionCommit(commit.message)
|
|
121
|
+
const shouldStart = isStartCommit(commit.message)
|
|
122
|
+
|
|
123
|
+
for (const ref of taskRefs) {
|
|
124
|
+
const clickupId = registry.taskMap?.[ref]
|
|
125
|
+
|
|
126
|
+
// Update ClickUp task status
|
|
127
|
+
if (hasClickUp && clickupId) {
|
|
128
|
+
if (shouldComplete) {
|
|
129
|
+
runTool(repoRoot, 'clickup.js', ['task', 'update', '--task-id', clickupId, '--status', 'complete'])
|
|
130
|
+
} else if (shouldStart) {
|
|
131
|
+
runTool(repoRoot, 'clickup.js', ['task', 'update', '--task-id', clickupId, '--status', 'in progress'])
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Notify Slack
|
|
136
|
+
if (hasSlack) {
|
|
137
|
+
const status = shouldComplete ? 'concluida' : shouldStart ? 'em andamento' : 'atualizada'
|
|
138
|
+
runTool(repoRoot, 'slack-notify.js', [
|
|
139
|
+
'task-update',
|
|
140
|
+
'--task', ref,
|
|
141
|
+
'--status', status,
|
|
142
|
+
'--commit', commit.hash,
|
|
143
|
+
'--author', commit.author,
|
|
144
|
+
'--message', commit.message,
|
|
145
|
+
])
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Sprint completion check (if task completed)
|
|
149
|
+
if (shouldComplete) {
|
|
150
|
+
const sprint = findSprintForTask(registry, ref)
|
|
151
|
+
if (sprint) {
|
|
152
|
+
const tasksInSprint = Object.entries(registry.taskSprintMap || {})
|
|
153
|
+
.filter(([, sid]) => sid === sprint.id)
|
|
154
|
+
.map(([tid]) => tid)
|
|
155
|
+
const completedTasks = tasksInSprint.filter(tid => {
|
|
156
|
+
const cuId = registry.taskMap?.[tid]
|
|
157
|
+
if (!cuId) return false
|
|
158
|
+
return true
|
|
159
|
+
})
|
|
160
|
+
if (tasksInSprint.length > 0 && completedTasks.length === tasksInSprint.length) {
|
|
161
|
+
if (hasSlack) {
|
|
162
|
+
runTool(repoRoot, 'slack-notify.js', [
|
|
163
|
+
'sprint-complete',
|
|
164
|
+
'--sprint', sprint.id,
|
|
165
|
+
'--name', sprint.name,
|
|
166
|
+
'--tasks', tasksInSprint.join(', '),
|
|
167
|
+
])
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
main().catch(() => {}).finally(() => process.exit(0))
|
|
@@ -20,7 +20,8 @@ const required = [
|
|
|
20
20
|
'.gos/integrations/antigravity/command-map.json',
|
|
21
21
|
'.gos/integrations/gemini/command-map.json',
|
|
22
22
|
'.gos/integrations/cursor/command-map.json',
|
|
23
|
-
'.gos/integrations/kilo-code/command-map.json'
|
|
23
|
+
'.gos/integrations/kilo-code/command-map.json',
|
|
24
|
+
'.gos/integrations/qwen/command-map.json'
|
|
24
25
|
];
|
|
25
26
|
|
|
26
27
|
const missing = required.filter((entry) => !fs.existsSync(path.join(root, entry)));
|
|
@@ -46,11 +46,14 @@ function main() {
|
|
|
46
46
|
const codexSkill = path.join(root, '.codex', 'skills', `gos-${skill.slug}.md`);
|
|
47
47
|
const geminiSkill = path.join(root, '.gemini', 'skills', `gos-${skill.slug}`, 'SKILL.md');
|
|
48
48
|
const opencodeSkill = path.join(root, '.opencode', 'skills', `gos-${skill.slug}`, 'SKILL.md');
|
|
49
|
+
const qwenSkill = path.join(root, '.qwen', 'skills', `gos-${skill.slug}`, 'SKILL.md');
|
|
49
50
|
|
|
50
51
|
writeFile(claudeSkill, skillWrapper(skill.slug, relativeTarget(claudeSkill, path.join(root, '.gos', skillTargetPath))));
|
|
51
52
|
writeFile(codexSkill, skillWrapper(skill.slug, relativeTarget(codexSkill, path.join(root, '.gos', skillTargetPath))));
|
|
52
53
|
writeFile(geminiSkill, skillWrapper(skill.slug, relativeTarget(geminiSkill, path.join(root, '.gos', skillTargetPath))));
|
|
53
54
|
writeFile(opencodeSkill, skillWrapper(skill.slug, relativeTarget(opencodeSkill, path.join(root, '.gos', skillTargetPath))));
|
|
55
|
+
ensureDir(path.dirname(qwenSkill));
|
|
56
|
+
writeFile(qwenSkill, skillWrapper(skill.slug, relativeTarget(qwenSkill, path.join(root, '.gos', skillTargetPath))));
|
|
54
57
|
}
|
|
55
58
|
|
|
56
59
|
const antigravityInstructions = [
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// slack-notify.js — Slack notification tool via Incoming Webhooks (zero-dep)
|
|
4
|
+
// Usage: node slack-notify.js <command> [--options]
|
|
5
|
+
// Auth: SLACK_WEBHOOK_URL env var (Incoming Webhook URL)
|
|
6
|
+
|
|
7
|
+
const WEBHOOK_URL = process.env.SLACK_WEBHOOK_URL
|
|
8
|
+
|
|
9
|
+
if (!WEBHOOK_URL) {
|
|
10
|
+
// Graceful skip — no webhook configured is not an error
|
|
11
|
+
console.log(JSON.stringify({ skipped: true, reason: 'SLACK_WEBHOOK_URL not set' }))
|
|
12
|
+
process.exit(0)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function parseArgs(argv) {
|
|
16
|
+
const result = { _: [] }
|
|
17
|
+
for (let i = 0; i < argv.length; i++) {
|
|
18
|
+
const arg = argv[i]
|
|
19
|
+
if (arg.startsWith('--')) {
|
|
20
|
+
const key = arg.slice(2)
|
|
21
|
+
const next = argv[i + 1]
|
|
22
|
+
if (next && !next.startsWith('--')) {
|
|
23
|
+
result[key] = next
|
|
24
|
+
i++
|
|
25
|
+
} else {
|
|
26
|
+
result[key] = true
|
|
27
|
+
}
|
|
28
|
+
} else {
|
|
29
|
+
result._.push(arg)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return result
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const args = parseArgs(process.argv.slice(2))
|
|
36
|
+
const [cmd, sub, ...rest] = args._
|
|
37
|
+
|
|
38
|
+
async function sendWebhook(payload) {
|
|
39
|
+
if (args['dry-run']) {
|
|
40
|
+
return { _dry_run: true, url: WEBHOOK_URL.replace(/\/[^/]{6,}$/, '/***'), payload }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const res = await fetch(WEBHOOK_URL, {
|
|
44
|
+
method: 'POST',
|
|
45
|
+
headers: { 'Content-Type': 'application/json' },
|
|
46
|
+
body: JSON.stringify(payload),
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
const text = await res.text()
|
|
50
|
+
if (res.ok) {
|
|
51
|
+
return { sent: true, status: res.status }
|
|
52
|
+
}
|
|
53
|
+
return { sent: false, status: res.status, body: text }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function escapeSlack(text) {
|
|
57
|
+
return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function buildTaskDonePayload(taskId, commit, author, extra = {}) {
|
|
61
|
+
const sprint = extra.sprint || ''
|
|
62
|
+
const track = extra.track || ''
|
|
63
|
+
const message = extra.message || ''
|
|
64
|
+
|
|
65
|
+
const lines = [`*:white_check_mark: Task Concluída*`]
|
|
66
|
+
lines.push(`>*Task:* ${escapeSlack(taskId)}${message ? ` — ${escapeSlack(message)}` : ''}`)
|
|
67
|
+
if (sprint) lines.push(`>*Sprint:* ${escapeSlack(sprint)}`)
|
|
68
|
+
if (track) lines.push(`>*Track:* ${escapeSlack(track)}`)
|
|
69
|
+
lines.push(`>*Commit:* \`${escapeSlack(commit.slice(0, 7))}\`${message ? ` — ${escapeSlack(message)}` : ''}`)
|
|
70
|
+
lines.push(`>*Author:* ${escapeSlack(author)}`)
|
|
71
|
+
|
|
72
|
+
return { text: lines.join('\n') }
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function buildSprintSummaryPayload(data) {
|
|
76
|
+
const summary = data.summary || data
|
|
77
|
+
const name = data.sprint?.name || data.name || 'Sprint'
|
|
78
|
+
const now = Math.floor(Date.now() / 1000)
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
blocks: [
|
|
82
|
+
{
|
|
83
|
+
type: 'header',
|
|
84
|
+
text: { type: 'plain_text', text: `:bar_chart: ${name} — Status Update` }
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
type: 'section',
|
|
88
|
+
fields: [
|
|
89
|
+
{ type: 'mrkdwn', text: `*Total:*\n${summary.totalTasks || 0} tasks` },
|
|
90
|
+
{ type: 'mrkdwn', text: `*Done:*\n${summary.done || 0} (${summary.completionPct || 0}%)` }
|
|
91
|
+
]
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
type: 'section',
|
|
95
|
+
fields: [
|
|
96
|
+
{ type: 'mrkdwn', text: `*In Progress:*\n${summary.inProgress || 0}` },
|
|
97
|
+
{ type: 'mrkdwn', text: `*Blocked:*\n${summary.blocked || 0}` }
|
|
98
|
+
]
|
|
99
|
+
},
|
|
100
|
+
{ type: 'divider' },
|
|
101
|
+
{
|
|
102
|
+
type: 'context',
|
|
103
|
+
elements: [
|
|
104
|
+
{ type: 'mrkdwn', text: `Updated: <!date^${now}^{date_short} {time}|${new Date().toISOString()}>` }
|
|
105
|
+
]
|
|
106
|
+
}
|
|
107
|
+
]
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function main() {
|
|
112
|
+
let result
|
|
113
|
+
|
|
114
|
+
switch (cmd) {
|
|
115
|
+
case 'send': {
|
|
116
|
+
if (args.text) {
|
|
117
|
+
result = await sendWebhook({ text: args.text })
|
|
118
|
+
} else if (args['blocks-file']) {
|
|
119
|
+
const { readFileSync } = require('node:fs')
|
|
120
|
+
try {
|
|
121
|
+
const payload = JSON.parse(readFileSync(args['blocks-file'], 'utf-8'))
|
|
122
|
+
result = await sendWebhook(payload)
|
|
123
|
+
} catch (e) {
|
|
124
|
+
result = { error: `Failed to read ${args['blocks-file']}: ${e.message}` }
|
|
125
|
+
}
|
|
126
|
+
} else {
|
|
127
|
+
result = { error: '--text or --blocks-file required' }
|
|
128
|
+
}
|
|
129
|
+
break
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
case 'task-done': {
|
|
133
|
+
if (!args.task || !args.commit || !args.author) {
|
|
134
|
+
result = { error: '--task, --commit, and --author required' }; break
|
|
135
|
+
}
|
|
136
|
+
const payload = buildTaskDonePayload(args.task, args.commit, args.author, {
|
|
137
|
+
sprint: args.sprint,
|
|
138
|
+
track: args.track,
|
|
139
|
+
message: args.message,
|
|
140
|
+
})
|
|
141
|
+
result = await sendWebhook(payload)
|
|
142
|
+
break
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
case 'sprint-summary': {
|
|
146
|
+
if (!args.file) { result = { error: '--file (sprint status JSON) required' }; break }
|
|
147
|
+
const { readFileSync } = require('node:fs')
|
|
148
|
+
try {
|
|
149
|
+
const data = JSON.parse(readFileSync(args.file, 'utf-8'))
|
|
150
|
+
const payload = buildSprintSummaryPayload(data)
|
|
151
|
+
result = await sendWebhook(payload)
|
|
152
|
+
} catch (e) {
|
|
153
|
+
result = { error: `Failed to read ${args.file}: ${e.message}` }
|
|
154
|
+
}
|
|
155
|
+
break
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
default:
|
|
159
|
+
result = {
|
|
160
|
+
error: cmd ? `Unknown command: ${cmd}` : 'No command provided',
|
|
161
|
+
usage: {
|
|
162
|
+
send: 'send --text "Hello *bold* _italic_" | send --blocks-file payload.json',
|
|
163
|
+
'task-done': 'task-done --task T-001 --commit abc1234 --author "Name" [--sprint "S01"] [--track backend] [--message "feat: ..."]',
|
|
164
|
+
'sprint-summary': 'sprint-summary --file sprint-status.json',
|
|
165
|
+
},
|
|
166
|
+
formatting: {
|
|
167
|
+
bold: '*text*',
|
|
168
|
+
italic: '_text_',
|
|
169
|
+
strike: '~text~',
|
|
170
|
+
code: '`code`',
|
|
171
|
+
codeBlock: '```multi-line```',
|
|
172
|
+
quote: '>quoted text',
|
|
173
|
+
link: '<url|display text>',
|
|
174
|
+
mention: '<@USER_ID>',
|
|
175
|
+
channel: '<#CHANNEL_ID>',
|
|
176
|
+
date: '<!date^UNIX_TS^{date_short} {time}|fallback>',
|
|
177
|
+
},
|
|
178
|
+
flags: {
|
|
179
|
+
'--dry-run': 'Show payload without sending',
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
console.log(JSON.stringify(result, null, 2))
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
main().catch(err => {
|
|
188
|
+
console.error(JSON.stringify({ error: err.message }))
|
|
189
|
+
process.exit(0) // Always exit 0 — notification failure should never block
|
|
190
|
+
})
|
package/AGENTS.md
CHANGED
|
@@ -39,8 +39,8 @@ Antes de executar qualquer tarefa complexa, SEMPRE entre em plan mode.
|
|
|
39
39
|
|
|
40
40
|
**Protocolo:**
|
|
41
41
|
1. **RESEARCH** — leia arquivos relevantes sem alterar nada
|
|
42
|
-
2. **PLAN** — crie `implementation_plan.md` com `[NEW]`/`[MODIFY]`/`[DELETE]`, perguntas abertas, plano de verificação. Apresente e
|
|
42
|
+
2. **PLAN** — crie `implementation_plan.md` com `[NEW]`/`[MODIFY]`/`[DELETE]`, perguntas abertas, plano de verificação. Apresente e aguarde aprovação na resposta principal, sem bloquear via hook
|
|
43
43
|
3. **AWAIT** — aguarde aprovação: "ok", "aprovado", "go", "execute", "pode ir"
|
|
44
44
|
4. **EXECUTE + TRACK** — crie `task.md`, execute, finalize com `walkthrough.md`
|
|
45
45
|
|
|
46
|
-
**Exceções — NÃO ativar:** git operations, leitura/explicação, edições simples de 1 arquivo, lint/typo, "execute direto", "sem plano", quando já aprovado na sessão.
|
|
46
|
+
**Exceções — NÃO ativar:** git operations, leitura/explicação, edições simples de 1 arquivo, lint/typo, "execute direto", "sem plano", comandos de retomada como `continue`/`continuar`/`resume`, aprovações como `ok`/`aprovado`/`pode ir`, quando já aprovado na sessão.
|