@jaimevalasek/aioson 1.5.1 → 1.7.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.
- package/CHANGELOG.md +49 -0
- package/README.md +729 -226
- package/docs/design-previews/aurora-command-ui-website.html +884 -0
- package/docs/design-previews/aurora-command-ui.html +682 -0
- package/docs/design-previews/bold-editorial-ui-website.html +658 -0
- package/docs/design-previews/bold-editorial-ui.html +717 -0
- package/docs/design-previews/clean-saas-ui-website.html +1202 -0
- package/docs/design-previews/clean-saas-ui.html +549 -0
- package/docs/design-previews/cognitive-core-ui-website.html +1009 -0
- package/docs/design-previews/cognitive-core-ui.html +463 -0
- package/docs/design-previews/glassmorphism-ui-website.html +572 -0
- package/docs/design-previews/glassmorphism-ui.html +886 -0
- package/docs/design-previews/index.html +699 -0
- package/docs/design-previews/interface-design-website.html +1187 -0
- package/docs/design-previews/interface-design.html +513 -0
- package/docs/design-previews/neo-brutalist-ui-website.html +621 -0
- package/docs/design-previews/neo-brutalist-ui.html +797 -0
- package/docs/design-previews/premium-command-center-ui-website.html +1217 -0
- package/docs/design-previews/premium-command-center-ui.html +552 -0
- package/docs/design-previews/pt.squarespace.com-homepage.html +889 -0
- package/docs/design-previews/warm-craft-ui-website.html +684 -0
- package/docs/design-previews/warm-craft-ui.html +739 -0
- package/docs/en/cli-reference.md +20 -9
- package/docs/integrations/sdlc-genius-boundary.md +76 -0
- package/docs/integrations/sdlc-genius-eval-matrix.md +75 -0
- package/docs/integrations/sdlc-genius-install-checklist.md +93 -0
- package/docs/integrations/sdlc-genius-review-samples.md +86 -0
- package/docs/pt/README.md +10 -0
- package/docs/pt/agent-sharding.md +132 -0
- package/docs/pt/agentes.md +9 -2
- package/docs/pt/busca-de-contexto.md +129 -0
- package/docs/pt/cache-de-contexto.md +156 -0
- package/docs/pt/comandos-cli.md +915 -1
- package/docs/pt/design-hybrid-forge.md +356 -0
- package/docs/pt/devlog-pipeline.md +270 -0
- package/docs/pt/fluxo-artefatos.md +178 -0
- package/docs/pt/hooks-session-guard.md +454 -0
- package/docs/pt/inicio-rapido.md +54 -3
- package/docs/pt/inteligencia-adaptativa.md +324 -0
- package/docs/pt/monitor-de-contexto.md +158 -0
- package/docs/pt/recuperacao-de-sessao.md +125 -0
- package/docs/pt/sandbox.md +125 -0
- package/docs/pt/sdd-automation-scripts.md +557 -0
- package/docs/pt/site-forge.md +309 -0
- package/docs/pt/skills.md +98 -6
- package/docs/pt/spec-learnings-pipeline.md +265 -0
- package/package.json +1 -1
- package/src/a2a/client.js +165 -0
- package/src/a2a/server.js +223 -0
- package/src/agent-loader.js +280 -0
- package/src/cli.js +329 -1
- package/src/commands/agent-audit.js +397 -0
- package/src/commands/agent-export-skill.js +229 -0
- package/src/commands/agent-loader.js +85 -0
- package/src/commands/artifact-validate.js +189 -0
- package/src/commands/brief-gen.js +405 -0
- package/src/commands/brief-validate.js +65 -0
- package/src/commands/classify.js +256 -0
- package/src/commands/context-cache.js +90 -0
- package/src/commands/context-compact.js +49 -0
- package/src/commands/context-health.js +175 -0
- package/src/commands/context-monitor.js +163 -0
- package/src/commands/context-search.js +66 -0
- package/src/commands/context-trim.js +177 -0
- package/src/commands/design-hybrid-options.js +385 -0
- package/src/commands/detect-test-runner.js +55 -0
- package/src/commands/devlog-export-brains.js +27 -0
- package/src/commands/devlog-process.js +292 -0
- package/src/commands/devlog-watch.js +131 -0
- package/src/commands/feature-close.js +165 -0
- package/src/commands/gate-check.js +228 -0
- package/src/commands/health.js +214 -0
- package/src/commands/hooks-emit.js +253 -0
- package/src/commands/hooks-install.js +347 -0
- package/src/commands/init.js +54 -13
- package/src/commands/install.js +52 -13
- package/src/commands/learning-auto-promote.js +195 -0
- package/src/commands/learning-evolve.js +364 -0
- package/src/commands/learning-export.js +103 -0
- package/src/commands/learning-rollback.js +164 -0
- package/src/commands/live.js +59 -1
- package/src/commands/pattern-detect.js +33 -0
- package/src/commands/preflight-context.js +30 -0
- package/src/commands/preflight.js +208 -0
- package/src/commands/pulse-update.js +130 -0
- package/src/commands/recovery.js +43 -0
- package/src/commands/runner-daemon.js +274 -0
- package/src/commands/runner-plan.js +70 -0
- package/src/commands/runner-queue-from-plan.js +166 -0
- package/src/commands/runner-queue.js +189 -0
- package/src/commands/runner-run.js +129 -0
- package/src/commands/runtime.js +47 -1
- package/src/commands/sandbox.js +37 -0
- package/src/commands/self-implement-loop.js +256 -0
- package/src/commands/session-guard.js +218 -0
- package/src/commands/setup-context.js +22 -2
- package/src/commands/setup.js +178 -0
- package/src/commands/sizing.js +165 -0
- package/src/commands/skill.js +144 -32
- package/src/commands/spec-checkpoint.js +177 -0
- package/src/commands/spec-status.js +79 -0
- package/src/commands/spec-sync.js +190 -0
- package/src/commands/spec-tasks.js +288 -0
- package/src/commands/squad-autorun.js +1220 -0
- package/src/commands/squad-bus.js +217 -0
- package/src/commands/squad-card.js +149 -0
- package/src/commands/squad-daemon.js +134 -0
- package/src/commands/squad-dependency-graph.js +164 -0
- package/src/commands/squad-review.js +106 -0
- package/src/commands/squad-scaffold.js +55 -0
- package/src/commands/squad-tool-register.js +157 -0
- package/src/commands/state-save.js +122 -0
- package/src/commands/tool-registry-cmd.js +232 -0
- package/src/commands/update.js +9 -0
- package/src/commands/verify-gate.js +572 -0
- package/src/commands/workflow-execute.js +241 -0
- package/src/constants.js +18 -0
- package/src/context-cache.js +159 -0
- package/src/context-search.js +326 -0
- package/src/design-variation-catalog.js +503 -0
- package/src/i18n/messages/en.js +32 -2
- package/src/i18n/messages/es.js +30 -2
- package/src/i18n/messages/fr.js +30 -2
- package/src/i18n/messages/pt-BR.js +32 -2
- package/src/install-animation.js +260 -0
- package/src/install-profile.js +143 -0
- package/src/install-wizard.js +475 -0
- package/src/installer.js +44 -10
- package/src/lib/health-check.js +158 -0
- package/src/lib/hook-protocol.js +76 -0
- package/src/mcp/apps/squad-dashboard/app.js +163 -0
- package/src/mcp/apps/squad-dashboard/index.html +261 -0
- package/src/mcp/apps/squad-dashboard/mcp-manifest.json +23 -0
- package/src/mcp/resources/squad-state.js +130 -0
- package/src/parser.js +7 -1
- package/src/preflight-engine.js +443 -0
- package/src/recovery-context-session.js +154 -0
- package/src/runner/cascade.js +97 -0
- package/src/runner/cli-launcher.js +109 -0
- package/src/runner/plan-importer.js +63 -0
- package/src/runner/queue-store.js +159 -0
- package/src/runtime-store.js +158 -4
- package/src/sandbox.js +177 -0
- package/src/squad/agent-teams-adapter.js +264 -0
- package/src/squad/brief-validator.js +350 -0
- package/src/squad/bus-bridge.js +140 -0
- package/src/squad/context-compactor.js +265 -0
- package/src/squad/cross-ai-synthesizer.js +250 -0
- package/src/squad/hooks-generator.js +196 -0
- package/src/squad/inter-squad-events.js +175 -0
- package/src/squad/intra-bus.js +345 -0
- package/src/squad/learning-extractor.js +213 -0
- package/src/squad/pattern-detector.js +365 -0
- package/src/squad/preflight-context.js +296 -0
- package/src/squad/recovery-context.js +242 -71
- package/src/squad/reflection.js +365 -0
- package/src/squad/squad-scaffold.js +177 -0
- package/src/squad/state-manager.js +310 -0
- package/src/squad/task-decomposer.js +652 -0
- package/src/squad/verify-gate.js +303 -0
- package/src/tool-executor.js +94 -0
- package/src/updater.js +10 -3
- package/src/worker-runner.js +186 -1
- package/template/.aioson/agents/analyst.md +119 -3
- package/template/.aioson/agents/architect.md +98 -0
- package/template/.aioson/agents/design-hybrid-forge.md +141 -0
- package/template/.aioson/agents/dev.md +335 -14
- package/template/.aioson/agents/deyvin.md +117 -2
- package/template/.aioson/agents/discovery-design-doc.md +44 -0
- package/template/.aioson/agents/genome.md +14 -0
- package/template/.aioson/agents/neo.md +78 -1
- package/template/.aioson/agents/orache.md +50 -4
- package/template/.aioson/agents/orchestrator.md +197 -1
- package/template/.aioson/agents/pm.md +93 -0
- package/template/.aioson/agents/product.md +77 -4
- package/template/.aioson/agents/profiler-enricher.md +14 -0
- package/template/.aioson/agents/profiler-forge.md +14 -0
- package/template/.aioson/agents/profiler-researcher.md +14 -0
- package/template/.aioson/agents/qa.md +249 -19
- package/template/.aioson/agents/setup.md +144 -12
- package/template/.aioson/agents/sheldon.md +237 -11
- package/template/.aioson/agents/site-forge.md +1753 -0
- package/template/.aioson/agents/squad.md +162 -0
- package/template/.aioson/agents/tester.md +209 -0
- package/template/.aioson/agents/ux-ui.md +34 -1
- package/template/.aioson/brains/README.md +128 -0
- package/template/.aioson/brains/_index.json +16 -0
- package/template/.aioson/brains/scripts/query.js +103 -0
- package/template/.aioson/brains/site-forge/visual-patterns.brain.json +205 -0
- package/template/.aioson/config.md +158 -13
- package/template/.aioson/constitution.md +33 -0
- package/template/.aioson/context/forensics/.gitkeep +0 -0
- package/template/.aioson/context/project-pulse.md +34 -0
- package/template/.aioson/context/seeds/seed-example.md +27 -0
- package/template/.aioson/context/user-profile.md +42 -0
- package/template/.aioson/docs/LAYERS.md +79 -0
- package/template/.aioson/docs/README.md +76 -0
- package/template/.aioson/docs/example-external-api-context.md +72 -0
- package/template/.aioson/locales/en/agents/architect.md +17 -0
- package/template/.aioson/locales/en/agents/dev.md +79 -13
- package/template/.aioson/locales/en/agents/orache.md +6 -0
- package/template/.aioson/locales/en/agents/orchestrator.md +24 -0
- package/template/.aioson/locales/en/agents/product.md +50 -0
- package/template/.aioson/locales/en/agents/setup.md +33 -1
- package/template/.aioson/locales/en/agents/sheldon.md +115 -0
- package/template/.aioson/locales/en/agents/squad.md +14 -0
- package/template/.aioson/locales/en/agents/tester.md +6 -0
- package/template/.aioson/locales/es/agents/analyst.md +2 -0
- package/template/.aioson/locales/es/agents/architect.md +19 -0
- package/template/.aioson/locales/es/agents/dev.md +64 -4
- package/template/.aioson/locales/es/agents/deyvin.md +2 -0
- package/template/.aioson/locales/es/agents/discovery-design-doc.md +2 -0
- package/template/.aioson/locales/es/agents/genome.md +2 -0
- package/template/.aioson/locales/es/agents/neo.md +2 -0
- package/template/.aioson/locales/es/agents/orache.md +2 -0
- package/template/.aioson/locales/es/agents/orchestrator.md +26 -0
- package/template/.aioson/locales/es/agents/pair.md +2 -0
- package/template/.aioson/locales/es/agents/pm.md +2 -0
- package/template/.aioson/locales/es/agents/product.md +52 -0
- package/template/.aioson/locales/es/agents/profiler-enricher.md +2 -0
- package/template/.aioson/locales/es/agents/profiler-forge.md +2 -0
- package/template/.aioson/locales/es/agents/profiler-researcher.md +2 -0
- package/template/.aioson/locales/es/agents/qa.md +2 -0
- package/template/.aioson/locales/es/agents/setup.md +35 -1
- package/template/.aioson/locales/es/agents/sheldon.md +117 -0
- package/template/.aioson/locales/es/agents/squad.md +16 -0
- package/template/.aioson/locales/es/agents/tester.md +9 -0
- package/template/.aioson/locales/es/agents/ux-ui.md +2 -0
- package/template/.aioson/locales/fr/agents/analyst.md +2 -0
- package/template/.aioson/locales/fr/agents/architect.md +19 -0
- package/template/.aioson/locales/fr/agents/dev.md +64 -4
- package/template/.aioson/locales/fr/agents/deyvin.md +2 -0
- package/template/.aioson/locales/fr/agents/discovery-design-doc.md +2 -0
- package/template/.aioson/locales/fr/agents/genome.md +2 -0
- package/template/.aioson/locales/fr/agents/neo.md +2 -0
- package/template/.aioson/locales/fr/agents/orache.md +2 -0
- package/template/.aioson/locales/fr/agents/orchestrator.md +26 -0
- package/template/.aioson/locales/fr/agents/pair.md +2 -0
- package/template/.aioson/locales/fr/agents/pm.md +2 -0
- package/template/.aioson/locales/fr/agents/product.md +52 -0
- package/template/.aioson/locales/fr/agents/profiler-enricher.md +2 -0
- package/template/.aioson/locales/fr/agents/profiler-forge.md +2 -0
- package/template/.aioson/locales/fr/agents/profiler-researcher.md +2 -0
- package/template/.aioson/locales/fr/agents/qa.md +2 -0
- package/template/.aioson/locales/fr/agents/setup.md +35 -1
- package/template/.aioson/locales/fr/agents/sheldon.md +117 -0
- package/template/.aioson/locales/fr/agents/squad.md +16 -0
- package/template/.aioson/locales/fr/agents/tester.md +9 -0
- package/template/.aioson/locales/fr/agents/ux-ui.md +2 -0
- package/template/.aioson/locales/pt-BR/agents/analyst.md +64 -3
- package/template/.aioson/locales/pt-BR/agents/architect.md +42 -0
- package/template/.aioson/locales/pt-BR/agents/dev.md +147 -14
- package/template/.aioson/locales/pt-BR/agents/deyvin.md +47 -0
- package/template/.aioson/locales/pt-BR/agents/neo.md +62 -1
- package/template/.aioson/locales/pt-BR/agents/orchestrator.md +158 -2
- package/template/.aioson/locales/pt-BR/agents/pm.md +95 -1
- package/template/.aioson/locales/pt-BR/agents/product.md +145 -18
- package/template/.aioson/locales/pt-BR/agents/qa.md +16 -0
- package/template/.aioson/locales/pt-BR/agents/setup.md +134 -19
- package/template/.aioson/locales/pt-BR/agents/sheldon.md +132 -1
- package/template/.aioson/locales/pt-BR/agents/squad.md +14 -0
- package/template/.aioson/locales/pt-BR/agents/tester.md +449 -0
- package/template/.aioson/rules/README.md +69 -0
- package/template/.aioson/rules/data-format-convention.md +136 -0
- package/template/.aioson/rules/example-monetary-values.md +30 -0
- package/template/.aioson/schemas/squad-manifest.schema.json +124 -3
- package/template/.aioson/skills/design/aurora-command-ui/SKILL.md +243 -0
- package/template/.aioson/skills/design/aurora-command-ui/references/art-direction.md +293 -0
- package/template/.aioson/skills/design/aurora-command-ui/references/components.md +827 -0
- package/template/.aioson/skills/design/aurora-command-ui/references/dashboards.md +250 -0
- package/template/.aioson/skills/design/aurora-command-ui/references/design-tokens.md +585 -0
- package/template/.aioson/skills/design/aurora-command-ui/references/motion.md +365 -0
- package/template/.aioson/skills/design/aurora-command-ui/references/patterns.md +482 -0
- package/template/.aioson/skills/design/aurora-command-ui/references/websites.md +387 -0
- package/template/.aioson/skills/design/glassmorphism-ui/SKILL.md +222 -0
- package/template/.aioson/skills/design/glassmorphism-ui/references/art-direction.md +159 -0
- package/template/.aioson/skills/design/glassmorphism-ui/references/components.md +498 -0
- package/template/.aioson/skills/design/glassmorphism-ui/references/dashboards.md +236 -0
- package/template/.aioson/skills/design/glassmorphism-ui/references/design-tokens.md +274 -0
- package/template/.aioson/skills/design/glassmorphism-ui/references/motion.md +355 -0
- package/template/.aioson/skills/design/glassmorphism-ui/references/patterns.md +198 -0
- package/template/.aioson/skills/design/glassmorphism-ui/references/websites.md +307 -0
- package/template/.aioson/skills/design/neo-brutalist-ui/SKILL.md +213 -0
- package/template/.aioson/skills/design/neo-brutalist-ui/references/art-direction.md +228 -0
- package/template/.aioson/skills/design/neo-brutalist-ui/references/components.md +855 -0
- package/template/.aioson/skills/design/neo-brutalist-ui/references/dashboards.md +334 -0
- package/template/.aioson/skills/design/neo-brutalist-ui/references/design-tokens.md +342 -0
- package/template/.aioson/skills/design/neo-brutalist-ui/references/motion.md +286 -0
- package/template/.aioson/skills/design/neo-brutalist-ui/references/patterns.md +458 -0
- package/template/.aioson/skills/design/neo-brutalist-ui/references/websites.md +723 -0
- package/template/.aioson/skills/design/pt.squarespace.com/.skill-meta.json +31 -0
- package/template/.aioson/skills/design/pt.squarespace.com/SKILL.md +66 -0
- package/template/.aioson/skills/design/pt.squarespace.com/references/components.md +368 -0
- package/template/.aioson/skills/design/pt.squarespace.com/references/design-tokens.md +150 -0
- package/template/.aioson/skills/design/pt.squarespace.com/references/motion.md +270 -0
- package/template/.aioson/skills/design/pt.squarespace.com/references/patterns.md +189 -0
- package/template/.aioson/skills/design/pt.squarespace.com/references/websites.md +165 -0
- package/template/.aioson/skills/process/aioson-spec-driven/SKILL.md +46 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/analyst.md +30 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/approval-gates.md +109 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/architect.md +23 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/artifact-map.md +44 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/classification-map.md +37 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/dev.md +47 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/deyvin.md +27 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/hardening-lane.md +49 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/maintenance-and-state.md +101 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/product.md +25 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/qa.md +30 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/sheldon.md +25 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/ui-language.md +75 -0
- package/template/.aioson/skills/process/design-hybrid-forge/SKILL.md +147 -0
- package/template/.aioson/skills/process/design-hybrid-forge/references/crossover-protocol.md +221 -0
- package/template/.aioson/skills/process/design-hybrid-forge/references/naming-registry.md +88 -0
- package/template/.aioson/skills/process/design-hybrid-forge/references/output-contract.md +306 -0
- package/template/.aioson/skills/process/design-hybrid-forge/references/pair-compatibility.md +149 -0
- package/template/.aioson/skills/process/design-hybrid-forge/references/quality-gates.md +208 -0
- package/template/.aioson/skills/process/design-hybrid-forge/references/variation-library.md +125 -0
- package/template/.aioson/skills/process/simplify/SKILL.md +173 -0
- package/template/.aioson/skills/static/context-budget-guide.md +46 -0
- package/template/.aioson/skills/static/harness-sensors.md +74 -0
- package/template/.aioson/skills/static/multi-agent-patterns.md +43 -0
- package/template/.aioson/skills/static/react-motion-patterns.md +22 -0
- package/template/.aioson/skills/static/static-html-patterns/checklists.md +43 -0
- package/template/.aioson/skills/static/static-html-patterns/css-tokens.md +609 -0
- package/template/.aioson/skills/static/static-html-patterns/motion.md +193 -0
- package/template/.aioson/skills/static/static-html-patterns/premium.md +711 -0
- package/template/.aioson/skills/static/static-html-patterns/structure.md +209 -0
- package/template/.aioson/skills/static/static-html-patterns/utilities.md +190 -0
- package/template/.aioson/skills/static/static-html-patterns.md +58 -1913
- package/template/.aioson/skills/static/threejs-patterns.md +929 -0
- package/template/.aioson/skills/static/web-research-cache.md +112 -0
- package/template/.aioson/tasks/implementation-plan.md +21 -1
- package/template/.claude/commands/aioson/agent/design-hybrid-forge.md +5 -0
- package/template/.claude/commands/aioson/agent/orache.md +5 -0
- package/template/.claude/commands/aioson/agent/sheldon.md +5 -0
- package/template/.claude/commands/aioson/agent/site-forge.md +5 -0
- package/template/AGENTS.md +75 -1
- package/template/CLAUDE.md +31 -0
- package/template/OPENCODE.md +4 -0
- package/template/researchs/.gitkeep +0 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* aioson learning:auto-promote — auto-promote frequent learnings to rules files.
|
|
5
|
+
*
|
|
6
|
+
* Scans project_learnings with frequency >= threshold and promotes eligible ones
|
|
7
|
+
* to .aioson/rules/. Domain learnings (not promotable to universal rules) are noted
|
|
8
|
+
* but not written. No LLM calls.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* aioson learning:auto-promote .
|
|
12
|
+
* aioson learning:auto-promote . --threshold=3
|
|
13
|
+
* aioson learning:auto-promote . --threshold=2 --dry-run
|
|
14
|
+
* aioson learning:auto-promote . --json
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const fs = require('node:fs/promises');
|
|
18
|
+
const path = require('node:path');
|
|
19
|
+
const { openRuntimeDb, listProjectLearnings } = require('../runtime-store');
|
|
20
|
+
|
|
21
|
+
const DEFAULT_THRESHOLD = 3;
|
|
22
|
+
const RULES_DIR = '.aioson/rules';
|
|
23
|
+
const BAR = '━'.repeat(30);
|
|
24
|
+
|
|
25
|
+
// Only 'process' and 'quality' type learnings become universal rules.
|
|
26
|
+
// 'domain' learnings are project-specific and should not become global rules.
|
|
27
|
+
// 'preference' learnings go to project.context.md (handled by learning:evolve).
|
|
28
|
+
const PROMOTABLE_TYPES = new Set(['process', 'quality']);
|
|
29
|
+
|
|
30
|
+
function slugify(title) {
|
|
31
|
+
return title
|
|
32
|
+
.toLowerCase()
|
|
33
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
34
|
+
.replace(/^-+|-+$/g, '')
|
|
35
|
+
.slice(0, 50);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function buildRuleContent(learning) {
|
|
39
|
+
const confidence = learning.confidence === 'high' ? ' (high confidence)' : learning.confidence === 'low' ? ' (low confidence)' : '';
|
|
40
|
+
const lines = [
|
|
41
|
+
'---',
|
|
42
|
+
`title: ${learning.title}`,
|
|
43
|
+
`type: ${learning.type}`,
|
|
44
|
+
`frequency: ${learning.frequency}`,
|
|
45
|
+
`source: auto-promoted`,
|
|
46
|
+
'agents: all',
|
|
47
|
+
'---',
|
|
48
|
+
'',
|
|
49
|
+
`# ${learning.title}${confidence}`,
|
|
50
|
+
''
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
if (learning.evidence) {
|
|
54
|
+
lines.push(`> ${learning.evidence}`, '');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (learning.type === 'process') {
|
|
58
|
+
lines.push('**When:** During any implementation or workflow session.');
|
|
59
|
+
lines.push(`**Rule:** ${learning.title}`);
|
|
60
|
+
} else if (learning.type === 'quality') {
|
|
61
|
+
lines.push('**When:** Before committing or delivering a deliverable.');
|
|
62
|
+
lines.push(`**Rule:** ${learning.title}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
lines.push('');
|
|
66
|
+
return lines.join('\n');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function runLearningAutoPromote({ args, options = {}, logger }) {
|
|
70
|
+
const targetDir = path.resolve(process.cwd(), args[0] || '.');
|
|
71
|
+
const threshold = options.threshold ? parseInt(options.threshold) : DEFAULT_THRESHOLD;
|
|
72
|
+
const dryRun = Boolean(options['dry-run'] || options.dry);
|
|
73
|
+
|
|
74
|
+
if (isNaN(threshold) || threshold < 1) {
|
|
75
|
+
if (options.json) return { ok: false, reason: 'invalid_threshold' };
|
|
76
|
+
logger.log('--threshold must be a positive integer (default: 3)');
|
|
77
|
+
return { ok: false };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const handle = await openRuntimeDb(targetDir, { mustExist: true });
|
|
81
|
+
if (!handle) {
|
|
82
|
+
if (options.json) return { ok: false, reason: 'no_runtime', message: 'No runtime database found. Run aioson runtime:init first.' };
|
|
83
|
+
logger.log('No runtime database found. Run aioson runtime:init first.');
|
|
84
|
+
return { ok: false };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
let learnings;
|
|
88
|
+
try {
|
|
89
|
+
learnings = listProjectLearnings(handle.db, 'active');
|
|
90
|
+
} finally {
|
|
91
|
+
handle.db.close();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Filter by threshold
|
|
95
|
+
const eligible = learnings.filter((l) => Number(l.frequency || 1) >= threshold);
|
|
96
|
+
|
|
97
|
+
if (!options.json) {
|
|
98
|
+
logger.log('');
|
|
99
|
+
logger.log('Learning Auto-Promotion');
|
|
100
|
+
logger.log(BAR);
|
|
101
|
+
logger.log(`Scanning project_learnings (frequency ≥ ${threshold}):`);
|
|
102
|
+
logger.log('');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const promoted = [];
|
|
106
|
+
const noted = [];
|
|
107
|
+
const skipped = [];
|
|
108
|
+
|
|
109
|
+
for (const learning of eligible) {
|
|
110
|
+
if (!PROMOTABLE_TYPES.has(learning.type)) {
|
|
111
|
+
noted.push({
|
|
112
|
+
title: learning.title,
|
|
113
|
+
type: learning.type,
|
|
114
|
+
frequency: learning.frequency,
|
|
115
|
+
reason: `${learning.type} learning — not promotable to universal rule`
|
|
116
|
+
});
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const slug = slugify(learning.title);
|
|
121
|
+
const fileName = `process-${slug}.md`;
|
|
122
|
+
const filePath = path.join(targetDir, RULES_DIR, fileName);
|
|
123
|
+
|
|
124
|
+
// Check if already exists
|
|
125
|
+
let alreadyExists = false;
|
|
126
|
+
try {
|
|
127
|
+
await fs.access(filePath);
|
|
128
|
+
alreadyExists = true;
|
|
129
|
+
} catch { /* ok */ }
|
|
130
|
+
|
|
131
|
+
if (alreadyExists) {
|
|
132
|
+
skipped.push({ title: learning.title, file: fileName, reason: 'already exists' });
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const content = buildRuleContent(learning);
|
|
137
|
+
|
|
138
|
+
if (!dryRun) {
|
|
139
|
+
await fs.mkdir(path.join(targetDir, RULES_DIR), { recursive: true });
|
|
140
|
+
await fs.writeFile(filePath, content, 'utf8');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
promoted.push({
|
|
144
|
+
learning_id: learning.learning_id,
|
|
145
|
+
title: learning.title,
|
|
146
|
+
type: learning.type,
|
|
147
|
+
frequency: learning.frequency,
|
|
148
|
+
file: path.join(RULES_DIR, fileName)
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const result = {
|
|
153
|
+
ok: true,
|
|
154
|
+
threshold,
|
|
155
|
+
dry_run: dryRun,
|
|
156
|
+
eligible: eligible.length,
|
|
157
|
+
promoted: promoted.length,
|
|
158
|
+
noted: noted.length,
|
|
159
|
+
skipped: skipped.length,
|
|
160
|
+
promoted_items: promoted,
|
|
161
|
+
noted_items: noted,
|
|
162
|
+
skipped_items: skipped
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
if (options.json) return result;
|
|
166
|
+
|
|
167
|
+
// Human output
|
|
168
|
+
for (const p of promoted) {
|
|
169
|
+
const dryStr = dryRun ? ' (dry-run)' : '';
|
|
170
|
+
logger.log(` ✓ "${p.title}" (freq: ${p.frequency}) → promoted to ${p.file}${dryStr}`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
for (const n of noted) {
|
|
174
|
+
logger.log(` ○ "${n.title}" (freq: ${n.frequency}) → ${n.reason}`);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
for (const s of skipped) {
|
|
178
|
+
logger.log(` — "${s.title}" → ${s.reason}`);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
logger.log('');
|
|
182
|
+
if (promoted.length > 0) {
|
|
183
|
+
logger.log(`${promoted.length} rule${promoted.length !== 1 ? 's' : ''} ${dryRun ? 'would be' : ''} created.`);
|
|
184
|
+
if (!dryRun) logger.log('Run: aioson learning:evolve to apply to agent genomes.');
|
|
185
|
+
} else if (eligible.length === 0) {
|
|
186
|
+
logger.log(`No learnings with frequency ≥ ${threshold} found.`);
|
|
187
|
+
} else {
|
|
188
|
+
logger.log('No new rules to create.');
|
|
189
|
+
}
|
|
190
|
+
logger.log('');
|
|
191
|
+
|
|
192
|
+
return result;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
module.exports = { runLearningAutoPromote };
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs/promises');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const { randomUUID } = require('node:crypto');
|
|
6
|
+
const { openRuntimeDb, listSquadLearnings, listProjectLearnings, promoteSquadLearning, promoteProjectLearning } = require('../runtime-store');
|
|
7
|
+
|
|
8
|
+
const AGENTS_DIR = path.join('.aioson', 'agents');
|
|
9
|
+
const EVOLUTION_DIR = path.join('.aioson', 'evolution');
|
|
10
|
+
const CONTEXT_FILE = path.join('.aioson', 'context', 'project.context.md');
|
|
11
|
+
const MAX_FILE_LINES = 300;
|
|
12
|
+
const MIN_FREQUENCY = 2;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Mapeia tipo de learning para seção e arquivo alvo.
|
|
16
|
+
*/
|
|
17
|
+
function resolveDeltaTarget(type, squadSlug) {
|
|
18
|
+
const rulesDir = path.join('.aioson', 'rules');
|
|
19
|
+
if (type === 'preference') {
|
|
20
|
+
return { file: CONTEXT_FILE, section: '## Preferências dos Agentes' };
|
|
21
|
+
}
|
|
22
|
+
if (type === 'process') {
|
|
23
|
+
const filename = squadSlug ? `${squadSlug}-process.md` : 'project-process.md';
|
|
24
|
+
return { file: path.join(rulesDir, filename), section: null };
|
|
25
|
+
}
|
|
26
|
+
if (type === 'domain') {
|
|
27
|
+
return { file: CONTEXT_FILE, section: '## Conhecimento de Domínio' };
|
|
28
|
+
}
|
|
29
|
+
if (type === 'quality') {
|
|
30
|
+
return { file: CONTEXT_FILE, section: '## Padrões de Qualidade' };
|
|
31
|
+
}
|
|
32
|
+
return { file: CONTEXT_FILE, section: '## Observações' };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Gate 1: arquivo não pode estar dentro de .aioson/agents/
|
|
37
|
+
*/
|
|
38
|
+
function passesConstitutionGate(filePath, projectDir) {
|
|
39
|
+
const absolute = path.isAbsolute(filePath) ? filePath : path.resolve(projectDir, filePath);
|
|
40
|
+
const agentsAbsolute = path.resolve(projectDir, AGENTS_DIR);
|
|
41
|
+
return !absolute.startsWith(agentsAbsolute + path.sep) && absolute !== agentsAbsolute;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Gate 2: arquivo alvo não pode ultrapassar MAX_FILE_LINES após append.
|
|
46
|
+
*/
|
|
47
|
+
async function passesSizeGate(filePath, projectDir, newContent) {
|
|
48
|
+
const absolute = path.isAbsolute(filePath) ? filePath : path.resolve(projectDir, filePath);
|
|
49
|
+
let existingLines = 0;
|
|
50
|
+
try {
|
|
51
|
+
const content = await fs.readFile(absolute, 'utf8');
|
|
52
|
+
existingLines = content.split('\n').length;
|
|
53
|
+
} catch {
|
|
54
|
+
existingLines = 0;
|
|
55
|
+
}
|
|
56
|
+
const newLines = String(newContent || '').split('\n').length;
|
|
57
|
+
return (existingLines + newLines) <= MAX_FILE_LINES;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Gera o conteúdo textual de um delta a partir de um grupo de learnings.
|
|
62
|
+
*/
|
|
63
|
+
function buildDeltaContent(learnings, section) {
|
|
64
|
+
const lines = [];
|
|
65
|
+
if (section) {
|
|
66
|
+
lines.push('');
|
|
67
|
+
}
|
|
68
|
+
for (const l of learnings) {
|
|
69
|
+
const confidence = l.confidence === 'high' ? '(alta confiança)' : l.confidence === 'low' ? '(baixa confiança)' : '';
|
|
70
|
+
lines.push(`- ${l.title} ${confidence}`.trim());
|
|
71
|
+
if (l.evidence) {
|
|
72
|
+
lines.push(` > ${l.evidence}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return lines.join('\n');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Aplica um delta aprovado no sistema de arquivos.
|
|
80
|
+
*/
|
|
81
|
+
async function applyDelta(delta, projectDir) {
|
|
82
|
+
const absolute = path.isAbsolute(delta.file) ? delta.file : path.resolve(projectDir, delta.file);
|
|
83
|
+
|
|
84
|
+
await fs.mkdir(path.dirname(absolute), { recursive: true });
|
|
85
|
+
|
|
86
|
+
let existing = '';
|
|
87
|
+
try {
|
|
88
|
+
existing = await fs.readFile(absolute, 'utf8');
|
|
89
|
+
} catch {
|
|
90
|
+
existing = '';
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (delta.section && existing) {
|
|
94
|
+
if (existing.includes(delta.section)) {
|
|
95
|
+
// Insere após o cabeçalho da seção
|
|
96
|
+
const updated = existing.replace(delta.section, `${delta.section}\n${delta.content}`);
|
|
97
|
+
await fs.writeFile(absolute, updated, 'utf8');
|
|
98
|
+
} else {
|
|
99
|
+
// Adiciona a seção no final
|
|
100
|
+
await fs.writeFile(absolute, `${existing}\n${delta.section}\n${delta.content}\n`, 'utf8');
|
|
101
|
+
}
|
|
102
|
+
} else {
|
|
103
|
+
// Append simples
|
|
104
|
+
await fs.writeFile(absolute, `${existing}\n${delta.content}\n`, 'utf8');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Ponto de entrada principal.
|
|
110
|
+
*/
|
|
111
|
+
async function runLearningEvolve({ args = [], options = {}, logger = console, t = (k) => k } = {}) {
|
|
112
|
+
const projectDir = path.resolve(process.cwd(), args[0] || '.');
|
|
113
|
+
const squadSlug = options.squad || null;
|
|
114
|
+
const dryRun = Boolean(options['dry-run'] || options.dry);
|
|
115
|
+
const autoApply = Boolean(options['auto-apply'] || options.auto);
|
|
116
|
+
const quiet = Boolean(options.quiet);
|
|
117
|
+
|
|
118
|
+
const handle = await openRuntimeDb(projectDir, { mustExist: true });
|
|
119
|
+
if (!handle) {
|
|
120
|
+
logger.error('Runtime store não encontrado. Execute aioson runtime:init primeiro.');
|
|
121
|
+
return { ok: false, error: 'no_runtime' };
|
|
122
|
+
}
|
|
123
|
+
const { db } = handle;
|
|
124
|
+
|
|
125
|
+
let learnings;
|
|
126
|
+
try {
|
|
127
|
+
if (squadSlug) {
|
|
128
|
+
learnings = listSquadLearnings(db, squadSlug, 'active');
|
|
129
|
+
} else {
|
|
130
|
+
const squad = listSquadLearnings(db, null, 'active');
|
|
131
|
+
const project = listProjectLearnings(db, 'active');
|
|
132
|
+
learnings = [...squad, ...project];
|
|
133
|
+
}
|
|
134
|
+
} finally {
|
|
135
|
+
db.close();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Filtra por frequência mínima
|
|
139
|
+
const eligible = learnings.filter((l) => Number(l.frequency || 1) >= MIN_FREQUENCY);
|
|
140
|
+
|
|
141
|
+
if (eligible.length === 0) {
|
|
142
|
+
if (!quiet) logger.log('Nenhum learning com frequência suficiente para evoluir (mínimo: 2 ocorrências).');
|
|
143
|
+
return { ok: true, evolved: 0, skipped: 0, proposed: [] };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (!quiet) logger.log(`Analisando ${eligible.length} learnings elegíveis...`);
|
|
147
|
+
|
|
148
|
+
// Agrupa por tipo
|
|
149
|
+
const grouped = {};
|
|
150
|
+
for (const l of eligible) {
|
|
151
|
+
const key = l.type;
|
|
152
|
+
if (!grouped[key]) grouped[key] = [];
|
|
153
|
+
grouped[key].push(l);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Gera deltas
|
|
157
|
+
const proposed = [];
|
|
158
|
+
const rejected = [];
|
|
159
|
+
|
|
160
|
+
for (const [type, group] of Object.entries(grouped)) {
|
|
161
|
+
const { file, section } = resolveDeltaTarget(type, group[0].squad_slug || null);
|
|
162
|
+
const content = buildDeltaContent(group, section);
|
|
163
|
+
|
|
164
|
+
// Gate 1: Constitution
|
|
165
|
+
if (!passesConstitutionGate(file, projectDir)) {
|
|
166
|
+
rejected.push({ type, reason: 'constitution_gate', file, count: group.length });
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Gate 2: Size
|
|
171
|
+
const sizeOk = await passesSizeGate(file, projectDir, content);
|
|
172
|
+
if (!sizeOk) {
|
|
173
|
+
rejected.push({ type, reason: 'size_gate', file, count: group.length });
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
proposed.push({
|
|
178
|
+
type,
|
|
179
|
+
file,
|
|
180
|
+
section,
|
|
181
|
+
content,
|
|
182
|
+
sourceIds: group.map((l) => l.learning_id),
|
|
183
|
+
count: group.length
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Exibe resultados
|
|
188
|
+
if (!quiet) {
|
|
189
|
+
logger.log('');
|
|
190
|
+
logger.log(`Deltas propostos: ${proposed.length} aprovados, ${rejected.length} rejeitados`);
|
|
191
|
+
logger.log('');
|
|
192
|
+
|
|
193
|
+
for (let i = 0; i < proposed.length; i++) {
|
|
194
|
+
const d = proposed[i];
|
|
195
|
+
logger.log(` [${i + 1}] APPEND → ${d.file}`);
|
|
196
|
+
if (d.section) logger.log(` Seção: ${d.section}`);
|
|
197
|
+
logger.log(` Learnings: ${d.count} (${d.type})`);
|
|
198
|
+
logger.log(d.content.split('\n').map((l) => ` ${l}`).join('\n'));
|
|
199
|
+
logger.log('');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
for (const r of rejected) {
|
|
203
|
+
const reason = r.reason === 'constitution_gate' ? 'gate constitucional (arquivo imutável)' : `gate de tamanho (>${MAX_FILE_LINES} linhas)`;
|
|
204
|
+
logger.log(` ✗ Rejeitado [${r.type}] → ${r.file}: ${reason}`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (proposed.length === 0) {
|
|
209
|
+
return { ok: true, evolved: 0, skipped: rejected.length, proposed: [] };
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Dry-run: só exibe, não aplica
|
|
213
|
+
if (dryRun) {
|
|
214
|
+
logger.log('Modo dry-run: nenhuma alteração foi feita.');
|
|
215
|
+
return { ok: true, evolved: 0, skipped: rejected.length, proposed };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Auto-apply: aplica diretamente
|
|
219
|
+
if (autoApply) {
|
|
220
|
+
return applyProposed(proposed, projectDir, db, logger, quiet, squadSlug);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Modo padrão: salva arquivo pendente
|
|
224
|
+
const evolutionDir = path.resolve(projectDir, EVOLUTION_DIR);
|
|
225
|
+
await fs.mkdir(evolutionDir, { recursive: true });
|
|
226
|
+
|
|
227
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
228
|
+
const pendingFile = path.join(evolutionDir, `pending-${timestamp}.json`);
|
|
229
|
+
await fs.writeFile(pendingFile, JSON.stringify({ createdAt: new Date().toISOString(), projectDir, proposed, rejected }, null, 2), 'utf8');
|
|
230
|
+
|
|
231
|
+
if (!quiet) {
|
|
232
|
+
logger.log(`Proposta salva em: ${pendingFile}`);
|
|
233
|
+
logger.log('Para aplicar: aioson learning:apply . --file=' + path.relative(projectDir, pendingFile));
|
|
234
|
+
logger.log('Para aplicar automaticamente: aioson learning:evolve . --auto-apply');
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return { ok: true, evolved: 0, skipped: rejected.length, proposed, pendingFile };
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async function applyProposed(proposed, projectDir, db, logger, quiet, squadSlug) {
|
|
241
|
+
const handle = await openRuntimeDb(projectDir, { mustExist: true });
|
|
242
|
+
if (!handle) return { ok: false, error: 'no_runtime' };
|
|
243
|
+
const applyDb = handle.db;
|
|
244
|
+
|
|
245
|
+
let evolved = 0;
|
|
246
|
+
try {
|
|
247
|
+
for (const delta of proposed) {
|
|
248
|
+
await applyDelta(delta, projectDir);
|
|
249
|
+
|
|
250
|
+
// Promove learnings no DB
|
|
251
|
+
for (const id of delta.sourceIds) {
|
|
252
|
+
try {
|
|
253
|
+
if (id.startsWith('sl-') || id.startsWith('pl-')) {
|
|
254
|
+
if (id.startsWith('sl-')) {
|
|
255
|
+
promoteSquadLearning(applyDb, id, delta.file);
|
|
256
|
+
} else {
|
|
257
|
+
promoteProjectLearning(applyDb, id, delta.file);
|
|
258
|
+
}
|
|
259
|
+
} else {
|
|
260
|
+
promoteSquadLearning(applyDb, id, delta.file);
|
|
261
|
+
}
|
|
262
|
+
} catch { /* learning pode já ter sido promovido */ }
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
evolved++;
|
|
266
|
+
if (!quiet) logger.log(` ✓ Aplicado: ${delta.file} (+${delta.count} learnings)`);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Registra no evolution-log.jsonl (5.5: per-delta entries with UUIDs for rollback)
|
|
270
|
+
const evolutionDir = path.resolve(projectDir, EVOLUTION_DIR);
|
|
271
|
+
await fs.mkdir(evolutionDir, { recursive: true });
|
|
272
|
+
const perDeltaLogFile = path.join(evolutionDir, 'evolution-log.jsonl');
|
|
273
|
+
const ts = new Date().toISOString();
|
|
274
|
+
for (const delta of proposed.slice(0, evolved)) {
|
|
275
|
+
const logEntry = JSON.stringify({
|
|
276
|
+
id: randomUUID(),
|
|
277
|
+
ts,
|
|
278
|
+
type: 'append',
|
|
279
|
+
file: delta.file,
|
|
280
|
+
section: delta.section || null,
|
|
281
|
+
content: delta.content,
|
|
282
|
+
learning_ids: delta.sourceIds || [],
|
|
283
|
+
squad: squadSlug || null,
|
|
284
|
+
status: 'applied'
|
|
285
|
+
});
|
|
286
|
+
await fs.appendFile(perDeltaLogFile, `${logEntry}\n`, 'utf8');
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (!quiet) logger.log(`\n${evolved} delta(s) aplicado(s) com sucesso.`);
|
|
290
|
+
} finally {
|
|
291
|
+
applyDb.close();
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return { ok: true, evolved, skipped: 0, proposed };
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Subcomando: apply — aplica um arquivo de deltas pendentes.
|
|
299
|
+
*/
|
|
300
|
+
async function runLearningApply({ args = [], options = {}, logger = console } = {}) {
|
|
301
|
+
const projectDir = path.resolve(process.cwd(), args[0] || '.');
|
|
302
|
+
const filePath = options.file ? String(options.file) : null;
|
|
303
|
+
|
|
304
|
+
if (!filePath) {
|
|
305
|
+
logger.error('--file é obrigatório. Exemplo: aioson learning:apply . --file=.aioson/evolution/pending-XXX.json');
|
|
306
|
+
return { ok: false, error: 'file_required' };
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const absolute = path.isAbsolute(filePath) ? filePath : path.resolve(projectDir, filePath);
|
|
310
|
+
|
|
311
|
+
let pendingData;
|
|
312
|
+
try {
|
|
313
|
+
pendingData = JSON.parse(await fs.readFile(absolute, 'utf8'));
|
|
314
|
+
} catch (err) {
|
|
315
|
+
logger.error(`Não foi possível ler o arquivo: ${absolute}\n${err.message}`);
|
|
316
|
+
return { ok: false, error: 'file_not_readable' };
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const { proposed = [], rejected = [] } = pendingData;
|
|
320
|
+
|
|
321
|
+
if (proposed.length === 0) {
|
|
322
|
+
logger.log('Nenhum delta para aplicar neste arquivo.');
|
|
323
|
+
return { ok: true, evolved: 0 };
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
logger.log(`Aplicando ${proposed.length} delta(s)...`);
|
|
327
|
+
|
|
328
|
+
const handle = await openRuntimeDb(projectDir, { mustExist: true });
|
|
329
|
+
const db = handle ? handle.db : null;
|
|
330
|
+
|
|
331
|
+
let evolved = 0;
|
|
332
|
+
for (const delta of proposed) {
|
|
333
|
+
try {
|
|
334
|
+
await applyDelta(delta, projectDir);
|
|
335
|
+
if (db) {
|
|
336
|
+
for (const id of (delta.sourceIds || [])) {
|
|
337
|
+
try {
|
|
338
|
+
if (id.startsWith('pl-')) {
|
|
339
|
+
promoteProjectLearning(db, id, delta.file);
|
|
340
|
+
} else {
|
|
341
|
+
promoteSquadLearning(db, id, delta.file);
|
|
342
|
+
}
|
|
343
|
+
} catch { /* ok */ }
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
evolved++;
|
|
347
|
+
logger.log(` ✓ ${delta.file}`);
|
|
348
|
+
} catch (err) {
|
|
349
|
+
logger.error(` ✗ Falha em ${delta.file}: ${err.message}`);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (db) db.close();
|
|
354
|
+
|
|
355
|
+
// Remove o arquivo pendente após aplicar
|
|
356
|
+
try { await fs.unlink(absolute); } catch { /* ok */ }
|
|
357
|
+
|
|
358
|
+
logger.log(`\n${evolved}/${proposed.length} delta(s) aplicado(s).`);
|
|
359
|
+
if (rejected.length > 0) logger.log(`${rejected.length} rejeitado(s) previamente pelos gates.`);
|
|
360
|
+
|
|
361
|
+
return { ok: true, evolved };
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
module.exports = { runLearningEvolve, runLearningApply };
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs/promises');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const { openRuntimeDb } = require('../runtime-store');
|
|
6
|
+
|
|
7
|
+
function slugify(text) {
|
|
8
|
+
return String(text || '')
|
|
9
|
+
.toLowerCase()
|
|
10
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
11
|
+
.replace(/^-+|-+$/g, '')
|
|
12
|
+
.slice(0, 80) || 'learning';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function buildNodeContent(learning) {
|
|
16
|
+
const id = slugify(`${learning.type}-${learning.title}`);
|
|
17
|
+
const now = new Date().toISOString().slice(0, 10);
|
|
18
|
+
return {
|
|
19
|
+
id,
|
|
20
|
+
content: `---
|
|
21
|
+
id: ${id}
|
|
22
|
+
type: ${learning.type}
|
|
23
|
+
title: ${learning.title}
|
|
24
|
+
frequency: ${learning.frequency || 1}
|
|
25
|
+
last_reinforced: ${learning.last_reinforced ? learning.last_reinforced.slice(0, 10) : now}
|
|
26
|
+
source_feature: ${learning.feature_slug || 'project'}
|
|
27
|
+
promoted_to: null
|
|
28
|
+
created_at: ${learning.created_at ? learning.created_at.slice(0, 10) : now}
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
# ${learning.title}
|
|
32
|
+
|
|
33
|
+
**Evidence:** ${learning.evidence || `Detected in ${learning.frequency || 1} session(s).`}
|
|
34
|
+
|
|
35
|
+
## Applications
|
|
36
|
+
- Review and apply this learning in future sessions of type: ${learning.type}
|
|
37
|
+
|
|
38
|
+
## Links
|
|
39
|
+
<!-- Add cross-references here -->
|
|
40
|
+
`
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function runLearningExport({ args, options = {}, logger }) {
|
|
45
|
+
const targetDir = path.resolve(process.cwd(), args[0] || '.');
|
|
46
|
+
const minFrequency = Number(options['min-frequency'] || options.minFrequency || 1);
|
|
47
|
+
const brainsDir = path.join(targetDir, '.aioson', 'brains');
|
|
48
|
+
|
|
49
|
+
const { db, dbPath } = await openRuntimeDb(targetDir, { mustExist: true });
|
|
50
|
+
|
|
51
|
+
if (!db) {
|
|
52
|
+
if (!options.json) logger.log('No runtime database found.');
|
|
53
|
+
return { ok: false, reason: 'no_db' };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
const learnings = db.prepare(`
|
|
58
|
+
SELECT learning_id, feature_slug, type, title, frequency, last_reinforced,
|
|
59
|
+
evidence, source_session, created_at
|
|
60
|
+
FROM project_learnings
|
|
61
|
+
WHERE status = 'active' AND frequency >= ?
|
|
62
|
+
ORDER BY frequency DESC, updated_at DESC
|
|
63
|
+
`).all(minFrequency);
|
|
64
|
+
|
|
65
|
+
if (learnings.length === 0) {
|
|
66
|
+
if (!options.json) logger.log(`No learnings with frequency >= ${minFrequency}.`);
|
|
67
|
+
return { ok: true, exported: 0, dbPath };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
await fs.mkdir(brainsDir, { recursive: true });
|
|
71
|
+
|
|
72
|
+
const exported = [];
|
|
73
|
+
for (const learning of learnings) {
|
|
74
|
+
const { id, content } = buildNodeContent(learning);
|
|
75
|
+
const filePath = path.join(brainsDir, `${id}.md`);
|
|
76
|
+
await fs.writeFile(filePath, content, 'utf8');
|
|
77
|
+
exported.push({ id, filePath, frequency: learning.frequency });
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const promotable = learnings.filter((l) => l.frequency >= 5).length;
|
|
81
|
+
|
|
82
|
+
if (options.json) {
|
|
83
|
+
return { ok: true, exported: exported.length, nodes: exported, promotable, dbPath };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
logger.log(`Learning Export — min-frequency: ${minFrequency}`);
|
|
87
|
+
logger.log('─'.repeat(50));
|
|
88
|
+
for (const { id, frequency } of exported) {
|
|
89
|
+
logger.log(` ${id}.md ✓ (frequency: ${frequency})`);
|
|
90
|
+
}
|
|
91
|
+
logger.log('─'.repeat(50));
|
|
92
|
+
logger.log(`${exported.length} nodes written to .aioson/brains/`);
|
|
93
|
+
if (promotable > 0) {
|
|
94
|
+
logger.log(`${promotable} learning(s) with frequency ≥ 5 — run: aioson learning:evolve to promote to genome`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return { ok: true, exported: exported.length, nodes: exported, promotable, dbPath };
|
|
98
|
+
} finally {
|
|
99
|
+
db.close();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
module.exports = { runLearningExport };
|