mega-brain-ai 1.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.
Potentially problematic release.
This version of mega-brain-ai might be problematic. Click here for more details.
- package/.claude/CLAUDE.md +155 -0
- package/.claude/commands/agents.md +161 -0
- package/.claude/commands/ask.md +117 -0
- package/.claude/commands/benchmark.md +224 -0
- package/.claude/commands/chat.md +343 -0
- package/.claude/commands/compare.md +116 -0
- package/.claude/commands/conclave.md +194 -0
- package/.claude/commands/config.md +133 -0
- package/.claude/commands/council.md +194 -0
- package/.claude/commands/create-agent.md +452 -0
- package/.claude/commands/debate.md +157 -0
- package/.claude/commands/documentation/create-architecture-documentation.md +175 -0
- package/.claude/commands/dossiers.md +180 -0
- package/.claude/commands/evolve.md +223 -0
- package/.claude/commands/extract-dna.md +170 -0
- package/.claude/commands/extract-knowledge.md +507 -0
- package/.claude/commands/inbox.md +296 -0
- package/.claude/commands/ingest-empresa.md +191 -0
- package/.claude/commands/ingest.md +182 -0
- package/.claude/commands/jarvis-briefing.md +67 -0
- package/.claude/commands/jarvis-control.md +169 -0
- package/.claude/commands/jarvis-full.md +181 -0
- package/.claude/commands/jarvis.md +212 -0
- package/.claude/commands/ler-drive.md +212 -0
- package/.claude/commands/log.md +158 -0
- package/.claude/commands/loop.md +133 -0
- package/.claude/commands/loops.md +73 -0
- package/.claude/commands/mission-autopilot.md +538 -0
- package/.claude/commands/mission.md +353 -0
- package/.claude/commands/process-inbox.md +148 -0
- package/.claude/commands/process-jarvis.md +3036 -0
- package/.claude/commands/process-video.md +131 -0
- package/.claude/commands/rag-search.md +78 -0
- package/.claude/commands/resume.md +33 -0
- package/.claude/commands/save.md +38 -0
- package/.claude/commands/scan-inbox.md +125 -0
- package/.claude/commands/setup.md +99 -0
- package/.claude/commands/system-digest.md +243 -0
- package/.claude/commands/verify.md +182 -0
- package/.claude/commands/view-dna.md +169 -0
- package/.claude/hooks/agent_doctor.py +433 -0
- package/.claude/hooks/agent_memory_persister.py +203 -0
- package/.claude/hooks/auto_formatter.py +158 -0
- package/.claude/hooks/checkpoint_writer.py +244 -0
- package/.claude/hooks/claude_md_guard.py +146 -0
- package/.claude/hooks/creation_validator.py +357 -0
- package/.claude/hooks/enforce_dual_location.py +501 -0
- package/.claude/hooks/enforce_plan_mode.py +220 -0
- package/.claude/hooks/inbox_age_alert.py +367 -0
- package/.claude/hooks/jarvis_briefing.py +506 -0
- package/.claude/hooks/ledger_updater.py +301 -0
- package/.claude/hooks/memory_hints_injector.py +251 -0
- package/.claude/hooks/memory_updater.py +202 -0
- package/.claude/hooks/multi_agent_hook.py +464 -0
- package/.claude/hooks/notification_system.py +120 -0
- package/.claude/hooks/pattern_analyzer.py +526 -0
- package/.claude/hooks/pending_tracker.py +188 -0
- package/.claude/hooks/post_batch_cascading.py +1740 -0
- package/.claude/hooks/post_output_validator.py +358 -0
- package/.claude/hooks/post_tool_use.py +120 -0
- package/.claude/hooks/post_write_validator.py +200 -0
- package/.claude/hooks/quality_watchdog.py +394 -0
- package/.claude/hooks/ralph_wiggum.py +277 -0
- package/.claude/hooks/session-source-sync.py +218 -0
- package/.claude/hooks/session_autosave_v2.py +1135 -0
- package/.claude/hooks/session_end.py +203 -0
- package/.claude/hooks/session_start.py +939 -0
- package/.claude/hooks/skill_indexer.py +48 -0
- package/.claude/hooks/skill_router.py +358 -0
- package/.claude/hooks/stop_hook_completeness.py +178 -0
- package/.claude/hooks/subagent_tracker.py +163 -0
- package/.claude/hooks/token_checkpoint.py +584 -0
- package/.claude/hooks/user_prompt_submit.py +125 -0
- package/.claude/rules/ANTHROPIC-STANDARDS.md +384 -0
- package/.claude/rules/CLAUDE-LITE.md +201 -0
- package/.claude/rules/RULE-GROUP-1.md +320 -0
- package/.claude/rules/RULE-GROUP-2.md +307 -0
- package/.claude/rules/RULE-GROUP-3.md +248 -0
- package/.claude/rules/RULE-GROUP-4.md +427 -0
- package/.claude/rules/RULE-GROUP-5.md +388 -0
- package/.claude/rules/RULE-GROUP-6.md +387 -0
- package/.claude/rules/logging.md +53 -0
- package/.claude/rules/mcp-governance.md +128 -0
- package/.claude/rules/pipeline.md +60 -0
- package/.claude/rules/state-management.md +93 -0
- package/.claude/scripts/apply-tags.py +77 -0
- package/.claude/scripts/batch-extract-transcriptions.py +132 -0
- package/.claude/scripts/build-complete-index.py +250 -0
- package/.claude/scripts/build-planilha-index.py +170 -0
- package/.claude/scripts/complete-tag-matching.py +250 -0
- package/.claude/scripts/deduplicate-inbox.py +139 -0
- package/.claude/scripts/docx-xml-extractor.py +141 -0
- package/.claude/scripts/extract-docx-text.py +58 -0
- package/.claude/scripts/extract-single-transcription.py +74 -0
- package/.claude/scripts/extract_docx_from_gdrive.py +77 -0
- package/.claude/scripts/organized-downloader.py +246 -0
- package/.claude/scripts/planilha-tagger.py +187 -0
- package/.claude/scripts/revert-tags.py +70 -0
- package/.claude/scripts/source-sync.py +265 -0
- package/.claude/scripts/tag-inbox-files.py +276 -0
- package/.claude/scripts/tag-inbox-v2.py +253 -0
- package/.claude/scripts/test-extraction.py +35 -0
- package/.claude/scripts/test-full-extraction.py +74 -0
- package/.claude/skills/00-SKILL-CREATOR/SKILL.md +186 -0
- package/.claude/skills/01-SKILL-DOCS-MEGABRAIN/SKILL.md +251 -0
- package/.claude/skills/02-SKILL-PYTHON-MEGABRAIN/SKILL.md +323 -0
- package/.claude/skills/03-SKILL-AGENT-CREATION/SKILL.md +374 -0
- package/.claude/skills/04-SKILL-KNOWLEDGE-EXTRACTION/SKILL.md +318 -0
- package/.claude/skills/05-SKILL-PIPELINE-JARVIS/SKILL.md +430 -0
- package/.claude/skills/06-SKILL-BRAINSTORMING/SKILL.md +72 -0
- package/.claude/skills/07-SKILL-DISPATCHING-PARALLEL-AGENTS/SKILL.md +193 -0
- package/.claude/skills/08-SKILL-EXECUTING-PLANS/SKILL.md +114 -0
- package/.claude/skills/09-SKILL-WRITING-PLANS/SKILL.md +184 -0
- package/.claude/skills/10-SKILL-VERIFICATION-BEFORE-COMPLETION/SKILL.md +130 -0
- package/.claude/skills/11-SKILL-USING-SUPERPOWERS/SKILL.md +105 -0
- package/.claude/skills/DETECTION-PROTOCOL.md +217 -0
- package/.claude/skills/README.md +240 -0
- package/.claude/skills/SKILL-REGISTRY.md +284 -0
- package/.claude/skills/SKILL-SUGGESTIONS.md +114 -0
- package/.claude/skills/_TEMPLATES/SKILL-WRITER-GUIDE.md +385 -0
- package/.claude/skills/chronicler/SKILL.md +146 -0
- package/.claude/skills/chronicler/chronicler_core.py +468 -0
- package/.claude/skills/code-review/SKILL.md +160 -0
- package/.claude/skills/council/SKILL.md +210 -0
- package/.claude/skills/executor/SKILL.md +161 -0
- package/.claude/skills/fase-2-5-tagging/SKILL.md +182 -0
- package/.claude/skills/feature-dev/SKILL.md +154 -0
- package/.claude/skills/finance-agent/SKILL.md +137 -0
- package/.claude/skills/frontend-design/SKILL.md +165 -0
- package/.claude/skills/gdrive-transcription-downloader/SKILL.md +249 -0
- package/.claude/skills/gemini-fallback/SKILL.md +67 -0
- package/.claude/skills/gemini-fallback/gemini_fetch.py +0 -0
- package/.claude/skills/gha/SKILL.md +96 -0
- package/.claude/skills/gha/gha_diagnostic.py +227 -0
- package/.claude/skills/github-workflow/SKILL.md +190 -0
- package/.claude/skills/hookify/SKILL.md +134 -0
- package/.claude/skills/hybrid-source-reading/SKILL.md +265 -0
- package/.claude/skills/jarvis/SKILL.md +546 -0
- package/.claude/skills/jarvis-briefing/SKILL.md +340 -0
- package/.claude/skills/ler-planilha/SKILL.md +281 -0
- package/.claude/skills/plugin-dev/SKILL.md +176 -0
- package/.claude/skills/pr-review-toolkit/SKILL.md +178 -0
- package/.claude/skills/resume/SKILL.md +61 -0
- package/.claude/skills/save/SKILL.md +87 -0
- package/.claude/skills/skill-writer/SKILL.md +153 -0
- package/.claude/skills/skill-writer/examples.md +191 -0
- package/.claude/skills/skill-writer/troubleshooting.md +205 -0
- package/.claude/skills/smart-download-tagger/SKILL.md +148 -0
- package/.claude/skills/source-sync/SKILL.md +240 -0
- package/.claude/skills/sync-docs/SKILL.md +193 -0
- package/.claude/skills/sync-docs/config.json +37 -0
- package/.claude/skills/sync-docs/gdrive_sync.py +358 -0
- package/.claude/skills/sync-docs/reauth.py +71 -0
- package/.claude/skills/talent-agent/SKILL.md +183 -0
- package/.claude/skills/verify/SKILL.md +154 -0
- package/.claude/skills/verify/verify_runner.py +0 -0
- package/.claude/skills/verify-6-levels/SKILL.md +234 -0
- package/.claude/templates/BATCH-LOG-TEMPLATE.md +221 -0
- package/.claudeignore +9 -0
- package/.gitattributes +4 -0
- package/.github/layer1-allowlist.txt +80 -0
- package/.github/layer2-manifest.txt +40 -0
- package/.gitignore +219 -0
- package/README.md +1210 -0
- package/agents/_templates/INDEX.md +741 -0
- package/agents/_templates/TEMPLATE-AGENT-MD-ULTRA-ROBUSTO-V3.md +2399 -0
- package/agents/boardroom/CHECKLIST-MASTER.md +281 -0
- package/agents/boardroom/INTEGRATION-GUIDE.md +406 -0
- package/agents/boardroom/README.md +238 -0
- package/agents/boardroom/config/BOARDROOM-CONFIG.md +186 -0
- package/agents/boardroom/config/TTS-INTEGRATION.md +258 -0
- package/agents/boardroom/config/VOICE-PROFILES.md +624 -0
- package/agents/boardroom/config/voice_mapping.json +128 -0
- package/agents/boardroom/scripts/audio_generator.py +375 -0
- package/agents/boardroom/scripts/audio_generator_edge.py +353 -0
- package/agents/boardroom/scripts/jarvis_boardroom_hook.py +415 -0
- package/agents/boardroom/scripts/notebooklm_generator.py +578 -0
- package/agents/boardroom/templates/EPISODE-TEMPLATE.md +367 -0
- package/agents/boardroom/templates/scene-templates/SCENE-AGENT-DEBATE.md +252 -0
- package/agents/boardroom/templates/scene-templates/SCENE-COUNCIL.md +270 -0
- package/agents/boardroom/templates/scene-templates/SCENE-DNA-CONSULTATION.md +126 -0
- package/agents/boardroom/templates/scene-templates/SCENE-QUESTION.md +174 -0
- package/agents/boardroom/workflows/WORKFLOW-AUDIO-GENERATION.md +421 -0
- package/agents/constitution/BASE-CONSTITUTION.md +254 -0
- package/agents/council/CRITIC.md +197 -0
- package/agents/council/DEVILS-ADVOCATE.md +274 -0
- package/agents/council/SYNTHESIZER.md +293 -0
- package/agents/council/advogado-do-diabo/AGENT.md +489 -0
- package/agents/council/advogado-do-diabo/SOUL.md +100 -0
- package/agents/council/critico-metodologico/AGENT.md +670 -0
- package/agents/council/critico-metodologico/SOUL.md +107 -0
- package/agents/council/sintetizador/AGENT.md +558 -0
- package/agents/council/sintetizador/SOUL.md +94 -0
- package/agents/persons/_example/AGENT-EXAMPLE.md +42 -0
- package/agents/persons/_example/DNA-EXAMPLE.yaml +61 -0
- package/agents/protocols/AGENT-COGNITION-PROTOCOL.md +779 -0
- package/agents/protocols/AGENT-INTEGRITY-PROTOCOL.md +692 -0
- package/agents/protocols/BATCH-VISUAL-PROTOCOL.md +841 -0
- package/agents/protocols/DNA-CONFIG-TEMPLATE.yaml +181 -0
- package/agents/protocols/DNA-EXTRACTION-PROTOCOL.md +370 -0
- package/agents/protocols/EPISTEMIC-PROTOCOL.md +333 -0
- package/agents/protocols/LOG-STRUCTURE-PROTOCOL.md +65 -0
- package/agents/protocols/MEMORY-PROTOCOL.md +567 -0
- package/agents/protocols/NARRATIVE-SYNTHESIS-PROTOCOL.md +278 -0
- package/agents/protocols/PHASE-4-VERIFICATION-CHECKPOINT.md +146 -0
- package/agents/protocols/SOUL-TEMPLATE.md +416 -0
- package/agents/protocols/TEMPLATE-EVOLUTION-PROTOCOL.md +544 -0
- package/agents/protocols/VISUAL-DIFF-PROTOCOL.md +159 -0
- package/agents/sua-empresa/README.md +44 -0
- package/agents/sua-empresa/_example/jds/EXAMPLE-JD.md +42 -0
- package/agents/sua-empresa/_example/org/EXAMPLE-ORG.md +32 -0
- package/agents/sua-empresa/_example/roles/EXAMPLE-ROLE.md +38 -0
- package/bin/cli.js +2 -0
- package/bin/lib/ascii-art.js +234 -0
- package/bin/lib/installer.js +402 -0
- package/bin/lib/setup-wizard.js +95 -0
- package/bin/lib/validate-email.js +109 -0
- package/bin/mega-brain.js +97 -0
- package/bin/push.js +342 -0
- package/bin/templates/env.example +38 -0
- package/inbox/.gitkeep +0 -0
- package/integrations/README.md +46 -0
- package/integrations/mcps/MCP-REGISTRY.md +56 -0
- package/integrations/mcps/excalidraw/CONFIG.md +56 -0
- package/integrations/mcps/gdrive/CONFIG.md +38 -0
- package/knowledge/dna/.gitkeep +0 -0
- package/knowledge/dossiers/persons/.gitkeep +0 -0
- package/knowledge/dossiers/persons/DOSSIER-EXAMPLE.md +49 -0
- package/knowledge/dossiers/system/.gitkeep +0 -0
- package/knowledge/dossiers/themes/.gitkeep +0 -0
- package/knowledge/playbooks/.gitkeep +0 -0
- package/knowledge/playbooks/PLAYBOOK-EXAMPLE.md +50 -0
- package/knowledge/sources/.gitkeep +0 -0
- package/logs/.gitkeep +0 -0
- package/package.json +128 -0
- package/processing/canonical/.gitkeep +0 -0
- package/processing/chunks/.gitkeep +0 -0
- package/processing/insights/.gitkeep +0 -0
- package/processing/narratives/.gitkeep +0 -0
- package/reference/CONSELHO.md +337 -0
- package/reference/CONTEXT7_README.md +28 -0
- package/reference/JARVIS-LOGGING-PROTOCOL.md +380 -0
- package/reference/QUICK-START.md +197 -0
- package/reference/README-RALPH-CASCATEAMENTO.md +207 -0
- package/reference/TEMPLATE-MASTER.md +727 -0
- package/reference/prds/prd-jarvis-mega-brain-v3.md +1305 -0
- package/reference/templates/phase5/IMPLEMENTATION-GUIDE.md +355 -0
- package/reference/templates/phase5/MOGA-BRAIN-PHASE5-TEMPLATES.md +1284 -0
- package/reference/templates/phase5/README.md +165 -0
- package/reference/workflow-claude-code-boris-cherny-continuous-claude.md +2232 -0
- package/system/database/001_moneyclub_buyers.sql +160 -0
- package/system/database/002_premium_token.sql +97 -0
- package/system/database/apply-migration.mjs +129 -0
- package/system/docs/MEGA-BRAIN-DEMO-COMPLETA.md +1226 -0
- package/system/docs/MEGA-BRAIN-MANIFESTO-COMPLETO.md +1054 -0
- package/system/docs/MOGA-BRAIN-EXPLICACAO-COMPLETA.md +791 -0
- package/system/docs/STRATEGIC-INTEGRATION-GUIDE.md +725 -0
- package/system/docs/architecture/01-system-context.md +136 -0
- package/system/docs/architecture/02-components.md +225 -0
- package/system/docs/architecture/03-data-flow.md +235 -0
- package/system/docs/architecture/04-integrations.md +283 -0
- package/system/docs/architecture/README.md +71 -0
- package/system/docs/architecture/diagrams/component-diagram.mmd +50 -0
- package/system/docs/architecture/diagrams/data-flow.mmd +39 -0
- package/system/docs/architecture/diagrams/system-overview.mmd +68 -0
- package/system/protocols/AGENT-AUTHORITY.md +217 -0
- package/system/protocols/CONSTITUICAO-BASE.md +115 -0
- package/system/protocols/CONSTITUTION.md +231 -0
- package/system/protocols/GOVERNANCE-MAP.md +123 -0
- package/system/protocols/HOOK-SECURITY-THREAT-MODEL.md +152 -0
- package/system/protocols/ORQUESTRACAO-PROTOCOL.md +215 -0
- package/system/protocols/_archive/CHUNKING-PROTOCOL.md +207 -0
- package/system/protocols/_archive/ENTITY-RESOLUTION-PROTOCOL.md +269 -0
- package/system/protocols/_archive/INSIGHT-EXTRACTION-PROTOCOL.md +257 -0
- package/system/protocols/_archive/NARRATIVE-SYNTHESIS-PROTOCOL.md +290 -0
- package/system/protocols/agents/AGENT-INTERACTION.md +315 -0
- package/system/protocols/agents/CORTEX-PROTOCOL.md +520 -0
- package/system/protocols/agents/EPISTEMIC-PROTOCOL.md +465 -0
- package/system/protocols/agents/MEMORY-PROTOCOL.md +366 -0
- package/system/protocols/agents/WAR-ROOM.md +355 -0
- package/system/protocols/company/COMPANY-DOCUMENT-PROTOCOL.md +793 -0
- package/system/protocols/company/COMPANY-ENRICHMENT-PROTOCOL.md +679 -0
- package/system/protocols/conclave/CONCLAVE-LOG-TEMPLATE-v2.md +309 -0
- package/system/protocols/conclave/CONCLAVE-PROTOCOL.md +518 -0
- package/system/protocols/conclave/DEBATE-DYNAMICS-CONFIG.yaml +322 -0
- package/system/protocols/conclave/DEBATE-DYNAMICS-PROTOCOL.md +613 -0
- package/system/protocols/conclave/DEBATE-PROTOCOL.md +323 -0
- package/system/protocols/council/COUNCIL-LOG-TEMPLATE-v2.md +309 -0
- package/system/protocols/council/COUNCIL-PROTOCOL.md +518 -0
- package/system/protocols/council/DEBATE-DYNAMICS-CONFIG.yaml +322 -0
- package/system/protocols/council/DEBATE-DYNAMICS-PROTOCOL.md +613 -0
- package/system/protocols/council/DEBATE-PROTOCOL.md +323 -0
- package/system/protocols/dna/DNA-EXTRACTION-PROTOCOL.md +1214 -0
- package/system/protocols/dna/ENRICHMENT-PROTOCOL.md +408 -0
- package/system/protocols/dna/REASONING-MODEL-PROTOCOL.md +331 -0
- package/system/protocols/pipeline/DOSSIER-COMPILATION-PROTOCOL.md +790 -0
- package/system/protocols/pipeline/NARRATIVE-METABOLISM-PROTOCOL.md +292 -0
- package/system/protocols/pipeline/PIPELINE-JARVIS-v2.1.md +606 -0
- package/system/protocols/pipeline/PROMPT-1.1-CHUNKING.md +154 -0
- package/system/protocols/pipeline/PROMPT-1.2-ENTITY-RESOLUTION.md +186 -0
- package/system/protocols/pipeline/PROMPT-2.1-DNA-TAGS-INCREMENT.md +208 -0
- package/system/protocols/pipeline/PROMPT-2.1-INSIGHT-EXTRACTION.md +191 -0
- package/system/protocols/pipeline/PROMPT-3.1-NARRATIVE-SYNTHESIS.md +331 -0
- package/system/protocols/pipeline/SOURCES-COMPILATION-PROTOCOL.md +340 -0
- package/system/protocols/system/AUTO-LOG-PROTOCOL.md +369 -0
- package/system/protocols/system/CHECKPOINT-ENFORCEMENT.md +176 -0
- package/system/protocols/system/ENFORCEMENT.md +435 -0
- package/system/protocols/system/LOG-TEMPLATES.md +1068 -0
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Agent Doctor - Layer 2 do META-AGENT System v1.0
|
|
4
|
+
|
|
5
|
+
FUNÇÃO: Diagnostica gaps de qualidade e PROPÕE fixes para revisão humana.
|
|
6
|
+
NÃO aplica fixes automaticamente - salva em doctor_proposals.jsonl.
|
|
7
|
+
|
|
8
|
+
REGRA #28: META-AGENT QUALITY AWARENESS
|
|
9
|
+
PRINCÍPIO: DOCTOR PROPÕE, NÃO APLICA
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
import os
|
|
14
|
+
import re
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from datetime import datetime
|
|
17
|
+
from typing import Dict, List, Optional
|
|
18
|
+
|
|
19
|
+
PROJECT_ROOT = Path(os.environ.get('CLAUDE_PROJECT_DIR', '.'))
|
|
20
|
+
LOGS_PATH = PROJECT_ROOT / "logs"
|
|
21
|
+
DOCTOR_PROPOSALS_LOG = LOGS_PATH / "doctor_proposals.jsonl"
|
|
22
|
+
DOCTOR_FIXES_LOG = LOGS_PATH / "doctor_fixes.jsonl"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
26
|
+
# MANDATORY HEADER TEMPLATE
|
|
27
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
28
|
+
|
|
29
|
+
MANDATORY_HEADER_TEMPLATE = '''## ⚠️ MANDATORY OUTPUT SECTIONS (NEVER SKIP)
|
|
30
|
+
<!-- MANDATORY -->
|
|
31
|
+
|
|
32
|
+
| Section | Required | Marker | Example |
|
|
33
|
+
|---------|----------|--------|---------|
|
|
34
|
+
{sections_table}
|
|
35
|
+
|
|
36
|
+
## MINIMUM OUTPUT REQUIREMENTS
|
|
37
|
+
{requirements_list}
|
|
38
|
+
|
|
39
|
+
## QUALITY CHECKLIST (score 0-100)
|
|
40
|
+
- Section present: +10 points each
|
|
41
|
+
- Section complete: +15 points each
|
|
42
|
+
- Format correct: +10 points each
|
|
43
|
+
- MINIMUM TO DELIVER: 70 points
|
|
44
|
+
|
|
45
|
+
<!-- End MANDATORY -->
|
|
46
|
+
|
|
47
|
+
'''
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
51
|
+
# DIAGNOSIS
|
|
52
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
53
|
+
|
|
54
|
+
def diagnose_gap(agent_path: Path, missing_sections: List[str]) -> Dict:
|
|
55
|
+
"""
|
|
56
|
+
Diagnostica a causa raiz do gap de qualidade e propõe estratégia de fix.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
agent_path: Caminho para pasta do agente
|
|
60
|
+
missing_sections: Lista de seções que faltaram no output
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
dict com diagnóstico e proposta de fix
|
|
64
|
+
"""
|
|
65
|
+
agent_md = agent_path / "AGENT.md"
|
|
66
|
+
|
|
67
|
+
if not agent_md.exists():
|
|
68
|
+
return {
|
|
69
|
+
"root_cause": "agent_not_found",
|
|
70
|
+
"fix_strategy": "create_agent",
|
|
71
|
+
"changes_needed": [],
|
|
72
|
+
"error": f"AGENT.md not found at {agent_md}"
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
try:
|
|
76
|
+
content = agent_md.read_text(encoding='utf-8')
|
|
77
|
+
except Exception as e:
|
|
78
|
+
return {
|
|
79
|
+
"root_cause": "read_error",
|
|
80
|
+
"fix_strategy": "manual_review",
|
|
81
|
+
"changes_needed": [],
|
|
82
|
+
"error": str(e)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
diagnosis = {
|
|
86
|
+
"root_cause": None,
|
|
87
|
+
"fix_strategy": None,
|
|
88
|
+
"changes_needed": [],
|
|
89
|
+
"agent_path": str(agent_path),
|
|
90
|
+
"file_stats": {
|
|
91
|
+
"total_lines": len(content.split('\n')),
|
|
92
|
+
"total_chars": len(content)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
# Verificação 1: Tem MANDATORY_SECTIONS header?
|
|
97
|
+
has_mandatory = "MANDATORY OUTPUT SECTIONS" in content or "<!-- MANDATORY -->" in content
|
|
98
|
+
|
|
99
|
+
if not has_mandatory:
|
|
100
|
+
diagnosis["root_cause"] = "no_mandatory_header"
|
|
101
|
+
diagnosis["fix_strategy"] = "add_mandatory_header"
|
|
102
|
+
diagnosis["changes_needed"].append({
|
|
103
|
+
"action": "prepend",
|
|
104
|
+
"description": "Add MANDATORY_SECTIONS header to top of file",
|
|
105
|
+
"content_preview": generate_mandatory_header(missing_sections)[:200] + "..."
|
|
106
|
+
})
|
|
107
|
+
else:
|
|
108
|
+
# MANDATORY existe, mas seções não estão completas
|
|
109
|
+
diagnosis["root_cause"] = "incomplete_mandatory"
|
|
110
|
+
diagnosis["fix_strategy"] = "update_mandatory_header"
|
|
111
|
+
diagnosis["changes_needed"].append({
|
|
112
|
+
"action": "update_mandatory",
|
|
113
|
+
"description": "Update MANDATORY_SECTIONS with missing items",
|
|
114
|
+
"missing": missing_sections
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
# Verificação 2: Arquivo muito longo (pode ser truncado)?
|
|
118
|
+
lines = len(content.split('\n'))
|
|
119
|
+
if lines > 200:
|
|
120
|
+
diagnosis["changes_needed"].append({
|
|
121
|
+
"action": "generate_compact_summary",
|
|
122
|
+
"description": f"Agent file has {lines} lines - may be truncated in context",
|
|
123
|
+
"reason": "Long files may lose MANDATORY sections when loaded",
|
|
124
|
+
"suggested_max": 150
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
# Verificação 3: MANDATORY está no final (bad position)?
|
|
128
|
+
if has_mandatory:
|
|
129
|
+
mandatory_position = content.find("MANDATORY OUTPUT SECTIONS")
|
|
130
|
+
if mandatory_position > len(content) * 0.3: # Depois de 30% do arquivo
|
|
131
|
+
diagnosis["changes_needed"].append({
|
|
132
|
+
"action": "move_mandatory_to_top",
|
|
133
|
+
"description": "MANDATORY section is not at the top of file",
|
|
134
|
+
"current_position": f"{int(mandatory_position/len(content)*100)}% into file",
|
|
135
|
+
"recommended": "First 50 lines"
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
return diagnosis
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def generate_mandatory_header(missing_sections: List[str]) -> str:
|
|
142
|
+
"""
|
|
143
|
+
Gera header MANDATORY_SECTIONS baseado nas seções faltantes.
|
|
144
|
+
"""
|
|
145
|
+
# Mapeia seções para marcadores
|
|
146
|
+
section_markers = {
|
|
147
|
+
"ASCII Header": ("╔═══", "Large title with double border"),
|
|
148
|
+
"Progress Bar": ("████", "Visual progress ████████░░"),
|
|
149
|
+
"Metrics Panel": ("┌─ MÉTRICAS", "Metrics card with [explanations]"),
|
|
150
|
+
"Changes Section": ("O QUE MUDOU", "What changed since last"),
|
|
151
|
+
"Personal Notes": ("NOTAS DO", "Personal observations from agent"),
|
|
152
|
+
"ASCII Boxes": ("┌───┐", "Structured information boxes"),
|
|
153
|
+
"Explanations": ("[explanation]", "Technical terms with context"),
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
sections_table = ""
|
|
157
|
+
requirements_list = ""
|
|
158
|
+
|
|
159
|
+
for section in missing_sections:
|
|
160
|
+
if section in section_markers:
|
|
161
|
+
marker, example = section_markers[section]
|
|
162
|
+
sections_table += f"| {section} | YES | `{marker}` | {example} |\n"
|
|
163
|
+
requirements_list += f"- [ ] Include {section}\n"
|
|
164
|
+
else:
|
|
165
|
+
sections_table += f"| {section} | YES | `{section[:5]}` | Required element |\n"
|
|
166
|
+
requirements_list += f"- [ ] Include {section}\n"
|
|
167
|
+
|
|
168
|
+
return MANDATORY_HEADER_TEMPLATE.format(
|
|
169
|
+
sections_table=sections_table.strip(),
|
|
170
|
+
requirements_list=requirements_list.strip()
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
175
|
+
# PROPOSAL SYSTEM (NÃO APLICA AUTOMATICAMENTE)
|
|
176
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
177
|
+
|
|
178
|
+
def create_fix_proposal(agent_path: Path, diagnosis: Dict) -> Dict:
|
|
179
|
+
"""
|
|
180
|
+
Cria proposta de fix para revisão humana.
|
|
181
|
+
|
|
182
|
+
IMPORTANTE: NÃO modifica arquivos diretamente.
|
|
183
|
+
Salva proposta em doctor_proposals.jsonl para revisão.
|
|
184
|
+
"""
|
|
185
|
+
proposal_id = f"PROP-{datetime.now().strftime('%Y%m%d-%H%M%S')}"
|
|
186
|
+
|
|
187
|
+
proposal = {
|
|
188
|
+
"proposal_id": proposal_id,
|
|
189
|
+
"timestamp": datetime.now().isoformat(),
|
|
190
|
+
"agent": agent_path.name if agent_path else "unknown",
|
|
191
|
+
"agent_path": str(agent_path) if agent_path else None,
|
|
192
|
+
"diagnosis": {
|
|
193
|
+
"root_cause": diagnosis.get("root_cause"),
|
|
194
|
+
"fix_strategy": diagnosis.get("fix_strategy"),
|
|
195
|
+
"file_stats": diagnosis.get("file_stats", {})
|
|
196
|
+
},
|
|
197
|
+
"proposed_changes": diagnosis.get("changes_needed", []),
|
|
198
|
+
"status": "pending_approval",
|
|
199
|
+
"reviewed_by": None,
|
|
200
|
+
"review_date": None,
|
|
201
|
+
"applied": False
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
# Salva proposta para revisão humana
|
|
205
|
+
save_proposal(proposal)
|
|
206
|
+
|
|
207
|
+
# Log que proposta foi criada (não aplicada)
|
|
208
|
+
log_proposal_created(proposal)
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
"success": True,
|
|
212
|
+
"action": "proposed",
|
|
213
|
+
"proposal_id": proposal_id,
|
|
214
|
+
"review_required": True,
|
|
215
|
+
"message": f"Fix proposal created: {proposal_id}. Awaiting human review."
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def save_proposal(proposal: Dict):
|
|
220
|
+
"""Salva proposta no log de proposals."""
|
|
221
|
+
DOCTOR_PROPOSALS_LOG.parent.mkdir(parents=True, exist_ok=True)
|
|
222
|
+
|
|
223
|
+
try:
|
|
224
|
+
with open(DOCTOR_PROPOSALS_LOG, "a", encoding='utf-8') as f:
|
|
225
|
+
f.write(json.dumps(proposal, ensure_ascii=False) + "\n")
|
|
226
|
+
except Exception as e:
|
|
227
|
+
print(f"Error saving proposal: {e}")
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def log_proposal_created(proposal: Dict):
|
|
231
|
+
"""Loga que proposta foi criada (para histórico/auditoria)."""
|
|
232
|
+
DOCTOR_FIXES_LOG.parent.mkdir(parents=True, exist_ok=True)
|
|
233
|
+
|
|
234
|
+
entry = {
|
|
235
|
+
"timestamp": datetime.now().isoformat(),
|
|
236
|
+
"proposal_id": proposal.get("proposal_id"),
|
|
237
|
+
"agent": proposal.get("agent"),
|
|
238
|
+
"root_cause": proposal.get("diagnosis", {}).get("root_cause"),
|
|
239
|
+
"fix_strategy": proposal.get("diagnosis", {}).get("fix_strategy"),
|
|
240
|
+
"status": "proposed_pending_review",
|
|
241
|
+
"auto_applied": False
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
try:
|
|
245
|
+
with open(DOCTOR_FIXES_LOG, "a", encoding='utf-8') as f:
|
|
246
|
+
f.write(json.dumps(entry, ensure_ascii=False) + "\n")
|
|
247
|
+
except Exception:
|
|
248
|
+
pass
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
252
|
+
# PROPOSAL MANAGEMENT
|
|
253
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
254
|
+
|
|
255
|
+
def list_pending_proposals() -> List[Dict]:
|
|
256
|
+
"""Lista todas as propostas pendentes de aprovação."""
|
|
257
|
+
if not DOCTOR_PROPOSALS_LOG.exists():
|
|
258
|
+
return []
|
|
259
|
+
|
|
260
|
+
pending = []
|
|
261
|
+
try:
|
|
262
|
+
with open(DOCTOR_PROPOSALS_LOG, "r", encoding='utf-8') as f:
|
|
263
|
+
for line in f:
|
|
264
|
+
if line.strip():
|
|
265
|
+
proposal = json.loads(line)
|
|
266
|
+
if proposal.get("status") == "pending_approval":
|
|
267
|
+
pending.append(proposal)
|
|
268
|
+
except Exception:
|
|
269
|
+
pass
|
|
270
|
+
|
|
271
|
+
return pending
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def approve_proposal(proposal_id: str, reviewer: str = "human") -> Dict:
|
|
275
|
+
"""
|
|
276
|
+
Marca proposta como aprovada (ainda não aplica).
|
|
277
|
+
|
|
278
|
+
Para aplicar de fato, use apply_approved_proposal().
|
|
279
|
+
"""
|
|
280
|
+
proposals = []
|
|
281
|
+
approved = None
|
|
282
|
+
|
|
283
|
+
if DOCTOR_PROPOSALS_LOG.exists():
|
|
284
|
+
with open(DOCTOR_PROPOSALS_LOG, "r", encoding='utf-8') as f:
|
|
285
|
+
for line in f:
|
|
286
|
+
if line.strip():
|
|
287
|
+
proposal = json.loads(line)
|
|
288
|
+
if proposal.get("proposal_id") == proposal_id:
|
|
289
|
+
proposal["status"] = "approved"
|
|
290
|
+
proposal["reviewed_by"] = reviewer
|
|
291
|
+
proposal["review_date"] = datetime.now().isoformat()
|
|
292
|
+
approved = proposal
|
|
293
|
+
proposals.append(proposal)
|
|
294
|
+
|
|
295
|
+
if approved:
|
|
296
|
+
# Reescreve arquivo com status atualizado
|
|
297
|
+
with open(DOCTOR_PROPOSALS_LOG, "w", encoding='utf-8') as f:
|
|
298
|
+
for p in proposals:
|
|
299
|
+
f.write(json.dumps(p, ensure_ascii=False) + "\n")
|
|
300
|
+
|
|
301
|
+
return {"success": True, "proposal": approved}
|
|
302
|
+
|
|
303
|
+
return {"success": False, "error": f"Proposal {proposal_id} not found"}
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def reject_proposal(proposal_id: str, reason: str, reviewer: str = "human") -> Dict:
|
|
307
|
+
"""Marca proposta como rejeitada."""
|
|
308
|
+
proposals = []
|
|
309
|
+
rejected = None
|
|
310
|
+
|
|
311
|
+
if DOCTOR_PROPOSALS_LOG.exists():
|
|
312
|
+
with open(DOCTOR_PROPOSALS_LOG, "r", encoding='utf-8') as f:
|
|
313
|
+
for line in f:
|
|
314
|
+
if line.strip():
|
|
315
|
+
proposal = json.loads(line)
|
|
316
|
+
if proposal.get("proposal_id") == proposal_id:
|
|
317
|
+
proposal["status"] = "rejected"
|
|
318
|
+
proposal["reviewed_by"] = reviewer
|
|
319
|
+
proposal["review_date"] = datetime.now().isoformat()
|
|
320
|
+
proposal["rejection_reason"] = reason
|
|
321
|
+
rejected = proposal
|
|
322
|
+
proposals.append(proposal)
|
|
323
|
+
|
|
324
|
+
if rejected:
|
|
325
|
+
with open(DOCTOR_PROPOSALS_LOG, "w", encoding='utf-8') as f:
|
|
326
|
+
for p in proposals:
|
|
327
|
+
f.write(json.dumps(p, ensure_ascii=False) + "\n")
|
|
328
|
+
|
|
329
|
+
return {"success": True, "proposal": rejected}
|
|
330
|
+
|
|
331
|
+
return {"success": False, "error": f"Proposal {proposal_id} not found"}
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
335
|
+
# MAIN INTERFACE
|
|
336
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
337
|
+
|
|
338
|
+
def process_quality_gap(
|
|
339
|
+
agent_path: Path,
|
|
340
|
+
missing_sections: List[str],
|
|
341
|
+
score: int
|
|
342
|
+
) -> Dict:
|
|
343
|
+
"""
|
|
344
|
+
Processa um gap de qualidade detectado pelo WATCHDOG.
|
|
345
|
+
|
|
346
|
+
Interface principal para integração com post_output_validator.py
|
|
347
|
+
|
|
348
|
+
Args:
|
|
349
|
+
agent_path: Path para a pasta do agente
|
|
350
|
+
missing_sections: Seções que faltaram no output
|
|
351
|
+
score: Score de qualidade (0-100)
|
|
352
|
+
|
|
353
|
+
Returns:
|
|
354
|
+
dict com resultado do processamento
|
|
355
|
+
"""
|
|
356
|
+
result = {
|
|
357
|
+
"timestamp": datetime.now().isoformat(),
|
|
358
|
+
"agent": agent_path.name if agent_path else "unknown",
|
|
359
|
+
"score": score,
|
|
360
|
+
"missing": missing_sections
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
# Só processa se score for muito baixo (< 50)
|
|
364
|
+
if score >= 50:
|
|
365
|
+
result["action"] = "skipped"
|
|
366
|
+
result["reason"] = "Score above proposal threshold (50)"
|
|
367
|
+
return result
|
|
368
|
+
|
|
369
|
+
# Diagnostica o problema
|
|
370
|
+
diagnosis = diagnose_gap(agent_path, missing_sections)
|
|
371
|
+
|
|
372
|
+
# Cria proposta (não aplica)
|
|
373
|
+
proposal_result = create_fix_proposal(agent_path, diagnosis)
|
|
374
|
+
|
|
375
|
+
result["diagnosis"] = diagnosis
|
|
376
|
+
result["proposal"] = proposal_result
|
|
377
|
+
result["action"] = "proposal_created"
|
|
378
|
+
|
|
379
|
+
return result
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
383
|
+
# CLI INTERFACE
|
|
384
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
385
|
+
|
|
386
|
+
if __name__ == "__main__":
|
|
387
|
+
import sys
|
|
388
|
+
|
|
389
|
+
if len(sys.argv) > 1:
|
|
390
|
+
command = sys.argv[1]
|
|
391
|
+
|
|
392
|
+
if command == "list":
|
|
393
|
+
# Listar propostas pendentes
|
|
394
|
+
pending = list_pending_proposals()
|
|
395
|
+
print(f"Pending proposals: {len(pending)}\n")
|
|
396
|
+
for p in pending:
|
|
397
|
+
print(f" {p['proposal_id']}: {p['agent']} - {p['diagnosis']['root_cause']}")
|
|
398
|
+
|
|
399
|
+
elif command == "approve" and len(sys.argv) > 2:
|
|
400
|
+
# Aprovar proposta
|
|
401
|
+
proposal_id = sys.argv[2]
|
|
402
|
+
result = approve_proposal(proposal_id)
|
|
403
|
+
print(f"Approve result: {result}")
|
|
404
|
+
|
|
405
|
+
elif command == "reject" and len(sys.argv) > 3:
|
|
406
|
+
# Rejeitar proposta
|
|
407
|
+
proposal_id = sys.argv[2]
|
|
408
|
+
reason = sys.argv[3]
|
|
409
|
+
result = reject_proposal(proposal_id, reason)
|
|
410
|
+
print(f"Reject result: {result}")
|
|
411
|
+
|
|
412
|
+
else:
|
|
413
|
+
print("Usage:")
|
|
414
|
+
print(" python agent_doctor.py list - List pending proposals")
|
|
415
|
+
print(" python agent_doctor.py approve <proposal_id> - Approve proposal")
|
|
416
|
+
print(" python agent_doctor.py reject <proposal_id> <reason> - Reject proposal")
|
|
417
|
+
|
|
418
|
+
else:
|
|
419
|
+
# Test mode
|
|
420
|
+
test_path = PROJECT_ROOT / ".claude" / "jarvis" / "sub-agents" / "chronicler"
|
|
421
|
+
test_missing = ["ASCII Header", "Progress Bar", "Changes Section"]
|
|
422
|
+
|
|
423
|
+
print(f"Testing diagnosis for: {test_path}\n")
|
|
424
|
+
|
|
425
|
+
diagnosis = diagnose_gap(test_path, test_missing)
|
|
426
|
+
print(f"Diagnosis:")
|
|
427
|
+
print(f" Root cause: {diagnosis['root_cause']}")
|
|
428
|
+
print(f" Fix strategy: {diagnosis['fix_strategy']}")
|
|
429
|
+
print(f" Changes needed: {len(diagnosis['changes_needed'])}")
|
|
430
|
+
|
|
431
|
+
print("\nCreating proposal (not applying)...")
|
|
432
|
+
result = create_fix_proposal(test_path, diagnosis)
|
|
433
|
+
print(f"Result: {result}")
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Agent Memory Persister Hook v1.0 (Mega Brain)
|
|
4
|
+
|
|
5
|
+
Persists session learnings to the active agent's memory file at session end.
|
|
6
|
+
Adapted from: aios-core/.claude/hooks/agent_memory_persister.py
|
|
7
|
+
|
|
8
|
+
LIFECYCLE:
|
|
9
|
+
READ: skill_router.py (UserPromptSubmit) -> loads .claude/agent-memory/{slug}/MEMORY.md
|
|
10
|
+
WRITE: post_batch_cascading.py (PostToolUse) -> writes batch learnings
|
|
11
|
+
WRITE: THIS HOOK (SessionEnd) -> writes session summary
|
|
12
|
+
|
|
13
|
+
BEHAVIOR:
|
|
14
|
+
- Reads active agent from STATE.json (session.agent_active)
|
|
15
|
+
- Appends a session entry to .claude/agent-memory/{slug}/MEMORY.md
|
|
16
|
+
- Creates the memory file if it doesn't exist
|
|
17
|
+
- Fail-open: NEVER blocks session end
|
|
18
|
+
|
|
19
|
+
EXIT CODES:
|
|
20
|
+
- 0: Always (advisory only, never blocks)
|
|
21
|
+
|
|
22
|
+
Hook Type: SessionEnd
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
import json
|
|
26
|
+
import sys
|
|
27
|
+
import os
|
|
28
|
+
import time
|
|
29
|
+
from datetime import datetime
|
|
30
|
+
from pathlib import Path
|
|
31
|
+
|
|
32
|
+
PROJECT_ROOT = Path(os.environ.get('CLAUDE_PROJECT_DIR', '.'))
|
|
33
|
+
STATE_DIR = PROJECT_ROOT / ".claude" / "jarvis"
|
|
34
|
+
STATE_FILE = STATE_DIR / "STATE.json"
|
|
35
|
+
AGENT_MEMORY_DIR = PROJECT_ROOT / ".claude" / "agent-memory"
|
|
36
|
+
|
|
37
|
+
# Internal time budget (ms)
|
|
38
|
+
INTERNAL_TIMEOUT_MS = 3000
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def load_state() -> dict:
|
|
42
|
+
"""Load current state."""
|
|
43
|
+
if not STATE_FILE.exists():
|
|
44
|
+
return {}
|
|
45
|
+
try:
|
|
46
|
+
with open(STATE_FILE, 'r', encoding='utf-8') as f:
|
|
47
|
+
return json.load(f)
|
|
48
|
+
except (json.JSONDecodeError, IOError):
|
|
49
|
+
return {}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def get_active_agent(state: dict) -> str | None:
|
|
53
|
+
"""Extract active agent slug from STATE.json."""
|
|
54
|
+
# Check session.agent_active (set by skill_router/post_tool_use)
|
|
55
|
+
session = state.get("session", {})
|
|
56
|
+
agent = session.get("agent_active")
|
|
57
|
+
if agent and isinstance(agent, str) and agent.strip():
|
|
58
|
+
return agent.strip()
|
|
59
|
+
return None
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def get_session_metadata(state: dict) -> dict:
|
|
63
|
+
"""Extract useful session metadata for the memory entry."""
|
|
64
|
+
accumulated = state.get("accumulated", {})
|
|
65
|
+
pipeline = state.get("pipeline", {})
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
"session_id": state.get("session_id", "unknown"),
|
|
69
|
+
"ended_at": datetime.now().isoformat(),
|
|
70
|
+
"files_processed": pipeline.get("files_processed", 0),
|
|
71
|
+
"progress_percent": accumulated.get("progress_percent", 0),
|
|
72
|
+
"current_step": pipeline.get("current_step", "unknown"),
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def get_memory_path(agent_slug: str) -> Path:
|
|
77
|
+
"""Resolve agent memory path."""
|
|
78
|
+
return AGENT_MEMORY_DIR / agent_slug / "MEMORY.md"
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def create_memory_file(memory_path: Path, agent_slug: str) -> None:
|
|
82
|
+
"""Create a new MEMORY.md with header."""
|
|
83
|
+
memory_path.parent.mkdir(parents=True, exist_ok=True)
|
|
84
|
+
header = f"""# {agent_slug} - Agent Memory
|
|
85
|
+
|
|
86
|
+
> Auto-managed by hooks. Do not edit manually.
|
|
87
|
+
> READ: skill_router.py (on activation)
|
|
88
|
+
> WRITE: agent_memory_persister.py (on session end)
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
"""
|
|
93
|
+
memory_path.write_text(header, encoding='utf-8')
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def build_session_entry(agent_slug: str, metadata: dict) -> str:
|
|
97
|
+
"""Build a markdown session entry to append to MEMORY.md."""
|
|
98
|
+
now = datetime.now()
|
|
99
|
+
date_str = now.strftime("%Y-%m-%d %H:%M")
|
|
100
|
+
session_id_short = metadata.get("session_id", "unknown")[:8]
|
|
101
|
+
|
|
102
|
+
lines = [
|
|
103
|
+
f"## Session {date_str} [{session_id_short}]",
|
|
104
|
+
"",
|
|
105
|
+
f"- **Step:** {metadata.get('current_step', 'unknown')}",
|
|
106
|
+
f"- **Progress:** {metadata.get('progress_percent', 0)}%",
|
|
107
|
+
f"- **Files processed:** {metadata.get('files_processed', 0)}",
|
|
108
|
+
"",
|
|
109
|
+
"---",
|
|
110
|
+
"",
|
|
111
|
+
]
|
|
112
|
+
|
|
113
|
+
return "\n".join(lines)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def append_to_memory(memory_path: Path, entry: str) -> bool:
|
|
117
|
+
"""Append session entry to MEMORY.md, trimming if over 200 lines."""
|
|
118
|
+
try:
|
|
119
|
+
if not memory_path.exists():
|
|
120
|
+
return False
|
|
121
|
+
|
|
122
|
+
content = memory_path.read_text(encoding='utf-8')
|
|
123
|
+
new_content = content.rstrip('\n') + '\n\n' + entry
|
|
124
|
+
|
|
125
|
+
final_lines = new_content.split('\n')
|
|
126
|
+
if len(final_lines) > 200:
|
|
127
|
+
header = final_lines[:20]
|
|
128
|
+
recent = final_lines[-175:]
|
|
129
|
+
final_lines = header + ["", "<!-- Older entries trimmed by agent_memory_persister.py -->", ""] + recent
|
|
130
|
+
|
|
131
|
+
memory_path.write_text('\n'.join(final_lines), encoding='utf-8')
|
|
132
|
+
return True
|
|
133
|
+
except Exception:
|
|
134
|
+
return False
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def check_timeout(start_time):
|
|
138
|
+
"""Check if we've exceeded internal time budget."""
|
|
139
|
+
elapsed_ms = (time.time() - start_time) * 1000
|
|
140
|
+
return elapsed_ms > INTERNAL_TIMEOUT_MS
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def main():
|
|
144
|
+
"""Main hook execution with timeout protection."""
|
|
145
|
+
start_time = time.time()
|
|
146
|
+
|
|
147
|
+
try:
|
|
148
|
+
input_data = sys.stdin.read()
|
|
149
|
+
hook_input = json.loads(input_data) if input_data.strip() else {}
|
|
150
|
+
|
|
151
|
+
state = load_state()
|
|
152
|
+
if not state:
|
|
153
|
+
print(json.dumps({"continue": True}))
|
|
154
|
+
return
|
|
155
|
+
|
|
156
|
+
raw_slug = get_active_agent(state)
|
|
157
|
+
if not raw_slug:
|
|
158
|
+
print(json.dumps({"continue": True}))
|
|
159
|
+
return
|
|
160
|
+
|
|
161
|
+
if check_timeout(start_time):
|
|
162
|
+
print(json.dumps({
|
|
163
|
+
"continue": True,
|
|
164
|
+
"feedback": "[MB] Memory persister skipped (timeout after state load)"
|
|
165
|
+
}))
|
|
166
|
+
return
|
|
167
|
+
|
|
168
|
+
agent_slug = raw_slug
|
|
169
|
+
memory_path = get_memory_path(agent_slug)
|
|
170
|
+
|
|
171
|
+
if not memory_path.exists():
|
|
172
|
+
create_memory_file(memory_path, agent_slug)
|
|
173
|
+
|
|
174
|
+
if check_timeout(start_time):
|
|
175
|
+
print(json.dumps({
|
|
176
|
+
"continue": True,
|
|
177
|
+
"feedback": "[MB] Memory persister skipped (timeout before entry build)"
|
|
178
|
+
}))
|
|
179
|
+
return
|
|
180
|
+
|
|
181
|
+
metadata = get_session_metadata(state)
|
|
182
|
+
entry = build_session_entry(agent_slug, metadata)
|
|
183
|
+
success = append_to_memory(memory_path, entry)
|
|
184
|
+
|
|
185
|
+
output = {"continue": True}
|
|
186
|
+
|
|
187
|
+
if success:
|
|
188
|
+
elapsed_ms = round((time.time() - start_time) * 1000)
|
|
189
|
+
output["feedback"] = f"[MB] Session memory persisted for @{agent_slug} ({elapsed_ms}ms)"
|
|
190
|
+
else:
|
|
191
|
+
output["feedback"] = f"[MB] Could not persist memory for @{agent_slug}"
|
|
192
|
+
|
|
193
|
+
print(json.dumps(output))
|
|
194
|
+
|
|
195
|
+
except Exception as e:
|
|
196
|
+
print(json.dumps({
|
|
197
|
+
"continue": True,
|
|
198
|
+
"feedback": f"[MB] Memory persister error (fail-open): {str(e)}"
|
|
199
|
+
}))
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
if __name__ == "__main__":
|
|
203
|
+
main()
|