@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,303 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Squad 4-tier verification gate
|
|
5
|
+
*
|
|
6
|
+
* Verifies task deliverables beyond pass/fail:
|
|
7
|
+
*
|
|
8
|
+
* Tier 1 — Exists: File/path exists on disk
|
|
9
|
+
* Tier 2 — Substantive: Not a stub/placeholder (min lines + anti-pattern scan)
|
|
10
|
+
* Tier 3 — Wired: Referenced/imported in expected target location
|
|
11
|
+
* Tier 4 — Functional: Smoke command returns expected output (opt-in, expensive)
|
|
12
|
+
*
|
|
13
|
+
* Used by reflection.js when a task has a `must_haves` contract.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const fs = require('node:fs/promises');
|
|
17
|
+
const { execFile } = require('node:child_process');
|
|
18
|
+
const { promisify } = require('node:util');
|
|
19
|
+
|
|
20
|
+
const execFileAsync = promisify(execFile);
|
|
21
|
+
|
|
22
|
+
// Patterns that indicate a file is a stub, not real implementation
|
|
23
|
+
const ANTI_PATTERNS = [
|
|
24
|
+
{ pattern: /\bTODO\b/, label: 'TODO marker' },
|
|
25
|
+
{ pattern: /\bFIXME\b/, label: 'FIXME marker' },
|
|
26
|
+
{ pattern: /\bplaceholder\b/i, label: 'placeholder text' },
|
|
27
|
+
{ pattern: /not\s+implemented/i, label: '"not implemented"' },
|
|
28
|
+
{ pattern: /return\s+null;\s*\n?\s*\}/m, label: 'empty return null' },
|
|
29
|
+
{ pattern: /throw\s+new\s+Error\s*\(['"]not\s+impl/i, label: 'NotImplemented throw' },
|
|
30
|
+
{ pattern: /^\s*pass\s*$/m, label: 'Python stub (pass)' },
|
|
31
|
+
{ pattern: /^\s*\.\.\.\s*$/m, label: 'ellipsis stub' }
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
// ─── Artifact string parser ───────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Parse an artifact descriptor string into structured fields.
|
|
38
|
+
*
|
|
39
|
+
* Examples:
|
|
40
|
+
* "src/routes/auth.ts"
|
|
41
|
+
* "src/routes/auth.ts (>50 lines)"
|
|
42
|
+
* "src/routes/auth.ts (>50 lines, exports router)"
|
|
43
|
+
*
|
|
44
|
+
* Returns: { filePath, minLines, wiredPattern }
|
|
45
|
+
*/
|
|
46
|
+
function parseArtifact(str, projectDir) {
|
|
47
|
+
const s = String(str).trim();
|
|
48
|
+
|
|
49
|
+
// Extract optional annotations in parentheses
|
|
50
|
+
const parenMatch = s.match(/^(.+?)\s*\(([^)]*)\)\s*$/);
|
|
51
|
+
const rawPath = parenMatch ? parenMatch[1].trim() : s;
|
|
52
|
+
const annotations = parenMatch ? parenMatch[2] : '';
|
|
53
|
+
|
|
54
|
+
// Build absolute path (relative to projectDir if given)
|
|
55
|
+
const filePath = projectDir
|
|
56
|
+
? require('node:path').resolve(projectDir, rawPath)
|
|
57
|
+
: rawPath;
|
|
58
|
+
|
|
59
|
+
// Parse min lines: ">50 lines" or "50 lines"
|
|
60
|
+
const linesMatch = annotations.match(/>?\s*(\d+)\s+lines?/i);
|
|
61
|
+
const minLines = linesMatch ? parseInt(linesMatch[1], 10) : 5;
|
|
62
|
+
|
|
63
|
+
// Parse wired pattern: any text after "exports" or "imports" or "registers"
|
|
64
|
+
const wiredMatch = annotations.match(/(?:exports?|imports?|registers?)\s+(.+)/i);
|
|
65
|
+
const wiredPattern = wiredMatch ? wiredMatch[1].trim() : null;
|
|
66
|
+
|
|
67
|
+
return { filePath, rawPath, minLines, wiredPattern };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Parse a key_link descriptor string.
|
|
72
|
+
*
|
|
73
|
+
* Example: "auth router registered in src/app.ts"
|
|
74
|
+
* Returns: { pattern, inFile }
|
|
75
|
+
*
|
|
76
|
+
* Pattern is extracted heuristically from the string.
|
|
77
|
+
*/
|
|
78
|
+
function parseKeyLink(str, projectDir) {
|
|
79
|
+
const path = require('node:path');
|
|
80
|
+
const s = String(str).trim();
|
|
81
|
+
|
|
82
|
+
// Pattern: "<thing> in <filepath>" or "<thing> registered/imported/used in <filepath>"
|
|
83
|
+
const inMatch = s.match(/^(.+?)\s+(?:in|from|inside)\s+(\S+\.(?:ts|js|tsx|jsx|py|rb|go|java|php|vue|svelte))\s*$/i);
|
|
84
|
+
if (inMatch) {
|
|
85
|
+
const rawFile = inMatch[2].trim();
|
|
86
|
+
const inFile = projectDir ? path.resolve(projectDir, rawFile) : rawFile;
|
|
87
|
+
|
|
88
|
+
// Extract meaningful keywords from the pattern part (first 3 significant words)
|
|
89
|
+
const patternWords = inMatch[1]
|
|
90
|
+
.replace(/\b(?:registered|imported|used|exported|referenced|wired|connected|added)\b/gi, '')
|
|
91
|
+
.trim()
|
|
92
|
+
.split(/\s+/)
|
|
93
|
+
.filter((w) => w.length > 2)
|
|
94
|
+
.slice(0, 3);
|
|
95
|
+
|
|
96
|
+
return { pattern: patternWords.join('|'), inFile, rawFile };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ─── Tier implementations ─────────────────────────────────────────────────────
|
|
103
|
+
|
|
104
|
+
/** Tier 1: file exists on disk */
|
|
105
|
+
async function verifyExists(filePath) {
|
|
106
|
+
try {
|
|
107
|
+
await fs.access(filePath);
|
|
108
|
+
return { passed: true, tier: 1, file: filePath };
|
|
109
|
+
} catch {
|
|
110
|
+
return { passed: false, tier: 1, file: filePath, reason: 'file does not exist' };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Tier 2: file is substantive (not a stub) */
|
|
115
|
+
async function verifySubstantive(filePath, minLines = 5) {
|
|
116
|
+
let content;
|
|
117
|
+
try {
|
|
118
|
+
content = await fs.readFile(filePath, 'utf8');
|
|
119
|
+
} catch {
|
|
120
|
+
return { passed: false, tier: 2, file: filePath, reason: 'cannot read file' };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const nonEmptyLines = content.split('\n').filter((l) => l.trim().length > 0).length;
|
|
124
|
+
if (nonEmptyLines < minLines) {
|
|
125
|
+
return {
|
|
126
|
+
passed: false, tier: 2, file: filePath,
|
|
127
|
+
reason: `only ${nonEmptyLines} non-empty lines (min: ${minLines})`
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
for (const { pattern, label } of ANTI_PATTERNS) {
|
|
132
|
+
if (pattern.test(content)) {
|
|
133
|
+
return { passed: false, tier: 2, file: filePath, reason: `contains ${label}` };
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return { passed: true, tier: 2, file: filePath, lines: nonEmptyLines };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Tier 3: pattern found in a target file (wired/imported/registered) */
|
|
141
|
+
async function verifyWired(pattern, inFile) {
|
|
142
|
+
if (!pattern || !inFile) {
|
|
143
|
+
return { passed: true, tier: 3, skipped: true, reason: 'no wired constraint to check' };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
let content;
|
|
147
|
+
try {
|
|
148
|
+
content = await fs.readFile(inFile, 'utf8');
|
|
149
|
+
} catch {
|
|
150
|
+
return { passed: false, tier: 3, file: inFile, reason: `cannot read target file: ${inFile}` };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const regex = typeof pattern === 'string'
|
|
154
|
+
? new RegExp(pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i')
|
|
155
|
+
: pattern;
|
|
156
|
+
|
|
157
|
+
if (regex.test(content)) {
|
|
158
|
+
return { passed: true, tier: 3, pattern, inFile };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
passed: false, tier: 3, pattern, inFile,
|
|
163
|
+
reason: `pattern "${pattern}" not found in ${inFile}`
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** Tier 4: smoke command returns expected output (optional, expensive) */
|
|
168
|
+
async function verifyFunctional(command, args = [], expectedPattern = null, timeoutMs = 10_000) {
|
|
169
|
+
try {
|
|
170
|
+
const { stdout, stderr } = await execFileAsync(command, args, {
|
|
171
|
+
timeout: timeoutMs,
|
|
172
|
+
shell: false
|
|
173
|
+
});
|
|
174
|
+
const output = stdout + stderr;
|
|
175
|
+
|
|
176
|
+
if (expectedPattern) {
|
|
177
|
+
const regex = typeof expectedPattern === 'string'
|
|
178
|
+
? new RegExp(expectedPattern, 'i')
|
|
179
|
+
: expectedPattern;
|
|
180
|
+
if (!regex.test(output)) {
|
|
181
|
+
return {
|
|
182
|
+
passed: false, tier: 4, command,
|
|
183
|
+
reason: `expected pattern "${expectedPattern}" not found in command output`
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return { passed: true, tier: 4, command, output: output.slice(0, 300) };
|
|
189
|
+
} catch (err) {
|
|
190
|
+
return { passed: false, tier: 4, command, reason: err.message.slice(0, 200) };
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// ─── must_haves checker ───────────────────────────────────────────────────────
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Run must_haves verification against a task's contract.
|
|
198
|
+
*
|
|
199
|
+
* @param {object} mustHaves — { truths?, artifacts?, key_links? }
|
|
200
|
+
* @param {string} output — Task output text (used for truths checks)
|
|
201
|
+
* @param {string} projectDir
|
|
202
|
+
* @returns {Promise<MustHavesResult>}
|
|
203
|
+
*
|
|
204
|
+
* MustHavesResult:
|
|
205
|
+
* {
|
|
206
|
+
* passed: boolean,
|
|
207
|
+
* failures: string[],
|
|
208
|
+
* warnings: string[],
|
|
209
|
+
* details: object[]
|
|
210
|
+
* }
|
|
211
|
+
*/
|
|
212
|
+
async function checkMustHaves(mustHaves, output, projectDir) {
|
|
213
|
+
if (!mustHaves) return { passed: true, failures: [], warnings: [], details: [] };
|
|
214
|
+
|
|
215
|
+
const failures = [];
|
|
216
|
+
const warnings = [];
|
|
217
|
+
const details = [];
|
|
218
|
+
const outputText = String(output || '').toLowerCase();
|
|
219
|
+
|
|
220
|
+
// ── Truths: heuristic — check if output mentions the expected state ──────
|
|
221
|
+
for (const truth of (mustHaves.truths || [])) {
|
|
222
|
+
// Extract key nouns/verbs from truth statement and check they appear in output
|
|
223
|
+
const keywords = String(truth)
|
|
224
|
+
.replace(/\b(?:the|a|an|is|are|can|will|should|must|to|of|in|and|or|that)\b/gi, '')
|
|
225
|
+
.split(/\s+/)
|
|
226
|
+
.filter((w) => w.length > 3)
|
|
227
|
+
.slice(0, 4);
|
|
228
|
+
|
|
229
|
+
const found = keywords.some((kw) => outputText.includes(kw.toLowerCase()));
|
|
230
|
+
const result = { type: 'truth', statement: truth, passed: found };
|
|
231
|
+
details.push(result);
|
|
232
|
+
|
|
233
|
+
if (!found) {
|
|
234
|
+
warnings.push(`Truth not evident in output: "${truth.slice(0, 80)}"`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// ── Artifacts: Tier 1 + Tier 2 checks ────────────────────────────────────
|
|
239
|
+
for (const artifactStr of (mustHaves.artifacts || [])) {
|
|
240
|
+
const { filePath, rawPath, minLines, wiredPattern } = parseArtifact(artifactStr, projectDir);
|
|
241
|
+
|
|
242
|
+
const t1 = await verifyExists(filePath);
|
|
243
|
+
details.push({ type: 'artifact', descriptor: artifactStr, ...t1 });
|
|
244
|
+
|
|
245
|
+
if (!t1.passed) {
|
|
246
|
+
failures.push(`Artifact missing: ${rawPath}`);
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const t2 = await verifySubstantive(filePath, minLines);
|
|
251
|
+
details.push({ type: 'artifact_substantive', descriptor: artifactStr, ...t2 });
|
|
252
|
+
|
|
253
|
+
if (!t2.passed) {
|
|
254
|
+
failures.push(`Artifact is a stub: ${rawPath} — ${t2.reason}`);
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// If wired pattern specified, verify it exists in the same file
|
|
259
|
+
if (wiredPattern) {
|
|
260
|
+
const t3 = await verifyWired(wiredPattern, filePath);
|
|
261
|
+
details.push({ type: 'artifact_wired', descriptor: artifactStr, ...t3 });
|
|
262
|
+
if (!t3.passed) {
|
|
263
|
+
warnings.push(`Artifact wiring issue: ${rawPath} — ${t3.reason}`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// ── Key links: Tier 3 checks ──────────────────────────────────────────────
|
|
269
|
+
for (const keyLink of (mustHaves.key_links || [])) {
|
|
270
|
+
const parsed = parseKeyLink(keyLink, projectDir);
|
|
271
|
+
|
|
272
|
+
if (!parsed) {
|
|
273
|
+
// Cannot parse — skip silently (heuristic limitation)
|
|
274
|
+
details.push({ type: 'key_link', descriptor: keyLink, passed: true, skipped: true });
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const t3 = await verifyWired(parsed.pattern, parsed.inFile);
|
|
279
|
+
details.push({ type: 'key_link', descriptor: keyLink, ...t3 });
|
|
280
|
+
|
|
281
|
+
if (!t3.passed) {
|
|
282
|
+
warnings.push(`Key link not wired: "${keyLink.slice(0, 80)}" — ${t3.reason}`);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return {
|
|
287
|
+
passed: failures.length === 0,
|
|
288
|
+
failures,
|
|
289
|
+
warnings,
|
|
290
|
+
details
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
module.exports = {
|
|
295
|
+
verifyExists,
|
|
296
|
+
verifySubstantive,
|
|
297
|
+
verifyWired,
|
|
298
|
+
verifyFunctional,
|
|
299
|
+
checkMustHaves,
|
|
300
|
+
parseArtifact,
|
|
301
|
+
parseKeyLink,
|
|
302
|
+
ANTI_PATTERNS
|
|
303
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { spawnSync } = require('node:child_process');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
|
|
6
|
+
const SAFE_ENV_KEYS = new Set(['PATH', 'HOME', 'LANG', 'TERM', 'USER', 'SHELL', 'TMPDIR', 'TMP', 'TEMP']);
|
|
7
|
+
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
8
|
+
|
|
9
|
+
function buildSafeEnv(inputJson) {
|
|
10
|
+
const safe = {};
|
|
11
|
+
for (const key of SAFE_ENV_KEYS) {
|
|
12
|
+
if (process.env[key] !== undefined) {
|
|
13
|
+
safe[key] = process.env[key];
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
safe.TOOL_INPUT = typeof inputJson === 'string' ? inputJson : JSON.stringify(inputJson ?? {});
|
|
17
|
+
return safe;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Executa uma dynamic tool de forma segura via subprocess.
|
|
22
|
+
* @param {object} toolDef - Registro da tool (da tabela dynamic_tools)
|
|
23
|
+
* @param {object|string} inputJson - Input para a tool
|
|
24
|
+
* @param {object} opts
|
|
25
|
+
* @param {string} [opts.projectDir] - Diretório do projeto (para resolver caminhos relativos)
|
|
26
|
+
* @param {number} [opts.timeoutMs] - Timeout em ms (default: 30s)
|
|
27
|
+
* @returns {{ ok: boolean, stdout: string, stderr: string, exitCode: number, error?: string }}
|
|
28
|
+
*/
|
|
29
|
+
function executeTool(toolDef, inputJson, opts = {}) {
|
|
30
|
+
const timeoutMs = opts.timeoutMs || DEFAULT_TIMEOUT_MS;
|
|
31
|
+
const projectDir = opts.projectDir || process.cwd();
|
|
32
|
+
const safeEnv = buildSafeEnv(inputJson);
|
|
33
|
+
|
|
34
|
+
let result;
|
|
35
|
+
|
|
36
|
+
if (toolDef.handler_type === 'shell') {
|
|
37
|
+
if (!toolDef.handler_code) {
|
|
38
|
+
return { ok: false, stdout: '', stderr: '', exitCode: 1, error: 'handler_code is required for shell tools' };
|
|
39
|
+
}
|
|
40
|
+
result = spawnSync('bash', ['-c', toolDef.handler_code], {
|
|
41
|
+
env: safeEnv,
|
|
42
|
+
encoding: 'utf8',
|
|
43
|
+
timeout: timeoutMs,
|
|
44
|
+
maxBuffer: 1024 * 1024
|
|
45
|
+
});
|
|
46
|
+
} else if (toolDef.handler_type === 'script') {
|
|
47
|
+
if (!toolDef.handler_path) {
|
|
48
|
+
return { ok: false, stdout: '', stderr: '', exitCode: 1, error: 'handler_path is required for script tools' };
|
|
49
|
+
}
|
|
50
|
+
const scriptPath = path.isAbsolute(toolDef.handler_path)
|
|
51
|
+
? toolDef.handler_path
|
|
52
|
+
: path.resolve(projectDir, toolDef.handler_path);
|
|
53
|
+
|
|
54
|
+
result = spawnSync('node', ['--env-file=', scriptPath], {
|
|
55
|
+
env: safeEnv,
|
|
56
|
+
encoding: 'utf8',
|
|
57
|
+
timeout: timeoutMs,
|
|
58
|
+
maxBuffer: 1024 * 1024,
|
|
59
|
+
input: safeEnv.TOOL_INPUT
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Fallback: node sem --env-file= (versões antigas do Node não suportam)
|
|
63
|
+
if (result.error && result.error.code === 'ERR_INVALID_ARG_VALUE') {
|
|
64
|
+
result = spawnSync('node', [scriptPath], {
|
|
65
|
+
env: safeEnv,
|
|
66
|
+
encoding: 'utf8',
|
|
67
|
+
timeout: timeoutMs,
|
|
68
|
+
maxBuffer: 1024 * 1024,
|
|
69
|
+
input: safeEnv.TOOL_INPUT
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
} else {
|
|
73
|
+
return { ok: false, stdout: '', stderr: '', exitCode: 1, error: `Unknown handler_type: ${toolDef.handler_type}` };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const stdout = String(result.stdout || '').trim();
|
|
77
|
+
const stderr = String(result.stderr || '').trim();
|
|
78
|
+
const exitCode = result.status ?? 1;
|
|
79
|
+
|
|
80
|
+
if (result.error) {
|
|
81
|
+
const isTimeout = result.error.code === 'ETIMEDOUT' || result.error.killed;
|
|
82
|
+
return {
|
|
83
|
+
ok: false,
|
|
84
|
+
stdout,
|
|
85
|
+
stderr,
|
|
86
|
+
exitCode,
|
|
87
|
+
error: isTimeout ? `Tool timed out after ${timeoutMs}ms` : result.error.message
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return { ok: exitCode === 0, stdout, stderr, exitCode };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
module.exports = { executeTool, buildSafeEnv };
|
package/src/updater.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const path = require('node:path');
|
|
4
|
-
const { detectExistingInstall, installTemplate } = require('./installer');
|
|
4
|
+
const { detectExistingInstall, installTemplate, readInstallProfile } = require('./installer');
|
|
5
5
|
|
|
6
6
|
async function updateInstallation(targetDir, options = {}) {
|
|
7
7
|
const installed = await detectExistingInstall(targetDir);
|
|
@@ -13,17 +13,24 @@ async function updateInstallation(targetDir, options = {}) {
|
|
|
13
13
|
};
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
const savedProfile = await readInstallProfile(targetDir);
|
|
17
|
+
|
|
18
|
+
// Default: only update files already present in the target (selective update).
|
|
19
|
+
// With --all: install every file from the template, including new ones not yet installed.
|
|
16
20
|
const result = await installTemplate(targetDir, {
|
|
17
21
|
overwrite: true,
|
|
18
22
|
dryRun: Boolean(options.dryRun),
|
|
19
23
|
mode: 'update',
|
|
20
24
|
backupOnOverwrite: true,
|
|
21
|
-
frameworkDetection: options.frameworkDetection || null
|
|
25
|
+
frameworkDetection: options.frameworkDetection || null,
|
|
26
|
+
installProfile: null,
|
|
27
|
+
selectiveUpdate: !options.all
|
|
22
28
|
});
|
|
23
29
|
|
|
24
30
|
return {
|
|
25
31
|
ok: true,
|
|
26
|
-
...result
|
|
32
|
+
...result,
|
|
33
|
+
savedProfile
|
|
27
34
|
};
|
|
28
35
|
}
|
|
29
36
|
|
package/src/worker-runner.js
CHANGED
|
@@ -123,12 +123,188 @@ function sleep(ms) {
|
|
|
123
123
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
+
// ─── Research Worker ──────────────────────────────────────────────────────────
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Handle a `type: 'research'` worker.
|
|
130
|
+
*
|
|
131
|
+
* Checks the researchs/ cache first (7-day default TTL), falls back to
|
|
132
|
+
* scraping declared URLs or the topic keyword via web.js fetchPage.
|
|
133
|
+
*
|
|
134
|
+
* Cache location: researchs/{topic}/summary.md
|
|
135
|
+
*/
|
|
136
|
+
async function runResearchWorker(projectDir, config, inputPayload) {
|
|
137
|
+
const { fetchPage } = require('./web');
|
|
138
|
+
const research = config.research || {};
|
|
139
|
+
const topic = String(research.topic || inputPayload?.topic || 'general').replace(/[^a-z0-9-]/gi, '-').toLowerCase();
|
|
140
|
+
const cacheHours = Number(research.cache_hours || 168); // 7 days default
|
|
141
|
+
const cacheDir = path.join(projectDir, research.cache_dir || 'researchs', topic);
|
|
142
|
+
const summaryPath = path.join(cacheDir, 'summary.md');
|
|
143
|
+
|
|
144
|
+
// ── Cache check ────────────────────────────────────────────────────────────
|
|
145
|
+
try {
|
|
146
|
+
const stat = await fs.stat(summaryPath);
|
|
147
|
+
const ageMs = Date.now() - stat.mtimeMs;
|
|
148
|
+
const ageHours = ageMs / (1000 * 60 * 60);
|
|
149
|
+
if (ageHours < cacheHours) {
|
|
150
|
+
const cached = await fs.readFile(summaryPath, 'utf8');
|
|
151
|
+
return {
|
|
152
|
+
ok: true,
|
|
153
|
+
output: { topic, summary: cached, cached: true, cache_age_hours: Math.round(ageHours) },
|
|
154
|
+
attempt: 1
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
} catch { /* no cache yet */ }
|
|
158
|
+
|
|
159
|
+
// ── Scrape sources ─────────────────────────────────────────────────────────
|
|
160
|
+
const urls = research.urls || inputPayload?.urls || [];
|
|
161
|
+
const maxSources = Number(research.max_sources || 5);
|
|
162
|
+
const pages = [];
|
|
163
|
+
|
|
164
|
+
for (const url of urls.slice(0, maxSources)) {
|
|
165
|
+
try {
|
|
166
|
+
const result = await fetchPage(url, { timeoutMs: 15000, extractLinks: false });
|
|
167
|
+
if (result.ok && result.text) {
|
|
168
|
+
pages.push({ url, content: result.text.slice(0, 3000) });
|
|
169
|
+
}
|
|
170
|
+
} catch { /* skip unreachable sources */ }
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (pages.length === 0) {
|
|
174
|
+
return {
|
|
175
|
+
ok: false,
|
|
176
|
+
error: `Research worker "${topic}": no URLs declared and no cached summary. Add "research.urls" to worker.json or provide ?topic= with cached data.`,
|
|
177
|
+
attempts: 1
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// ── Build summary ──────────────────────────────────────────────────────────
|
|
182
|
+
const ts = new Date().toISOString();
|
|
183
|
+
const summary = [
|
|
184
|
+
`# Research: ${topic}`,
|
|
185
|
+
`_Generated: ${ts} · Sources: ${pages.length}_`,
|
|
186
|
+
'',
|
|
187
|
+
...pages.map((p, i) => [
|
|
188
|
+
`## Source ${i + 1}: ${p.url}`,
|
|
189
|
+
'',
|
|
190
|
+
p.content.slice(0, 2000),
|
|
191
|
+
''
|
|
192
|
+
].join('\n'))
|
|
193
|
+
].join('\n');
|
|
194
|
+
|
|
195
|
+
await fs.mkdir(cacheDir, { recursive: true });
|
|
196
|
+
await fs.writeFile(summaryPath, summary, 'utf8');
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
ok: true,
|
|
200
|
+
output: { topic, summary, cached: false, sources: pages.length, generated_at: ts },
|
|
201
|
+
attempt: 1
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// ─── Skill Worker (Plan 81 §2.2) ─────────────────────────────────────────────
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Handle a `type: 'skill'` worker.
|
|
209
|
+
* Resolves an external Agent Skills Standard skill and executes it.
|
|
210
|
+
*
|
|
211
|
+
* Skill sources:
|
|
212
|
+
* - Local path: ./skills/my-skill/ or .claude/skills/my-skill/
|
|
213
|
+
* - NPM package: npm:@org/skill-name (resolved from node_modules)
|
|
214
|
+
*/
|
|
215
|
+
async function runSkillWorker(projectDir, config, inputPayload) {
|
|
216
|
+
const skillRef = config.skill || config.source || '';
|
|
217
|
+
let skillDir;
|
|
218
|
+
|
|
219
|
+
if (skillRef.startsWith('npm:')) {
|
|
220
|
+
// Resolve from node_modules
|
|
221
|
+
const pkgName = skillRef.slice(4);
|
|
222
|
+
skillDir = path.join(projectDir, 'node_modules', pkgName);
|
|
223
|
+
} else {
|
|
224
|
+
// Local path
|
|
225
|
+
skillDir = path.resolve(projectDir, skillRef);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Check SKILL.md exists
|
|
229
|
+
const skillMdPath = path.join(skillDir, 'SKILL.md');
|
|
230
|
+
if (!(await pathExists(skillMdPath))) {
|
|
231
|
+
return {
|
|
232
|
+
ok: false,
|
|
233
|
+
error: `Skill not found: ${skillRef} (expected SKILL.md at ${skillMdPath})`,
|
|
234
|
+
attempts: 0
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Check for executable scripts
|
|
239
|
+
const scriptsDir = path.join(skillDir, 'scripts');
|
|
240
|
+
const runScript = path.join(scriptsDir, 'run.js');
|
|
241
|
+
const runPyScript = path.join(scriptsDir, 'run.py');
|
|
242
|
+
|
|
243
|
+
if (await pathExists(runScript)) {
|
|
244
|
+
return spawnWorker(runScript, inputPayload || {}, {}, config.timeout_ms || DEFAULT_TIMEOUT);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (await pathExists(runPyScript)) {
|
|
248
|
+
return spawnWorker(runPyScript, inputPayload || {}, {}, config.timeout_ms || DEFAULT_TIMEOUT);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// No executable script — return skill content for LLM-based execution
|
|
252
|
+
const skillContent = await fs.readFile(skillMdPath, 'utf8');
|
|
253
|
+
return {
|
|
254
|
+
ok: true,
|
|
255
|
+
output: {
|
|
256
|
+
type: 'skill-prompt',
|
|
257
|
+
skillPath: skillMdPath,
|
|
258
|
+
content: skillContent.slice(0, 4000),
|
|
259
|
+
message: `Skill "${skillRef}" loaded. No run script found — use skill content as agent instructions.`
|
|
260
|
+
},
|
|
261
|
+
attempt: 1
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// ─── Agent Memory Loader (Plan 81 §Sprint 4) ────────────────────────────────
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Load per-agent persistent memory if it exists.
|
|
269
|
+
* Returns memory content or null.
|
|
270
|
+
*/
|
|
271
|
+
async function loadAgentMemory(projectDir, squadSlug, executorSlug) {
|
|
272
|
+
const memoryPath = path.join(
|
|
273
|
+
projectDir, SQUADS_DIR, squadSlug, 'agent-memory', `${executorSlug}.md`
|
|
274
|
+
);
|
|
275
|
+
try {
|
|
276
|
+
return await fs.readFile(memoryPath, 'utf8');
|
|
277
|
+
} catch {
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
126
282
|
async function runWorker(projectDir, squadSlug, workerSlug, inputPayload, options = {}) {
|
|
127
283
|
const config = await loadWorkerConfig(projectDir, squadSlug, workerSlug);
|
|
128
284
|
if (!config) {
|
|
129
285
|
return { ok: false, error: `Worker config not found: ${workerSlug}`, attempts: 0 };
|
|
130
286
|
}
|
|
131
287
|
|
|
288
|
+
// Skill worker — external skill execution (Plan 81 §2.2)
|
|
289
|
+
if (config.type === 'skill') {
|
|
290
|
+
return runSkillWorker(projectDir, config, inputPayload || {});
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Research worker — special handler (4.1)
|
|
294
|
+
if (config.type === 'research') {
|
|
295
|
+
return runResearchWorker(projectDir, config, inputPayload || {});
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Load per-agent persistent memory (Plan 81 §Sprint 4)
|
|
299
|
+
const agentMemory = await loadAgentMemory(projectDir, squadSlug, workerSlug);
|
|
300
|
+
if (agentMemory && inputPayload) {
|
|
301
|
+
// Inject into _agent_memory field (readable by Node.js workers via process.argv[2])
|
|
302
|
+
inputPayload._agent_memory = agentMemory;
|
|
303
|
+
// Prefix into context so LLM-based workers receive it as part of their task context
|
|
304
|
+
const existingContext = inputPayload.context || '';
|
|
305
|
+
inputPayload.context = `## Your accumulated knowledge:\n${agentMemory}\n\n---\n\n${existingContext}`.trimEnd();
|
|
306
|
+
}
|
|
307
|
+
|
|
132
308
|
// Validate inputs
|
|
133
309
|
const validation = validateInputs(inputPayload || {}, config.inputs);
|
|
134
310
|
if (!validation.valid) {
|
|
@@ -149,6 +325,13 @@ async function runWorker(projectDir, squadSlug, workerSlug, inputPayload, option
|
|
|
149
325
|
// Resolve env vars
|
|
150
326
|
const env = resolveEnvVars(config.env);
|
|
151
327
|
|
|
328
|
+
// Expose agent memory path as env var so workers can read it directly
|
|
329
|
+
const memoryFilePath = path.join(projectDir, SQUADS_DIR, squadSlug, 'agent-memory', `${workerSlug}.md`);
|
|
330
|
+
try {
|
|
331
|
+
await fs.access(memoryFilePath);
|
|
332
|
+
env.AIOSON_AGENT_MEMORY_PATH = memoryFilePath;
|
|
333
|
+
} catch { /* no memory file yet — env var omitted */ }
|
|
334
|
+
|
|
152
335
|
// Resolve MCP env vars if worker declares uses_mcp
|
|
153
336
|
if (config.uses_mcp && config.uses_mcp.length > 0) {
|
|
154
337
|
try {
|
|
@@ -335,5 +518,7 @@ module.exports = {
|
|
|
335
518
|
generateRunJs,
|
|
336
519
|
generateWorkerReadme,
|
|
337
520
|
validateInputs,
|
|
338
|
-
resolveEnvVars
|
|
521
|
+
resolveEnvVars,
|
|
522
|
+
runSkillWorker,
|
|
523
|
+
loadAgentMemory
|
|
339
524
|
};
|