@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,218 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* aioson session:guard [projectDir] --agent=<name> --tool=<tool>
|
|
5
|
+
*
|
|
6
|
+
* Background supervisor that keeps a live session alive.
|
|
7
|
+
* - If no live session exists: auto-starts one (no-launch mode)
|
|
8
|
+
* - Polls every 30s to verify the session is still open
|
|
9
|
+
* - Detects inactivity (no events for --idle-minutes, default: 60) and closes gracefully
|
|
10
|
+
* - Works alongside hooks:emit — guard handles session lifecycle, hooks handle events
|
|
11
|
+
*
|
|
12
|
+
* Run in background:
|
|
13
|
+
* aioson session:guard . --agent=dev --tool=claude &
|
|
14
|
+
*
|
|
15
|
+
* Or as a foreground check (--once):
|
|
16
|
+
* aioson session:guard . --agent=dev --tool=claude --once
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const path = require('node:path');
|
|
20
|
+
const fs = require('node:fs/promises');
|
|
21
|
+
const {
|
|
22
|
+
openRuntimeDb,
|
|
23
|
+
resolveRuntimePaths,
|
|
24
|
+
readAgentSession,
|
|
25
|
+
writeAgentSession,
|
|
26
|
+
startTask,
|
|
27
|
+
startRun,
|
|
28
|
+
updateRun,
|
|
29
|
+
updateTask,
|
|
30
|
+
appendRunEvent
|
|
31
|
+
} = require('../runtime-store');
|
|
32
|
+
|
|
33
|
+
const POLL_INTERVAL_MS = 30_000;
|
|
34
|
+
const DEFAULT_IDLE_MINUTES = 60;
|
|
35
|
+
|
|
36
|
+
function nowIso() { return new Date().toISOString(); }
|
|
37
|
+
function log(msg) { process.stderr.write(`[session:guard] ${msg}\n`); }
|
|
38
|
+
|
|
39
|
+
async function getLastEventTime(runtimeDir, sessionKey) {
|
|
40
|
+
const eventsPath = path.join(runtimeDir, 'live', sessionKey, 'events.ndjson');
|
|
41
|
+
try {
|
|
42
|
+
const content = await fs.readFile(eventsPath, 'utf8');
|
|
43
|
+
const lines = content.trim().split('\n').filter(Boolean);
|
|
44
|
+
if (lines.length === 0) return null;
|
|
45
|
+
const last = JSON.parse(lines[lines.length - 1]);
|
|
46
|
+
return last.ts ? new Date(last.ts) : null;
|
|
47
|
+
} catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function startLiveSession(targetDir, runtimeDir, agentName, tool) {
|
|
53
|
+
const now = nowIso();
|
|
54
|
+
const sessionKey = `guard-${agentName}-${Date.now()}`;
|
|
55
|
+
const title = `[guard] ${agentName} via ${tool}`;
|
|
56
|
+
|
|
57
|
+
const { db } = await openRuntimeDb(targetDir);
|
|
58
|
+
try {
|
|
59
|
+
const taskKey = startTask(db, {
|
|
60
|
+
sessionKey,
|
|
61
|
+
title,
|
|
62
|
+
status: 'running',
|
|
63
|
+
createdBy: agentName,
|
|
64
|
+
taskKind: 'live_session',
|
|
65
|
+
metaJson: { tool_session: tool, path: targetDir, guarded: true }
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const runKey = startRun(db, {
|
|
69
|
+
taskKey,
|
|
70
|
+
agentName,
|
|
71
|
+
agentKind: 'official',
|
|
72
|
+
sessionKey,
|
|
73
|
+
source: 'live',
|
|
74
|
+
title,
|
|
75
|
+
eventType: 'session_started',
|
|
76
|
+
phase: 'live',
|
|
77
|
+
message: `Session auto-started by session:guard (${tool})`,
|
|
78
|
+
payload: { tool_session: tool, path: targetDir, guarded: true }
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
await writeAgentSession(runtimeDir, agentName, {
|
|
82
|
+
runKey, taskKey, sessionKey,
|
|
83
|
+
startedAt: now, finished: false, source: 'live'
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// Create state.json for dashboard
|
|
87
|
+
const stateDir = path.join(runtimeDir, 'live', sessionKey);
|
|
88
|
+
await fs.mkdir(stateDir, { recursive: true });
|
|
89
|
+
await fs.writeFile(path.join(stateDir, 'state.json'), JSON.stringify({
|
|
90
|
+
session_key: sessionKey, run_key: runKey, task_key: taskKey,
|
|
91
|
+
agent_name: agentName, tool_session: tool,
|
|
92
|
+
status: 'running', started_at: now, updated_at: now, guarded: true,
|
|
93
|
+
last_events: [{ ts: now, type: 'session_started', summary: `Auto-started by session:guard (${tool})` }]
|
|
94
|
+
}, null, 2), 'utf8');
|
|
95
|
+
|
|
96
|
+
log(`Session started: ${sessionKey} (run: ${runKey})`);
|
|
97
|
+
return { runKey, taskKey, sessionKey };
|
|
98
|
+
} finally {
|
|
99
|
+
db.close();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function closeSession(targetDir, runtimeDir, agentName, runKey, taskKey, reason) {
|
|
104
|
+
const now = nowIso();
|
|
105
|
+
const { db } = await openRuntimeDb(targetDir, { mustExist: true });
|
|
106
|
+
try {
|
|
107
|
+
appendRunEvent(db, {
|
|
108
|
+
runKey, eventType: 'session_ended', phase: 'live',
|
|
109
|
+
status: 'completed', message: `Session closed by session:guard: ${reason}`,
|
|
110
|
+
createdAt: now
|
|
111
|
+
});
|
|
112
|
+
updateRun(db, runKey, { status: 'completed', summary: reason, finishedAt: now });
|
|
113
|
+
if (taskKey) updateTask(db, taskKey, { status: 'completed', finishedAt: now });
|
|
114
|
+
|
|
115
|
+
// Update state.json
|
|
116
|
+
const { db: _, ...rest } = await readAgentSession(runtimeDir, agentName).catch(() => ({}));
|
|
117
|
+
const sessionKey = rest?.sessionKey;
|
|
118
|
+
if (sessionKey) {
|
|
119
|
+
const statePath = path.join(runtimeDir, 'live', sessionKey, 'state.json');
|
|
120
|
+
try {
|
|
121
|
+
const state = JSON.parse(await fs.readFile(statePath, 'utf8'));
|
|
122
|
+
state.status = 'closed';
|
|
123
|
+
state.updated_at = now;
|
|
124
|
+
await fs.writeFile(statePath, JSON.stringify(state, null, 2), 'utf8');
|
|
125
|
+
} catch { /* non-fatal */ }
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Clear session file
|
|
129
|
+
const sessionFile = path.join(runtimeDir, '.sessions', `${agentName}.json`);
|
|
130
|
+
try { await fs.unlink(sessionFile); } catch { /* already gone */ }
|
|
131
|
+
|
|
132
|
+
log(`Session closed: ${runKey} (${reason})`);
|
|
133
|
+
} finally {
|
|
134
|
+
db.close();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function tick(targetDir, runtimeDir, agentName, tool, idleMs, state) {
|
|
139
|
+
const session = await readAgentSession(runtimeDir, agentName);
|
|
140
|
+
|
|
141
|
+
if (!session || session.finished) {
|
|
142
|
+
// No session — start one
|
|
143
|
+
const created = await startLiveSession(targetDir, runtimeDir, agentName, tool);
|
|
144
|
+
state.runKey = created.runKey;
|
|
145
|
+
state.taskKey = created.taskKey;
|
|
146
|
+
state.sessionKey = created.sessionKey;
|
|
147
|
+
state.startedAt = Date.now();
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Session exists — check for idle timeout
|
|
152
|
+
const sessionKey = session.sessionKey;
|
|
153
|
+
const lastEvent = await getLastEventTime(runtimeDir, sessionKey);
|
|
154
|
+
const now = Date.now();
|
|
155
|
+
const lastActivity = lastEvent ? lastEvent.getTime() : state.startedAt;
|
|
156
|
+
const idleFor = now - lastActivity;
|
|
157
|
+
|
|
158
|
+
if (idleFor > idleMs) {
|
|
159
|
+
const idleMin = Math.round(idleFor / 60000);
|
|
160
|
+
log(`Idle for ${idleMin}m — closing session`);
|
|
161
|
+
await closeSession(targetDir, runtimeDir, agentName, session.runKey, session.taskKey,
|
|
162
|
+
`Idle for ${idleMin} minutes`);
|
|
163
|
+
state.runKey = null;
|
|
164
|
+
state.taskKey = null;
|
|
165
|
+
state.sessionKey = null;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async function runSessionGuard({ args, options = {}, logger }) {
|
|
170
|
+
const targetDir = path.resolve(process.cwd(), args[0] || '.');
|
|
171
|
+
const agentName = options.agent ? String(options.agent).replace(/^@/, '') : 'dev';
|
|
172
|
+
const tool = options.tool ? String(options.tool).trim() : 'claude';
|
|
173
|
+
const once = options.once || options['once'] || false;
|
|
174
|
+
const idleMinutes = Number(options['idle-minutes'] || options.idleMinutes || DEFAULT_IDLE_MINUTES);
|
|
175
|
+
const idleMs = idleMinutes * 60 * 1000;
|
|
176
|
+
const intervalMs = Number(options.interval || POLL_INTERVAL_MS);
|
|
177
|
+
|
|
178
|
+
const { runtimeDir } = resolveRuntimePaths(targetDir);
|
|
179
|
+
const state = { runKey: null, taskKey: null, sessionKey: null, startedAt: Date.now() };
|
|
180
|
+
|
|
181
|
+
if (!options.json) {
|
|
182
|
+
logger.log(`[session:guard] Watching: ${targetDir}`);
|
|
183
|
+
logger.log(`[session:guard] Agent: @${agentName} | Tool: ${tool} | Idle timeout: ${idleMinutes}m`);
|
|
184
|
+
logger.log(`[session:guard] Press Ctrl+C to stop.`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
await tick(targetDir, runtimeDir, agentName, tool, idleMs, state);
|
|
188
|
+
|
|
189
|
+
if (once) {
|
|
190
|
+
return { ok: true, runKey: state.runKey, sessionKey: state.sessionKey };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return new Promise((resolve) => {
|
|
194
|
+
const timer = setInterval(async () => {
|
|
195
|
+
try {
|
|
196
|
+
await tick(targetDir, runtimeDir, agentName, tool, idleMs, state);
|
|
197
|
+
} catch (err) {
|
|
198
|
+
log(`Error: ${err.message}`);
|
|
199
|
+
}
|
|
200
|
+
}, intervalMs);
|
|
201
|
+
|
|
202
|
+
const shutdown = async () => {
|
|
203
|
+
clearInterval(timer);
|
|
204
|
+
if (state.runKey) {
|
|
205
|
+
try {
|
|
206
|
+
await closeSession(targetDir, runtimeDir, agentName, state.runKey, state.taskKey, 'session:guard stopped');
|
|
207
|
+
} catch { /* best-effort */ }
|
|
208
|
+
}
|
|
209
|
+
if (!options.json) logger.log('[session:guard] Stopped.');
|
|
210
|
+
resolve({ ok: true });
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
process.on('SIGINT', shutdown);
|
|
214
|
+
process.on('SIGTERM', shutdown);
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
module.exports = { runSessionGuard };
|
|
@@ -3,6 +3,25 @@
|
|
|
3
3
|
const path = require('node:path');
|
|
4
4
|
const readline = require('node:readline/promises');
|
|
5
5
|
const { detectFramework, isMonorepoDetection } = require('../detector');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Infer conversation language from the OS locale environment variables.
|
|
9
|
+
* Supports LANGUAGE, LANG, and LC_ALL in priority order.
|
|
10
|
+
* Maps POSIX locale codes (e.g. pt_BR.UTF-8) to AIOSON locale codes (e.g. pt-BR).
|
|
11
|
+
*/
|
|
12
|
+
function detectSystemLanguage() {
|
|
13
|
+
const raw = process.env.LANGUAGE || process.env.LANG || process.env.LC_ALL || '';
|
|
14
|
+
const base = raw.split(':')[0].split('.')[0].trim();
|
|
15
|
+
if (!base || base === 'C' || base === 'POSIX') return 'en';
|
|
16
|
+
const normalized = base.replace('_', '-');
|
|
17
|
+
const supported = ['en', 'pt-BR', 'es', 'fr'];
|
|
18
|
+
if (supported.includes(normalized)) return normalized;
|
|
19
|
+
const lang = normalized.split('-')[0].toLowerCase();
|
|
20
|
+
if (lang === 'pt') return 'pt-BR';
|
|
21
|
+
if (lang === 'es') return 'es';
|
|
22
|
+
if (lang === 'fr') return 'fr';
|
|
23
|
+
return 'en';
|
|
24
|
+
}
|
|
6
25
|
const { getCliVersionSync } = require('../version');
|
|
7
26
|
const {
|
|
8
27
|
calculateClassification,
|
|
@@ -469,7 +488,7 @@ async function runSetupContext({ args, options, logger, t }) {
|
|
|
469
488
|
profile: 'developer',
|
|
470
489
|
framework: detectedFramework,
|
|
471
490
|
frameworkInstalled: detectedInstalled,
|
|
472
|
-
conversationLanguage:
|
|
491
|
+
conversationLanguage: detectSystemLanguage(),
|
|
473
492
|
designSkill: '',
|
|
474
493
|
testRunner: '',
|
|
475
494
|
web3Enabled: inferredWeb3Enabled,
|
|
@@ -674,5 +693,6 @@ module.exports = {
|
|
|
674
693
|
runSetupContext,
|
|
675
694
|
servicesToContextFields,
|
|
676
695
|
mergeProfileData,
|
|
677
|
-
applyExplicitOverrides
|
|
696
|
+
applyExplicitOverrides,
|
|
697
|
+
detectSystemLanguage
|
|
678
698
|
};
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
const readline = require('node:readline/promises');
|
|
5
|
+
const { installTemplate, readInstallProfile } = require('../installer');
|
|
6
|
+
const { detectFramework } = require('../detector');
|
|
7
|
+
const { detectSystemLanguage } = require('./setup-context');
|
|
8
|
+
const { runSetupContext } = require('./setup-context');
|
|
9
|
+
const { resolvePromptTool } = require('../prompt-tool');
|
|
10
|
+
const { normalizeBoolean } = require('../context-writer');
|
|
11
|
+
const { runInstallWizard } = require('../install-wizard');
|
|
12
|
+
|
|
13
|
+
async function ask(rl, question, fallback = '') {
|
|
14
|
+
const suffix = fallback ? ` (${fallback})` : '';
|
|
15
|
+
const value = await rl.question(`${question}${suffix}: `);
|
|
16
|
+
const cleaned = String(value || '').trim();
|
|
17
|
+
if (!cleaned) return fallback;
|
|
18
|
+
return cleaned;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function runSetup({ args, options, logger, t }) {
|
|
22
|
+
const targetDir = path.resolve(process.cwd(), args[0] || '.');
|
|
23
|
+
const dryRun = Boolean(options['dry-run']);
|
|
24
|
+
const force = Boolean(options.force);
|
|
25
|
+
const defaultsMode = Boolean(options.defaults);
|
|
26
|
+
const promptTool = resolvePromptTool(options.tool);
|
|
27
|
+
|
|
28
|
+
// Step 1 — detect install profile (wizard if first time in TTY)
|
|
29
|
+
const isTTY = process.stdin.isTTY && process.stdout.isTTY;
|
|
30
|
+
let installProfile = null;
|
|
31
|
+
|
|
32
|
+
if (!dryRun && isTTY) {
|
|
33
|
+
const existingProfile = await readInstallProfile(targetDir);
|
|
34
|
+
if (!existingProfile) {
|
|
35
|
+
installProfile = await runInstallWizard({});
|
|
36
|
+
} else {
|
|
37
|
+
installProfile = existingProfile;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Step 2 — install template
|
|
42
|
+
logger.log(t('setup.installing'));
|
|
43
|
+
const installResult = await installTemplate(targetDir, {
|
|
44
|
+
overwrite: force,
|
|
45
|
+
dryRun,
|
|
46
|
+
mode: 'install',
|
|
47
|
+
installProfile
|
|
48
|
+
});
|
|
49
|
+
logger.log(t('setup.installed', { count: installResult.copied.length }));
|
|
50
|
+
|
|
51
|
+
// Step 3 — detect framework and system language
|
|
52
|
+
const detection = await detectFramework(targetDir);
|
|
53
|
+
const detectedFramework = detection.framework;
|
|
54
|
+
const detectedInstalled = detection.installed;
|
|
55
|
+
const systemLang = detectSystemLanguage();
|
|
56
|
+
|
|
57
|
+
// Build setup:context options by merging detected state with explicit flags
|
|
58
|
+
const contextOptions = { defaults: true };
|
|
59
|
+
|
|
60
|
+
// Propagate any explicit overrides the user passed to `setup`
|
|
61
|
+
const passthroughFlags = [
|
|
62
|
+
'project-name', 'project-type', 'framework', 'framework-installed',
|
|
63
|
+
'classification', 'lang', 'language', 'profile', 'backend', 'frontend',
|
|
64
|
+
'database', 'auth', 'uiux', 'design-skill', 'test-runner',
|
|
65
|
+
'web3-enabled', 'web3-networks', 'contract-framework',
|
|
66
|
+
'wallet-provider', 'indexer', 'rpc-provider',
|
|
67
|
+
'queues', 'storage', 'websockets', 'payments', 'email', 'cache', 'search'
|
|
68
|
+
];
|
|
69
|
+
for (const flag of passthroughFlags) {
|
|
70
|
+
if (Object.prototype.hasOwnProperty.call(options, flag)) {
|
|
71
|
+
contextOptions[flag] = options[flag];
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Apply language: explicit flag > system detection
|
|
76
|
+
if (!contextOptions.lang && !contextOptions.language) {
|
|
77
|
+
contextOptions.lang = systemLang;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// For greenfield projects (nothing detected), ask minimal interactive questions
|
|
81
|
+
// unless --defaults is set or the user already passed --framework
|
|
82
|
+
const isGreenfield = !detectedFramework;
|
|
83
|
+
const frameworkProvided = Object.prototype.hasOwnProperty.call(options, 'framework');
|
|
84
|
+
|
|
85
|
+
if (!defaultsMode && isGreenfield && !frameworkProvided) {
|
|
86
|
+
const rl = readline.createInterface({
|
|
87
|
+
input: process.stdin,
|
|
88
|
+
output: process.stdout
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
logger.log(t('setup.no_framework_detected'));
|
|
93
|
+
|
|
94
|
+
const projectName = await ask(
|
|
95
|
+
rl,
|
|
96
|
+
t('setup.q_project_name'),
|
|
97
|
+
path.basename(targetDir) || 'my-project'
|
|
98
|
+
);
|
|
99
|
+
if (projectName !== path.basename(targetDir)) {
|
|
100
|
+
contextOptions['project-name'] = projectName;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const framework = await ask(rl, t('setup.q_framework'), '');
|
|
104
|
+
if (framework) {
|
|
105
|
+
contextOptions.framework = framework;
|
|
106
|
+
contextOptions['framework-installed'] = 'false';
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const detectedLang = contextOptions.lang || systemLang;
|
|
110
|
+
const lang = await ask(rl, t('setup.q_lang'), detectedLang);
|
|
111
|
+
contextOptions.lang = lang;
|
|
112
|
+
} finally {
|
|
113
|
+
rl.close();
|
|
114
|
+
}
|
|
115
|
+
} else if (!defaultsMode && detectedFramework) {
|
|
116
|
+
// Existing project with detected framework — confirm before proceeding
|
|
117
|
+
const rl = readline.createInterface({
|
|
118
|
+
input: process.stdin,
|
|
119
|
+
output: process.stdout
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
logger.log(
|
|
124
|
+
t('setup.framework_detected', {
|
|
125
|
+
framework: detectedFramework,
|
|
126
|
+
installed: String(detectedInstalled)
|
|
127
|
+
})
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const confirmed = normalizeBoolean(
|
|
131
|
+
await ask(rl, t('setup.q_confirm_framework'), 'true'),
|
|
132
|
+
true
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
if (!confirmed) {
|
|
136
|
+
const override = await ask(rl, t('setup.q_override_framework'), detectedFramework);
|
|
137
|
+
contextOptions.framework = override;
|
|
138
|
+
contextOptions['framework-installed'] = await ask(
|
|
139
|
+
rl,
|
|
140
|
+
t('setup.q_framework_installed'),
|
|
141
|
+
'false'
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const detectedLang = contextOptions.lang || systemLang;
|
|
146
|
+
const lang = await ask(rl, t('setup.q_lang'), detectedLang);
|
|
147
|
+
contextOptions.lang = lang;
|
|
148
|
+
} finally {
|
|
149
|
+
rl.close();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Step 4 — run setup:context with fully resolved options
|
|
154
|
+
logger.log(t('setup.writing_context'));
|
|
155
|
+
const contextResult = await runSetupContext({
|
|
156
|
+
args: [targetDir],
|
|
157
|
+
options: contextOptions,
|
|
158
|
+
logger,
|
|
159
|
+
t
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
if (!dryRun) {
|
|
163
|
+
logger.log('');
|
|
164
|
+
logger.log(t('setup.done'));
|
|
165
|
+
logger.log(t('setup.step_agents'));
|
|
166
|
+
logger.log(t('setup.step_agent_prompt', { tool: promptTool }));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
ok: true,
|
|
171
|
+
targetDir,
|
|
172
|
+
installResult,
|
|
173
|
+
contextResult,
|
|
174
|
+
detection
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
module.exports = { runSetup };
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* aioson sizing — Sheldon's sizing decision (inplace / phased_inplace / phased_external).
|
|
5
|
+
*
|
|
6
|
+
* Reads a PRD file and counts entities, phases, integrations, user flows, and ACs.
|
|
7
|
+
* Returns a sizing score and delivery recommendation.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* aioson sizing . --prd=.aioson/context/prd-checkout.md
|
|
11
|
+
* aioson sizing . --feature=checkout
|
|
12
|
+
* aioson sizing . --feature=checkout --json
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const path = require('node:path');
|
|
16
|
+
const { readFileSafe, contextDir } = require('../preflight-engine');
|
|
17
|
+
|
|
18
|
+
const BAR = '━'.repeat(30);
|
|
19
|
+
|
|
20
|
+
// Scoring:
|
|
21
|
+
// main entities > 3 → +1
|
|
22
|
+
// delivery phases > 1 → +2
|
|
23
|
+
// external integrations → +1
|
|
24
|
+
// user flows > 3 → +0 (no penalty)
|
|
25
|
+
// AC count > 10 → +1
|
|
26
|
+
// Score 0-2 → inplace
|
|
27
|
+
// Score 3 → phased_inplace
|
|
28
|
+
// Score 4+ → phased_external
|
|
29
|
+
|
|
30
|
+
function countEntities(content) {
|
|
31
|
+
// Look for entity mentions: model/entity/table patterns
|
|
32
|
+
const patterns = [
|
|
33
|
+
/\b(model|entity|table|resource|object)\s+["`']?([A-Z][a-zA-Z]+)["`']?/g,
|
|
34
|
+
/## [A-Z][a-zA-Z]+ (Model|Entity|Table)/g,
|
|
35
|
+
/\b([A-Z][a-zA-Z]+Model)\b/g
|
|
36
|
+
];
|
|
37
|
+
const entities = new Set();
|
|
38
|
+
for (const pattern of patterns) {
|
|
39
|
+
let m;
|
|
40
|
+
while ((m = pattern.exec(content)) !== null) {
|
|
41
|
+
entities.add((m[2] || m[1]).toLowerCase());
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// Also count H3 headings as potential entities
|
|
45
|
+
const h3 = content.match(/^### [A-Z][a-zA-Z ]+$/gm) || [];
|
|
46
|
+
return Math.max(entities.size, Math.floor(h3.length / 2));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function countPhases(content) {
|
|
50
|
+
// Look for delivery phase / phase N patterns
|
|
51
|
+
const phaseRe = /\b(phase|stage|sprint|iteration)\s+\d+/gi;
|
|
52
|
+
const deliveryRe = /##\s+(phase|stage|delivery|sprint)\s+\d+/gi;
|
|
53
|
+
const phases = new Set();
|
|
54
|
+
let m;
|
|
55
|
+
while ((m = phaseRe.exec(content)) !== null) phases.add(m[0].toLowerCase());
|
|
56
|
+
while ((m = deliveryRe.exec(content)) !== null) phases.add(m[0].toLowerCase());
|
|
57
|
+
return phases.size;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function countIntegrations(content) {
|
|
61
|
+
const integrations = [
|
|
62
|
+
/\b(stripe|paypal|braintree|square|mercadopago)\b/gi,
|
|
63
|
+
/\b(sendgrid|mailchimp|ses|postmark|smtp)\b/gi,
|
|
64
|
+
/\b(twilio|vonage|nexmo|sms)\b/gi,
|
|
65
|
+
/\b(s3|cloudinary|gcs|azure)\b/gi,
|
|
66
|
+
/\b(oauth|auth0|firebase|cognito)\b/gi,
|
|
67
|
+
/\b(redis|elasticsearch|algolia)\b/gi,
|
|
68
|
+
/\bAPI\s+(call|integration)\b/gi,
|
|
69
|
+
/\bwebhook[s]?\b/gi,
|
|
70
|
+
/\bexternal\s+service[s]?\b/gi
|
|
71
|
+
];
|
|
72
|
+
const found = new Set();
|
|
73
|
+
for (const pattern of integrations) {
|
|
74
|
+
let m;
|
|
75
|
+
while ((m = pattern.exec(content)) !== null) found.add(m[0].toLowerCase());
|
|
76
|
+
}
|
|
77
|
+
return found.size;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function countUserFlows(content) {
|
|
81
|
+
// User flow = "as a X, I want to Y" or numbered flow steps
|
|
82
|
+
const asAre = content.match(/As an? [a-z]+, I want/gi) || [];
|
|
83
|
+
const flowRe = content.match(/\bflow\s+\d+\b/gi) || [];
|
|
84
|
+
return asAre.length + flowRe.length;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function countACs(content) {
|
|
88
|
+
// Acceptance criteria: checkboxes or "AC-N" patterns
|
|
89
|
+
const checkboxes = content.match(/^[-*]\s+\[[ x]\]/gmi) || [];
|
|
90
|
+
const acRe = content.match(/\bAC[-\s]?\d+\b/g) || [];
|
|
91
|
+
return Math.max(checkboxes.length, acRe.length);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function sizingDecision(score) {
|
|
95
|
+
if (score <= 2) return { decision: 'inplace', instruction: 'Implement directly in PRD' };
|
|
96
|
+
if (score === 3) return { decision: 'phased_inplace', instruction: 'Add ## Delivery plan section to PRD' };
|
|
97
|
+
return { decision: 'phased_external', instruction: 'Create separate delivery plan document' };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function runSizing({ args, options = {}, logger }) {
|
|
101
|
+
const targetDir = path.resolve(process.cwd(), args[0] || '.');
|
|
102
|
+
const slug = options.feature ? String(options.feature) : null;
|
|
103
|
+
|
|
104
|
+
let prdPath = options.prd ? path.resolve(targetDir, options.prd) : null;
|
|
105
|
+
|
|
106
|
+
if (!prdPath && slug) {
|
|
107
|
+
prdPath = path.join(contextDir(targetDir), `prd-${slug}.md`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (!prdPath) {
|
|
111
|
+
if (options.json) return { ok: false, reason: 'no_prd', message: 'Provide --prd=<path> or --feature=<slug>' };
|
|
112
|
+
logger.log('Provide --prd=<path> or --feature=<slug>');
|
|
113
|
+
return { ok: false };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const content = await readFileSafe(prdPath);
|
|
117
|
+
if (!content) {
|
|
118
|
+
if (options.json) return { ok: false, reason: 'file_not_found', path: prdPath };
|
|
119
|
+
logger.log(`File not found: ${path.relative(targetDir, prdPath)}`);
|
|
120
|
+
return { ok: false };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const entities = countEntities(content);
|
|
124
|
+
const phases = countPhases(content);
|
|
125
|
+
const integrations = countIntegrations(content);
|
|
126
|
+
const flows = countUserFlows(content);
|
|
127
|
+
const acs = countACs(content);
|
|
128
|
+
|
|
129
|
+
const entityScore = entities > 3 ? 1 : 0;
|
|
130
|
+
const phaseScore = phases > 1 ? 2 : 0;
|
|
131
|
+
const intScore = integrations >= 1 ? 1 : 0;
|
|
132
|
+
const flowScore = 0; // flows ≤ 3 → +0; only informational
|
|
133
|
+
const acScore = acs > 10 ? 1 : 0;
|
|
134
|
+
|
|
135
|
+
const totalScore = entityScore + phaseScore + intScore + flowScore + acScore;
|
|
136
|
+
const { decision, instruction } = sizingDecision(totalScore);
|
|
137
|
+
|
|
138
|
+
const result = {
|
|
139
|
+
ok: true,
|
|
140
|
+
prd_path: path.relative(targetDir, prdPath),
|
|
141
|
+
metrics: { entities, phases, integrations, user_flows: flows, ac_count: acs },
|
|
142
|
+
scores: { entities: entityScore, phases: phaseScore, integrations: intScore, acs: acScore, total: totalScore },
|
|
143
|
+
decision,
|
|
144
|
+
instruction
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
if (options.json) return result;
|
|
148
|
+
|
|
149
|
+
const header = slug ? `Sizing — ${slug}` : `Sizing — ${path.relative(targetDir, prdPath)}`;
|
|
150
|
+
logger.log('');
|
|
151
|
+
logger.log(header);
|
|
152
|
+
logger.log(BAR);
|
|
153
|
+
logger.log(`Main entities: ${entities} → +${entityScore}${entities > 3 ? ' (above 3)' : ''}`);
|
|
154
|
+
logger.log(`Delivery phases: ${phases} → +${phaseScore}${phases > 1 ? ' (above 1)' : ''}`);
|
|
155
|
+
logger.log(`External integrations: ${integrations} → +${intScore}`);
|
|
156
|
+
logger.log(`User flows: ${flows} → +0${flows > 3 ? ' (above 3 — informational)' : ''}`);
|
|
157
|
+
logger.log(`AC count: ${acs} → +${acScore}${acs > 10 ? ' (above 10)' : ''}`);
|
|
158
|
+
logger.log(BAR);
|
|
159
|
+
logger.log(`Score: ${totalScore} → ${decision} (${instruction})`);
|
|
160
|
+
logger.log('');
|
|
161
|
+
|
|
162
|
+
return result;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
module.exports = { runSizing };
|