@jaimevalasek/aioson 1.7.2 → 1.9.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 +595 -560
- package/CODE_OF_CONDUCT.md +12 -12
- package/CONTRIBUTING.md +13 -13
- package/LICENSE +661 -661
- package/README.md +919 -776
- package/bin/aioson.js +4 -4
- package/docs/design-previews/aurora-command-ui-website.html +884 -884
- package/docs/design-previews/aurora-command-ui.html +682 -682
- package/docs/design-previews/bold-editorial-ui-website.html +658 -658
- package/docs/design-previews/bold-editorial-ui.html +717 -717
- package/docs/design-previews/clean-saas-ui-website.html +1202 -1202
- package/docs/design-previews/clean-saas-ui.html +549 -549
- package/docs/design-previews/cognitive-core-ui-website.html +1009 -1009
- package/docs/design-previews/cognitive-core-ui.html +463 -463
- package/docs/design-previews/glassmorphism-ui-website.html +572 -572
- package/docs/design-previews/glassmorphism-ui.html +886 -886
- package/docs/design-previews/index.html +699 -699
- package/docs/design-previews/interface-design-website.html +1187 -1187
- package/docs/design-previews/interface-design.html +513 -513
- package/docs/design-previews/neo-brutalist-ui-website.html +621 -621
- package/docs/design-previews/neo-brutalist-ui.html +797 -797
- package/docs/design-previews/premium-command-center-ui-website.html +1217 -1217
- package/docs/design-previews/premium-command-center-ui.html +552 -552
- package/docs/design-previews/pt.squarespace.com-homepage.html +889 -889
- package/docs/design-previews/warm-craft-ui-website.html +684 -684
- package/docs/design-previews/warm-craft-ui.html +739 -739
- package/docs/en/1-understand/ecosystem-map.md +228 -0
- package/docs/en/1-understand/glossary.md +288 -0
- package/docs/en/1-understand/what-is-aioson.md +94 -0
- package/docs/en/1-understand/why-it-exists.md +106 -0
- package/docs/en/2-start/existing-project.md +246 -0
- package/docs/en/2-start/first-project.md +307 -0
- package/docs/en/2-start/initial-decisions.md +223 -0
- package/docs/en/3-recipes/README.md +28 -0
- package/docs/en/3-recipes/continuity-between-sessions.md +303 -0
- package/docs/en/3-recipes/from-idea-to-prd-via-briefing.md +235 -0
- package/docs/en/3-recipes/full-feature-with-sheldon.md +338 -0
- package/docs/en/4-agents/README.md +56 -0
- package/docs/en/5-reference/README.md +60 -0
- package/docs/en/{cli-reference.md → 5-reference/cli-reference.md} +639 -409
- package/docs/en/5-reference/i18n.md +52 -0
- package/docs/en/{json-schemas.md → 5-reference/json-schemas.md} +41 -41
- package/docs/en/{mcp.md → 5-reference/mcp.md} +56 -56
- package/docs/en/{parallel.md → 5-reference/parallel.md} +82 -82
- package/docs/en/{qa-browser.md → 5-reference/qa-browser.md} +339 -339
- package/docs/en/{release-flow.md → 5-reference/release-flow.md} +22 -22
- package/docs/en/{release-notes-template.md → 5-reference/release-notes-template.md} +41 -41
- package/docs/en/{release.md → 5-reference/release.md} +28 -28
- package/docs/en/{schemas → 5-reference/schemas}/agent-prompt.schema.json +17 -17
- package/docs/en/{schemas → 5-reference/schemas}/agents.schema.json +32 -32
- package/docs/en/{schemas → 5-reference/schemas}/context-validate.schema.json +36 -36
- package/docs/en/{schemas → 5-reference/schemas}/doctor.schema.json +89 -89
- package/docs/en/{schemas → 5-reference/schemas}/error.schema.json +24 -24
- package/docs/en/{schemas → 5-reference/schemas}/i18n-add.schema.json +15 -15
- package/docs/en/{schemas → 5-reference/schemas}/index.json +126 -116
- package/docs/en/{schemas → 5-reference/schemas}/info.schema.json +39 -39
- package/docs/en/{schemas → 5-reference/schemas}/init.schema.json +48 -48
- package/docs/en/{schemas → 5-reference/schemas}/install.schema.json +60 -60
- package/docs/en/{schemas → 5-reference/schemas}/locale-apply.schema.json +30 -30
- package/docs/en/{schemas → 5-reference/schemas}/mcp-doctor.schema.json +95 -95
- package/docs/en/{schemas → 5-reference/schemas}/mcp-init.schema.json +122 -122
- package/docs/en/{schemas → 5-reference/schemas}/package-test.schema.json +24 -24
- package/docs/en/{schemas → 5-reference/schemas}/parallel-assign.schema.json +66 -57
- package/docs/en/{schemas → 5-reference/schemas}/parallel-doctor.schema.json +122 -86
- package/docs/en/5-reference/schemas/parallel-guard.schema.json +63 -0
- package/docs/en/{schemas → 5-reference/schemas}/parallel-init.schema.json +53 -53
- package/docs/en/5-reference/schemas/parallel-merge.schema.json +84 -0
- package/docs/en/5-reference/schemas/parallel-status.schema.json +184 -0
- package/docs/en/{schemas → 5-reference/schemas}/setup-context.schema.json +39 -39
- package/docs/en/{schemas → 5-reference/schemas}/smoke.schema.json +23 -23
- package/docs/en/{schemas → 5-reference/schemas}/update.schema.json +48 -48
- package/docs/en/{schemas → 5-reference/schemas}/workflow-plan.schema.json +30 -30
- package/docs/en/{squad-dashboard.md → 5-reference/squad-dashboard.md} +372 -372
- package/docs/en/{web3.md → 5-reference/web3.md} +54 -54
- package/docs/en/README.md +115 -0
- package/docs/en/active-learning-loop/README.md +117 -0
- package/docs/en/active-learning-loop/active-learning-loop.md +117 -0
- package/docs/en/active-learning-loop/cli-commands.md +320 -0
- package/docs/en/active-learning-loop/diagrams.md +225 -0
- package/docs/en/active-learning-loop/doctor-checks.md +151 -0
- package/docs/en/active-learning-loop/how-to-use.md +313 -0
- package/docs/en/active-learning-loop/troubleshooting.md +283 -0
- package/docs/en/deyvin-subtask-scout/README.md +109 -0
- package/docs/en/deyvin-subtask-scout/cli-commands.md +248 -0
- package/docs/en/deyvin-subtask-scout/diagrams.md +124 -0
- package/docs/en/deyvin-subtask-scout/how-to-use.md +221 -0
- package/docs/en/deyvin-subtask-scout/sub-task-scout.md +115 -0
- package/docs/en/deyvin-subtask-scout/troubleshooting.md +184 -0
- package/docs/integrations/apps-publish-marketplace.md +94 -0
- package/docs/integrations/sdlc-genius-boundary.md +76 -76
- package/docs/integrations/sdlc-genius-eval-matrix.md +75 -75
- package/docs/integrations/sdlc-genius-install-checklist.md +93 -93
- package/docs/integrations/sdlc-genius-review-samples.md +86 -86
- package/docs/openclaw-bridge.md +308 -308
- package/docs/pt/1-entender/glossario.md +288 -0
- package/docs/pt/1-entender/mapa-do-ecossistema.md +228 -0
- package/docs/pt/1-entender/o-que-e-aioson.md +94 -0
- package/docs/pt/1-entender/por-que-existe.md +107 -0
- package/docs/pt/2-comecar/decisoes-iniciais.md +223 -0
- package/docs/pt/2-comecar/primeiro-projeto.md +307 -0
- package/docs/pt/2-comecar/projeto-existente.md +245 -0
- package/docs/pt/3-receitas/README.md +28 -0
- package/docs/pt/3-receitas/app-saas-do-zero.md +324 -0
- package/docs/pt/3-receitas/auditoria-seguranca.md +254 -0
- package/docs/pt/3-receitas/clonar-design-de-site.md +211 -0
- package/docs/pt/3-receitas/continuidade-entre-sessoes.md +303 -0
- package/docs/pt/3-receitas/da-ideia-ao-prd-via-briefing.md +234 -0
- package/docs/pt/3-receitas/feature-completa-com-sheldon.md +338 -0
- package/docs/pt/3-receitas/integracao-em-codebase-grande.md +243 -0
- package/docs/pt/3-receitas/landing-page.md +281 -0
- package/docs/pt/3-receitas/plans-externos-para-product.md +191 -0
- package/docs/pt/3-receitas/publicar-no-aioson-com.md +219 -0
- package/docs/pt/3-receitas/refatoracao-grande.md +251 -0
- package/docs/pt/4-agentes/README.md +65 -0
- package/docs/pt/4-agentes/analyst.md +111 -0
- package/docs/pt/4-agentes/architect.md +113 -0
- package/docs/pt/4-agentes/briefing.md +95 -0
- package/docs/pt/4-agentes/committer.md +108 -0
- package/docs/pt/4-agentes/copywriter.md +279 -0
- package/docs/pt/4-agentes/design-hybrid-forge.md +116 -0
- package/docs/pt/4-agentes/dev.md +136 -0
- package/docs/pt/4-agentes/deyvin.md +99 -0
- package/docs/pt/4-agentes/discover.md +122 -0
- package/docs/pt/4-agentes/discovery-design-doc.md +91 -0
- package/docs/pt/4-agentes/genome.md +115 -0
- package/docs/pt/4-agentes/neo.md +93 -0
- package/docs/pt/4-agentes/orache.md +107 -0
- package/docs/pt/4-agentes/orchestrator.md +118 -0
- package/docs/pt/4-agentes/pentester.md +131 -0
- package/docs/pt/4-agentes/pm.md +97 -0
- package/docs/pt/4-agentes/product.md +114 -0
- package/docs/pt/4-agentes/profiler-enricher.md +93 -0
- package/docs/pt/4-agentes/profiler-forge.md +93 -0
- package/docs/pt/4-agentes/profiler-researcher.md +98 -0
- package/docs/pt/4-agentes/qa.md +124 -0
- package/docs/pt/4-agentes/setup.md +104 -0
- package/docs/pt/4-agentes/sheldon.md +95 -0
- package/docs/pt/4-agentes/site-forge.md +104 -0
- package/docs/pt/4-agentes/squad.md +127 -0
- package/docs/pt/4-agentes/tester.md +105 -0
- package/docs/pt/4-agentes/ux-ui.md +110 -0
- package/docs/pt/4-agentes/validator.md +118 -0
- package/docs/pt/5-referencia/README.md +88 -0
- package/docs/pt/5-referencia/agent-chain-continuity.md +124 -0
- package/docs/pt/{agent-sharding.md → 5-referencia/agent-sharding.md} +132 -132
- package/docs/pt/5-referencia/aioson-com-store.md +119 -0
- package/docs/pt/{automacao-squads.md → 5-referencia/automacao-squads.md} +407 -407
- package/docs/pt/{clientes-ai.md → 5-referencia/clientes-ai.md} +300 -286
- package/docs/pt/{comandos-cli.md → 5-referencia/comandos-cli.md} +1823 -1634
- package/docs/pt/5-referencia/compress-agents.md +304 -0
- package/docs/pt/5-referencia/design-docs-governance.md +59 -0
- package/docs/pt/{devlog-pipeline.md → 5-referencia/devlog-pipeline.md} +270 -270
- package/docs/pt/5-referencia/feature-archive.md +199 -0
- package/docs/pt/5-referencia/feature-dossier.md +121 -0
- package/docs/pt/{fluxo-artefatos.md → 5-referencia/fluxo-artefatos.md} +179 -178
- package/docs/pt/{genome-3.0-spec.md → 5-referencia/genome-4.0-spec.md} +407 -296
- package/docs/pt/5-referencia/genome-distribution.md +232 -0
- package/docs/pt/{hooks-session-guard.md → 5-referencia/hooks-session-guard.md} +454 -454
- package/docs/pt/{inteligencia-adaptativa.md → 5-referencia/inteligencia-adaptativa.md} +324 -324
- package/docs/pt/5-referencia/live-sessions.md +144 -0
- package/docs/pt/5-referencia/memoria-e-contexto.md +340 -0
- package/docs/pt/5-referencia/motor-hardening.md +493 -0
- package/docs/pt/{output-strategy-delivery.md → 5-referencia/output-strategy-delivery.md} +655 -655
- package/docs/pt/5-referencia/runner-system.md +113 -0
- package/docs/pt/{runtime-observability.md → 5-referencia/runtime-observability.md} +76 -76
- package/docs/pt/{sandbox.md → 5-referencia/sandbox.md} +125 -125
- package/docs/pt/{sdd-automation-scripts.md → 5-referencia/sdd-automation-scripts.md} +559 -557
- package/docs/pt/5-referencia/sdd-framework.md +115 -0
- package/docs/pt/5-referencia/sdd-planos-e-estrutura.md +321 -0
- package/docs/pt/5-referencia/secure-by-default.md +117 -0
- package/docs/pt/{skills.md → 5-referencia/skills.md} +275 -267
- package/docs/pt/{spec-learnings-pipeline.md → 5-referencia/spec-learnings-pipeline.md} +265 -265
- package/docs/pt/{squad-dashboard.md → 5-referencia/squad-dashboard.md} +373 -373
- package/docs/pt/{web3.md → 5-referencia/web3.md} +797 -797
- package/docs/pt/README.md +111 -116
- package/docs/pt/_arquivo/README.md +130 -0
- package/docs/pt/{advisor-spec.md → _arquivo/advisor-spec.md} +343 -335
- package/docs/pt/{agentes-customizados.md → _arquivo/agentes-customizados.md} +678 -670
- package/docs/pt/{busca-de-contexto.md → _arquivo/busca-de-contexto.md} +136 -129
- package/docs/pt/{cache-de-contexto.md → _arquivo/cache-de-contexto.md} +163 -156
- package/docs/pt/{cenarios.md → _arquivo/cenarios.md} +1282 -1274
- package/docs/pt/{design-hybrid-forge.md → _arquivo/design-hybrid-forge.md} +365 -356
- package/docs/pt/{deyvin.md → _arquivo/deyvin.md} +123 -115
- package/docs/pt/{guia-engineer.md → _arquivo/guia-engineer.md} +234 -226
- package/docs/pt/{inicio-rapido.md → _arquivo/inicio-rapido.md} +261 -250
- package/docs/pt/{memoria-contexto.md → _arquivo/memoria-contexto.md} +262 -255
- package/docs/pt/{monitor-de-contexto.md → _arquivo/monitor-de-contexto.md} +165 -158
- package/docs/pt/{profiler-system.md → _arquivo/profiler-system.md} +222 -214
- package/docs/pt/{recuperacao-de-sessao.md → _arquivo/recuperacao-de-sessao.md} +134 -125
- package/docs/pt/{site-forge.md → _arquivo/site-forge.md} +318 -309
- package/docs/pt/{squad-genome.md → _arquivo/squad-genome.md} +793 -783
- package/docs/pt/active-learning-loop/README.md +117 -0
- package/docs/pt/active-learning-loop/ativo-learning-loop.md +117 -0
- package/docs/pt/active-learning-loop/comandos-cli.md +320 -0
- package/docs/pt/active-learning-loop/como-usar.md +313 -0
- package/docs/pt/active-learning-loop/diagramas.md +225 -0
- package/docs/pt/active-learning-loop/doctor-checks.md +151 -0
- package/docs/pt/active-learning-loop/troubleshooting.md +283 -0
- package/docs/pt/agentes.md +996 -672
- package/docs/pt/deyvin-subtask-scout/README.md +109 -0
- package/docs/pt/deyvin-subtask-scout/comandos-cli.md +248 -0
- package/docs/pt/deyvin-subtask-scout/como-usar.md +221 -0
- package/docs/pt/deyvin-subtask-scout/diagramas.md +124 -0
- package/docs/pt/deyvin-subtask-scout/sub-task-scout.md +113 -0
- package/docs/pt/deyvin-subtask-scout/troubleshooting.md +184 -0
- package/docs/pt/living-memory/README.md +81 -0
- package/docs/pt/living-memory/autonomy-contract.md +206 -0
- package/docs/pt/living-memory/diagramas.md +365 -0
- package/docs/pt/living-memory/memoria-viva.md +141 -0
- package/docs/pt/living-memory/notificacoes-info.md +142 -0
- package/docs/pt/living-memory/reflexao-in-harness.md +218 -0
- package/docs/pt/living-memory/troubleshooting.md +286 -0
- package/docs/testing/genome-2.0-manual-regression.md +23 -23
- package/docs/testing/genome-2.0-matrix.md +36 -36
- package/docs/testing/genome-2.0-rollout.md +184 -184
- package/package.json +51 -50
- package/src/a2a/client.js +165 -165
- package/src/a2a/server.js +223 -223
- package/src/agent-loader.js +280 -280
- package/src/agent-manifests.js +86 -0
- package/src/agents.js +92 -72
- package/src/autonomy-policy.js +163 -0
- package/src/backup-local.js +74 -74
- package/src/backup-provider.js +303 -303
- package/src/brain-query.js +171 -0
- package/src/cli.js +1450 -1099
- package/src/commands/agent-audit.js +397 -397
- package/src/commands/agent-export-skill.js +229 -229
- package/src/commands/agent-loader.js +85 -85
- package/src/commands/agents.js +273 -160
- package/src/commands/artifact-validate.js +218 -189
- package/src/commands/auth.js +298 -0
- package/src/commands/backup-local-cmd.js +25 -25
- package/src/commands/backup.js +533 -533
- package/src/commands/brain-query.js +44 -0
- package/src/commands/brief-gen.js +405 -405
- package/src/commands/brief-validate.js +65 -65
- package/src/commands/briefing.js +344 -0
- package/src/commands/classify.js +256 -256
- package/src/commands/cloud.js +1767 -1767
- package/src/commands/commit-prepare.js +610 -0
- package/src/commands/compress-agents.js +416 -0
- package/src/commands/config.js +90 -90
- package/src/commands/context-cache.js +90 -90
- package/src/commands/context-compact.js +49 -49
- package/src/commands/context-health.js +187 -175
- package/src/commands/context-load.js +219 -0
- package/src/commands/context-monitor.js +163 -163
- package/src/commands/context-pack.js +45 -45
- package/src/commands/context-search.js +66 -66
- package/src/commands/context-trim.js +183 -177
- package/src/commands/context-validate.js +91 -91
- package/src/commands/design-hybrid-options.js +385 -385
- package/src/commands/detect-test-runner.js +55 -55
- package/src/commands/dev-resume.js +32 -0
- package/src/commands/devlog-export-brains.js +27 -27
- package/src/commands/devlog-process.js +294 -292
- package/src/commands/devlog-watch.js +131 -131
- package/src/commands/doctor.js +123 -123
- package/src/commands/dossier-add-research.js +114 -0
- package/src/commands/dossier-audit.js +222 -0
- package/src/commands/dossier.js +423 -0
- package/src/commands/feature-archive.js +513 -0
- package/src/commands/feature-close.js +554 -165
- package/src/commands/gate-approve.js +198 -0
- package/src/commands/gate-check.js +247 -228
- package/src/commands/genome-doctor.js +489 -41
- package/src/commands/genome-migrate.js +49 -49
- package/src/commands/git-guard.js +170 -0
- package/src/commands/harness.js +307 -0
- package/src/commands/health.js +214 -214
- package/src/commands/hooks-emit.js +253 -253
- package/src/commands/hooks-install.js +347 -347
- package/src/commands/i18n-add.js +56 -56
- package/src/commands/implementation-plan.js +367 -340
- package/src/commands/info.js +41 -41
- package/src/commands/init.js +120 -116
- package/src/commands/install.js +162 -107
- package/src/commands/learning-auto-promote.js +197 -195
- package/src/commands/learning-evolve.js +364 -364
- package/src/commands/learning-export.js +103 -103
- package/src/commands/learning-rollback.js +164 -164
- package/src/commands/learning.js +134 -134
- package/src/commands/live.js +2101 -1641
- package/src/commands/locale-apply.js +54 -51
- package/src/commands/locale-diff.js +25 -126
- package/src/commands/mcp-doctor.js +407 -406
- package/src/commands/mcp-init.js +373 -379
- package/src/commands/memory-archive.js +193 -0
- package/src/commands/memory-reflect-commit.js +148 -0
- package/src/commands/memory-reflect-prepare.js +97 -0
- package/src/commands/memory-restore.js +177 -0
- package/src/commands/memory-search.js +135 -0
- package/src/commands/memory.js +299 -0
- package/src/commands/notify.js +68 -0
- package/src/commands/package-e2e.js +273 -273
- package/src/commands/parallel-assign.js +483 -403
- package/src/commands/parallel-doctor.js +850 -437
- package/src/commands/parallel-guard.js +241 -0
- package/src/commands/parallel-init.js +311 -249
- package/src/commands/parallel-merge.js +299 -0
- package/src/commands/parallel-status.js +434 -290
- package/src/commands/pattern-detect.js +33 -33
- package/src/commands/preflight-context.js +30 -30
- package/src/commands/preflight.js +267 -208
- package/src/commands/pulse-update.js +130 -130
- package/src/commands/qa-doctor.js +185 -185
- package/src/commands/qa-init.js +166 -161
- package/src/commands/qa-report.js +58 -58
- package/src/commands/qa-run.js +873 -873
- package/src/commands/qa-scan.js +337 -337
- package/src/commands/recovery.js +43 -43
- package/src/commands/revision.js +235 -0
- package/src/commands/runner-daemon.js +274 -274
- package/src/commands/runner-plan.js +70 -70
- package/src/commands/runner-queue-from-plan.js +166 -166
- package/src/commands/runner-queue.js +189 -189
- package/src/commands/runner-run.js +129 -129
- package/src/commands/runtime.js +2086 -2067
- package/src/commands/sandbox.js +37 -37
- package/src/commands/scaffold-complete.js +188 -0
- package/src/commands/scan-project.js +1371 -1371
- package/src/commands/scout-commit.js +163 -0
- package/src/commands/scout-prep.js +214 -0
- package/src/commands/scout-validate.js +112 -0
- package/src/commands/security-audit.js +275 -0
- package/src/commands/security-scan.js +376 -0
- package/src/commands/self-implement-loop.js +306 -256
- package/src/commands/session-guard.js +218 -218
- package/src/commands/setup-context.js +699 -698
- package/src/commands/setup.js +178 -178
- package/src/commands/sizing.js +165 -165
- package/src/commands/skill.js +670 -670
- package/src/commands/smoke.js +426 -426
- package/src/commands/spec-checkpoint.js +177 -177
- package/src/commands/spec-status.js +79 -79
- package/src/commands/spec-sync.js +190 -190
- package/src/commands/spec-tasks.js +288 -288
- package/src/commands/squad-agent-create.js +830 -788
- package/src/commands/squad-autorun.js +1220 -1220
- package/src/commands/squad-bus.js +217 -217
- package/src/commands/squad-card.js +149 -149
- package/src/commands/squad-daemon.js +343 -343
- package/src/commands/squad-dashboard.js +39 -39
- package/src/commands/squad-dependency-graph.js +164 -164
- package/src/commands/squad-deploy.js +64 -64
- package/src/commands/squad-doctor.js +460 -460
- package/src/commands/squad-export.js +77 -46
- package/src/commands/squad-investigate.js +314 -261
- package/src/commands/squad-learning.js +209 -209
- package/src/commands/squad-mcp.js +270 -270
- package/src/commands/squad-pipeline.js +343 -343
- package/src/commands/squad-plan.js +361 -329
- package/src/commands/squad-processes.js +56 -56
- package/src/commands/squad-recovery.js +42 -42
- package/src/commands/squad-repair-genomes.js +39 -39
- package/src/commands/squad-review.js +106 -106
- package/src/commands/squad-roi.js +291 -291
- package/src/commands/squad-scaffold.js +56 -55
- package/src/commands/squad-score.js +311 -250
- package/src/commands/squad-status.js +481 -460
- package/src/commands/squad-tool-register.js +157 -157
- package/src/commands/squad-validate.js +438 -347
- package/src/commands/squad-webhook.js +160 -160
- package/src/commands/squad-worker.js +191 -191
- package/src/commands/squad-worktrees.js +75 -75
- package/src/commands/state-save.js +122 -122
- package/src/commands/store-genome.js +667 -0
- package/src/commands/store-skill.js +247 -0
- package/src/commands/store-squad.js +431 -0
- package/src/commands/store-system.js +392 -0
- package/src/commands/sync-agents-preflight.js +176 -0
- package/src/commands/test-agents.js +199 -199
- package/src/commands/tool-capabilities.js +63 -0
- package/src/commands/tool-registry-cmd.js +232 -232
- package/src/commands/update.js +64 -64
- package/src/commands/verify-gate.js +612 -572
- package/src/commands/web-map.js +70 -70
- package/src/commands/web-scrape.js +71 -71
- package/src/commands/workflow-execute.js +730 -241
- package/src/commands/workflow-harden.js +231 -0
- package/src/commands/workflow-heal.js +136 -0
- package/src/commands/workflow-next.js +1279 -601
- package/src/commands/workflow-plan.js +108 -108
- package/src/commands/workflow-status.js +440 -250
- package/src/commands/workspace.js +144 -0
- package/src/constants.js +413 -417
- package/src/context-cache.js +159 -159
- package/src/context-memory.js +975 -837
- package/src/context-parse-reason.js +22 -22
- package/src/context-search.js +326 -326
- package/src/context-writer.js +197 -196
- package/src/context.js +247 -217
- package/src/delivery-runner.js +319 -319
- package/src/design-variation-catalog.js +503 -503
- package/src/detector.js +261 -261
- package/src/doctor.js +760 -289
- package/src/dossier/codemap-store.js +267 -0
- package/src/dossier/dossier-bootstrap.js +222 -0
- package/src/dossier/dossier-compact.js +159 -0
- package/src/dossier/lock.js +128 -0
- package/src/dossier/research-index-store.js +233 -0
- package/src/dossier/revision-store.js +313 -0
- package/src/dossier/schema.js +162 -0
- package/src/dossier/scout-section.js +127 -0
- package/src/dossier/store.js +406 -0
- package/src/execution-gateway.js +464 -461
- package/src/friction-scanner.js +202 -0
- package/src/genome-files.js +198 -198
- package/src/genome-format.js +442 -442
- package/src/genome-schema.js +238 -215
- package/src/genomes/bindings.js +281 -281
- package/src/genomes.js +500 -467
- package/src/handoff-contract.js +417 -0
- package/src/handoff-validator.js +45 -0
- package/src/harness/circuit-breaker.js +135 -0
- package/src/i18n/index.js +103 -103
- package/src/i18n/messages/en.js +1541 -1139
- package/src/i18n/messages/es.js +1325 -980
- package/src/i18n/messages/fr.js +1333 -987
- package/src/i18n/messages/pt-BR.js +1561 -1166
- package/src/i18n/scaffold.js +64 -64
- package/src/install-animation.js +260 -260
- package/src/install-profile.js +127 -143
- package/src/install-wizard.js +475 -475
- package/src/installer-config-merge.js +207 -0
- package/src/installer.js +449 -294
- package/src/learning-loop-archive.js +595 -0
- package/src/learning-loop-doctor.js +217 -0
- package/src/learning-loop-engine.js +254 -0
- package/src/learning-loop-fts5.js +132 -0
- package/src/learning-loop-migration.js +163 -0
- package/src/lib/dev-resume.js +140 -0
- package/src/lib/dossier-telemetry.js +36 -0
- package/src/lib/genomes/compat.js +206 -206
- package/src/lib/genomes/migrate.js +90 -90
- package/src/lib/git-commit-guard.js +751 -0
- package/src/lib/health-check.js +158 -158
- package/src/lib/hook-protocol.js +76 -76
- package/src/lib/llm-content-sanitizer.js +44 -0
- package/src/lib/security/artifact-reader.js +167 -0
- package/src/lib/security/exit-codes.js +51 -0
- package/src/lib/security/findings-writer.js +176 -0
- package/src/lib/security/runtime-events.js +77 -0
- package/src/lib/security/secrets-regex.js +115 -0
- package/src/lib/squads/genome-repair.js +49 -49
- package/src/lib/store/security-scan.js +175 -0
- package/src/lib/terminal-checkbox.js +135 -0
- package/src/lib/terminal-picker.js +447 -0
- package/src/lib/tmux-launcher.js +163 -0
- package/src/lib/tool-capabilities.js +102 -0
- package/src/lib/webhook-server.js +328 -328
- package/src/locales.js +88 -84
- package/src/mcp/apps/squad-dashboard/app.js +163 -163
- package/src/mcp/apps/squad-dashboard/index.html +261 -261
- package/src/mcp/apps/squad-dashboard/mcp-manifest.json +23 -23
- package/src/mcp/resources/squad-state.js +130 -130
- package/src/mcp-connectors/registry.js +602 -602
- package/src/memory-reflect-engine.js +359 -0
- package/src/notify-renderer.js +32 -0
- package/src/onboarding.js +305 -305
- package/src/parallel-workspace.js +756 -0
- package/src/parser.js +66 -59
- package/src/path-guard.js +47 -0
- package/src/permissions-generator.js +400 -0
- package/src/preflight-engine.js +654 -443
- package/src/prompt-tool.js +20 -20
- package/src/qa-html-report.js +472 -472
- package/src/recovery-context-session.js +154 -154
- package/src/runner/cascade.js +97 -97
- package/src/runner/cli-launcher.js +109 -109
- package/src/runner/plan-importer.js +63 -63
- package/src/runner/queue-store.js +159 -159
- package/src/runtime-store.js +2720 -2676
- package/src/sandbox.js +194 -177
- package/src/self-healing.js +142 -0
- package/src/session-handoff.js +295 -77
- package/src/squad/agent-teams-adapter.js +270 -264
- package/src/squad/brief-validator.js +350 -350
- package/src/squad/bus-bridge.js +140 -140
- package/src/squad/context-compactor.js +265 -265
- package/src/squad/cross-ai-synthesizer.js +250 -250
- package/src/squad/external-session.js +180 -180
- package/src/squad/hooks-generator.js +196 -196
- package/src/squad/inter-squad-events.js +175 -175
- package/src/squad/inter-squad.js +74 -74
- package/src/squad/intra-bus.js +345 -345
- package/src/squad/learning-extractor.js +213 -213
- package/src/squad/pattern-detector.js +365 -365
- package/src/squad/preflight-context.js +296 -296
- package/src/squad/recovery-context.js +372 -372
- package/src/squad/reflection.js +365 -365
- package/src/squad/squad-scaffold.js +341 -177
- package/src/squad/state-manager.js +310 -310
- package/src/squad/task-decomposer.js +652 -652
- package/src/squad/verify-gate.js +303 -303
- package/src/squad/worktree-manager.js +114 -114
- package/src/squad-daemon.js +490 -490
- package/src/squad-dashboard/api.js +223 -223
- package/src/squad-dashboard/attachment-handler.js +93 -93
- package/src/squad-dashboard/context-monitor.js +157 -157
- package/src/squad-dashboard/execution-logs.js +115 -115
- package/src/squad-dashboard/hunk-review.js +209 -209
- package/src/squad-dashboard/metrics.js +133 -133
- package/src/squad-dashboard/process-monitor.js +125 -125
- package/src/squad-dashboard/renderer.js +858 -858
- package/src/squad-dashboard/server.js +232 -232
- package/src/squad-dashboard/styles.js +525 -525
- package/src/squad-dashboard/token-tracker.js +99 -99
- package/src/squads/apply-genome.js +21 -21
- package/src/squads/genome-binding-service.js +154 -154
- package/src/sub-task-engine.js +415 -0
- package/src/sub-task-schemas.js +150 -0
- package/src/sub-task-state.js +152 -0
- package/src/sub-task-telemetry.js +69 -0
- package/src/test-briefing.js +226 -0
- package/src/tool-executor.js +94 -94
- package/src/updater.js +39 -39
- package/src/utils.js +49 -46
- package/src/version.js +50 -50
- package/src/web.js +284 -284
- package/src/worker-runner.js +541 -524
- package/src/workflow-gates.js +185 -0
- package/template/.aioson/advisors/.gitkeep +1 -1
- package/template/.aioson/agents/analyst.md +333 -372
- package/template/.aioson/agents/architect.md +325 -338
- package/template/.aioson/agents/briefing.md +264 -0
- package/template/.aioson/agents/committer.md +161 -0
- package/template/.aioson/agents/copywriter.md +937 -463
- package/template/.aioson/agents/design-hybrid-forge.md +141 -141
- package/template/.aioson/agents/dev.md +295 -779
- package/template/.aioson/agents/deyvin.md +198 -290
- package/template/.aioson/agents/discover.md +235 -0
- package/template/.aioson/agents/discovery-design-doc.md +56 -264
- package/template/.aioson/agents/genome.md +1904 -314
- package/template/.aioson/agents/manifests/analyst.manifest.json +26 -0
- package/template/.aioson/agents/manifests/architect.manifest.json +23 -0
- package/template/.aioson/agents/manifests/committer.manifest.json +23 -0
- package/template/.aioson/agents/manifests/dev.manifest.json +54 -0
- package/template/.aioson/agents/manifests/deyvin.manifest.json +41 -0
- package/template/.aioson/agents/manifests/orchestrator.manifest.json +30 -0
- package/template/.aioson/agents/manifests/pentester.manifest.json +39 -0
- package/template/.aioson/agents/manifests/pm.manifest.json +26 -0
- package/template/.aioson/agents/manifests/product.manifest.json +23 -0
- package/template/.aioson/agents/manifests/qa.manifest.json +41 -0
- package/template/.aioson/agents/manifests/setup.manifest.json +20 -0
- package/template/.aioson/agents/manifests/ux-ui.manifest.json +24 -0
- package/template/.aioson/agents/neo.md +341 -233
- package/template/.aioson/agents/orache.md +430 -434
- package/template/.aioson/agents/orchestrator.md +274 -364
- package/template/.aioson/agents/pair.md +5 -5
- package/template/.aioson/agents/pentester.md +289 -0
- package/template/.aioson/agents/pm.md +141 -194
- package/template/.aioson/agents/product.md +351 -518
- package/template/.aioson/agents/profiler-enricher.md +331 -280
- package/template/.aioson/agents/profiler-forge.md +212 -202
- package/template/.aioson/agents/profiler-researcher.md +282 -259
- package/template/.aioson/agents/qa.md +432 -688
- package/template/.aioson/agents/setup.md +423 -649
- package/template/.aioson/agents/sheldon.md +259 -829
- package/template/.aioson/agents/site-forge.md +281 -1753
- package/template/.aioson/agents/squad.md +160 -2027
- package/template/.aioson/agents/tester.md +536 -463
- package/template/.aioson/agents/ux-ui.md +195 -870
- package/template/.aioson/agents/validator.md +101 -0
- package/template/.aioson/brains/README.md +132 -128
- package/template/.aioson/brains/_archived/.gitkeep +0 -0
- package/template/.aioson/brains/_index.json +34 -16
- package/template/.aioson/brains/dev/patterns.brain.json +79 -0
- package/template/.aioson/brains/scripts/query.js +107 -103
- package/template/.aioson/brains/sheldon/architecture-decisions.brain.json +79 -0
- package/template/.aioson/brains/site-forge/visual-patterns.brain.json +205 -205
- package/template/.aioson/config/autonomy-protocol.json +125 -0
- package/template/.aioson/config/learning-loop.json +10 -0
- package/template/.aioson/config/scout-engine.json +1 -0
- package/template/.aioson/config.md +410 -382
- package/template/.aioson/constitution.md +36 -33
- package/template/.aioson/context/_archived/.gitkeep +0 -0
- package/template/.aioson/context/design-doc.md +136 -0
- package/template/.aioson/context/project-map.md +57 -0
- package/template/.aioson/context/project-pulse.md +34 -34
- package/template/.aioson/context/seeds/seed-example.md +27 -27
- package/template/.aioson/context/spec.md.template +54 -54
- package/template/.aioson/context/user-profile.md +42 -42
- package/template/.aioson/design-docs/code-reuse.md +48 -0
- package/template/.aioson/design-docs/componentization.md +47 -0
- package/template/.aioson/design-docs/file-size.md +52 -0
- package/template/.aioson/design-docs/folder-structure.md +51 -0
- package/template/.aioson/design-docs/naming.md +54 -0
- package/template/.aioson/docs/LAYERS.md +89 -79
- package/template/.aioson/docs/README.md +76 -76
- package/template/.aioson/docs/autonomy-protocol.md +80 -0
- package/template/.aioson/docs/briefing/briefing-craft.md +237 -0
- package/template/.aioson/docs/dev/execution-discipline.md +106 -0
- package/template/.aioson/docs/dev/stack-conventions.md +83 -0
- package/template/.aioson/docs/deyvin/continuity-recovery.md +57 -0
- package/template/.aioson/docs/deyvin/debugging-escalation.md +30 -0
- package/template/.aioson/docs/deyvin/pair-execution.md +44 -0
- package/template/.aioson/docs/deyvin/runtime-handoffs.md +36 -0
- package/template/.aioson/docs/example-external-api-context.md +72 -72
- package/template/.aioson/docs/pentester/app-playbooks.md +206 -0
- package/template/.aioson/docs/pentester/llm-supplychain.md +165 -0
- package/template/.aioson/docs/product/conversation-playbook.md +116 -0
- package/template/.aioson/docs/product/prd-contract.md +107 -0
- package/template/.aioson/docs/product/quality-lens.md +57 -0
- package/template/.aioson/docs/product/research-loop.md +65 -0
- package/template/.aioson/docs/sheldon/enrichment-paths.md +134 -0
- package/template/.aioson/docs/sheldon/harness-contract.md +118 -0
- package/template/.aioson/docs/sheldon/quality-lens.md +57 -0
- package/template/.aioson/docs/sheldon/research-loop.md +56 -0
- package/template/.aioson/docs/sheldon/web-intelligence.md +75 -0
- package/template/.aioson/docs/site-forge-build.md +195 -0
- package/template/.aioson/docs/site-forge-extraction.md +135 -0
- package/template/.aioson/docs/site-forge-qa.md +155 -0
- package/template/.aioson/docs/site-forge-recon.md +434 -0
- package/template/.aioson/docs/site-forge-transform.md +249 -0
- package/template/.aioson/docs/squad/content-output.md +91 -0
- package/template/.aioson/docs/squad/creation-flow.md +149 -0
- package/template/.aioson/docs/squad/domain-breadth.md +322 -0
- package/template/.aioson/docs/squad/domain-classification.md +117 -0
- package/template/.aioson/docs/squad/genome-bindings.md +47 -0
- package/template/.aioson/docs/squad/package-contract.md +260 -0
- package/template/.aioson/docs/squad/quality-lens.md +60 -0
- package/template/.aioson/docs/squad/research-loop.md +59 -0
- package/template/.aioson/docs/squad/session-operations.md +117 -0
- package/template/.aioson/docs/squad/workflow-quality.md +165 -0
- package/template/.aioson/docs/tester/coverage-quality.md +351 -0
- package/template/.aioson/docs/ux-ui/accessibility-audit.md +55 -0
- package/template/.aioson/docs/ux-ui/audit-mode.md +86 -0
- package/template/.aioson/docs/ux-ui/component-map.md +35 -0
- package/template/.aioson/docs/ux-ui/design-execution.md +111 -0
- package/template/.aioson/docs/ux-ui/design-gate.md +27 -0
- package/template/.aioson/docs/ux-ui/research-mode.md +39 -0
- package/template/.aioson/docs/ux-ui/site-delivery.md +156 -0
- package/template/.aioson/docs/ux-ui/token-contract.md +57 -0
- package/template/.aioson/genomes/INDEX.md +195 -0
- package/template/.aioson/genomes/copywriting/SKILL.md +137 -0
- package/template/.aioson/genomes/copywriting/manifest.json +140 -0
- package/template/.aioson/genomes/copywriting/references/application-notes.md +145 -0
- package/template/.aioson/genomes/copywriting/references/decision-weights.md +45 -0
- package/template/.aioson/genomes/copywriting/references/frameworks/5-act-narrative.md +184 -0
- package/template/.aioson/genomes/copywriting/references/frameworks/classical-formulas.md +164 -0
- package/template/.aioson/genomes/copywriting/references/frameworks/offer-stack.md +195 -0
- package/template/.aioson/genomes/copywriting/references/frameworks/one-belief.md +135 -0
- package/template/.aioson/genomes/copywriting/references/frameworks/pms-research.md +211 -0
- package/template/.aioson/genomes/copywriting/references/frameworks/two-paths-close.md +190 -0
- package/template/.aioson/genomes/copywriting/references/heuristics.md +114 -0
- package/template/.aioson/genomes/copywriting/references/meta-axioms.md +68 -0
- package/template/.aioson/genomes/copywriting/references/methodology.md +115 -0
- package/template/.aioson/genomes/copywriting-brunson/SKILL.md +133 -0
- package/template/.aioson/genomes/copywriting-brunson/manifest.json +152 -0
- package/template/.aioson/genomes/copywriting-brunson/references/application-notes.md +113 -0
- package/template/.aioson/genomes/copywriting-brunson/references/decision-weights.md +33 -0
- package/template/.aioson/genomes/copywriting-brunson/references/evidence-and-attribution.md +81 -0
- package/template/.aioson/genomes/copywriting-brunson/references/frameworks/6-part-structure.md +136 -0
- package/template/.aioson/genomes/copywriting-brunson/references/frameworks/origin-story.md +121 -0
- package/template/.aioson/genomes/copywriting-brunson/references/frameworks/perfect-webinar-script.md +139 -0
- package/template/.aioson/genomes/copywriting-brunson/references/frameworks/persuasive-storytelling-5-structures.md +164 -0
- package/template/.aioson/genomes/copywriting-brunson/references/frameworks/value-stack.md +136 -0
- package/template/.aioson/genomes/copywriting-brunson/references/frameworks/who-what-why-how.md +110 -0
- package/template/.aioson/genomes/copywriting-brunson/references/meta-axioms.md +36 -0
- package/template/.aioson/genomes/copywriting-brunson/references/methodology.md +112 -0
- package/template/.aioson/git-guard.json +12 -0
- package/template/.aioson/mcp/servers.md +23 -24
- package/template/.aioson/profiler-reports/.gitkeep +1 -1
- package/template/.aioson/rules/README.md +69 -69
- package/template/.aioson/rules/_archived/.gitkeep +0 -0
- package/template/.aioson/rules/agent-language-policy.md +93 -0
- package/template/.aioson/rules/aioson-context-boundary.md +63 -0
- package/template/.aioson/rules/canonical-path-contract.md +47 -0
- package/template/.aioson/rules/data-format-convention.md +74 -136
- package/template/.aioson/rules/disk-first-artifacts.md +44 -0
- package/template/.aioson/rules/example-monetary-values.md +30 -30
- package/template/.aioson/rules/output-brevity.md +44 -0
- package/template/.aioson/rules/prd-section-ownership.md +49 -0
- package/template/.aioson/rules/security-baseline.md +139 -0
- package/template/.aioson/rules/spec-level-ownership.md +61 -0
- package/template/.aioson/rules/squad/README.md +50 -50
- package/template/.aioson/rules/squad-driver-pattern.md +81 -0
- package/template/.aioson/schemas/content-blueprint.schema.json +30 -30
- package/template/.aioson/schemas/genome-meta.schema.json +150 -150
- package/template/.aioson/schemas/genome.schema.json +115 -115
- package/template/.aioson/schemas/readiness.schema.json +27 -27
- package/template/.aioson/schemas/squad-blueprint.schema.json +228 -204
- package/template/.aioson/schemas/squad-manifest.schema.json +874 -830
- package/template/.aioson/skills/design/aurora-command-ui/SKILL.md +243 -243
- package/template/.aioson/skills/design/aurora-command-ui/references/art-direction.md +293 -293
- package/template/.aioson/skills/design/aurora-command-ui/references/components.md +827 -827
- package/template/.aioson/skills/design/aurora-command-ui/references/dashboards.md +250 -250
- package/template/.aioson/skills/design/aurora-command-ui/references/design-tokens.md +585 -585
- package/template/.aioson/skills/design/aurora-command-ui/references/motion.md +365 -365
- package/template/.aioson/skills/design/aurora-command-ui/references/patterns.md +482 -482
- package/template/.aioson/skills/design/aurora-command-ui/references/websites.md +387 -387
- package/template/.aioson/skills/design/bold-editorial-ui/SKILL.md +205 -205
- package/template/.aioson/skills/design/bold-editorial-ui/references/art-direction.md +338 -338
- package/template/.aioson/skills/design/bold-editorial-ui/references/components.md +977 -977
- package/template/.aioson/skills/design/bold-editorial-ui/references/dashboards.md +218 -218
- package/template/.aioson/skills/design/bold-editorial-ui/references/design-tokens.md +326 -326
- package/template/.aioson/skills/design/bold-editorial-ui/references/motion.md +461 -461
- package/template/.aioson/skills/design/bold-editorial-ui/references/patterns.md +293 -293
- package/template/.aioson/skills/design/bold-editorial-ui/references/websites.md +352 -352
- package/template/.aioson/skills/design/clean-saas-ui/SKILL.md +210 -210
- package/template/.aioson/skills/design/clean-saas-ui/references/art-direction.md +319 -319
- package/template/.aioson/skills/design/clean-saas-ui/references/components.md +365 -365
- package/template/.aioson/skills/design/clean-saas-ui/references/dashboards.md +196 -196
- package/template/.aioson/skills/design/clean-saas-ui/references/design-tokens.md +244 -244
- package/template/.aioson/skills/design/clean-saas-ui/references/motion.md +235 -235
- package/template/.aioson/skills/design/clean-saas-ui/references/patterns.md +215 -215
- package/template/.aioson/skills/design/clean-saas-ui/references/websites.md +295 -295
- package/template/.aioson/skills/design/cognitive-core-ui/SKILL.md +203 -203
- package/template/.aioson/skills/design/cognitive-core-ui/references/art-direction.md +339 -339
- package/template/.aioson/skills/design/cognitive-core-ui/references/components.md +407 -407
- package/template/.aioson/skills/design/cognitive-core-ui/references/dashboards.md +272 -272
- package/template/.aioson/skills/design/cognitive-core-ui/references/design-tokens.md +524 -524
- package/template/.aioson/skills/design/cognitive-core-ui/references/motion.md +279 -279
- package/template/.aioson/skills/design/cognitive-core-ui/references/patterns.md +289 -289
- package/template/.aioson/skills/design/cognitive-core-ui/references/websites.md +437 -437
- package/template/.aioson/skills/design/glassmorphism-ui/SKILL.md +222 -222
- package/template/.aioson/skills/design/glassmorphism-ui/references/art-direction.md +159 -159
- package/template/.aioson/skills/design/glassmorphism-ui/references/components.md +498 -498
- package/template/.aioson/skills/design/glassmorphism-ui/references/dashboards.md +236 -236
- package/template/.aioson/skills/design/glassmorphism-ui/references/design-tokens.md +274 -274
- package/template/.aioson/skills/design/glassmorphism-ui/references/motion.md +355 -355
- package/template/.aioson/skills/design/glassmorphism-ui/references/patterns.md +198 -198
- package/template/.aioson/skills/design/glassmorphism-ui/references/websites.md +307 -307
- package/template/.aioson/skills/design/interface-design/SKILL.md +47 -47
- package/template/.aioson/skills/design/interface-design/references/components-and-states.md +105 -105
- package/template/.aioson/skills/design/interface-design/references/design-directions.md +101 -101
- package/template/.aioson/skills/design/interface-design/references/handoff-and-quality.md +71 -71
- package/template/.aioson/skills/design/interface-design/references/intent-and-domain.md +74 -74
- package/template/.aioson/skills/design/interface-design/references/tokens-and-depth.md +173 -173
- package/template/.aioson/skills/design/neo-brutalist-ui/SKILL.md +213 -213
- package/template/.aioson/skills/design/neo-brutalist-ui/references/art-direction.md +228 -228
- package/template/.aioson/skills/design/neo-brutalist-ui/references/components.md +855 -855
- package/template/.aioson/skills/design/neo-brutalist-ui/references/dashboards.md +334 -334
- package/template/.aioson/skills/design/neo-brutalist-ui/references/design-tokens.md +342 -342
- package/template/.aioson/skills/design/neo-brutalist-ui/references/motion.md +286 -286
- package/template/.aioson/skills/design/neo-brutalist-ui/references/patterns.md +458 -458
- package/template/.aioson/skills/design/neo-brutalist-ui/references/websites.md +723 -723
- package/template/.aioson/skills/design/premium-command-center-ui/SKILL.md +62 -62
- package/template/.aioson/skills/design/premium-command-center-ui/references/operations.md +74 -74
- package/template/.aioson/skills/design/premium-command-center-ui/references/patterns.md +116 -116
- package/template/.aioson/skills/design/premium-command-center-ui/references/validation.md +47 -47
- package/template/.aioson/skills/design/premium-command-center-ui/references/visual-system.md +215 -215
- package/template/.aioson/skills/design/pt.squarespace.com/.skill-meta.json +31 -31
- package/template/.aioson/skills/design/pt.squarespace.com/SKILL.md +66 -66
- package/template/.aioson/skills/design/pt.squarespace.com/references/components.md +368 -368
- package/template/.aioson/skills/design/pt.squarespace.com/references/design-tokens.md +150 -150
- package/template/.aioson/skills/design/pt.squarespace.com/references/motion.md +270 -270
- package/template/.aioson/skills/design/pt.squarespace.com/references/patterns.md +189 -189
- package/template/.aioson/skills/design/pt.squarespace.com/references/websites.md +165 -165
- package/template/.aioson/skills/design/warm-craft-ui/SKILL.md +209 -209
- package/template/.aioson/skills/design/warm-craft-ui/references/art-direction.md +324 -324
- package/template/.aioson/skills/design/warm-craft-ui/references/components.md +508 -508
- package/template/.aioson/skills/design/warm-craft-ui/references/dashboards.md +223 -223
- package/template/.aioson/skills/design/warm-craft-ui/references/design-tokens.md +374 -374
- package/template/.aioson/skills/design/warm-craft-ui/references/motion.md +356 -356
- package/template/.aioson/skills/design/warm-craft-ui/references/patterns.md +288 -288
- package/template/.aioson/skills/design/warm-craft-ui/references/websites.md +289 -289
- package/template/.aioson/skills/design-system/SKILL.md +92 -92
- package/template/.aioson/skills/design-system/components/SKILL.md +274 -274
- package/template/.aioson/skills/design-system/dashboards/SKILL.md +184 -184
- package/template/.aioson/skills/design-system/foundations/SKILL.md +250 -250
- package/template/.aioson/skills/design-system/motion/SKILL.md +197 -197
- package/template/.aioson/skills/design-system/patterns/SKILL.md +231 -231
- package/template/.aioson/skills/dynamic/README.md +30 -30
- package/template/.aioson/skills/dynamic/cardano-docs.md +16 -16
- package/template/.aioson/skills/dynamic/ethereum-docs.md +17 -17
- package/template/.aioson/skills/dynamic/flux-ui-docs.md +13 -13
- package/template/.aioson/skills/dynamic/laravel-docs.md +41 -41
- package/template/.aioson/skills/dynamic/npm-packages.md +16 -16
- package/template/.aioson/skills/dynamic/solana-docs.md +16 -16
- package/template/.aioson/skills/marketing/references/anti-patterns.md +254 -254
- package/template/.aioson/skills/marketing/references/cta-matrix.md +361 -0
- package/template/.aioson/skills/marketing/references/fascinations.md +192 -192
- package/template/.aioson/skills/marketing/references/five-acts.md +248 -248
- package/template/.aioson/skills/marketing/references/headline-matrix.md +358 -0
- package/template/.aioson/skills/marketing/references/market-intelligence.md +198 -198
- package/template/.aioson/skills/marketing/references/offer-structure.md +203 -203
- package/template/.aioson/skills/marketing/references/one-belief.md +149 -149
- package/template/.aioson/skills/marketing/references/patterns.md +218 -218
- package/template/.aioson/skills/marketing/references/platform-constraints.md +337 -0
- package/template/.aioson/skills/marketing/references/pms-research.md +193 -193
- package/template/.aioson/skills/marketing/vsl-craft.md +385 -385
- package/template/.aioson/skills/premium-visual-design/SKILL.md +83 -83
- package/template/.aioson/skills/premium-visual-design/components/agent-badge.md +92 -92
- package/template/.aioson/skills/premium-visual-design/components/dependency-node.md +102 -102
- package/template/.aioson/skills/premium-visual-design/components/mention-autocomplete.md +136 -136
- package/template/.aioson/skills/premium-visual-design/components/notification-center.md +136 -136
- package/template/.aioson/skills/premium-visual-design/components/review-action-bar.md +188 -188
- package/template/.aioson/skills/premium-visual-design/components/team-switcher.md +131 -131
- package/template/.aioson/skills/premium-visual-design/patterns/agent-message-thread.md +198 -198
- package/template/.aioson/skills/premium-visual-design/patterns/notification-panel.md +275 -275
- package/template/.aioson/skills/premium-visual-design/patterns/review-workflow-ui.md +234 -234
- package/template/.aioson/skills/premium-visual-design/patterns/task-dependency-graph.md +147 -147
- package/template/.aioson/skills/premium-visual-design/tokens/status-extended.md +142 -142
- package/template/.aioson/skills/process/aioson-spec-driven/SKILL.md +46 -46
- package/template/.aioson/skills/process/aioson-spec-driven/references/analyst.md +30 -30
- package/template/.aioson/skills/process/aioson-spec-driven/references/approval-gates.md +109 -109
- package/template/.aioson/skills/process/aioson-spec-driven/references/architect.md +23 -23
- package/template/.aioson/skills/process/aioson-spec-driven/references/artifact-map.md +44 -44
- package/template/.aioson/skills/process/aioson-spec-driven/references/classification-map.md +37 -37
- package/template/.aioson/skills/process/aioson-spec-driven/references/dev.md +47 -47
- package/template/.aioson/skills/process/aioson-spec-driven/references/deyvin.md +27 -27
- package/template/.aioson/skills/process/aioson-spec-driven/references/hardening-lane.md +49 -49
- package/template/.aioson/skills/process/aioson-spec-driven/references/maintenance-and-state.md +101 -101
- package/template/.aioson/skills/process/aioson-spec-driven/references/pm.md +30 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/product.md +25 -25
- package/template/.aioson/skills/process/aioson-spec-driven/references/qa.md +30 -30
- package/template/.aioson/skills/process/aioson-spec-driven/references/sheldon.md +25 -25
- package/template/.aioson/skills/process/aioson-spec-driven/references/ui-language.md +75 -75
- package/template/.aioson/skills/process/design-hybrid-forge/SKILL.md +147 -147
- package/template/.aioson/skills/process/design-hybrid-forge/references/crossover-protocol.md +221 -221
- package/template/.aioson/skills/process/design-hybrid-forge/references/naming-registry.md +88 -88
- package/template/.aioson/skills/process/design-hybrid-forge/references/output-contract.md +306 -306
- package/template/.aioson/skills/process/design-hybrid-forge/references/pair-compatibility.md +149 -149
- package/template/.aioson/skills/process/design-hybrid-forge/references/quality-gates.md +208 -208
- package/template/.aioson/skills/process/design-hybrid-forge/references/variation-library.md +125 -125
- package/template/.aioson/skills/process/secure-tdd/SKILL.md +97 -0
- package/template/.aioson/skills/process/simplify/SKILL.md +173 -173
- package/template/.aioson/skills/references/premium-command-center-ui/master-application-prompt.md +79 -79
- package/template/.aioson/skills/references/premium-command-center-ui/operational-ux-playbook.md +253 -253
- package/template/.aioson/skills/references/premium-command-center-ui/quality-validation-checklist.md +82 -82
- package/template/.aioson/skills/references/premium-command-center-ui/visual-system-and-component-patterns.md +270 -270
- package/template/.aioson/skills/squad/SKILL.md +58 -58
- package/template/.aioson/skills/squad/formats/catalog.json +15 -15
- package/template/.aioson/skills/squad/formats/content/blog-post.md +47 -47
- package/template/.aioson/skills/squad/formats/content/newsletter.md +47 -47
- package/template/.aioson/skills/squad/formats/creative/podcast-script.md +43 -43
- package/template/.aioson/skills/squad/formats/creative/video-script.md +41 -41
- package/template/.aioson/skills/squad/formats/social/instagram-feed.md +42 -42
- package/template/.aioson/skills/squad/formats/social/linkedin-post.md +42 -42
- package/template/.aioson/skills/squad/formats/social/tiktok.md +39 -39
- package/template/.aioson/skills/squad/formats/social/twitter-thread.md +39 -39
- package/template/.aioson/skills/squad/formats/social/youtube-long.md +47 -47
- package/template/.aioson/skills/squad/formats/social/youtube-shorts.md +39 -39
- package/template/.aioson/skills/squad/patterns/multi-platform-pattern.md +108 -108
- package/template/.aioson/skills/squad/patterns/persona-based-pattern.md +98 -98
- package/template/.aioson/skills/squad/patterns/pipeline-pattern.md +106 -106
- package/template/.aioson/skills/squad/patterns/review-loop-pattern.md +81 -81
- package/template/.aioson/skills/squad/references/checklist-templates.md +122 -122
- package/template/.aioson/skills/squad/references/executor-archetypes.md +123 -123
- package/template/.aioson/skills/squad/references/workflow-templates.md +169 -169
- package/template/.aioson/skills/static/context-budget-guide.md +46 -46
- package/template/.aioson/skills/static/debugging-protocol.md +42 -42
- package/template/.aioson/skills/static/django-patterns.md +342 -342
- package/template/.aioson/skills/static/fastapi-patterns.md +344 -344
- package/template/.aioson/skills/static/filament-patterns.md +267 -267
- package/template/.aioson/skills/static/flux-ui-components.md +262 -262
- package/template/.aioson/skills/static/git-conventions.md +227 -227
- package/template/.aioson/skills/static/git-worktrees.md +36 -36
- package/template/.aioson/skills/static/harness-sensors.md +74 -74
- package/template/.aioson/skills/static/harness-validate/SKILL.md +46 -0
- package/template/.aioson/skills/static/jetstream-setup.md +200 -200
- package/template/.aioson/skills/static/landing-page-deploy.md +192 -192
- package/template/.aioson/skills/static/landing-page-forge.md +730 -730
- package/template/.aioson/skills/static/laravel-conventions.md +491 -491
- package/template/.aioson/skills/static/multi-agent-patterns.md +43 -43
- package/template/.aioson/skills/static/nextjs-patterns.md +321 -321
- package/template/.aioson/skills/static/node-express-patterns.md +317 -317
- package/template/.aioson/skills/static/node-typescript-patterns.md +282 -282
- package/template/.aioson/skills/static/rails-conventions.md +307 -307
- package/template/.aioson/skills/static/react-motion-patterns.md +599 -599
- package/template/.aioson/skills/static/static-html-patterns/checklists.md +43 -43
- package/template/.aioson/skills/static/static-html-patterns/css-tokens.md +609 -609
- package/template/.aioson/skills/static/static-html-patterns/motion.md +193 -193
- package/template/.aioson/skills/static/static-html-patterns/premium.md +711 -711
- package/template/.aioson/skills/static/static-html-patterns/structure.md +209 -209
- package/template/.aioson/skills/static/static-html-patterns/utilities.md +190 -190
- package/template/.aioson/skills/static/static-html-patterns.md +80 -80
- package/template/.aioson/skills/static/tall-stack-patterns.md +286 -286
- package/template/.aioson/skills/static/threejs-patterns.md +929 -929
- package/template/.aioson/skills/static/ui-ux-modern.md +76 -76
- package/template/.aioson/skills/static/web-research-cache.md +115 -112
- package/template/.aioson/skills/static/web3-cardano-patterns.md +337 -337
- package/template/.aioson/skills/static/web3-ethereum-patterns.md +310 -310
- package/template/.aioson/skills/static/web3-security-checklist.md +284 -284
- package/template/.aioson/skills/static/web3-solana-patterns.md +324 -324
- package/template/.aioson/squads/memory.md +5 -5
- package/template/.aioson/tasks/implementation-plan.md +327 -327
- package/template/.aioson/tasks/squad-analyze.md +83 -83
- package/template/.aioson/tasks/squad-create.md +148 -121
- package/template/.aioson/tasks/squad-design.md +206 -158
- package/template/.aioson/tasks/squad-execution-plan.md +279 -279
- package/template/.aioson/tasks/squad-export.md +20 -20
- package/template/.aioson/tasks/squad-extend.md +68 -68
- package/template/.aioson/tasks/squad-investigate.md +57 -44
- package/template/.aioson/tasks/squad-learning-review.md +44 -44
- package/template/.aioson/tasks/squad-output-config.md +177 -177
- package/template/.aioson/tasks/squad-pipeline.md +122 -122
- package/template/.aioson/tasks/squad-profile.md +48 -48
- package/template/.aioson/tasks/squad-refresh.md +236 -0
- package/template/.aioson/tasks/squad-repair.md +85 -85
- package/template/.aioson/tasks/squad-review.md +61 -61
- package/template/.aioson/tasks/squad-task-decompose.md +66 -66
- package/template/.aioson/tasks/squad-validate.md +58 -58
- package/template/.aioson/templates/reflect-prompts/current-state.md +36 -0
- package/template/.aioson/templates/reflect-prompts/how-it-works.md +23 -0
- package/template/.aioson/templates/reflect-prompts/what-it-does.md +21 -0
- package/template/.aioson/templates/squads/content-basic/template.json +21 -21
- package/template/.aioson/templates/squads/digital-marketing-agency/template.json +96 -96
- package/template/.aioson/templates/squads/media-channel/template.json +24 -24
- package/template/.aioson/templates/squads/research-analysis/template.json +22 -22
- package/template/.aioson/templates/squads/software-delivery/template.json +21 -21
- package/template/.claude/commands/aioson/agent/analyst.md +5 -5
- package/template/.claude/commands/aioson/agent/architect.md +5 -5
- package/template/.claude/commands/aioson/agent/briefing.md +5 -0
- package/template/.claude/commands/aioson/agent/committer.md +5 -0
- package/template/.claude/commands/aioson/agent/copywriter.md +5 -0
- package/template/.claude/commands/aioson/agent/design-hybrid-forge.md +5 -5
- package/template/.claude/commands/aioson/agent/dev.md +5 -5
- package/template/.claude/commands/aioson/agent/deyvin.md +5 -5
- package/template/.claude/commands/aioson/agent/discover.md +5 -0
- package/template/.claude/commands/aioson/agent/discovery-design-doc.md +5 -5
- package/template/.claude/commands/aioson/agent/genome.md +5 -5
- package/template/.claude/commands/aioson/agent/neo.md +5 -5
- package/template/.claude/commands/aioson/agent/orache.md +5 -5
- package/template/.claude/commands/aioson/agent/orchestrator.md +5 -5
- package/template/.claude/commands/aioson/agent/pair.md +5 -0
- package/template/.claude/commands/aioson/agent/pentester.md +5 -0
- package/template/.claude/commands/aioson/agent/pm.md +5 -5
- package/template/.claude/commands/aioson/agent/product.md +5 -5
- package/template/.claude/commands/aioson/agent/profiler-enricher.md +5 -5
- package/template/.claude/commands/aioson/agent/profiler-forge.md +5 -5
- package/template/.claude/commands/aioson/agent/profiler-researcher.md +5 -5
- package/template/.claude/commands/aioson/agent/qa.md +5 -5
- package/template/.claude/commands/aioson/agent/setup.md +5 -5
- package/template/.claude/commands/aioson/agent/sheldon.md +5 -5
- package/template/.claude/commands/aioson/agent/site-forge.md +5 -5
- package/template/.claude/commands/aioson/agent/squad.md +5 -5
- package/template/.claude/commands/aioson/agent/tester.md +5 -5
- package/template/.claude/commands/aioson/agent/ux-ui.md +5 -5
- package/template/.claude/commands/aioson/agent/validator.md +5 -0
- package/template/.gemini/GEMINI.md +13 -13
- package/template/.gemini/commands/aios-analyst.toml +7 -4
- package/template/.gemini/commands/aios-architect.toml +8 -7
- package/template/.gemini/commands/aios-committer.toml +7 -0
- package/template/.gemini/commands/aios-copywriter.toml +7 -0
- package/template/.gemini/commands/aios-cypher.toml +7 -0
- package/template/.gemini/commands/aios-dev.toml +9 -8
- package/template/.gemini/commands/aios-deyvin.toml +7 -6
- package/template/.gemini/commands/aios-discover.toml +6 -0
- package/template/.gemini/commands/aios-discovery-design-doc.toml +7 -4
- package/template/.gemini/commands/aios-genome.toml +7 -0
- package/template/.gemini/commands/aios-neo.toml +6 -4
- package/template/.gemini/commands/aios-orache.toml +7 -0
- package/template/.gemini/commands/aios-orchestrator.toml +9 -8
- package/template/.gemini/commands/aios-pair.toml +7 -6
- package/template/.gemini/commands/aios-pm.toml +9 -8
- package/template/.gemini/commands/aios-product.toml +6 -4
- package/template/.gemini/commands/aios-qa.toml +7 -6
- package/template/.gemini/commands/aios-setup.toml +6 -3
- package/template/.gemini/commands/aios-sheldon.toml +7 -0
- package/template/.gemini/commands/aios-site-forge.toml +7 -0
- package/template/.gemini/commands/aios-squad.toml +7 -0
- package/template/.gemini/commands/aios-tester.toml +7 -6
- package/template/.gemini/commands/aios-ux-ui.toml +9 -8
- package/template/.gemini/commands/aios-validator.toml +7 -0
- package/template/AGENTS.md +184 -172
- package/template/CLAUDE.md +98 -93
- package/template/OPENCODE.md +35 -34
- package/template/aioson-models.json +40 -40
- package/docs/en/i18n.md +0 -52
- package/docs/en/schemas/parallel-status.schema.json +0 -94
- package/template/.aioson/genomes/copywriting.md +0 -204
- package/template/.aioson/locales/en/agents/analyst.md +0 -244
- package/template/.aioson/locales/en/agents/architect.md +0 -245
- package/template/.aioson/locales/en/agents/dev.md +0 -397
- package/template/.aioson/locales/en/agents/deyvin.md +0 -137
- package/template/.aioson/locales/en/agents/discovery-design-doc.md +0 -27
- package/template/.aioson/locales/en/agents/genome.md +0 -212
- package/template/.aioson/locales/en/agents/neo.md +0 -8
- package/template/.aioson/locales/en/agents/orache.md +0 -6
- package/template/.aioson/locales/en/agents/orchestrator.md +0 -189
- package/template/.aioson/locales/en/agents/pair.md +0 -5
- package/template/.aioson/locales/en/agents/pm.md +0 -84
- package/template/.aioson/locales/en/agents/product.md +0 -378
- package/template/.aioson/locales/en/agents/profiler-enricher.md +0 -5
- package/template/.aioson/locales/en/agents/profiler-forge.md +0 -5
- package/template/.aioson/locales/en/agents/profiler-researcher.md +0 -5
- package/template/.aioson/locales/en/agents/qa.md +0 -270
- package/template/.aioson/locales/en/agents/setup.md +0 -421
- package/template/.aioson/locales/en/agents/sheldon.md +0 -455
- package/template/.aioson/locales/en/agents/squad.md +0 -449
- package/template/.aioson/locales/en/agents/tester.md +0 -6
- package/template/.aioson/locales/en/agents/ux-ui.md +0 -668
- package/template/.aioson/locales/es/agents/analyst.md +0 -225
- package/template/.aioson/locales/es/agents/architect.md +0 -245
- package/template/.aioson/locales/es/agents/dev.md +0 -370
- package/template/.aioson/locales/es/agents/deyvin.md +0 -99
- package/template/.aioson/locales/es/agents/discovery-design-doc.md +0 -21
- package/template/.aioson/locales/es/agents/genome.md +0 -104
- package/template/.aioson/locales/es/agents/neo.md +0 -50
- package/template/.aioson/locales/es/agents/orache.md +0 -105
- package/template/.aioson/locales/es/agents/orchestrator.md +0 -194
- package/template/.aioson/locales/es/agents/pair.md +0 -7
- package/template/.aioson/locales/es/agents/pm.md +0 -90
- package/template/.aioson/locales/es/agents/product.md +0 -372
- package/template/.aioson/locales/es/agents/profiler-enricher.md +0 -7
- package/template/.aioson/locales/es/agents/profiler-forge.md +0 -7
- package/template/.aioson/locales/es/agents/profiler-researcher.md +0 -7
- package/template/.aioson/locales/es/agents/qa.md +0 -198
- package/template/.aioson/locales/es/agents/setup.md +0 -405
- package/template/.aioson/locales/es/agents/sheldon.md +0 -309
- package/template/.aioson/locales/es/agents/squad.md +0 -532
- package/template/.aioson/locales/es/agents/tester.md +0 -9
- package/template/.aioson/locales/es/agents/ux-ui.md +0 -212
- package/template/.aioson/locales/fr/agents/analyst.md +0 -225
- package/template/.aioson/locales/fr/agents/architect.md +0 -245
- package/template/.aioson/locales/fr/agents/dev.md +0 -370
- package/template/.aioson/locales/fr/agents/deyvin.md +0 -99
- package/template/.aioson/locales/fr/agents/discovery-design-doc.md +0 -21
- package/template/.aioson/locales/fr/agents/genome.md +0 -104
- package/template/.aioson/locales/fr/agents/neo.md +0 -50
- package/template/.aioson/locales/fr/agents/orache.md +0 -106
- package/template/.aioson/locales/fr/agents/orchestrator.md +0 -194
- package/template/.aioson/locales/fr/agents/pair.md +0 -7
- package/template/.aioson/locales/fr/agents/pm.md +0 -90
- package/template/.aioson/locales/fr/agents/product.md +0 -372
- package/template/.aioson/locales/fr/agents/profiler-enricher.md +0 -7
- package/template/.aioson/locales/fr/agents/profiler-forge.md +0 -7
- package/template/.aioson/locales/fr/agents/profiler-researcher.md +0 -7
- package/template/.aioson/locales/fr/agents/qa.md +0 -198
- package/template/.aioson/locales/fr/agents/setup.md +0 -405
- package/template/.aioson/locales/fr/agents/sheldon.md +0 -309
- package/template/.aioson/locales/fr/agents/squad.md +0 -532
- package/template/.aioson/locales/fr/agents/tester.md +0 -9
- package/template/.aioson/locales/fr/agents/ux-ui.md +0 -212
- package/template/.aioson/locales/pt-BR/agents/analyst.md +0 -319
- package/template/.aioson/locales/pt-BR/agents/architect.md +0 -284
- package/template/.aioson/locales/pt-BR/agents/dev.md +0 -483
- package/template/.aioson/locales/pt-BR/agents/deyvin.md +0 -184
- package/template/.aioson/locales/pt-BR/agents/discovery-design-doc.md +0 -198
- package/template/.aioson/locales/pt-BR/agents/genome.md +0 -297
- package/template/.aioson/locales/pt-BR/agents/neo.md +0 -208
- package/template/.aioson/locales/pt-BR/agents/orache.md +0 -137
- package/template/.aioson/locales/pt-BR/agents/orchestrator.md +0 -324
- package/template/.aioson/locales/pt-BR/agents/pair.md +0 -5
- package/template/.aioson/locales/pt-BR/agents/pm.md +0 -182
- package/template/.aioson/locales/pt-BR/agents/product.md +0 -466
- package/template/.aioson/locales/pt-BR/agents/profiler-enricher.md +0 -5
- package/template/.aioson/locales/pt-BR/agents/profiler-forge.md +0 -5
- package/template/.aioson/locales/pt-BR/agents/profiler-researcher.md +0 -5
- package/template/.aioson/locales/pt-BR/agents/qa.md +0 -300
- package/template/.aioson/locales/pt-BR/agents/setup.md +0 -533
- package/template/.aioson/locales/pt-BR/agents/sheldon.md +0 -323
- package/template/.aioson/locales/pt-BR/agents/squad.md +0 -1330
- package/template/.aioson/locales/pt-BR/agents/tester.md +0 -449
- package/template/.aioson/locales/pt-BR/agents/ux-ui.md +0 -669
|
@@ -1,1371 +1,1371 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* scan:project — Brownfield project scanner
|
|
5
|
-
*
|
|
6
|
-
* Walks the project directory, reads key files, calls a cheap LLM to generate:
|
|
7
|
-
* - .aioson/context/discovery.md
|
|
8
|
-
* - .aioson/context/skeleton-system.md
|
|
9
|
-
*
|
|
10
|
-
* Config: aioson-models.json in the target project root.
|
|
11
|
-
* Zero npm dependencies — uses node:fs, node:https, node:http only.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
const path = require('node:path');
|
|
15
|
-
const fs = require('node:fs/promises');
|
|
16
|
-
const https = require('node:https');
|
|
17
|
-
const http = require('node:http');
|
|
18
|
-
const { ensureDir, exists, copyFileWithDir, nowStamp, toRelativeSafe } = require('../utils');
|
|
19
|
-
const { ensureGitignoreEntry, ensureProjectGitignorePolicy } = require('../installer');
|
|
20
|
-
const {
|
|
21
|
-
MEMORY_INDEX_FILE,
|
|
22
|
-
SPEC_CURRENT_FILE,
|
|
23
|
-
SPEC_HISTORY_FILE,
|
|
24
|
-
writeDerivedContextMemory
|
|
25
|
-
} = require('../context-memory');
|
|
26
|
-
|
|
27
|
-
// ── Constants ────────────────────────────────────────────────────────────────
|
|
28
|
-
|
|
29
|
-
const CONFIG_FILE = 'aioson-models.json';
|
|
30
|
-
const OUTPUT_FILE = '.aioson/context/discovery.md';
|
|
31
|
-
const SKELETON_FILE = '.aioson/context/skeleton-system.md';
|
|
32
|
-
const INDEX_FILE = '.aioson/context/scan-index.md';
|
|
33
|
-
const FOLDERS_FILE = '.aioson/context/scan-folders.md';
|
|
34
|
-
const FORGE_FILE = '.aioson/context/scan-aioson.md';
|
|
35
|
-
const CONTEXT_FILE = '.aioson/context/project.context.md';
|
|
36
|
-
const SPEC_FILE = '.aioson/context/spec.md';
|
|
37
|
-
const DELIMITER = '<<<SKELETON>>>';
|
|
38
|
-
const SUMMARY_MODES = new Set(['titles', 'summaries', 'raw']);
|
|
39
|
-
const CONTEXT_MODES = new Set(['merge', 'rewrite']);
|
|
40
|
-
const FORGE_SCAN_ROOTS = [
|
|
41
|
-
'.aioson/context',
|
|
42
|
-
'.aioson/squads',
|
|
43
|
-
'.aioson/genomes',
|
|
44
|
-
'.aioson/mcp'
|
|
45
|
-
];
|
|
46
|
-
const FORGE_SECTION_ROOTS = [
|
|
47
|
-
{
|
|
48
|
-
root: '.aioson/context',
|
|
49
|
-
title: 'Context Pages',
|
|
50
|
-
empty: '_No generated context pages detected yet_'
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
root: '.aioson/squads',
|
|
54
|
-
title: 'Squads',
|
|
55
|
-
empty: '_No squads detected yet_'
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
root: '.aioson/genomes',
|
|
59
|
-
title: 'Genomes',
|
|
60
|
-
empty: '_No genomes detected yet_'
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
root: '.aioson/mcp',
|
|
64
|
-
title: 'MCP',
|
|
65
|
-
empty: '_No project-specific MCP artifacts detected yet_'
|
|
66
|
-
}
|
|
67
|
-
];
|
|
68
|
-
const FORGE_SKIP_GENERATED_FILES = new Set([
|
|
69
|
-
'.aioson/context/.gitkeep',
|
|
70
|
-
'.aioson/context/spec.md.template',
|
|
71
|
-
'.aioson/install.json'
|
|
72
|
-
]);
|
|
73
|
-
const BACKUPS_GITIGNORE_ENTRY = '.aioson/backups/';
|
|
74
|
-
|
|
75
|
-
const SKIP_DIRS = new Set([
|
|
76
|
-
'.git', 'node_modules', 'vendor', '.next', 'dist', 'build',
|
|
77
|
-
'__pycache__', '.cache', 'coverage', '.nyc_output', 'target',
|
|
78
|
-
'.gradle', 'venv', '.venv', 'env', '.env', 'storage',
|
|
79
|
-
'bootstrap/cache', '.idea', '.vscode', 'tmp', 'temp', 'logs',
|
|
80
|
-
'public/build', 'public/hot', '.aioson/backups',
|
|
81
|
-
]);
|
|
82
|
-
|
|
83
|
-
const SKIP_EXTENSIONS = new Set([
|
|
84
|
-
'.lock', '.log', '.map', '.min.js', '.min.css',
|
|
85
|
-
'.jpg', '.jpeg', '.png', '.gif', '.svg', '.ico', '.webp',
|
|
86
|
-
'.woff', '.woff2', '.ttf', '.eot', '.otf',
|
|
87
|
-
'.mp4', '.mp3', '.wav', '.avi',
|
|
88
|
-
'.zip', '.tar', '.gz', '.rar', '.7z',
|
|
89
|
-
'.pdf', '.doc', '.docx', '.xls', '.xlsx',
|
|
90
|
-
'.pyc', '.pyo', '.class', '.o', '.a', '.so',
|
|
91
|
-
'.sqlite', '.db', '.sqlite3',
|
|
92
|
-
]);
|
|
93
|
-
|
|
94
|
-
const KEY_FILE_NAMES = new Set([
|
|
95
|
-
'package.json', 'composer.json', 'requirements.txt', 'pyproject.toml',
|
|
96
|
-
'Gemfile', 'go.mod', 'Cargo.toml', 'pom.xml', 'build.gradle',
|
|
97
|
-
'docker-compose.yml', 'docker-compose.yaml', 'Dockerfile',
|
|
98
|
-
'.env.example', '.env.sample', 'README.md',
|
|
99
|
-
'schema.prisma', 'schema.rb', 'routes.rb',
|
|
100
|
-
'tsconfig.json', 'next.config.js', 'next.config.ts',
|
|
101
|
-
'vite.config.js', 'vite.config.ts',
|
|
102
|
-
'tailwind.config.js', 'tailwind.config.ts',
|
|
103
|
-
'webpack.config.js',
|
|
104
|
-
]);
|
|
105
|
-
|
|
106
|
-
const KEY_FILE_PATHS = new Set([
|
|
107
|
-
'prisma/schema.prisma',
|
|
108
|
-
'database/schema.rb',
|
|
109
|
-
'config/routes.rb',
|
|
110
|
-
'routes/web.php',
|
|
111
|
-
'routes/api.php',
|
|
112
|
-
'config/app.php',
|
|
113
|
-
'app/Http/Kernel.php',
|
|
114
|
-
'app/Providers/RouteServiceProvider.php',
|
|
115
|
-
]);
|
|
116
|
-
|
|
117
|
-
const MAX_KEY_FILE_CHARS = 3000;
|
|
118
|
-
|
|
119
|
-
const PROVIDER_BASE_URLS = {
|
|
120
|
-
deepseek: 'https://api.deepseek.com/v1',
|
|
121
|
-
openai: 'https://api.openai.com/v1',
|
|
122
|
-
gemini: 'https://generativelanguage.googleapis.com/v1beta/openai',
|
|
123
|
-
groq: 'https://api.groq.com/openai/v1',
|
|
124
|
-
together: 'https://api.together.xyz/v1',
|
|
125
|
-
mistral: 'https://api.mistral.ai/v1',
|
|
126
|
-
anthropic: null, // uses its own format
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
let managedForgePathCache = null;
|
|
130
|
-
|
|
131
|
-
// ── File system helpers ──────────────────────────────────────────────────────
|
|
132
|
-
|
|
133
|
-
async function readFileSafe(filePath, maxChars) {
|
|
134
|
-
try {
|
|
135
|
-
const content = await fs.readFile(filePath, 'utf8');
|
|
136
|
-
if (maxChars && content.length > maxChars) {
|
|
137
|
-
return content.slice(0, maxChars) + `\n... [truncated at ${maxChars} chars]`;
|
|
138
|
-
}
|
|
139
|
-
return content;
|
|
140
|
-
} catch {
|
|
141
|
-
return null;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
async function backupProjectFiles(targetDir, relPaths) {
|
|
146
|
-
const uniqueRelPaths = [...new Set(relPaths.filter(Boolean))];
|
|
147
|
-
if (uniqueRelPaths.length === 0) {
|
|
148
|
-
return { backupRoot: null, backedUp: [] };
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
const backupRoot = path.join(targetDir, '.aioson/backups', nowStamp());
|
|
152
|
-
const backedUp = [];
|
|
153
|
-
|
|
154
|
-
for (const relPath of uniqueRelPaths) {
|
|
155
|
-
const source = path.join(targetDir, relPath);
|
|
156
|
-
if (!(await exists(source))) continue;
|
|
157
|
-
const dest = path.join(backupRoot, relPath);
|
|
158
|
-
await copyFileWithDir(source, dest);
|
|
159
|
-
backedUp.push(toRelativeSafe(targetDir, dest));
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
if (backedUp.length === 0) {
|
|
163
|
-
return { backupRoot: null, backedUp: [] };
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
return { backupRoot, backedUp };
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
async function loadGitignorePatterns(root) {
|
|
170
|
-
const patterns = new Set();
|
|
171
|
-
try {
|
|
172
|
-
const gi = await fs.readFile(path.join(root, '.gitignore'), 'utf8');
|
|
173
|
-
for (const line of gi.split('\n')) {
|
|
174
|
-
const clean = line.trim().replace(/^\//, '').replace(/\/$/, '');
|
|
175
|
-
if (clean && !clean.startsWith('#')) patterns.add(clean);
|
|
176
|
-
}
|
|
177
|
-
} catch { /* no .gitignore */ }
|
|
178
|
-
return patterns;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
function shouldSkip(relPath, ext, gitignorePatterns) {
|
|
182
|
-
const parts = relPath.split('/');
|
|
183
|
-
for (const part of parts) {
|
|
184
|
-
if (SKIP_DIRS.has(part)) return true;
|
|
185
|
-
if (gitignorePatterns.has(part)) return true;
|
|
186
|
-
}
|
|
187
|
-
if (SKIP_EXTENSIONS.has(ext)) return true;
|
|
188
|
-
return false;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
async function walkRelativeFiles(rootDir, prefix = '') {
|
|
192
|
-
const out = [];
|
|
193
|
-
let entries;
|
|
194
|
-
try {
|
|
195
|
-
entries = await fs.readdir(rootDir, { withFileTypes: true });
|
|
196
|
-
} catch {
|
|
197
|
-
return out;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
for (const entry of entries) {
|
|
201
|
-
const fullPath = path.join(rootDir, entry.name);
|
|
202
|
-
const relPath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
203
|
-
if (entry.isDirectory()) {
|
|
204
|
-
out.push(...await walkRelativeFiles(fullPath, relPath));
|
|
205
|
-
} else {
|
|
206
|
-
out.push(relPath.replace(/\\/g, '/'));
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
return out;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
async function loadManagedForgePaths() {
|
|
213
|
-
if (managedForgePathCache) return managedForgePathCache;
|
|
214
|
-
|
|
215
|
-
const templateForgeDir = path.join(__dirname, '..', '..', 'template', '.aioson');
|
|
216
|
-
const relPaths = await walkRelativeFiles(templateForgeDir);
|
|
217
|
-
managedForgePathCache = new Set(relPaths.map((relPath) => `.aioson/${relPath}`));
|
|
218
|
-
return managedForgePathCache;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
async function walkProject(root) {
|
|
222
|
-
const gitignore = await loadGitignorePatterns(root);
|
|
223
|
-
const keyContents = {};
|
|
224
|
-
const keyFiles = [];
|
|
225
|
-
const topLevelStats = new Map();
|
|
226
|
-
const mappedEntries = [];
|
|
227
|
-
|
|
228
|
-
async function walk(dir, depth) {
|
|
229
|
-
let entries;
|
|
230
|
-
try {
|
|
231
|
-
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
232
|
-
} catch { return; }
|
|
233
|
-
|
|
234
|
-
// dirs first (alphabetical), then files (alphabetical)
|
|
235
|
-
entries.sort((a, b) => {
|
|
236
|
-
if (a.isDirectory() !== b.isDirectory()) return a.isDirectory() ? -1 : 1;
|
|
237
|
-
return a.name.localeCompare(b.name);
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
for (const entry of entries) {
|
|
241
|
-
const fullPath = path.join(dir, entry.name);
|
|
242
|
-
const relPath = path.relative(root, fullPath).replace(/\\/g, '/');
|
|
243
|
-
const ext = path.extname(entry.name).toLowerCase();
|
|
244
|
-
const indent = ' '.repeat(depth);
|
|
245
|
-
|
|
246
|
-
if (shouldSkip(relPath, ext, gitignore)) continue;
|
|
247
|
-
|
|
248
|
-
if (entry.isDirectory()) {
|
|
249
|
-
mappedEntries.push({ type: 'dir', relPath, depth, sizeBytes: 0 });
|
|
250
|
-
await walk(fullPath, depth + 1);
|
|
251
|
-
} else {
|
|
252
|
-
let sizeBytes = 0;
|
|
253
|
-
try {
|
|
254
|
-
const stat = await fs.stat(fullPath);
|
|
255
|
-
sizeBytes = Number(stat.size || 0);
|
|
256
|
-
} catch {}
|
|
257
|
-
|
|
258
|
-
mappedEntries.push({ type: 'file', relPath, depth, sizeBytes });
|
|
259
|
-
|
|
260
|
-
const parts = relPath.split('/');
|
|
261
|
-
const topLevel = parts.length > 1 ? parts[0] : '[root files]';
|
|
262
|
-
const currentStat = topLevelStats.get(topLevel) || { files: 0, sizeBytes: 0 };
|
|
263
|
-
currentStat.files += 1;
|
|
264
|
-
currentStat.sizeBytes += sizeBytes;
|
|
265
|
-
topLevelStats.set(topLevel, currentStat);
|
|
266
|
-
|
|
267
|
-
const isKeyName = KEY_FILE_NAMES.has(entry.name);
|
|
268
|
-
const isKeyPath = KEY_FILE_PATHS.has(relPath) || [...KEY_FILE_PATHS].some((p) => relPath.endsWith(p));
|
|
269
|
-
if ((isKeyName || isKeyPath) && !(relPath in keyContents)) {
|
|
270
|
-
const content = await readFileSafe(fullPath, MAX_KEY_FILE_CHARS);
|
|
271
|
-
if (content) {
|
|
272
|
-
keyContents[relPath] = content;
|
|
273
|
-
keyFiles.push({
|
|
274
|
-
path: relPath,
|
|
275
|
-
sizeBytes,
|
|
276
|
-
title: inferKeyFileTitle(relPath, content),
|
|
277
|
-
summary: inferKeyFileSummary(relPath, content)
|
|
278
|
-
});
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
await walk(root, 0);
|
|
286
|
-
return { keyContents, keyFiles, topLevelStats, entries: mappedEntries };
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
// ── HTTP helper (zero external deps) ────────────────────────────────────────
|
|
290
|
-
|
|
291
|
-
function httpPost(url, headers, body) {
|
|
292
|
-
return new Promise((resolve, reject) => {
|
|
293
|
-
const parsed = new URL(url);
|
|
294
|
-
const lib = parsed.protocol === 'https:' ? https : http;
|
|
295
|
-
const data = Buffer.from(JSON.stringify(body), 'utf8');
|
|
296
|
-
|
|
297
|
-
const req = lib.request({
|
|
298
|
-
hostname: parsed.hostname,
|
|
299
|
-
port: parsed.port || (parsed.protocol === 'https:' ? 443 : 80),
|
|
300
|
-
path: parsed.pathname + parsed.search,
|
|
301
|
-
method: 'POST',
|
|
302
|
-
headers: { 'Content-Type': 'application/json', 'Content-Length': data.length, ...headers },
|
|
303
|
-
}, (res) => {
|
|
304
|
-
const chunks = [];
|
|
305
|
-
res.on('data', (c) => chunks.push(c));
|
|
306
|
-
res.on('end', () => {
|
|
307
|
-
const text = Buffer.concat(chunks).toString('utf8');
|
|
308
|
-
if (res.statusCode >= 400) {
|
|
309
|
-
reject(new Error(`HTTP ${res.statusCode}: ${text.slice(0, 400)}`));
|
|
310
|
-
} else {
|
|
311
|
-
resolve(text);
|
|
312
|
-
}
|
|
313
|
-
});
|
|
314
|
-
});
|
|
315
|
-
|
|
316
|
-
req.setTimeout(180000, () => { req.destroy(new Error('Request timed out (180s)')); });
|
|
317
|
-
req.on('error', reject);
|
|
318
|
-
req.write(data);
|
|
319
|
-
req.end();
|
|
320
|
-
});
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
// ── LLM providers ───────────────────────────────────────────────────────────
|
|
324
|
-
|
|
325
|
-
async function callOpenAICompatible(baseUrl, apiKey, model, prompt) {
|
|
326
|
-
const url = `${baseUrl.replace(/\/$/, '')}/chat/completions`;
|
|
327
|
-
const baseBody = {
|
|
328
|
-
model,
|
|
329
|
-
messages: [{ role: 'user', content: prompt }],
|
|
330
|
-
temperature: 0.2
|
|
331
|
-
};
|
|
332
|
-
|
|
333
|
-
let text;
|
|
334
|
-
try {
|
|
335
|
-
text = await httpPost(
|
|
336
|
-
url,
|
|
337
|
-
{ Authorization: `Bearer ${apiKey}` },
|
|
338
|
-
{ ...baseBody, max_tokens: 4096 }
|
|
339
|
-
);
|
|
340
|
-
} catch (error) {
|
|
341
|
-
const message = String(error && error.message || '');
|
|
342
|
-
const requiresMaxCompletionTokens =
|
|
343
|
-
message.includes("Unsupported parameter: 'max_tokens'") &&
|
|
344
|
-
message.includes('max_completion_tokens');
|
|
345
|
-
|
|
346
|
-
if (!requiresMaxCompletionTokens) {
|
|
347
|
-
throw error;
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
text = await httpPost(
|
|
351
|
-
url,
|
|
352
|
-
{ Authorization: `Bearer ${apiKey}` },
|
|
353
|
-
{ ...baseBody, max_completion_tokens: 4096 }
|
|
354
|
-
);
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
const data = JSON.parse(text);
|
|
358
|
-
return data.choices[0].message.content;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
async function callAnthropic(apiKey, model, prompt) {
|
|
362
|
-
const text = await httpPost(
|
|
363
|
-
'https://api.anthropic.com/v1/messages',
|
|
364
|
-
{ 'x-api-key': apiKey, 'anthropic-version': '2023-06-01' },
|
|
365
|
-
{ model, max_tokens: 4096, messages: [{ role: 'user', content: prompt }] }
|
|
366
|
-
);
|
|
367
|
-
const data = JSON.parse(text);
|
|
368
|
-
return data.content[0].text;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
async function callLLM(providerName, providerCfg, prompt) {
|
|
372
|
-
const apiKey = providerCfg.api_key || '';
|
|
373
|
-
const model = providerCfg.model || '';
|
|
374
|
-
const baseUrl = providerCfg.base_url || PROVIDER_BASE_URLS[providerName] || '';
|
|
375
|
-
|
|
376
|
-
if (!apiKey || apiKey.startsWith('YOUR_')) {
|
|
377
|
-
const error = new Error(`API key not configured for provider '${providerName}'`);
|
|
378
|
-
error.code = 'MISSING_API_KEY';
|
|
379
|
-
throw error;
|
|
380
|
-
}
|
|
381
|
-
if (!model) {
|
|
382
|
-
throw new Error(`Model not configured for provider '${providerName}'`);
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
if (providerName === 'anthropic') return callAnthropic(apiKey, model, prompt);
|
|
386
|
-
if (!baseUrl) throw new Error(`No base_url for provider '${providerName}'`);
|
|
387
|
-
return callOpenAICompatible(baseUrl, apiKey, model, prompt);
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
// ── Prompt builder ───────────────────────────────────────────────────────────
|
|
391
|
-
|
|
392
|
-
function resolveSummaryMode(value) {
|
|
393
|
-
const normalized = String(value || '').trim().toLowerCase();
|
|
394
|
-
return SUMMARY_MODES.has(normalized) ? normalized : 'summaries';
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
function resolveContextMode(value) {
|
|
398
|
-
const normalized = String(value || '').trim().toLowerCase();
|
|
399
|
-
return CONTEXT_MODES.has(normalized) ? normalized : 'merge';
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
function formatBytesCompact(sizeBytes) {
|
|
403
|
-
const bytes = Number(sizeBytes || 0);
|
|
404
|
-
const kb = bytes / 1024;
|
|
405
|
-
const mb = kb / 1024;
|
|
406
|
-
if (mb >= 1) return `${mb.toFixed(mb >= 10 ? 1 : 2)} MB`;
|
|
407
|
-
return `${kb.toFixed(kb >= 10 ? 1 : 2)} KB`;
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
function humanizeName(value) {
|
|
411
|
-
return String(value || '')
|
|
412
|
-
.replace(/\.[^.]+$/, '')
|
|
413
|
-
.replace(/[_-]+/g, ' ')
|
|
414
|
-
.replace(/\b\w/g, (char) => char.toUpperCase());
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
function firstMeaningfulLine(content) {
|
|
418
|
-
const lines = String(content || '')
|
|
419
|
-
.split('\n')
|
|
420
|
-
.map((line) => line.trim())
|
|
421
|
-
.filter(Boolean);
|
|
422
|
-
|
|
423
|
-
for (const line of lines) {
|
|
424
|
-
if (line.startsWith('#')) return line.replace(/^#+\s*/, '').slice(0, 120);
|
|
425
|
-
if (line.startsWith('{') || line.startsWith('[') || line.startsWith('<') || line.startsWith('---')) continue;
|
|
426
|
-
if (line.length < 4) continue;
|
|
427
|
-
return line.slice(0, 120);
|
|
428
|
-
}
|
|
429
|
-
return '';
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
function inferFrameworkClues(pkg) {
|
|
433
|
-
const deps = {
|
|
434
|
-
...(pkg.dependencies || {}),
|
|
435
|
-
...(pkg.devDependencies || {})
|
|
436
|
-
};
|
|
437
|
-
const clues = [];
|
|
438
|
-
if (deps.next) clues.push('Next.js');
|
|
439
|
-
if (deps.react) clues.push('React');
|
|
440
|
-
if (deps.vue) clues.push('Vue');
|
|
441
|
-
if (deps.nuxt) clues.push('Nuxt');
|
|
442
|
-
if (deps.express) clues.push('Express');
|
|
443
|
-
if (deps.nestjs || deps['@nestjs/core']) clues.push('NestJS');
|
|
444
|
-
return clues;
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
function inferKeyFileTitle(relPath, content) {
|
|
448
|
-
const base = path.basename(relPath).toLowerCase();
|
|
449
|
-
|
|
450
|
-
if (base === 'package.json') {
|
|
451
|
-
try {
|
|
452
|
-
const pkg = JSON.parse(content);
|
|
453
|
-
if (pkg && pkg.name) return `${pkg.name} package manifest`;
|
|
454
|
-
} catch {}
|
|
455
|
-
return 'NPM package manifest';
|
|
456
|
-
}
|
|
457
|
-
if (base === 'composer.json') return 'Composer package manifest';
|
|
458
|
-
if (base === 'readme.md') {
|
|
459
|
-
const headline = firstMeaningfulLine(content);
|
|
460
|
-
return headline || 'Project overview';
|
|
461
|
-
}
|
|
462
|
-
if (base === 'dockerfile') return 'Container build recipe';
|
|
463
|
-
if (base.startsWith('next.config')) return 'Next.js runtime configuration';
|
|
464
|
-
if (base.startsWith('vite.config')) return 'Vite build configuration';
|
|
465
|
-
if (base.startsWith('tailwind.config')) return 'Tailwind theme configuration';
|
|
466
|
-
if (base === 'tsconfig.json') return 'TypeScript compiler configuration';
|
|
467
|
-
if (relPath.includes('routes/')) return `${humanizeName(base)} route map`;
|
|
468
|
-
if (relPath.includes('schema.prisma') || base === 'schema.rb') return 'Database schema definition';
|
|
469
|
-
return humanizeName(base || relPath);
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
function inferKeyFileSummary(relPath, content) {
|
|
473
|
-
const base = path.basename(relPath).toLowerCase();
|
|
474
|
-
|
|
475
|
-
if (base === 'package.json') {
|
|
476
|
-
try {
|
|
477
|
-
const pkg = JSON.parse(content);
|
|
478
|
-
const scripts = Object.keys(pkg.scripts || {}).length;
|
|
479
|
-
const depsCount = Object.keys(pkg.dependencies || {}).length + Object.keys(pkg.devDependencies || {}).length;
|
|
480
|
-
const clues = inferFrameworkClues(pkg);
|
|
481
|
-
const pieces = [`Scripts: ${scripts}`, `Dependencies: ${depsCount}`];
|
|
482
|
-
if (clues.length > 0) pieces.push(`Framework clues: ${clues.join(', ')}`);
|
|
483
|
-
return pieces.join(' | ');
|
|
484
|
-
} catch {
|
|
485
|
-
return 'Package metadata, scripts and dependency graph.';
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
if (base === 'composer.json') return 'PHP dependencies, autoload rules and package metadata.';
|
|
490
|
-
if (base === 'requirements.txt' || base === 'pyproject.toml') return 'Python dependencies and project metadata.';
|
|
491
|
-
if (base === 'readme.md') return firstMeaningfulLine(content) || 'Project overview, setup notes and developer guidance.';
|
|
492
|
-
if (base === 'dockerfile' || base.startsWith('docker-compose')) return 'Container runtime and service topology.';
|
|
493
|
-
if (base.startsWith('next.config')) return 'Next.js configuration for routing, build and runtime behavior.';
|
|
494
|
-
if (base.startsWith('vite.config')) return 'Bundler and development server configuration.';
|
|
495
|
-
if (base.startsWith('tailwind.config')) return 'Design tokens, theme extensions and content scan paths.';
|
|
496
|
-
if (base === 'tsconfig.json') return 'TypeScript path aliases, compiler options and module targets.';
|
|
497
|
-
if (relPath.includes('routes/')) return 'Entry points and HTTP route declarations.';
|
|
498
|
-
if (relPath.includes('schema.prisma') || base === 'schema.rb') return 'Entities, fields and relationship structure for the data model.';
|
|
499
|
-
if (base === '.env.example' || base === '.env.sample') return 'Environment variable template and required secrets.';
|
|
500
|
-
|
|
501
|
-
return firstMeaningfulLine(content) || `Key implementation or configuration file detected at ${relPath}.`;
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
function renderEntryTreeLines(entries, predicate) {
|
|
505
|
-
const lines = [];
|
|
506
|
-
for (const entry of entries) {
|
|
507
|
-
if (!predicate(entry)) continue;
|
|
508
|
-
const label = `${path.basename(entry.relPath)}${entry.type === 'dir' ? '/' : ''}`;
|
|
509
|
-
lines.push(renderTreeLine(label, entry.depth));
|
|
510
|
-
}
|
|
511
|
-
return lines;
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
function renderTreeLine(label, depth) {
|
|
515
|
-
return `${'| '.repeat(Math.max(0, depth))}|-- ${label}`;
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
function normalizeFolderPath(value) {
|
|
519
|
-
const normalized = String(value || '')
|
|
520
|
-
.trim()
|
|
521
|
-
.replace(/\\/g, '/')
|
|
522
|
-
.replace(/^\.\/+/, '')
|
|
523
|
-
.replace(/\/+/g, '/')
|
|
524
|
-
.replace(/\/$/, '');
|
|
525
|
-
|
|
526
|
-
if (!normalized || normalized === '.') return '.';
|
|
527
|
-
return normalized;
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
function resolveRequestedFolders(value) {
|
|
531
|
-
const rawValues = Array.isArray(value) ? value : [value];
|
|
532
|
-
const folders = [];
|
|
533
|
-
const seen = new Set();
|
|
534
|
-
|
|
535
|
-
for (const rawValue of rawValues) {
|
|
536
|
-
const parts = String(rawValue || '')
|
|
537
|
-
.split(',')
|
|
538
|
-
.map((part) => normalizeFolderPath(part))
|
|
539
|
-
.filter((part) => part && part !== '.');
|
|
540
|
-
|
|
541
|
-
for (const folder of parts) {
|
|
542
|
-
if (seen.has(folder)) continue;
|
|
543
|
-
seen.add(folder);
|
|
544
|
-
folders.push(folder);
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
return folders;
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
function isWithinPrefix(relPath, prefix) {
|
|
552
|
-
return relPath === prefix || relPath.startsWith(`${prefix}/`);
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
function isWithinAnyPrefix(relPath, prefixes) {
|
|
556
|
-
return prefixes.some((prefix) => isWithinPrefix(relPath, prefix));
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
function collectForgeArtifactPaths(entries, managedForgePaths) {
|
|
560
|
-
const included = new Set();
|
|
561
|
-
|
|
562
|
-
for (const entry of entries) {
|
|
563
|
-
if (!isWithinAnyPrefix(entry.relPath, FORGE_SCAN_ROOTS)) continue;
|
|
564
|
-
|
|
565
|
-
if (entry.type === 'file') {
|
|
566
|
-
if (managedForgePaths.has(entry.relPath)) continue;
|
|
567
|
-
if (FORGE_SKIP_GENERATED_FILES.has(entry.relPath)) continue;
|
|
568
|
-
included.add(entry.relPath);
|
|
569
|
-
continue;
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
if (entry.type === 'dir' && !FORGE_SCAN_ROOTS.includes(entry.relPath)) {
|
|
573
|
-
included.add(entry.relPath);
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
if (included.size === 0) return included;
|
|
578
|
-
|
|
579
|
-
const withAncestors = new Set(included);
|
|
580
|
-
for (const relPath of included) {
|
|
581
|
-
let current = relPath;
|
|
582
|
-
while (current && current.includes('/')) {
|
|
583
|
-
current = current.slice(0, current.lastIndexOf('/'));
|
|
584
|
-
if (!current) break;
|
|
585
|
-
withAncestors.add(current);
|
|
586
|
-
if (current === '.aioson') break;
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
return withAncestors;
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
function buildFolderMapMarkdown({ entries, generatedAt }) {
|
|
593
|
-
const lines = [
|
|
594
|
-
'# Folder Map',
|
|
595
|
-
`_Generated by aioson scan:project — ${generatedAt}_`,
|
|
596
|
-
'',
|
|
597
|
-
'## Scope',
|
|
598
|
-
'- Project directories only.',
|
|
599
|
-
'- `.aioson/` internals are intentionally omitted here and tracked in `scan-aioson.md`.',
|
|
600
|
-
'',
|
|
601
|
-
'## Tree'
|
|
602
|
-
];
|
|
603
|
-
|
|
604
|
-
const treeLines = renderEntryTreeLines(entries, (entry) => {
|
|
605
|
-
if (entry.type !== 'dir') return false;
|
|
606
|
-
if (entry.relPath === '.aioson') return true;
|
|
607
|
-
return !entry.relPath.startsWith('.aioson/');
|
|
608
|
-
});
|
|
609
|
-
|
|
610
|
-
if (treeLines.length === 0) {
|
|
611
|
-
lines.push('_No directories mapped_');
|
|
612
|
-
return lines.join('\n');
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
lines.push('```text', ...treeLines, '```');
|
|
616
|
-
return lines.join('\n');
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
function sanitizeScanFileSegment(folder) {
|
|
620
|
-
const normalized = normalizeFolderPath(folder);
|
|
621
|
-
if (normalized === '.') return 'root';
|
|
622
|
-
return normalized
|
|
623
|
-
.replace(/[/.]+/g, '-')
|
|
624
|
-
.replace(/[^a-zA-Z0-9_-]+/g, '-')
|
|
625
|
-
.replace(/-+/g, '-')
|
|
626
|
-
.replace(/^-|-$/g, '') || 'folder';
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
function buildFolderScanRelativePath(folder) {
|
|
630
|
-
return `.aioson/context/scan-${sanitizeScanFileSegment(folder)}.md`;
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
function renderRequestedFolderTree(entries, folder) {
|
|
634
|
-
const normalized = normalizeFolderPath(folder);
|
|
635
|
-
const lines = [];
|
|
636
|
-
|
|
637
|
-
if (normalized === '.') {
|
|
638
|
-
return renderEntryTreeLines(entries, () => true);
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
lines.push(renderTreeLine(`${normalized}/`, 0));
|
|
642
|
-
for (const entry of entries) {
|
|
643
|
-
if (!isWithinPrefix(entry.relPath, normalized) || entry.relPath === normalized) continue;
|
|
644
|
-
const relativePath = entry.relPath.slice(normalized.length + 1);
|
|
645
|
-
const depth = relativePath.split('/').length;
|
|
646
|
-
const label = `${path.basename(entry.relPath)}${entry.type === 'dir' ? '/' : ''}`;
|
|
647
|
-
lines.push(renderTreeLine(label, depth));
|
|
648
|
-
}
|
|
649
|
-
return lines;
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
function buildRequestedFolderMarkdown({ entries, generatedAt, folder }) {
|
|
653
|
-
const normalized = normalizeFolderPath(folder);
|
|
654
|
-
const lines = [
|
|
655
|
-
`# Folder Scan: ${normalized}`,
|
|
656
|
-
`_Generated by aioson scan:project — ${generatedAt}_`,
|
|
657
|
-
'',
|
|
658
|
-
'## Scope',
|
|
659
|
-
`- Requested folder: \`${normalized}/\``,
|
|
660
|
-
'- Includes all mapped directories and files under this folder.',
|
|
661
|
-
'',
|
|
662
|
-
'## Tree'
|
|
663
|
-
];
|
|
664
|
-
|
|
665
|
-
const treeLines = renderRequestedFolderTree(entries, normalized);
|
|
666
|
-
if (treeLines.length === 0) {
|
|
667
|
-
lines.push('_No mapped entries for this folder_');
|
|
668
|
-
return lines.join('\n');
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
lines.push('```text', ...treeLines, '```');
|
|
672
|
-
return lines.join('\n');
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
function renderForgeSectionTree(entries, root, artifactPaths) {
|
|
676
|
-
const sectionEntries = entries.filter((entry) => artifactPaths.has(entry.relPath) && isWithinPrefix(entry.relPath, root));
|
|
677
|
-
if (sectionEntries.length === 0) return [];
|
|
678
|
-
|
|
679
|
-
const lines = [renderTreeLine(`${root}/`, 0)];
|
|
680
|
-
for (const entry of sectionEntries) {
|
|
681
|
-
if (entry.relPath === root) continue;
|
|
682
|
-
const relativePath = entry.relPath.slice(root.length + 1);
|
|
683
|
-
const depth = relativePath.split('/').length;
|
|
684
|
-
const label = `${path.basename(entry.relPath)}${entry.type === 'dir' ? '/' : ''}`;
|
|
685
|
-
lines.push(renderTreeLine(label, depth));
|
|
686
|
-
}
|
|
687
|
-
return lines;
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
function buildForgeArtifactsMarkdown({ entries, generatedAt, managedForgePaths }) {
|
|
691
|
-
const lines = [
|
|
692
|
-
'# AIOSON Generated Map',
|
|
693
|
-
`_Generated by aioson scan:project — ${generatedAt}_`,
|
|
694
|
-
'',
|
|
695
|
-
'## Scope',
|
|
696
|
-
'- Shows generated or project-specific artifacts inside `.aioson/`.',
|
|
697
|
-
'- Groups what matters for client analysis, especially context pages, squads, genomes and local MCP artifacts.',
|
|
698
|
-
'- Hides framework-managed defaults such as agents, locales, schemas, static skills and task docs.'
|
|
699
|
-
];
|
|
700
|
-
|
|
701
|
-
const artifactPaths = collectForgeArtifactPaths(entries, managedForgePaths);
|
|
702
|
-
if (artifactPaths.size === 0) {
|
|
703
|
-
lines.push('', '_No generated AIOSON artifacts detected yet_');
|
|
704
|
-
return { markdown: lines.join('\n'), artifactCount: 0 };
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
let artifactCount = 0;
|
|
708
|
-
for (const section of FORGE_SECTION_ROOTS) {
|
|
709
|
-
lines.push('', `## ${section.title}`);
|
|
710
|
-
const treeLines = renderForgeSectionTree(entries, section.root, artifactPaths);
|
|
711
|
-
if (treeLines.length === 0) {
|
|
712
|
-
lines.push(section.empty);
|
|
713
|
-
continue;
|
|
714
|
-
}
|
|
715
|
-
artifactCount += treeLines.length;
|
|
716
|
-
lines.push('```text', ...treeLines, '```');
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
return { markdown: lines.join('\n'), artifactCount };
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
function buildScanIndexMarkdown({
|
|
723
|
-
keyFiles,
|
|
724
|
-
topLevelStats,
|
|
725
|
-
generatedAt,
|
|
726
|
-
includeSummaries = true,
|
|
727
|
-
foldersPath,
|
|
728
|
-
folderScans = [],
|
|
729
|
-
forgePath,
|
|
730
|
-
forgeArtifactCount = 0,
|
|
731
|
-
memoryIndexPath = null,
|
|
732
|
-
specCurrentPath = null,
|
|
733
|
-
specHistoryPath = null,
|
|
734
|
-
moduleDocs = []
|
|
735
|
-
}) {
|
|
736
|
-
const lines = [
|
|
737
|
-
'# Scan Index',
|
|
738
|
-
`_Generated by aioson scan:project — ${generatedAt}_`,
|
|
739
|
-
'',
|
|
740
|
-
'## Scan outputs',
|
|
741
|
-
'| File | Purpose |',
|
|
742
|
-
'|------|---------|',
|
|
743
|
-
`| ${INDEX_FILE} | Summary index with footprint, key files and links to specialized scan maps |`,
|
|
744
|
-
`| ${FOLDERS_FILE} | Directory-only map of the project |`,
|
|
745
|
-
...folderScans.map((scan) =>
|
|
746
|
-
`| ${scan.relativePath} | Full folder and file map for requested folder \`${scan.folder}/\` |`
|
|
747
|
-
),
|
|
748
|
-
`| ${FORGE_FILE} | Generated or project-specific artifacts inside .aioson/ |`,
|
|
749
|
-
...(memoryIndexPath
|
|
750
|
-
? [`| ${MEMORY_INDEX_FILE} | Read-this-first index of context docs and when to load them |`]
|
|
751
|
-
: []),
|
|
752
|
-
...(specCurrentPath
|
|
753
|
-
? [`| ${SPEC_CURRENT_FILE} | Current development snapshot derived from spec.md |`]
|
|
754
|
-
: []),
|
|
755
|
-
...(specHistoryPath
|
|
756
|
-
? [`| ${SPEC_HISTORY_FILE} | Historical implementation and decision view derived from spec.md |`]
|
|
757
|
-
: []),
|
|
758
|
-
...moduleDocs.map((doc) =>
|
|
759
|
-
`| ${doc.relativePath} | Focused module memory for requested folder \`${doc.folder}/\` |`
|
|
760
|
-
),
|
|
761
|
-
'',
|
|
762
|
-
`- Folder map: \`${foldersPath}\``,
|
|
763
|
-
...(
|
|
764
|
-
folderScans.length === 0
|
|
765
|
-
? ['- Requested folder scans: none']
|
|
766
|
-
: folderScans.map((scan) => `- Folder \`${scan.folder}/\`: \`${scan.absolutePath}\``)
|
|
767
|
-
),
|
|
768
|
-
`- AIOSON generated map: \`${forgePath}\``,
|
|
769
|
-
`- AIOSON generated entries: ${forgeArtifactCount}`,
|
|
770
|
-
...(memoryIndexPath ? [`- Memory index: \`${memoryIndexPath}\``] : []),
|
|
771
|
-
...(specCurrentPath ? [`- Spec current view: \`${specCurrentPath}\``] : []),
|
|
772
|
-
...(specHistoryPath ? [`- Spec history view: \`${specHistoryPath}\``] : []),
|
|
773
|
-
...moduleDocs.map((doc) => `- Module memory \`${doc.folder}/\`: \`${doc.absolutePath}\``),
|
|
774
|
-
'',
|
|
775
|
-
'## Top-level footprint',
|
|
776
|
-
'| Path | Files | Approx size |',
|
|
777
|
-
'|------|-------|-------------|'
|
|
778
|
-
];
|
|
779
|
-
|
|
780
|
-
const topLevelRows = [...topLevelStats.entries()].sort((a, b) => a[0].localeCompare(b[0]));
|
|
781
|
-
if (topLevelRows.length === 0) {
|
|
782
|
-
lines.push('| [root files] | 0 | 0 KB |');
|
|
783
|
-
} else {
|
|
784
|
-
for (const [name, stat] of topLevelRows) {
|
|
785
|
-
lines.push(`| ${name} | ${stat.files} | ${formatBytesCompact(stat.sizeBytes)} |`);
|
|
786
|
-
}
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
lines.push('', '## Key files');
|
|
790
|
-
if (!keyFiles || keyFiles.length === 0) {
|
|
791
|
-
lines.push('- No key files detected.');
|
|
792
|
-
} else {
|
|
793
|
-
for (const file of keyFiles.slice(0, 20)) {
|
|
794
|
-
lines.push(`### ${file.path}`);
|
|
795
|
-
lines.push(`- Title: ${file.title}`);
|
|
796
|
-
if (includeSummaries) lines.push(`- Summary: ${file.summary}`);
|
|
797
|
-
lines.push(`- Approx size: ${formatBytesCompact(file.sizeBytes)}`);
|
|
798
|
-
lines.push('');
|
|
799
|
-
}
|
|
800
|
-
}
|
|
801
|
-
return lines.join('\n');
|
|
802
|
-
}
|
|
803
|
-
|
|
804
|
-
function buildPrompt({
|
|
805
|
-
scanIndexMarkdown,
|
|
806
|
-
folderMapMarkdown,
|
|
807
|
-
folderScans = [],
|
|
808
|
-
forgeMapMarkdown,
|
|
809
|
-
keyContents,
|
|
810
|
-
projectContext,
|
|
811
|
-
specContent,
|
|
812
|
-
existingDiscoveryContent,
|
|
813
|
-
existingSkeletonContent,
|
|
814
|
-
summaryMode
|
|
815
|
-
}) {
|
|
816
|
-
const now = new Date().toISOString().replace(/\.\d+Z$/, 'Z');
|
|
817
|
-
const parts = ['You are analyzing a software project to generate a structured discovery document.\n'];
|
|
818
|
-
|
|
819
|
-
if (projectContext) {
|
|
820
|
-
parts.push(`## Project Context (aioson)\n\`\`\`\n${projectContext}\n\`\`\`\n`);
|
|
821
|
-
}
|
|
822
|
-
parts.push(`## Scan Index\n\`\`\`md\n${scanIndexMarkdown}\n\`\`\`\n`);
|
|
823
|
-
parts.push(`## Folder Map\n\`\`\`md\n${folderMapMarkdown}\n\`\`\`\n`);
|
|
824
|
-
for (const scan of folderScans) {
|
|
825
|
-
parts.push(`## Folder Scan: ${scan.folder}\n\`\`\`md\n${scan.markdown}\n\`\`\`\n`);
|
|
826
|
-
}
|
|
827
|
-
parts.push(`## AIOSON Generated Map\n\`\`\`md\n${forgeMapMarkdown}\n\`\`\`\n`);
|
|
828
|
-
|
|
829
|
-
if (summaryMode === 'raw' && Object.keys(keyContents).length > 0) {
|
|
830
|
-
parts.push('## Key Files\n');
|
|
831
|
-
for (const [filePath, content] of Object.entries(keyContents).slice(0, 12)) {
|
|
832
|
-
parts.push(`### ${filePath}\n\`\`\`\n${content}\n\`\`\`\n`);
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
if (specContent) {
|
|
837
|
-
parts.push(`## Development Memory (spec.md)\n\`\`\`\n${specContent}\n\`\`\`\n`);
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
if (existingDiscoveryContent) {
|
|
841
|
-
parts.push(`## Existing Discovery Memory (update in place)\n\`\`\`md\n${existingDiscoveryContent}\n\`\`\`\n`);
|
|
842
|
-
}
|
|
843
|
-
|
|
844
|
-
if (existingSkeletonContent) {
|
|
845
|
-
parts.push(`## Existing Skeleton Memory (update in place)\n\`\`\`md\n${existingSkeletonContent}\n\`\`\`\n`);
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
parts.push(`
|
|
849
|
-
## Task
|
|
850
|
-
Generate TWO documents. Separate them with exactly this delimiter on its own line:
|
|
851
|
-
<<<SKELETON>>>
|
|
852
|
-
|
|
853
|
-
If existing discovery or skeleton documents were provided above, treat them as the current memory baseline and UPDATE them in place.
|
|
854
|
-
- Preserve stable system knowledge, conventions, and still-valid human notes.
|
|
855
|
-
- Remove or correct only what is clearly contradicted by the current scan, project.context.md, or spec.md.
|
|
856
|
-
- Do not throw away useful prior context just because the current scan sample is smaller.
|
|
857
|
-
- Keep the required output sections exactly as specified below.
|
|
858
|
-
|
|
859
|
-
### Document 1: \`.aioson/context/discovery.md\`
|
|
860
|
-
Generate with exactly these sections:
|
|
861
|
-
|
|
862
|
-
# Discovery
|
|
863
|
-
|
|
864
|
-
## 1. What this project builds
|
|
865
|
-
2-3 objective lines describing what the system does.
|
|
866
|
-
|
|
867
|
-
## 2. Project structure overview
|
|
868
|
-
Key directories and their responsibilities. Identify the architectural pattern (MVC, layered, feature-based, etc.).
|
|
869
|
-
|
|
870
|
-
## 3. Key entities and relationships
|
|
871
|
-
Entities inferred from models, migrations, or schema files. Include relationships if detectable.
|
|
872
|
-
|
|
873
|
-
## 4. Entry points and routes
|
|
874
|
-
Main route files, controllers, or API handlers identified.
|
|
875
|
-
|
|
876
|
-
## 5. Dependencies and services
|
|
877
|
-
Key packages from package.json / composer.json / requirements.txt. External services detected.
|
|
878
|
-
|
|
879
|
-
## 6. Existing patterns and conventions
|
|
880
|
-
Coding patterns already in use (naming, folder organization, auth approach, etc.). These must be preserved.
|
|
881
|
-
|
|
882
|
-
## 7. Development state
|
|
883
|
-
What appears to be done, in-progress, or missing. Use spec.md if available.
|
|
884
|
-
|
|
885
|
-
## 8. Risks and technical debt
|
|
886
|
-
Issues, inconsistencies, or missing pieces that could become problems.
|
|
887
|
-
|
|
888
|
-
## 9. What to preserve
|
|
889
|
-
Explicit list of conventions and structures the AI must NOT change or override.
|
|
890
|
-
|
|
891
|
-
---
|
|
892
|
-
_Generated by aioson scan:project — ${now}_
|
|
893
|
-
|
|
894
|
-
<<<SKELETON>>>
|
|
895
|
-
|
|
896
|
-
### Document 2: \`.aioson/context/skeleton-system.md\`
|
|
897
|
-
A lightweight living index of the system. Keep it concise — AI agents read this frequently as a quick-reference index. Do NOT repeat the full analysis from Document 1 here.
|
|
898
|
-
|
|
899
|
-
Generate with exactly this format:
|
|
900
|
-
|
|
901
|
-
# System Skeleton
|
|
902
|
-
_Generated by aioson scan:project — ${now}_
|
|
903
|
-
|
|
904
|
-
## File map
|
|
905
|
-
Indented tree of key files and directories grouped by domain/module.
|
|
906
|
-
Skip: detailed migration lists, test fixtures, config boilerplate, lock files.
|
|
907
|
-
Mark each module or file with inferred status:
|
|
908
|
-
✓ complete — code present and appears fully implemented
|
|
909
|
-
◑ partial — scaffolded or incomplete implementation
|
|
910
|
-
○ missing — referenced but not found or empty
|
|
911
|
-
|
|
912
|
-
## Key routes
|
|
913
|
-
Main routes mapped to their handlers. One per line.
|
|
914
|
-
Format: \`METHOD /path → Handler@method\`
|
|
915
|
-
Skip standard auth boilerplate (login/logout/password-reset) unless customized.
|
|
916
|
-
If no route files found: _No route files detected_
|
|
917
|
-
|
|
918
|
-
## Module status
|
|
919
|
-
| Module | Status | Key files |
|
|
920
|
-
|--------|--------|-----------|
|
|
921
|
-
One row per logical module or feature area.
|
|
922
|
-
Status: ✓ done | ◑ in-progress | ○ pending
|
|
923
|
-
|
|
924
|
-
## Key relationships
|
|
925
|
-
Entity relationships in plain English, one per line.
|
|
926
|
-
Example: \`User hasMany Orders → OrderItem → Product\`
|
|
927
|
-
If no models/schema found: _No entities detected_
|
|
928
|
-
`);
|
|
929
|
-
|
|
930
|
-
return parts.join('\n');
|
|
931
|
-
}
|
|
932
|
-
|
|
933
|
-
function listTopLevelDirectories(entries) {
|
|
934
|
-
const names = new Set();
|
|
935
|
-
for (const entry of entries) {
|
|
936
|
-
if (entry.type !== 'dir') continue;
|
|
937
|
-
const topLevel = entry.relPath.split('/')[0];
|
|
938
|
-
if (topLevel) names.add(topLevel);
|
|
939
|
-
}
|
|
940
|
-
return [...names].sort((a, b) => a.localeCompare(b));
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
// ── Main ─────────────────────────────────────────────────────────────────────
|
|
944
|
-
|
|
945
|
-
async function runScanProject({ args, options = {}, logger, t }) {
|
|
946
|
-
const targetArg = args[0] || '.';
|
|
947
|
-
const targetDir = path.resolve(process.cwd(), targetArg);
|
|
948
|
-
const summaryMode = resolveSummaryMode(options['summary-mode']);
|
|
949
|
-
const contextMode = resolveContextMode(options['context-mode']);
|
|
950
|
-
const requestedFolders = resolveRequestedFolders(options.folder);
|
|
951
|
-
const llmRequested = Boolean(options['with-llm']);
|
|
952
|
-
const llmModelOverride = String(options['llm-model'] || options.model || '').trim();
|
|
953
|
-
|
|
954
|
-
logger.log(t('scan_project.scanning', { dir: targetDir }));
|
|
955
|
-
|
|
956
|
-
if (requestedFolders.length === 0) {
|
|
957
|
-
logger.error(t('scan_project.folder_required'));
|
|
958
|
-
logger.error(t('scan_project.folder_required_examples_title'));
|
|
959
|
-
logger.error(t('scan_project.folder_required_example_local'));
|
|
960
|
-
logger.error(t('scan_project.folder_required_example_multi'));
|
|
961
|
-
logger.error(t('scan_project.folder_required_example_llm'));
|
|
962
|
-
logger.error(t('scan_project.folder_required_example_cli'));
|
|
963
|
-
logger.error(t('scan_project.folder_required_example_prompt'));
|
|
964
|
-
logger.error(t('scan_project.folder_required_example_next'));
|
|
965
|
-
process.exitCode = 1;
|
|
966
|
-
return { ok: false, error: 'folder_required' };
|
|
967
|
-
}
|
|
968
|
-
|
|
969
|
-
let providerName = null;
|
|
970
|
-
let providerCfg = null;
|
|
971
|
-
let model = null;
|
|
972
|
-
|
|
973
|
-
if (!options['dry-run']) {
|
|
974
|
-
const gitignoreRulesAdded = await ensureProjectGitignorePolicy(targetDir);
|
|
975
|
-
if (gitignoreRulesAdded > 0) {
|
|
976
|
-
logger.log(t('scan_project.gitignore_policy_written', { path: path.join(targetDir, '.gitignore') }));
|
|
977
|
-
logger.log(t('scan_project.gitignore_tracked_note'));
|
|
978
|
-
}
|
|
979
|
-
}
|
|
980
|
-
|
|
981
|
-
if (!llmRequested) {
|
|
982
|
-
logger.log(t('scan_project.local_only'));
|
|
983
|
-
} else if (!options['dry-run']) {
|
|
984
|
-
const configPath = path.join(targetDir, CONFIG_FILE);
|
|
985
|
-
if (!(await exists(configPath))) {
|
|
986
|
-
logger.error(t('scan_project.config_missing', { file: CONFIG_FILE }));
|
|
987
|
-
process.exitCode = 1;
|
|
988
|
-
return { ok: false, error: 'config_not_found' };
|
|
989
|
-
}
|
|
990
|
-
|
|
991
|
-
let config;
|
|
992
|
-
try {
|
|
993
|
-
config = JSON.parse(await fs.readFile(configPath, 'utf8'));
|
|
994
|
-
} catch (err) {
|
|
995
|
-
logger.error(t('scan_project.config_invalid', { error: err.message }));
|
|
996
|
-
process.exitCode = 1;
|
|
997
|
-
return { ok: false, error: 'config_invalid' };
|
|
998
|
-
}
|
|
999
|
-
|
|
1000
|
-
providerName = String(options.provider || config.preferred_scan_provider || '');
|
|
1001
|
-
const providers = config.providers || {};
|
|
1002
|
-
|
|
1003
|
-
if (!providerName || !providers[providerName]) {
|
|
1004
|
-
const available = Object.keys(providers).join(', ') || '(none)';
|
|
1005
|
-
logger.error(t('scan_project.provider_missing', { provider: providerName, available }));
|
|
1006
|
-
process.exitCode = 1;
|
|
1007
|
-
return { ok: false, error: 'provider_not_found' };
|
|
1008
|
-
}
|
|
1009
|
-
|
|
1010
|
-
providerCfg = { ...providers[providerName] };
|
|
1011
|
-
if (llmModelOverride) providerCfg.model = llmModelOverride;
|
|
1012
|
-
model = providerCfg.model || '?';
|
|
1013
|
-
logger.log(t('scan_project.provider_info', { provider: providerName, model }));
|
|
1014
|
-
}
|
|
1015
|
-
|
|
1016
|
-
// Read context files
|
|
1017
|
-
const projectContext = await readFileSafe(path.join(targetDir, CONTEXT_FILE));
|
|
1018
|
-
const specContent = await readFileSafe(path.join(targetDir, SPEC_FILE));
|
|
1019
|
-
const existingDiscoveryPath = path.join(targetDir, OUTPUT_FILE);
|
|
1020
|
-
const existingSkeletonPath = path.join(targetDir, SKELETON_FILE);
|
|
1021
|
-
const existingDiscoveryContent = contextMode === 'merge' ? await readFileSafe(existingDiscoveryPath) : null;
|
|
1022
|
-
const existingSkeletonContent = contextMode === 'merge' ? await readFileSafe(existingSkeletonPath) : null;
|
|
1023
|
-
|
|
1024
|
-
if (projectContext) logger.log(t('scan_project.context_found'));
|
|
1025
|
-
else logger.log(t('scan_project.context_missing'));
|
|
1026
|
-
if (specContent) logger.log(t('scan_project.spec_found'));
|
|
1027
|
-
if (llmRequested && existingDiscoveryContent) logger.log(t('scan_project.existing_discovery_found', { path: existingDiscoveryPath }));
|
|
1028
|
-
if (llmRequested && existingSkeletonContent) logger.log(t('scan_project.existing_skeleton_found', { path: existingSkeletonPath }));
|
|
1029
|
-
if (llmRequested && (existingDiscoveryContent || existingSkeletonContent)) logger.log(t('scan_project.context_update_mode'));
|
|
1030
|
-
if (llmRequested) logger.log(t('scan_project.context_mode', { mode: contextMode }));
|
|
1031
|
-
|
|
1032
|
-
// Walk project
|
|
1033
|
-
logger.log(t('scan_project.walking'));
|
|
1034
|
-
const { keyContents, keyFiles, topLevelStats, entries } = await walkProject(targetDir);
|
|
1035
|
-
logger.log(t('scan_project.walk_done', {
|
|
1036
|
-
files: entries.filter((entry) => entry.type === 'file').length,
|
|
1037
|
-
keys: Object.keys(keyContents).length
|
|
1038
|
-
}));
|
|
1039
|
-
|
|
1040
|
-
const generatedAt = new Date().toISOString().replace(/\.\d+Z$/, 'Z');
|
|
1041
|
-
const managedForgePaths = await loadManagedForgePaths();
|
|
1042
|
-
const folderMapMarkdown = buildFolderMapMarkdown({
|
|
1043
|
-
entries,
|
|
1044
|
-
generatedAt
|
|
1045
|
-
});
|
|
1046
|
-
const forgeArtifacts = buildForgeArtifactsMarkdown({
|
|
1047
|
-
entries,
|
|
1048
|
-
generatedAt,
|
|
1049
|
-
managedForgePaths
|
|
1050
|
-
});
|
|
1051
|
-
|
|
1052
|
-
const availableTopLevelDirs = listTopLevelDirectories(entries);
|
|
1053
|
-
for (const folder of requestedFolders) {
|
|
1054
|
-
if (folder === '.') continue;
|
|
1055
|
-
const existsAsDirectory = entries.some((entry) => entry.type === 'dir' && entry.relPath === folder);
|
|
1056
|
-
if (!existsAsDirectory) {
|
|
1057
|
-
logger.error(t('scan_project.folder_not_found', {
|
|
1058
|
-
folder,
|
|
1059
|
-
available: availableTopLevelDirs.join(', ') || '(none)'
|
|
1060
|
-
}));
|
|
1061
|
-
process.exitCode = 1;
|
|
1062
|
-
return { ok: false, error: 'folder_not_found' };
|
|
1063
|
-
}
|
|
1064
|
-
}
|
|
1065
|
-
|
|
1066
|
-
const folderScans = requestedFolders.map((folder) => {
|
|
1067
|
-
const relativePath = buildFolderScanRelativePath(folder);
|
|
1068
|
-
return {
|
|
1069
|
-
folder,
|
|
1070
|
-
relativePath,
|
|
1071
|
-
absolutePath: path.join(targetDir, relativePath),
|
|
1072
|
-
markdown: buildRequestedFolderMarkdown({
|
|
1073
|
-
entries,
|
|
1074
|
-
generatedAt,
|
|
1075
|
-
folder
|
|
1076
|
-
})
|
|
1077
|
-
};
|
|
1078
|
-
});
|
|
1079
|
-
|
|
1080
|
-
const scanIndexPath = path.join(targetDir, INDEX_FILE);
|
|
1081
|
-
const scanFoldersPath = path.join(targetDir, FOLDERS_FILE);
|
|
1082
|
-
const scanForgePath = path.join(targetDir, FORGE_FILE);
|
|
1083
|
-
const scanIndexMarkdown = buildScanIndexMarkdown({
|
|
1084
|
-
keyFiles,
|
|
1085
|
-
topLevelStats,
|
|
1086
|
-
generatedAt,
|
|
1087
|
-
includeSummaries: summaryMode !== 'titles',
|
|
1088
|
-
foldersPath: scanFoldersPath,
|
|
1089
|
-
folderScans,
|
|
1090
|
-
forgePath: scanForgePath,
|
|
1091
|
-
forgeArtifactCount: forgeArtifacts.artifactCount
|
|
1092
|
-
});
|
|
1093
|
-
let derivedArtifacts = {
|
|
1094
|
-
memoryIndexPath: null,
|
|
1095
|
-
specCurrentPath: null,
|
|
1096
|
-
specHistoryPath: null,
|
|
1097
|
-
moduleDocs: []
|
|
1098
|
-
};
|
|
1099
|
-
if (!options['dry-run']) {
|
|
1100
|
-
await ensureDir(path.dirname(scanIndexPath));
|
|
1101
|
-
await fs.writeFile(scanFoldersPath, folderMapMarkdown, 'utf8');
|
|
1102
|
-
for (const scan of folderScans) {
|
|
1103
|
-
await fs.writeFile(scan.absolutePath, scan.markdown, 'utf8');
|
|
1104
|
-
}
|
|
1105
|
-
await fs.writeFile(scanIndexPath, scanIndexMarkdown, 'utf8');
|
|
1106
|
-
await fs.writeFile(scanForgePath, forgeArtifacts.markdown, 'utf8');
|
|
1107
|
-
logger.log(t('scan_project.folders_written', { path: scanFoldersPath }));
|
|
1108
|
-
for (const scan of folderScans) {
|
|
1109
|
-
logger.log(t('scan_project.folder_written', { folder: `${scan.folder}/`, path: scan.absolutePath }));
|
|
1110
|
-
}
|
|
1111
|
-
}
|
|
1112
|
-
|
|
1113
|
-
if (options['dry-run']) {
|
|
1114
|
-
const output = {
|
|
1115
|
-
ok: true,
|
|
1116
|
-
dryRun: true,
|
|
1117
|
-
treeLines: entries.length,
|
|
1118
|
-
keyFiles: Object.keys(keyContents).length,
|
|
1119
|
-
provider: providerName,
|
|
1120
|
-
model,
|
|
1121
|
-
llmRequested,
|
|
1122
|
-
summaryMode,
|
|
1123
|
-
contextMode,
|
|
1124
|
-
requestedFolders,
|
|
1125
|
-
scanIndexPath,
|
|
1126
|
-
scanFoldersPath,
|
|
1127
|
-
scanFolderPaths: folderScans.map((scan) => scan.absolutePath),
|
|
1128
|
-
scanForgePath
|
|
1129
|
-
};
|
|
1130
|
-
if (options.json) return output;
|
|
1131
|
-
logger.log(t('scan_project.dry_run_done', { treeCount: entries.length, keyCount: Object.keys(keyContents).length }));
|
|
1132
|
-
return output;
|
|
1133
|
-
}
|
|
1134
|
-
|
|
1135
|
-
if (!llmRequested) {
|
|
1136
|
-
derivedArtifacts = await writeDerivedContextMemory({
|
|
1137
|
-
targetDir,
|
|
1138
|
-
generatedAt,
|
|
1139
|
-
folderScans
|
|
1140
|
-
});
|
|
1141
|
-
|
|
1142
|
-
const refreshedWalk = await walkProject(targetDir);
|
|
1143
|
-
const refreshedForgeArtifacts = buildForgeArtifactsMarkdown({
|
|
1144
|
-
entries: refreshedWalk.entries,
|
|
1145
|
-
generatedAt,
|
|
1146
|
-
managedForgePaths
|
|
1147
|
-
});
|
|
1148
|
-
const refreshedScanIndexMarkdown = buildScanIndexMarkdown({
|
|
1149
|
-
keyFiles: refreshedWalk.keyFiles,
|
|
1150
|
-
topLevelStats: refreshedWalk.topLevelStats,
|
|
1151
|
-
generatedAt,
|
|
1152
|
-
includeSummaries: summaryMode !== 'titles',
|
|
1153
|
-
foldersPath: path.join(targetDir, FOLDERS_FILE),
|
|
1154
|
-
folderScans,
|
|
1155
|
-
forgePath: path.join(targetDir, FORGE_FILE),
|
|
1156
|
-
forgeArtifactCount: refreshedForgeArtifacts.artifactCount,
|
|
1157
|
-
memoryIndexPath: derivedArtifacts.memoryIndexPath,
|
|
1158
|
-
specCurrentPath: derivedArtifacts.specCurrentPath,
|
|
1159
|
-
specHistoryPath: derivedArtifacts.specHistoryPath,
|
|
1160
|
-
moduleDocs: derivedArtifacts.moduleDocs
|
|
1161
|
-
});
|
|
1162
|
-
|
|
1163
|
-
await fs.writeFile(scanIndexPath, refreshedScanIndexMarkdown, 'utf8');
|
|
1164
|
-
await fs.writeFile(scanForgePath, refreshedForgeArtifacts.markdown, 'utf8');
|
|
1165
|
-
logger.log(t('scan_project.index_written', { path: scanIndexPath, mode: summaryMode }));
|
|
1166
|
-
logger.log(t('scan_project.forge_written', { path: scanForgePath }));
|
|
1167
|
-
if (derivedArtifacts.memoryIndexPath) logger.log(t('scan_project.memory_index_written', { path: derivedArtifacts.memoryIndexPath }));
|
|
1168
|
-
if (derivedArtifacts.specCurrentPath) logger.log(t('scan_project.spec_current_written', { path: derivedArtifacts.specCurrentPath }));
|
|
1169
|
-
if (derivedArtifacts.specHistoryPath) logger.log(t('scan_project.spec_history_written', { path: derivedArtifacts.specHistoryPath }));
|
|
1170
|
-
for (const doc of derivedArtifacts.moduleDocs) {
|
|
1171
|
-
logger.log(t('scan_project.module_memory_written', { folder: `${doc.folder}/`, path: doc.absolutePath }));
|
|
1172
|
-
}
|
|
1173
|
-
|
|
1174
|
-
logger.log(t('scan_project.local_done', { path: scanIndexPath }));
|
|
1175
|
-
logger.log(t('scan_project.local_missing'));
|
|
1176
|
-
logger.log(t('scan_project.architecture_note'));
|
|
1177
|
-
logger.log(t('scan_project.local_paths_title'));
|
|
1178
|
-
logger.log(t('scan_project.local_path_api'));
|
|
1179
|
-
logger.log(t('scan_project.local_next_steps', {
|
|
1180
|
-
target: targetArg,
|
|
1181
|
-
folders: requestedFolders.join(',')
|
|
1182
|
-
}));
|
|
1183
|
-
logger.log(t('scan_project.local_path_cli'));
|
|
1184
|
-
logger.log(t('scan_project.local_cli_step_analyst'));
|
|
1185
|
-
logger.log(t('scan_project.local_cli_step_prompt_codex'));
|
|
1186
|
-
logger.log(t('scan_project.local_cli_step_prompt_claude'));
|
|
1187
|
-
logger.log(t('scan_project.local_cli_step_model_hint'));
|
|
1188
|
-
logger.log(t('scan_project.local_workflow_title'));
|
|
1189
|
-
logger.log(t('scan_project.local_step_architect'));
|
|
1190
|
-
logger.log(t('scan_project.local_step_dev'));
|
|
1191
|
-
return {
|
|
1192
|
-
ok: true,
|
|
1193
|
-
targetDir,
|
|
1194
|
-
provider: null,
|
|
1195
|
-
model: null,
|
|
1196
|
-
llmRequested: false,
|
|
1197
|
-
summaryMode,
|
|
1198
|
-
contextMode,
|
|
1199
|
-
requestedFolders,
|
|
1200
|
-
scanIndexPath,
|
|
1201
|
-
scanFoldersPath,
|
|
1202
|
-
scanFolderPaths: folderScans.map((scan) => scan.absolutePath),
|
|
1203
|
-
scanForgePath,
|
|
1204
|
-
memoryIndexPath: derivedArtifacts.memoryIndexPath,
|
|
1205
|
-
specCurrentPath: derivedArtifacts.specCurrentPath,
|
|
1206
|
-
specHistoryPath: derivedArtifacts.specHistoryPath,
|
|
1207
|
-
moduleDocPaths: derivedArtifacts.moduleDocs.map((doc) => doc.absolutePath),
|
|
1208
|
-
discoveryPath: null,
|
|
1209
|
-
skeletonPath: null
|
|
1210
|
-
};
|
|
1211
|
-
}
|
|
1212
|
-
|
|
1213
|
-
// Build prompt and call LLM
|
|
1214
|
-
const prompt = buildPrompt({
|
|
1215
|
-
scanIndexMarkdown,
|
|
1216
|
-
folderMapMarkdown,
|
|
1217
|
-
folderScans,
|
|
1218
|
-
forgeMapMarkdown: forgeArtifacts.markdown,
|
|
1219
|
-
keyContents,
|
|
1220
|
-
projectContext,
|
|
1221
|
-
specContent,
|
|
1222
|
-
existingDiscoveryContent,
|
|
1223
|
-
existingSkeletonContent,
|
|
1224
|
-
summaryMode
|
|
1225
|
-
});
|
|
1226
|
-
logger.log(t('scan_project.calling_llm', { provider: providerName, model }));
|
|
1227
|
-
|
|
1228
|
-
let result;
|
|
1229
|
-
try {
|
|
1230
|
-
result = await callLLM(providerName, providerCfg, prompt);
|
|
1231
|
-
} catch (err) {
|
|
1232
|
-
if (err && err.code === 'MISSING_API_KEY') {
|
|
1233
|
-
logger.error(t('scan_project.llm_missing_api_key', { provider: providerName, file: CONFIG_FILE }));
|
|
1234
|
-
} else {
|
|
1235
|
-
logger.error(t('scan_project.llm_error', { error: err.message }));
|
|
1236
|
-
}
|
|
1237
|
-
process.exitCode = 1;
|
|
1238
|
-
return { ok: false, error: err.message };
|
|
1239
|
-
}
|
|
1240
|
-
|
|
1241
|
-
// Parse and write both documents
|
|
1242
|
-
const outputPath = existingDiscoveryPath;
|
|
1243
|
-
const skeletonPath = existingSkeletonPath;
|
|
1244
|
-
|
|
1245
|
-
await ensureDir(path.dirname(outputPath));
|
|
1246
|
-
|
|
1247
|
-
let discoveryContent, skeletonContent;
|
|
1248
|
-
if (result.includes(DELIMITER)) {
|
|
1249
|
-
const parts = result.split(DELIMITER);
|
|
1250
|
-
discoveryContent = parts[0].trim();
|
|
1251
|
-
skeletonContent = parts[1].trim();
|
|
1252
|
-
} else {
|
|
1253
|
-
discoveryContent = result.trim();
|
|
1254
|
-
skeletonContent = null;
|
|
1255
|
-
}
|
|
1256
|
-
|
|
1257
|
-
if (!discoveryContent) {
|
|
1258
|
-
logger.error(t('scan_project.invalid_llm_output_discovery_empty'));
|
|
1259
|
-
process.exitCode = 1;
|
|
1260
|
-
return { ok: false, error: 'empty_discovery' };
|
|
1261
|
-
}
|
|
1262
|
-
|
|
1263
|
-
if (result.includes(DELIMITER) && !skeletonContent) {
|
|
1264
|
-
logger.error(t('scan_project.invalid_llm_output_skeleton_empty'));
|
|
1265
|
-
process.exitCode = 1;
|
|
1266
|
-
return { ok: false, error: 'empty_skeleton' };
|
|
1267
|
-
}
|
|
1268
|
-
|
|
1269
|
-
const contextFilesToBackup = [];
|
|
1270
|
-
if (await exists(existingDiscoveryPath)) contextFilesToBackup.push(OUTPUT_FILE);
|
|
1271
|
-
if (skeletonContent && await exists(existingSkeletonPath)) contextFilesToBackup.push(SKELETON_FILE);
|
|
1272
|
-
|
|
1273
|
-
if (contextFilesToBackup.length > 0) {
|
|
1274
|
-
const gitignoreChanged = await ensureGitignoreEntry(targetDir, BACKUPS_GITIGNORE_ENTRY);
|
|
1275
|
-
if (gitignoreChanged) {
|
|
1276
|
-
logger.log(t('scan_project.gitignore_backups_written', { path: path.join(targetDir, '.gitignore') }));
|
|
1277
|
-
}
|
|
1278
|
-
|
|
1279
|
-
const backupResult = await backupProjectFiles(targetDir, contextFilesToBackup);
|
|
1280
|
-
if (backupResult.backedUp.length > 0) {
|
|
1281
|
-
logger.log(t('scan_project.backups_written', {
|
|
1282
|
-
count: backupResult.backedUp.length,
|
|
1283
|
-
path: backupResult.backupRoot
|
|
1284
|
-
}));
|
|
1285
|
-
}
|
|
1286
|
-
}
|
|
1287
|
-
|
|
1288
|
-
await fs.writeFile(outputPath, discoveryContent, 'utf8');
|
|
1289
|
-
logger.log(t('scan_project.discovery_written', { path: outputPath, chars: discoveryContent.length }));
|
|
1290
|
-
|
|
1291
|
-
if (skeletonContent) {
|
|
1292
|
-
await fs.writeFile(skeletonPath, skeletonContent, 'utf8');
|
|
1293
|
-
logger.log(t('scan_project.skeleton_written', { path: skeletonPath, chars: skeletonContent.length }));
|
|
1294
|
-
} else {
|
|
1295
|
-
logger.log(t('scan_project.skeleton_missing'));
|
|
1296
|
-
}
|
|
1297
|
-
|
|
1298
|
-
derivedArtifacts = await writeDerivedContextMemory({
|
|
1299
|
-
targetDir,
|
|
1300
|
-
generatedAt,
|
|
1301
|
-
folderScans
|
|
1302
|
-
});
|
|
1303
|
-
const refreshedWalk = await walkProject(targetDir);
|
|
1304
|
-
const refreshedForgeArtifacts = buildForgeArtifactsMarkdown({
|
|
1305
|
-
entries: refreshedWalk.entries,
|
|
1306
|
-
generatedAt,
|
|
1307
|
-
managedForgePaths
|
|
1308
|
-
});
|
|
1309
|
-
const refreshedScanIndexMarkdown = buildScanIndexMarkdown({
|
|
1310
|
-
keyFiles: refreshedWalk.keyFiles,
|
|
1311
|
-
topLevelStats: refreshedWalk.topLevelStats,
|
|
1312
|
-
generatedAt,
|
|
1313
|
-
includeSummaries: summaryMode !== 'titles',
|
|
1314
|
-
foldersPath: path.join(targetDir, FOLDERS_FILE),
|
|
1315
|
-
folderScans,
|
|
1316
|
-
forgePath: path.join(targetDir, FORGE_FILE),
|
|
1317
|
-
forgeArtifactCount: refreshedForgeArtifacts.artifactCount,
|
|
1318
|
-
memoryIndexPath: derivedArtifacts.memoryIndexPath,
|
|
1319
|
-
specCurrentPath: derivedArtifacts.specCurrentPath,
|
|
1320
|
-
specHistoryPath: derivedArtifacts.specHistoryPath,
|
|
1321
|
-
moduleDocs: derivedArtifacts.moduleDocs
|
|
1322
|
-
});
|
|
1323
|
-
await fs.writeFile(scanIndexPath, refreshedScanIndexMarkdown, 'utf8');
|
|
1324
|
-
await fs.writeFile(scanForgePath, refreshedForgeArtifacts.markdown, 'utf8');
|
|
1325
|
-
logger.log(t('scan_project.index_written', { path: scanIndexPath, mode: summaryMode }));
|
|
1326
|
-
logger.log(t('scan_project.forge_written', { path: scanForgePath }));
|
|
1327
|
-
if (derivedArtifacts.memoryIndexPath) logger.log(t('scan_project.memory_index_written', { path: derivedArtifacts.memoryIndexPath }));
|
|
1328
|
-
if (derivedArtifacts.specCurrentPath) logger.log(t('scan_project.spec_current_written', { path: derivedArtifacts.specCurrentPath }));
|
|
1329
|
-
if (derivedArtifacts.specHistoryPath) logger.log(t('scan_project.spec_history_written', { path: derivedArtifacts.specHistoryPath }));
|
|
1330
|
-
for (const doc of derivedArtifacts.moduleDocs) {
|
|
1331
|
-
logger.log(t('scan_project.module_memory_written', { folder: `${doc.folder}/`, path: doc.absolutePath }));
|
|
1332
|
-
}
|
|
1333
|
-
|
|
1334
|
-
logger.log(t('scan_project.architecture_note'));
|
|
1335
|
-
logger.log(t('scan_project.next_steps'));
|
|
1336
|
-
logger.log(t('scan_project.step_analyst'));
|
|
1337
|
-
logger.log(t('scan_project.step_architect'));
|
|
1338
|
-
logger.log(t('scan_project.step_dev'));
|
|
1339
|
-
|
|
1340
|
-
const output = {
|
|
1341
|
-
ok: true,
|
|
1342
|
-
targetDir,
|
|
1343
|
-
provider: providerName,
|
|
1344
|
-
model,
|
|
1345
|
-
llmRequested: true,
|
|
1346
|
-
summaryMode,
|
|
1347
|
-
contextMode,
|
|
1348
|
-
requestedFolders,
|
|
1349
|
-
scanIndexPath,
|
|
1350
|
-
scanFoldersPath,
|
|
1351
|
-
scanFolderPaths: folderScans.map((scan) => scan.absolutePath),
|
|
1352
|
-
scanForgePath,
|
|
1353
|
-
memoryIndexPath: derivedArtifacts.memoryIndexPath,
|
|
1354
|
-
specCurrentPath: derivedArtifacts.specCurrentPath,
|
|
1355
|
-
specHistoryPath: derivedArtifacts.specHistoryPath,
|
|
1356
|
-
moduleDocPaths: derivedArtifacts.moduleDocs.map((doc) => doc.absolutePath),
|
|
1357
|
-
discoveryPath: outputPath,
|
|
1358
|
-
skeletonPath: skeletonContent ? skeletonPath : null
|
|
1359
|
-
};
|
|
1360
|
-
if (options.json) return output;
|
|
1361
|
-
return output;
|
|
1362
|
-
}
|
|
1363
|
-
|
|
1364
|
-
module.exports = {
|
|
1365
|
-
runScanProject,
|
|
1366
|
-
resolveSummaryMode,
|
|
1367
|
-
resolveContextMode,
|
|
1368
|
-
resolveRequestedFolders,
|
|
1369
|
-
buildScanIndexMarkdown,
|
|
1370
|
-
buildPrompt
|
|
1371
|
-
};
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* scan:project — Brownfield project scanner
|
|
5
|
+
*
|
|
6
|
+
* Walks the project directory, reads key files, calls a cheap LLM to generate:
|
|
7
|
+
* - .aioson/context/discovery.md
|
|
8
|
+
* - .aioson/context/skeleton-system.md
|
|
9
|
+
*
|
|
10
|
+
* Config: aioson-models.json in the target project root.
|
|
11
|
+
* Zero npm dependencies — uses node:fs, node:https, node:http only.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const path = require('node:path');
|
|
15
|
+
const fs = require('node:fs/promises');
|
|
16
|
+
const https = require('node:https');
|
|
17
|
+
const http = require('node:http');
|
|
18
|
+
const { ensureDir, exists, copyFileWithDir, nowStamp, toRelativeSafe } = require('../utils');
|
|
19
|
+
const { ensureGitignoreEntry, ensureProjectGitignorePolicy } = require('../installer');
|
|
20
|
+
const {
|
|
21
|
+
MEMORY_INDEX_FILE,
|
|
22
|
+
SPEC_CURRENT_FILE,
|
|
23
|
+
SPEC_HISTORY_FILE,
|
|
24
|
+
writeDerivedContextMemory
|
|
25
|
+
} = require('../context-memory');
|
|
26
|
+
|
|
27
|
+
// ── Constants ────────────────────────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
const CONFIG_FILE = 'aioson-models.json';
|
|
30
|
+
const OUTPUT_FILE = '.aioson/context/discovery.md';
|
|
31
|
+
const SKELETON_FILE = '.aioson/context/skeleton-system.md';
|
|
32
|
+
const INDEX_FILE = '.aioson/context/scan-index.md';
|
|
33
|
+
const FOLDERS_FILE = '.aioson/context/scan-folders.md';
|
|
34
|
+
const FORGE_FILE = '.aioson/context/scan-aioson.md';
|
|
35
|
+
const CONTEXT_FILE = '.aioson/context/project.context.md';
|
|
36
|
+
const SPEC_FILE = '.aioson/context/spec.md';
|
|
37
|
+
const DELIMITER = '<<<SKELETON>>>';
|
|
38
|
+
const SUMMARY_MODES = new Set(['titles', 'summaries', 'raw']);
|
|
39
|
+
const CONTEXT_MODES = new Set(['merge', 'rewrite']);
|
|
40
|
+
const FORGE_SCAN_ROOTS = [
|
|
41
|
+
'.aioson/context',
|
|
42
|
+
'.aioson/squads',
|
|
43
|
+
'.aioson/genomes',
|
|
44
|
+
'.aioson/mcp'
|
|
45
|
+
];
|
|
46
|
+
const FORGE_SECTION_ROOTS = [
|
|
47
|
+
{
|
|
48
|
+
root: '.aioson/context',
|
|
49
|
+
title: 'Context Pages',
|
|
50
|
+
empty: '_No generated context pages detected yet_'
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
root: '.aioson/squads',
|
|
54
|
+
title: 'Squads',
|
|
55
|
+
empty: '_No squads detected yet_'
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
root: '.aioson/genomes',
|
|
59
|
+
title: 'Genomes',
|
|
60
|
+
empty: '_No genomes detected yet_'
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
root: '.aioson/mcp',
|
|
64
|
+
title: 'MCP',
|
|
65
|
+
empty: '_No project-specific MCP artifacts detected yet_'
|
|
66
|
+
}
|
|
67
|
+
];
|
|
68
|
+
const FORGE_SKIP_GENERATED_FILES = new Set([
|
|
69
|
+
'.aioson/context/.gitkeep',
|
|
70
|
+
'.aioson/context/spec.md.template',
|
|
71
|
+
'.aioson/install.json'
|
|
72
|
+
]);
|
|
73
|
+
const BACKUPS_GITIGNORE_ENTRY = '.aioson/backups/';
|
|
74
|
+
|
|
75
|
+
const SKIP_DIRS = new Set([
|
|
76
|
+
'.git', 'node_modules', 'vendor', '.next', 'dist', 'build',
|
|
77
|
+
'__pycache__', '.cache', 'coverage', '.nyc_output', 'target',
|
|
78
|
+
'.gradle', 'venv', '.venv', 'env', '.env', 'storage',
|
|
79
|
+
'bootstrap/cache', '.idea', '.vscode', 'tmp', 'temp', 'logs',
|
|
80
|
+
'public/build', 'public/hot', '.aioson/backups',
|
|
81
|
+
]);
|
|
82
|
+
|
|
83
|
+
const SKIP_EXTENSIONS = new Set([
|
|
84
|
+
'.lock', '.log', '.map', '.min.js', '.min.css',
|
|
85
|
+
'.jpg', '.jpeg', '.png', '.gif', '.svg', '.ico', '.webp',
|
|
86
|
+
'.woff', '.woff2', '.ttf', '.eot', '.otf',
|
|
87
|
+
'.mp4', '.mp3', '.wav', '.avi',
|
|
88
|
+
'.zip', '.tar', '.gz', '.rar', '.7z',
|
|
89
|
+
'.pdf', '.doc', '.docx', '.xls', '.xlsx',
|
|
90
|
+
'.pyc', '.pyo', '.class', '.o', '.a', '.so',
|
|
91
|
+
'.sqlite', '.db', '.sqlite3',
|
|
92
|
+
]);
|
|
93
|
+
|
|
94
|
+
const KEY_FILE_NAMES = new Set([
|
|
95
|
+
'package.json', 'composer.json', 'requirements.txt', 'pyproject.toml',
|
|
96
|
+
'Gemfile', 'go.mod', 'Cargo.toml', 'pom.xml', 'build.gradle',
|
|
97
|
+
'docker-compose.yml', 'docker-compose.yaml', 'Dockerfile',
|
|
98
|
+
'.env.example', '.env.sample', 'README.md',
|
|
99
|
+
'schema.prisma', 'schema.rb', 'routes.rb',
|
|
100
|
+
'tsconfig.json', 'next.config.js', 'next.config.ts',
|
|
101
|
+
'vite.config.js', 'vite.config.ts',
|
|
102
|
+
'tailwind.config.js', 'tailwind.config.ts',
|
|
103
|
+
'webpack.config.js',
|
|
104
|
+
]);
|
|
105
|
+
|
|
106
|
+
const KEY_FILE_PATHS = new Set([
|
|
107
|
+
'prisma/schema.prisma',
|
|
108
|
+
'database/schema.rb',
|
|
109
|
+
'config/routes.rb',
|
|
110
|
+
'routes/web.php',
|
|
111
|
+
'routes/api.php',
|
|
112
|
+
'config/app.php',
|
|
113
|
+
'app/Http/Kernel.php',
|
|
114
|
+
'app/Providers/RouteServiceProvider.php',
|
|
115
|
+
]);
|
|
116
|
+
|
|
117
|
+
const MAX_KEY_FILE_CHARS = 3000;
|
|
118
|
+
|
|
119
|
+
const PROVIDER_BASE_URLS = {
|
|
120
|
+
deepseek: 'https://api.deepseek.com/v1',
|
|
121
|
+
openai: 'https://api.openai.com/v1',
|
|
122
|
+
gemini: 'https://generativelanguage.googleapis.com/v1beta/openai',
|
|
123
|
+
groq: 'https://api.groq.com/openai/v1',
|
|
124
|
+
together: 'https://api.together.xyz/v1',
|
|
125
|
+
mistral: 'https://api.mistral.ai/v1',
|
|
126
|
+
anthropic: null, // uses its own format
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
let managedForgePathCache = null;
|
|
130
|
+
|
|
131
|
+
// ── File system helpers ──────────────────────────────────────────────────────
|
|
132
|
+
|
|
133
|
+
async function readFileSafe(filePath, maxChars) {
|
|
134
|
+
try {
|
|
135
|
+
const content = await fs.readFile(filePath, 'utf8');
|
|
136
|
+
if (maxChars && content.length > maxChars) {
|
|
137
|
+
return content.slice(0, maxChars) + `\n... [truncated at ${maxChars} chars]`;
|
|
138
|
+
}
|
|
139
|
+
return content;
|
|
140
|
+
} catch {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async function backupProjectFiles(targetDir, relPaths) {
|
|
146
|
+
const uniqueRelPaths = [...new Set(relPaths.filter(Boolean))];
|
|
147
|
+
if (uniqueRelPaths.length === 0) {
|
|
148
|
+
return { backupRoot: null, backedUp: [] };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const backupRoot = path.join(targetDir, '.aioson/backups', nowStamp());
|
|
152
|
+
const backedUp = [];
|
|
153
|
+
|
|
154
|
+
for (const relPath of uniqueRelPaths) {
|
|
155
|
+
const source = path.join(targetDir, relPath);
|
|
156
|
+
if (!(await exists(source))) continue;
|
|
157
|
+
const dest = path.join(backupRoot, relPath);
|
|
158
|
+
await copyFileWithDir(source, dest);
|
|
159
|
+
backedUp.push(toRelativeSafe(targetDir, dest));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (backedUp.length === 0) {
|
|
163
|
+
return { backupRoot: null, backedUp: [] };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return { backupRoot, backedUp };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async function loadGitignorePatterns(root) {
|
|
170
|
+
const patterns = new Set();
|
|
171
|
+
try {
|
|
172
|
+
const gi = await fs.readFile(path.join(root, '.gitignore'), 'utf8');
|
|
173
|
+
for (const line of gi.split('\n')) {
|
|
174
|
+
const clean = line.trim().replace(/^\//, '').replace(/\/$/, '');
|
|
175
|
+
if (clean && !clean.startsWith('#')) patterns.add(clean);
|
|
176
|
+
}
|
|
177
|
+
} catch { /* no .gitignore */ }
|
|
178
|
+
return patterns;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function shouldSkip(relPath, ext, gitignorePatterns) {
|
|
182
|
+
const parts = relPath.split('/');
|
|
183
|
+
for (const part of parts) {
|
|
184
|
+
if (SKIP_DIRS.has(part)) return true;
|
|
185
|
+
if (gitignorePatterns.has(part)) return true;
|
|
186
|
+
}
|
|
187
|
+
if (SKIP_EXTENSIONS.has(ext)) return true;
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async function walkRelativeFiles(rootDir, prefix = '') {
|
|
192
|
+
const out = [];
|
|
193
|
+
let entries;
|
|
194
|
+
try {
|
|
195
|
+
entries = await fs.readdir(rootDir, { withFileTypes: true });
|
|
196
|
+
} catch {
|
|
197
|
+
return out;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
for (const entry of entries) {
|
|
201
|
+
const fullPath = path.join(rootDir, entry.name);
|
|
202
|
+
const relPath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
203
|
+
if (entry.isDirectory()) {
|
|
204
|
+
out.push(...await walkRelativeFiles(fullPath, relPath));
|
|
205
|
+
} else {
|
|
206
|
+
out.push(relPath.replace(/\\/g, '/'));
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return out;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async function loadManagedForgePaths() {
|
|
213
|
+
if (managedForgePathCache) return managedForgePathCache;
|
|
214
|
+
|
|
215
|
+
const templateForgeDir = path.join(__dirname, '..', '..', 'template', '.aioson');
|
|
216
|
+
const relPaths = await walkRelativeFiles(templateForgeDir);
|
|
217
|
+
managedForgePathCache = new Set(relPaths.map((relPath) => `.aioson/${relPath}`));
|
|
218
|
+
return managedForgePathCache;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async function walkProject(root) {
|
|
222
|
+
const gitignore = await loadGitignorePatterns(root);
|
|
223
|
+
const keyContents = {};
|
|
224
|
+
const keyFiles = [];
|
|
225
|
+
const topLevelStats = new Map();
|
|
226
|
+
const mappedEntries = [];
|
|
227
|
+
|
|
228
|
+
async function walk(dir, depth) {
|
|
229
|
+
let entries;
|
|
230
|
+
try {
|
|
231
|
+
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
232
|
+
} catch { return; }
|
|
233
|
+
|
|
234
|
+
// dirs first (alphabetical), then files (alphabetical)
|
|
235
|
+
entries.sort((a, b) => {
|
|
236
|
+
if (a.isDirectory() !== b.isDirectory()) return a.isDirectory() ? -1 : 1;
|
|
237
|
+
return a.name.localeCompare(b.name);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
for (const entry of entries) {
|
|
241
|
+
const fullPath = path.join(dir, entry.name);
|
|
242
|
+
const relPath = path.relative(root, fullPath).replace(/\\/g, '/');
|
|
243
|
+
const ext = path.extname(entry.name).toLowerCase();
|
|
244
|
+
const indent = ' '.repeat(depth);
|
|
245
|
+
|
|
246
|
+
if (shouldSkip(relPath, ext, gitignore)) continue;
|
|
247
|
+
|
|
248
|
+
if (entry.isDirectory()) {
|
|
249
|
+
mappedEntries.push({ type: 'dir', relPath, depth, sizeBytes: 0 });
|
|
250
|
+
await walk(fullPath, depth + 1);
|
|
251
|
+
} else {
|
|
252
|
+
let sizeBytes = 0;
|
|
253
|
+
try {
|
|
254
|
+
const stat = await fs.stat(fullPath);
|
|
255
|
+
sizeBytes = Number(stat.size || 0);
|
|
256
|
+
} catch {}
|
|
257
|
+
|
|
258
|
+
mappedEntries.push({ type: 'file', relPath, depth, sizeBytes });
|
|
259
|
+
|
|
260
|
+
const parts = relPath.split('/');
|
|
261
|
+
const topLevel = parts.length > 1 ? parts[0] : '[root files]';
|
|
262
|
+
const currentStat = topLevelStats.get(topLevel) || { files: 0, sizeBytes: 0 };
|
|
263
|
+
currentStat.files += 1;
|
|
264
|
+
currentStat.sizeBytes += sizeBytes;
|
|
265
|
+
topLevelStats.set(topLevel, currentStat);
|
|
266
|
+
|
|
267
|
+
const isKeyName = KEY_FILE_NAMES.has(entry.name);
|
|
268
|
+
const isKeyPath = KEY_FILE_PATHS.has(relPath) || [...KEY_FILE_PATHS].some((p) => relPath.endsWith(p));
|
|
269
|
+
if ((isKeyName || isKeyPath) && !(relPath in keyContents)) {
|
|
270
|
+
const content = await readFileSafe(fullPath, MAX_KEY_FILE_CHARS);
|
|
271
|
+
if (content) {
|
|
272
|
+
keyContents[relPath] = content;
|
|
273
|
+
keyFiles.push({
|
|
274
|
+
path: relPath,
|
|
275
|
+
sizeBytes,
|
|
276
|
+
title: inferKeyFileTitle(relPath, content),
|
|
277
|
+
summary: inferKeyFileSummary(relPath, content)
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
await walk(root, 0);
|
|
286
|
+
return { keyContents, keyFiles, topLevelStats, entries: mappedEntries };
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// ── HTTP helper (zero external deps) ────────────────────────────────────────
|
|
290
|
+
|
|
291
|
+
function httpPost(url, headers, body) {
|
|
292
|
+
return new Promise((resolve, reject) => {
|
|
293
|
+
const parsed = new URL(url);
|
|
294
|
+
const lib = parsed.protocol === 'https:' ? https : http;
|
|
295
|
+
const data = Buffer.from(JSON.stringify(body), 'utf8');
|
|
296
|
+
|
|
297
|
+
const req = lib.request({
|
|
298
|
+
hostname: parsed.hostname,
|
|
299
|
+
port: parsed.port || (parsed.protocol === 'https:' ? 443 : 80),
|
|
300
|
+
path: parsed.pathname + parsed.search,
|
|
301
|
+
method: 'POST',
|
|
302
|
+
headers: { 'Content-Type': 'application/json', 'Content-Length': data.length, ...headers },
|
|
303
|
+
}, (res) => {
|
|
304
|
+
const chunks = [];
|
|
305
|
+
res.on('data', (c) => chunks.push(c));
|
|
306
|
+
res.on('end', () => {
|
|
307
|
+
const text = Buffer.concat(chunks).toString('utf8');
|
|
308
|
+
if (res.statusCode >= 400) {
|
|
309
|
+
reject(new Error(`HTTP ${res.statusCode}: ${text.slice(0, 400)}`));
|
|
310
|
+
} else {
|
|
311
|
+
resolve(text);
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
req.setTimeout(180000, () => { req.destroy(new Error('Request timed out (180s)')); });
|
|
317
|
+
req.on('error', reject);
|
|
318
|
+
req.write(data);
|
|
319
|
+
req.end();
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// ── LLM providers ───────────────────────────────────────────────────────────
|
|
324
|
+
|
|
325
|
+
async function callOpenAICompatible(baseUrl, apiKey, model, prompt) {
|
|
326
|
+
const url = `${baseUrl.replace(/\/$/, '')}/chat/completions`;
|
|
327
|
+
const baseBody = {
|
|
328
|
+
model,
|
|
329
|
+
messages: [{ role: 'user', content: prompt }],
|
|
330
|
+
temperature: 0.2
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
let text;
|
|
334
|
+
try {
|
|
335
|
+
text = await httpPost(
|
|
336
|
+
url,
|
|
337
|
+
{ Authorization: `Bearer ${apiKey}` },
|
|
338
|
+
{ ...baseBody, max_tokens: 4096 }
|
|
339
|
+
);
|
|
340
|
+
} catch (error) {
|
|
341
|
+
const message = String(error && error.message || '');
|
|
342
|
+
const requiresMaxCompletionTokens =
|
|
343
|
+
message.includes("Unsupported parameter: 'max_tokens'") &&
|
|
344
|
+
message.includes('max_completion_tokens');
|
|
345
|
+
|
|
346
|
+
if (!requiresMaxCompletionTokens) {
|
|
347
|
+
throw error;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
text = await httpPost(
|
|
351
|
+
url,
|
|
352
|
+
{ Authorization: `Bearer ${apiKey}` },
|
|
353
|
+
{ ...baseBody, max_completion_tokens: 4096 }
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const data = JSON.parse(text);
|
|
358
|
+
return data.choices[0].message.content;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
async function callAnthropic(apiKey, model, prompt) {
|
|
362
|
+
const text = await httpPost(
|
|
363
|
+
'https://api.anthropic.com/v1/messages',
|
|
364
|
+
{ 'x-api-key': apiKey, 'anthropic-version': '2023-06-01' },
|
|
365
|
+
{ model, max_tokens: 4096, messages: [{ role: 'user', content: prompt }] }
|
|
366
|
+
);
|
|
367
|
+
const data = JSON.parse(text);
|
|
368
|
+
return data.content[0].text;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
async function callLLM(providerName, providerCfg, prompt) {
|
|
372
|
+
const apiKey = providerCfg.api_key || '';
|
|
373
|
+
const model = providerCfg.model || '';
|
|
374
|
+
const baseUrl = providerCfg.base_url || PROVIDER_BASE_URLS[providerName] || '';
|
|
375
|
+
|
|
376
|
+
if (!apiKey || apiKey.startsWith('YOUR_')) {
|
|
377
|
+
const error = new Error(`API key not configured for provider '${providerName}'`);
|
|
378
|
+
error.code = 'MISSING_API_KEY';
|
|
379
|
+
throw error;
|
|
380
|
+
}
|
|
381
|
+
if (!model) {
|
|
382
|
+
throw new Error(`Model not configured for provider '${providerName}'`);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (providerName === 'anthropic') return callAnthropic(apiKey, model, prompt);
|
|
386
|
+
if (!baseUrl) throw new Error(`No base_url for provider '${providerName}'`);
|
|
387
|
+
return callOpenAICompatible(baseUrl, apiKey, model, prompt);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// ── Prompt builder ───────────────────────────────────────────────────────────
|
|
391
|
+
|
|
392
|
+
function resolveSummaryMode(value) {
|
|
393
|
+
const normalized = String(value || '').trim().toLowerCase();
|
|
394
|
+
return SUMMARY_MODES.has(normalized) ? normalized : 'summaries';
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function resolveContextMode(value) {
|
|
398
|
+
const normalized = String(value || '').trim().toLowerCase();
|
|
399
|
+
return CONTEXT_MODES.has(normalized) ? normalized : 'merge';
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function formatBytesCompact(sizeBytes) {
|
|
403
|
+
const bytes = Number(sizeBytes || 0);
|
|
404
|
+
const kb = bytes / 1024;
|
|
405
|
+
const mb = kb / 1024;
|
|
406
|
+
if (mb >= 1) return `${mb.toFixed(mb >= 10 ? 1 : 2)} MB`;
|
|
407
|
+
return `${kb.toFixed(kb >= 10 ? 1 : 2)} KB`;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function humanizeName(value) {
|
|
411
|
+
return String(value || '')
|
|
412
|
+
.replace(/\.[^.]+$/, '')
|
|
413
|
+
.replace(/[_-]+/g, ' ')
|
|
414
|
+
.replace(/\b\w/g, (char) => char.toUpperCase());
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function firstMeaningfulLine(content) {
|
|
418
|
+
const lines = String(content || '')
|
|
419
|
+
.split('\n')
|
|
420
|
+
.map((line) => line.trim())
|
|
421
|
+
.filter(Boolean);
|
|
422
|
+
|
|
423
|
+
for (const line of lines) {
|
|
424
|
+
if (line.startsWith('#')) return line.replace(/^#+\s*/, '').slice(0, 120);
|
|
425
|
+
if (line.startsWith('{') || line.startsWith('[') || line.startsWith('<') || line.startsWith('---')) continue;
|
|
426
|
+
if (line.length < 4) continue;
|
|
427
|
+
return line.slice(0, 120);
|
|
428
|
+
}
|
|
429
|
+
return '';
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function inferFrameworkClues(pkg) {
|
|
433
|
+
const deps = {
|
|
434
|
+
...(pkg.dependencies || {}),
|
|
435
|
+
...(pkg.devDependencies || {})
|
|
436
|
+
};
|
|
437
|
+
const clues = [];
|
|
438
|
+
if (deps.next) clues.push('Next.js');
|
|
439
|
+
if (deps.react) clues.push('React');
|
|
440
|
+
if (deps.vue) clues.push('Vue');
|
|
441
|
+
if (deps.nuxt) clues.push('Nuxt');
|
|
442
|
+
if (deps.express) clues.push('Express');
|
|
443
|
+
if (deps.nestjs || deps['@nestjs/core']) clues.push('NestJS');
|
|
444
|
+
return clues;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function inferKeyFileTitle(relPath, content) {
|
|
448
|
+
const base = path.basename(relPath).toLowerCase();
|
|
449
|
+
|
|
450
|
+
if (base === 'package.json') {
|
|
451
|
+
try {
|
|
452
|
+
const pkg = JSON.parse(content);
|
|
453
|
+
if (pkg && pkg.name) return `${pkg.name} package manifest`;
|
|
454
|
+
} catch {}
|
|
455
|
+
return 'NPM package manifest';
|
|
456
|
+
}
|
|
457
|
+
if (base === 'composer.json') return 'Composer package manifest';
|
|
458
|
+
if (base === 'readme.md') {
|
|
459
|
+
const headline = firstMeaningfulLine(content);
|
|
460
|
+
return headline || 'Project overview';
|
|
461
|
+
}
|
|
462
|
+
if (base === 'dockerfile') return 'Container build recipe';
|
|
463
|
+
if (base.startsWith('next.config')) return 'Next.js runtime configuration';
|
|
464
|
+
if (base.startsWith('vite.config')) return 'Vite build configuration';
|
|
465
|
+
if (base.startsWith('tailwind.config')) return 'Tailwind theme configuration';
|
|
466
|
+
if (base === 'tsconfig.json') return 'TypeScript compiler configuration';
|
|
467
|
+
if (relPath.includes('routes/')) return `${humanizeName(base)} route map`;
|
|
468
|
+
if (relPath.includes('schema.prisma') || base === 'schema.rb') return 'Database schema definition';
|
|
469
|
+
return humanizeName(base || relPath);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
function inferKeyFileSummary(relPath, content) {
|
|
473
|
+
const base = path.basename(relPath).toLowerCase();
|
|
474
|
+
|
|
475
|
+
if (base === 'package.json') {
|
|
476
|
+
try {
|
|
477
|
+
const pkg = JSON.parse(content);
|
|
478
|
+
const scripts = Object.keys(pkg.scripts || {}).length;
|
|
479
|
+
const depsCount = Object.keys(pkg.dependencies || {}).length + Object.keys(pkg.devDependencies || {}).length;
|
|
480
|
+
const clues = inferFrameworkClues(pkg);
|
|
481
|
+
const pieces = [`Scripts: ${scripts}`, `Dependencies: ${depsCount}`];
|
|
482
|
+
if (clues.length > 0) pieces.push(`Framework clues: ${clues.join(', ')}`);
|
|
483
|
+
return pieces.join(' | ');
|
|
484
|
+
} catch {
|
|
485
|
+
return 'Package metadata, scripts and dependency graph.';
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
if (base === 'composer.json') return 'PHP dependencies, autoload rules and package metadata.';
|
|
490
|
+
if (base === 'requirements.txt' || base === 'pyproject.toml') return 'Python dependencies and project metadata.';
|
|
491
|
+
if (base === 'readme.md') return firstMeaningfulLine(content) || 'Project overview, setup notes and developer guidance.';
|
|
492
|
+
if (base === 'dockerfile' || base.startsWith('docker-compose')) return 'Container runtime and service topology.';
|
|
493
|
+
if (base.startsWith('next.config')) return 'Next.js configuration for routing, build and runtime behavior.';
|
|
494
|
+
if (base.startsWith('vite.config')) return 'Bundler and development server configuration.';
|
|
495
|
+
if (base.startsWith('tailwind.config')) return 'Design tokens, theme extensions and content scan paths.';
|
|
496
|
+
if (base === 'tsconfig.json') return 'TypeScript path aliases, compiler options and module targets.';
|
|
497
|
+
if (relPath.includes('routes/')) return 'Entry points and HTTP route declarations.';
|
|
498
|
+
if (relPath.includes('schema.prisma') || base === 'schema.rb') return 'Entities, fields and relationship structure for the data model.';
|
|
499
|
+
if (base === '.env.example' || base === '.env.sample') return 'Environment variable template and required secrets.';
|
|
500
|
+
|
|
501
|
+
return firstMeaningfulLine(content) || `Key implementation or configuration file detected at ${relPath}.`;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function renderEntryTreeLines(entries, predicate) {
|
|
505
|
+
const lines = [];
|
|
506
|
+
for (const entry of entries) {
|
|
507
|
+
if (!predicate(entry)) continue;
|
|
508
|
+
const label = `${path.basename(entry.relPath)}${entry.type === 'dir' ? '/' : ''}`;
|
|
509
|
+
lines.push(renderTreeLine(label, entry.depth));
|
|
510
|
+
}
|
|
511
|
+
return lines;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
function renderTreeLine(label, depth) {
|
|
515
|
+
return `${'| '.repeat(Math.max(0, depth))}|-- ${label}`;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function normalizeFolderPath(value) {
|
|
519
|
+
const normalized = String(value || '')
|
|
520
|
+
.trim()
|
|
521
|
+
.replace(/\\/g, '/')
|
|
522
|
+
.replace(/^\.\/+/, '')
|
|
523
|
+
.replace(/\/+/g, '/')
|
|
524
|
+
.replace(/\/$/, '');
|
|
525
|
+
|
|
526
|
+
if (!normalized || normalized === '.') return '.';
|
|
527
|
+
return normalized;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function resolveRequestedFolders(value) {
|
|
531
|
+
const rawValues = Array.isArray(value) ? value : [value];
|
|
532
|
+
const folders = [];
|
|
533
|
+
const seen = new Set();
|
|
534
|
+
|
|
535
|
+
for (const rawValue of rawValues) {
|
|
536
|
+
const parts = String(rawValue || '')
|
|
537
|
+
.split(',')
|
|
538
|
+
.map((part) => normalizeFolderPath(part))
|
|
539
|
+
.filter((part) => part && part !== '.');
|
|
540
|
+
|
|
541
|
+
for (const folder of parts) {
|
|
542
|
+
if (seen.has(folder)) continue;
|
|
543
|
+
seen.add(folder);
|
|
544
|
+
folders.push(folder);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
return folders;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
function isWithinPrefix(relPath, prefix) {
|
|
552
|
+
return relPath === prefix || relPath.startsWith(`${prefix}/`);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
function isWithinAnyPrefix(relPath, prefixes) {
|
|
556
|
+
return prefixes.some((prefix) => isWithinPrefix(relPath, prefix));
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function collectForgeArtifactPaths(entries, managedForgePaths) {
|
|
560
|
+
const included = new Set();
|
|
561
|
+
|
|
562
|
+
for (const entry of entries) {
|
|
563
|
+
if (!isWithinAnyPrefix(entry.relPath, FORGE_SCAN_ROOTS)) continue;
|
|
564
|
+
|
|
565
|
+
if (entry.type === 'file') {
|
|
566
|
+
if (managedForgePaths.has(entry.relPath)) continue;
|
|
567
|
+
if (FORGE_SKIP_GENERATED_FILES.has(entry.relPath)) continue;
|
|
568
|
+
included.add(entry.relPath);
|
|
569
|
+
continue;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
if (entry.type === 'dir' && !FORGE_SCAN_ROOTS.includes(entry.relPath)) {
|
|
573
|
+
included.add(entry.relPath);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
if (included.size === 0) return included;
|
|
578
|
+
|
|
579
|
+
const withAncestors = new Set(included);
|
|
580
|
+
for (const relPath of included) {
|
|
581
|
+
let current = relPath;
|
|
582
|
+
while (current && current.includes('/')) {
|
|
583
|
+
current = current.slice(0, current.lastIndexOf('/'));
|
|
584
|
+
if (!current) break;
|
|
585
|
+
withAncestors.add(current);
|
|
586
|
+
if (current === '.aioson') break;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
return withAncestors;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
function buildFolderMapMarkdown({ entries, generatedAt }) {
|
|
593
|
+
const lines = [
|
|
594
|
+
'# Folder Map',
|
|
595
|
+
`_Generated by aioson scan:project — ${generatedAt}_`,
|
|
596
|
+
'',
|
|
597
|
+
'## Scope',
|
|
598
|
+
'- Project directories only.',
|
|
599
|
+
'- `.aioson/` internals are intentionally omitted here and tracked in `scan-aioson.md`.',
|
|
600
|
+
'',
|
|
601
|
+
'## Tree'
|
|
602
|
+
];
|
|
603
|
+
|
|
604
|
+
const treeLines = renderEntryTreeLines(entries, (entry) => {
|
|
605
|
+
if (entry.type !== 'dir') return false;
|
|
606
|
+
if (entry.relPath === '.aioson') return true;
|
|
607
|
+
return !entry.relPath.startsWith('.aioson/');
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
if (treeLines.length === 0) {
|
|
611
|
+
lines.push('_No directories mapped_');
|
|
612
|
+
return lines.join('\n');
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
lines.push('```text', ...treeLines, '```');
|
|
616
|
+
return lines.join('\n');
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
function sanitizeScanFileSegment(folder) {
|
|
620
|
+
const normalized = normalizeFolderPath(folder);
|
|
621
|
+
if (normalized === '.') return 'root';
|
|
622
|
+
return normalized
|
|
623
|
+
.replace(/[/.]+/g, '-')
|
|
624
|
+
.replace(/[^a-zA-Z0-9_-]+/g, '-')
|
|
625
|
+
.replace(/-+/g, '-')
|
|
626
|
+
.replace(/^-|-$/g, '') || 'folder';
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
function buildFolderScanRelativePath(folder) {
|
|
630
|
+
return `.aioson/context/scan-${sanitizeScanFileSegment(folder)}.md`;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
function renderRequestedFolderTree(entries, folder) {
|
|
634
|
+
const normalized = normalizeFolderPath(folder);
|
|
635
|
+
const lines = [];
|
|
636
|
+
|
|
637
|
+
if (normalized === '.') {
|
|
638
|
+
return renderEntryTreeLines(entries, () => true);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
lines.push(renderTreeLine(`${normalized}/`, 0));
|
|
642
|
+
for (const entry of entries) {
|
|
643
|
+
if (!isWithinPrefix(entry.relPath, normalized) || entry.relPath === normalized) continue;
|
|
644
|
+
const relativePath = entry.relPath.slice(normalized.length + 1);
|
|
645
|
+
const depth = relativePath.split('/').length;
|
|
646
|
+
const label = `${path.basename(entry.relPath)}${entry.type === 'dir' ? '/' : ''}`;
|
|
647
|
+
lines.push(renderTreeLine(label, depth));
|
|
648
|
+
}
|
|
649
|
+
return lines;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
function buildRequestedFolderMarkdown({ entries, generatedAt, folder }) {
|
|
653
|
+
const normalized = normalizeFolderPath(folder);
|
|
654
|
+
const lines = [
|
|
655
|
+
`# Folder Scan: ${normalized}`,
|
|
656
|
+
`_Generated by aioson scan:project — ${generatedAt}_`,
|
|
657
|
+
'',
|
|
658
|
+
'## Scope',
|
|
659
|
+
`- Requested folder: \`${normalized}/\``,
|
|
660
|
+
'- Includes all mapped directories and files under this folder.',
|
|
661
|
+
'',
|
|
662
|
+
'## Tree'
|
|
663
|
+
];
|
|
664
|
+
|
|
665
|
+
const treeLines = renderRequestedFolderTree(entries, normalized);
|
|
666
|
+
if (treeLines.length === 0) {
|
|
667
|
+
lines.push('_No mapped entries for this folder_');
|
|
668
|
+
return lines.join('\n');
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
lines.push('```text', ...treeLines, '```');
|
|
672
|
+
return lines.join('\n');
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
function renderForgeSectionTree(entries, root, artifactPaths) {
|
|
676
|
+
const sectionEntries = entries.filter((entry) => artifactPaths.has(entry.relPath) && isWithinPrefix(entry.relPath, root));
|
|
677
|
+
if (sectionEntries.length === 0) return [];
|
|
678
|
+
|
|
679
|
+
const lines = [renderTreeLine(`${root}/`, 0)];
|
|
680
|
+
for (const entry of sectionEntries) {
|
|
681
|
+
if (entry.relPath === root) continue;
|
|
682
|
+
const relativePath = entry.relPath.slice(root.length + 1);
|
|
683
|
+
const depth = relativePath.split('/').length;
|
|
684
|
+
const label = `${path.basename(entry.relPath)}${entry.type === 'dir' ? '/' : ''}`;
|
|
685
|
+
lines.push(renderTreeLine(label, depth));
|
|
686
|
+
}
|
|
687
|
+
return lines;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
function buildForgeArtifactsMarkdown({ entries, generatedAt, managedForgePaths }) {
|
|
691
|
+
const lines = [
|
|
692
|
+
'# AIOSON Generated Map',
|
|
693
|
+
`_Generated by aioson scan:project — ${generatedAt}_`,
|
|
694
|
+
'',
|
|
695
|
+
'## Scope',
|
|
696
|
+
'- Shows generated or project-specific artifacts inside `.aioson/`.',
|
|
697
|
+
'- Groups what matters for client analysis, especially context pages, squads, genomes and local MCP artifacts.',
|
|
698
|
+
'- Hides framework-managed defaults such as agents, locales, schemas, static skills and task docs.'
|
|
699
|
+
];
|
|
700
|
+
|
|
701
|
+
const artifactPaths = collectForgeArtifactPaths(entries, managedForgePaths);
|
|
702
|
+
if (artifactPaths.size === 0) {
|
|
703
|
+
lines.push('', '_No generated AIOSON artifacts detected yet_');
|
|
704
|
+
return { markdown: lines.join('\n'), artifactCount: 0 };
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
let artifactCount = 0;
|
|
708
|
+
for (const section of FORGE_SECTION_ROOTS) {
|
|
709
|
+
lines.push('', `## ${section.title}`);
|
|
710
|
+
const treeLines = renderForgeSectionTree(entries, section.root, artifactPaths);
|
|
711
|
+
if (treeLines.length === 0) {
|
|
712
|
+
lines.push(section.empty);
|
|
713
|
+
continue;
|
|
714
|
+
}
|
|
715
|
+
artifactCount += treeLines.length;
|
|
716
|
+
lines.push('```text', ...treeLines, '```');
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
return { markdown: lines.join('\n'), artifactCount };
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
function buildScanIndexMarkdown({
|
|
723
|
+
keyFiles,
|
|
724
|
+
topLevelStats,
|
|
725
|
+
generatedAt,
|
|
726
|
+
includeSummaries = true,
|
|
727
|
+
foldersPath,
|
|
728
|
+
folderScans = [],
|
|
729
|
+
forgePath,
|
|
730
|
+
forgeArtifactCount = 0,
|
|
731
|
+
memoryIndexPath = null,
|
|
732
|
+
specCurrentPath = null,
|
|
733
|
+
specHistoryPath = null,
|
|
734
|
+
moduleDocs = []
|
|
735
|
+
}) {
|
|
736
|
+
const lines = [
|
|
737
|
+
'# Scan Index',
|
|
738
|
+
`_Generated by aioson scan:project — ${generatedAt}_`,
|
|
739
|
+
'',
|
|
740
|
+
'## Scan outputs',
|
|
741
|
+
'| File | Purpose |',
|
|
742
|
+
'|------|---------|',
|
|
743
|
+
`| ${INDEX_FILE} | Summary index with footprint, key files and links to specialized scan maps |`,
|
|
744
|
+
`| ${FOLDERS_FILE} | Directory-only map of the project |`,
|
|
745
|
+
...folderScans.map((scan) =>
|
|
746
|
+
`| ${scan.relativePath} | Full folder and file map for requested folder \`${scan.folder}/\` |`
|
|
747
|
+
),
|
|
748
|
+
`| ${FORGE_FILE} | Generated or project-specific artifacts inside .aioson/ |`,
|
|
749
|
+
...(memoryIndexPath
|
|
750
|
+
? [`| ${MEMORY_INDEX_FILE} | Read-this-first index of context docs and when to load them |`]
|
|
751
|
+
: []),
|
|
752
|
+
...(specCurrentPath
|
|
753
|
+
? [`| ${SPEC_CURRENT_FILE} | Current development snapshot derived from spec.md |`]
|
|
754
|
+
: []),
|
|
755
|
+
...(specHistoryPath
|
|
756
|
+
? [`| ${SPEC_HISTORY_FILE} | Historical implementation and decision view derived from spec.md |`]
|
|
757
|
+
: []),
|
|
758
|
+
...moduleDocs.map((doc) =>
|
|
759
|
+
`| ${doc.relativePath} | Focused module memory for requested folder \`${doc.folder}/\` |`
|
|
760
|
+
),
|
|
761
|
+
'',
|
|
762
|
+
`- Folder map: \`${foldersPath}\``,
|
|
763
|
+
...(
|
|
764
|
+
folderScans.length === 0
|
|
765
|
+
? ['- Requested folder scans: none']
|
|
766
|
+
: folderScans.map((scan) => `- Folder \`${scan.folder}/\`: \`${scan.absolutePath}\``)
|
|
767
|
+
),
|
|
768
|
+
`- AIOSON generated map: \`${forgePath}\``,
|
|
769
|
+
`- AIOSON generated entries: ${forgeArtifactCount}`,
|
|
770
|
+
...(memoryIndexPath ? [`- Memory index: \`${memoryIndexPath}\``] : []),
|
|
771
|
+
...(specCurrentPath ? [`- Spec current view: \`${specCurrentPath}\``] : []),
|
|
772
|
+
...(specHistoryPath ? [`- Spec history view: \`${specHistoryPath}\``] : []),
|
|
773
|
+
...moduleDocs.map((doc) => `- Module memory \`${doc.folder}/\`: \`${doc.absolutePath}\``),
|
|
774
|
+
'',
|
|
775
|
+
'## Top-level footprint',
|
|
776
|
+
'| Path | Files | Approx size |',
|
|
777
|
+
'|------|-------|-------------|'
|
|
778
|
+
];
|
|
779
|
+
|
|
780
|
+
const topLevelRows = [...topLevelStats.entries()].sort((a, b) => a[0].localeCompare(b[0]));
|
|
781
|
+
if (topLevelRows.length === 0) {
|
|
782
|
+
lines.push('| [root files] | 0 | 0 KB |');
|
|
783
|
+
} else {
|
|
784
|
+
for (const [name, stat] of topLevelRows) {
|
|
785
|
+
lines.push(`| ${name} | ${stat.files} | ${formatBytesCompact(stat.sizeBytes)} |`);
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
lines.push('', '## Key files');
|
|
790
|
+
if (!keyFiles || keyFiles.length === 0) {
|
|
791
|
+
lines.push('- No key files detected.');
|
|
792
|
+
} else {
|
|
793
|
+
for (const file of keyFiles.slice(0, 20)) {
|
|
794
|
+
lines.push(`### ${file.path}`);
|
|
795
|
+
lines.push(`- Title: ${file.title}`);
|
|
796
|
+
if (includeSummaries) lines.push(`- Summary: ${file.summary}`);
|
|
797
|
+
lines.push(`- Approx size: ${formatBytesCompact(file.sizeBytes)}`);
|
|
798
|
+
lines.push('');
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
return lines.join('\n');
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
function buildPrompt({
|
|
805
|
+
scanIndexMarkdown,
|
|
806
|
+
folderMapMarkdown,
|
|
807
|
+
folderScans = [],
|
|
808
|
+
forgeMapMarkdown,
|
|
809
|
+
keyContents,
|
|
810
|
+
projectContext,
|
|
811
|
+
specContent,
|
|
812
|
+
existingDiscoveryContent,
|
|
813
|
+
existingSkeletonContent,
|
|
814
|
+
summaryMode
|
|
815
|
+
}) {
|
|
816
|
+
const now = new Date().toISOString().replace(/\.\d+Z$/, 'Z');
|
|
817
|
+
const parts = ['You are analyzing a software project to generate a structured discovery document.\n'];
|
|
818
|
+
|
|
819
|
+
if (projectContext) {
|
|
820
|
+
parts.push(`## Project Context (aioson)\n\`\`\`\n${projectContext}\n\`\`\`\n`);
|
|
821
|
+
}
|
|
822
|
+
parts.push(`## Scan Index\n\`\`\`md\n${scanIndexMarkdown}\n\`\`\`\n`);
|
|
823
|
+
parts.push(`## Folder Map\n\`\`\`md\n${folderMapMarkdown}\n\`\`\`\n`);
|
|
824
|
+
for (const scan of folderScans) {
|
|
825
|
+
parts.push(`## Folder Scan: ${scan.folder}\n\`\`\`md\n${scan.markdown}\n\`\`\`\n`);
|
|
826
|
+
}
|
|
827
|
+
parts.push(`## AIOSON Generated Map\n\`\`\`md\n${forgeMapMarkdown}\n\`\`\`\n`);
|
|
828
|
+
|
|
829
|
+
if (summaryMode === 'raw' && Object.keys(keyContents).length > 0) {
|
|
830
|
+
parts.push('## Key Files\n');
|
|
831
|
+
for (const [filePath, content] of Object.entries(keyContents).slice(0, 12)) {
|
|
832
|
+
parts.push(`### ${filePath}\n\`\`\`\n${content}\n\`\`\`\n`);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
if (specContent) {
|
|
837
|
+
parts.push(`## Development Memory (spec.md)\n\`\`\`\n${specContent}\n\`\`\`\n`);
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
if (existingDiscoveryContent) {
|
|
841
|
+
parts.push(`## Existing Discovery Memory (update in place)\n\`\`\`md\n${existingDiscoveryContent}\n\`\`\`\n`);
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
if (existingSkeletonContent) {
|
|
845
|
+
parts.push(`## Existing Skeleton Memory (update in place)\n\`\`\`md\n${existingSkeletonContent}\n\`\`\`\n`);
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
parts.push(`
|
|
849
|
+
## Task
|
|
850
|
+
Generate TWO documents. Separate them with exactly this delimiter on its own line:
|
|
851
|
+
<<<SKELETON>>>
|
|
852
|
+
|
|
853
|
+
If existing discovery or skeleton documents were provided above, treat them as the current memory baseline and UPDATE them in place.
|
|
854
|
+
- Preserve stable system knowledge, conventions, and still-valid human notes.
|
|
855
|
+
- Remove or correct only what is clearly contradicted by the current scan, project.context.md, or spec.md.
|
|
856
|
+
- Do not throw away useful prior context just because the current scan sample is smaller.
|
|
857
|
+
- Keep the required output sections exactly as specified below.
|
|
858
|
+
|
|
859
|
+
### Document 1: \`.aioson/context/discovery.md\`
|
|
860
|
+
Generate with exactly these sections:
|
|
861
|
+
|
|
862
|
+
# Discovery
|
|
863
|
+
|
|
864
|
+
## 1. What this project builds
|
|
865
|
+
2-3 objective lines describing what the system does.
|
|
866
|
+
|
|
867
|
+
## 2. Project structure overview
|
|
868
|
+
Key directories and their responsibilities. Identify the architectural pattern (MVC, layered, feature-based, etc.).
|
|
869
|
+
|
|
870
|
+
## 3. Key entities and relationships
|
|
871
|
+
Entities inferred from models, migrations, or schema files. Include relationships if detectable.
|
|
872
|
+
|
|
873
|
+
## 4. Entry points and routes
|
|
874
|
+
Main route files, controllers, or API handlers identified.
|
|
875
|
+
|
|
876
|
+
## 5. Dependencies and services
|
|
877
|
+
Key packages from package.json / composer.json / requirements.txt. External services detected.
|
|
878
|
+
|
|
879
|
+
## 6. Existing patterns and conventions
|
|
880
|
+
Coding patterns already in use (naming, folder organization, auth approach, etc.). These must be preserved.
|
|
881
|
+
|
|
882
|
+
## 7. Development state
|
|
883
|
+
What appears to be done, in-progress, or missing. Use spec.md if available.
|
|
884
|
+
|
|
885
|
+
## 8. Risks and technical debt
|
|
886
|
+
Issues, inconsistencies, or missing pieces that could become problems.
|
|
887
|
+
|
|
888
|
+
## 9. What to preserve
|
|
889
|
+
Explicit list of conventions and structures the AI must NOT change or override.
|
|
890
|
+
|
|
891
|
+
---
|
|
892
|
+
_Generated by aioson scan:project — ${now}_
|
|
893
|
+
|
|
894
|
+
<<<SKELETON>>>
|
|
895
|
+
|
|
896
|
+
### Document 2: \`.aioson/context/skeleton-system.md\`
|
|
897
|
+
A lightweight living index of the system. Keep it concise — AI agents read this frequently as a quick-reference index. Do NOT repeat the full analysis from Document 1 here.
|
|
898
|
+
|
|
899
|
+
Generate with exactly this format:
|
|
900
|
+
|
|
901
|
+
# System Skeleton
|
|
902
|
+
_Generated by aioson scan:project — ${now}_
|
|
903
|
+
|
|
904
|
+
## File map
|
|
905
|
+
Indented tree of key files and directories grouped by domain/module.
|
|
906
|
+
Skip: detailed migration lists, test fixtures, config boilerplate, lock files.
|
|
907
|
+
Mark each module or file with inferred status:
|
|
908
|
+
✓ complete — code present and appears fully implemented
|
|
909
|
+
◑ partial — scaffolded or incomplete implementation
|
|
910
|
+
○ missing — referenced but not found or empty
|
|
911
|
+
|
|
912
|
+
## Key routes
|
|
913
|
+
Main routes mapped to their handlers. One per line.
|
|
914
|
+
Format: \`METHOD /path → Handler@method\`
|
|
915
|
+
Skip standard auth boilerplate (login/logout/password-reset) unless customized.
|
|
916
|
+
If no route files found: _No route files detected_
|
|
917
|
+
|
|
918
|
+
## Module status
|
|
919
|
+
| Module | Status | Key files |
|
|
920
|
+
|--------|--------|-----------|
|
|
921
|
+
One row per logical module or feature area.
|
|
922
|
+
Status: ✓ done | ◑ in-progress | ○ pending
|
|
923
|
+
|
|
924
|
+
## Key relationships
|
|
925
|
+
Entity relationships in plain English, one per line.
|
|
926
|
+
Example: \`User hasMany Orders → OrderItem → Product\`
|
|
927
|
+
If no models/schema found: _No entities detected_
|
|
928
|
+
`);
|
|
929
|
+
|
|
930
|
+
return parts.join('\n');
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
function listTopLevelDirectories(entries) {
|
|
934
|
+
const names = new Set();
|
|
935
|
+
for (const entry of entries) {
|
|
936
|
+
if (entry.type !== 'dir') continue;
|
|
937
|
+
const topLevel = entry.relPath.split('/')[0];
|
|
938
|
+
if (topLevel) names.add(topLevel);
|
|
939
|
+
}
|
|
940
|
+
return [...names].sort((a, b) => a.localeCompare(b));
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
// ── Main ─────────────────────────────────────────────────────────────────────
|
|
944
|
+
|
|
945
|
+
async function runScanProject({ args, options = {}, logger, t }) {
|
|
946
|
+
const targetArg = args[0] || '.';
|
|
947
|
+
const targetDir = path.resolve(process.cwd(), targetArg);
|
|
948
|
+
const summaryMode = resolveSummaryMode(options['summary-mode']);
|
|
949
|
+
const contextMode = resolveContextMode(options['context-mode']);
|
|
950
|
+
const requestedFolders = resolveRequestedFolders(options.folder);
|
|
951
|
+
const llmRequested = Boolean(options['with-llm']);
|
|
952
|
+
const llmModelOverride = String(options['llm-model'] || options.model || '').trim();
|
|
953
|
+
|
|
954
|
+
logger.log(t('scan_project.scanning', { dir: targetDir }));
|
|
955
|
+
|
|
956
|
+
if (requestedFolders.length === 0) {
|
|
957
|
+
logger.error(t('scan_project.folder_required'));
|
|
958
|
+
logger.error(t('scan_project.folder_required_examples_title'));
|
|
959
|
+
logger.error(t('scan_project.folder_required_example_local'));
|
|
960
|
+
logger.error(t('scan_project.folder_required_example_multi'));
|
|
961
|
+
logger.error(t('scan_project.folder_required_example_llm'));
|
|
962
|
+
logger.error(t('scan_project.folder_required_example_cli'));
|
|
963
|
+
logger.error(t('scan_project.folder_required_example_prompt'));
|
|
964
|
+
logger.error(t('scan_project.folder_required_example_next'));
|
|
965
|
+
process.exitCode = 1;
|
|
966
|
+
return { ok: false, error: 'folder_required' };
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
let providerName = null;
|
|
970
|
+
let providerCfg = null;
|
|
971
|
+
let model = null;
|
|
972
|
+
|
|
973
|
+
if (!options['dry-run']) {
|
|
974
|
+
const gitignoreRulesAdded = await ensureProjectGitignorePolicy(targetDir);
|
|
975
|
+
if (gitignoreRulesAdded > 0) {
|
|
976
|
+
logger.log(t('scan_project.gitignore_policy_written', { path: path.join(targetDir, '.gitignore') }));
|
|
977
|
+
logger.log(t('scan_project.gitignore_tracked_note'));
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
if (!llmRequested) {
|
|
982
|
+
logger.log(t('scan_project.local_only'));
|
|
983
|
+
} else if (!options['dry-run']) {
|
|
984
|
+
const configPath = path.join(targetDir, CONFIG_FILE);
|
|
985
|
+
if (!(await exists(configPath))) {
|
|
986
|
+
logger.error(t('scan_project.config_missing', { file: CONFIG_FILE }));
|
|
987
|
+
process.exitCode = 1;
|
|
988
|
+
return { ok: false, error: 'config_not_found' };
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
let config;
|
|
992
|
+
try {
|
|
993
|
+
config = JSON.parse(await fs.readFile(configPath, 'utf8'));
|
|
994
|
+
} catch (err) {
|
|
995
|
+
logger.error(t('scan_project.config_invalid', { error: err.message }));
|
|
996
|
+
process.exitCode = 1;
|
|
997
|
+
return { ok: false, error: 'config_invalid' };
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
providerName = String(options.provider || config.preferred_scan_provider || '');
|
|
1001
|
+
const providers = config.providers || {};
|
|
1002
|
+
|
|
1003
|
+
if (!providerName || !providers[providerName]) {
|
|
1004
|
+
const available = Object.keys(providers).join(', ') || '(none)';
|
|
1005
|
+
logger.error(t('scan_project.provider_missing', { provider: providerName, available }));
|
|
1006
|
+
process.exitCode = 1;
|
|
1007
|
+
return { ok: false, error: 'provider_not_found' };
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
providerCfg = { ...providers[providerName] };
|
|
1011
|
+
if (llmModelOverride) providerCfg.model = llmModelOverride;
|
|
1012
|
+
model = providerCfg.model || '?';
|
|
1013
|
+
logger.log(t('scan_project.provider_info', { provider: providerName, model }));
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
// Read context files
|
|
1017
|
+
const projectContext = await readFileSafe(path.join(targetDir, CONTEXT_FILE));
|
|
1018
|
+
const specContent = await readFileSafe(path.join(targetDir, SPEC_FILE));
|
|
1019
|
+
const existingDiscoveryPath = path.join(targetDir, OUTPUT_FILE);
|
|
1020
|
+
const existingSkeletonPath = path.join(targetDir, SKELETON_FILE);
|
|
1021
|
+
const existingDiscoveryContent = contextMode === 'merge' ? await readFileSafe(existingDiscoveryPath) : null;
|
|
1022
|
+
const existingSkeletonContent = contextMode === 'merge' ? await readFileSafe(existingSkeletonPath) : null;
|
|
1023
|
+
|
|
1024
|
+
if (projectContext) logger.log(t('scan_project.context_found'));
|
|
1025
|
+
else logger.log(t('scan_project.context_missing'));
|
|
1026
|
+
if (specContent) logger.log(t('scan_project.spec_found'));
|
|
1027
|
+
if (llmRequested && existingDiscoveryContent) logger.log(t('scan_project.existing_discovery_found', { path: existingDiscoveryPath }));
|
|
1028
|
+
if (llmRequested && existingSkeletonContent) logger.log(t('scan_project.existing_skeleton_found', { path: existingSkeletonPath }));
|
|
1029
|
+
if (llmRequested && (existingDiscoveryContent || existingSkeletonContent)) logger.log(t('scan_project.context_update_mode'));
|
|
1030
|
+
if (llmRequested) logger.log(t('scan_project.context_mode', { mode: contextMode }));
|
|
1031
|
+
|
|
1032
|
+
// Walk project
|
|
1033
|
+
logger.log(t('scan_project.walking'));
|
|
1034
|
+
const { keyContents, keyFiles, topLevelStats, entries } = await walkProject(targetDir);
|
|
1035
|
+
logger.log(t('scan_project.walk_done', {
|
|
1036
|
+
files: entries.filter((entry) => entry.type === 'file').length,
|
|
1037
|
+
keys: Object.keys(keyContents).length
|
|
1038
|
+
}));
|
|
1039
|
+
|
|
1040
|
+
const generatedAt = new Date().toISOString().replace(/\.\d+Z$/, 'Z');
|
|
1041
|
+
const managedForgePaths = await loadManagedForgePaths();
|
|
1042
|
+
const folderMapMarkdown = buildFolderMapMarkdown({
|
|
1043
|
+
entries,
|
|
1044
|
+
generatedAt
|
|
1045
|
+
});
|
|
1046
|
+
const forgeArtifacts = buildForgeArtifactsMarkdown({
|
|
1047
|
+
entries,
|
|
1048
|
+
generatedAt,
|
|
1049
|
+
managedForgePaths
|
|
1050
|
+
});
|
|
1051
|
+
|
|
1052
|
+
const availableTopLevelDirs = listTopLevelDirectories(entries);
|
|
1053
|
+
for (const folder of requestedFolders) {
|
|
1054
|
+
if (folder === '.') continue;
|
|
1055
|
+
const existsAsDirectory = entries.some((entry) => entry.type === 'dir' && entry.relPath === folder);
|
|
1056
|
+
if (!existsAsDirectory) {
|
|
1057
|
+
logger.error(t('scan_project.folder_not_found', {
|
|
1058
|
+
folder,
|
|
1059
|
+
available: availableTopLevelDirs.join(', ') || '(none)'
|
|
1060
|
+
}));
|
|
1061
|
+
process.exitCode = 1;
|
|
1062
|
+
return { ok: false, error: 'folder_not_found' };
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
const folderScans = requestedFolders.map((folder) => {
|
|
1067
|
+
const relativePath = buildFolderScanRelativePath(folder);
|
|
1068
|
+
return {
|
|
1069
|
+
folder,
|
|
1070
|
+
relativePath,
|
|
1071
|
+
absolutePath: path.join(targetDir, relativePath),
|
|
1072
|
+
markdown: buildRequestedFolderMarkdown({
|
|
1073
|
+
entries,
|
|
1074
|
+
generatedAt,
|
|
1075
|
+
folder
|
|
1076
|
+
})
|
|
1077
|
+
};
|
|
1078
|
+
});
|
|
1079
|
+
|
|
1080
|
+
const scanIndexPath = path.join(targetDir, INDEX_FILE);
|
|
1081
|
+
const scanFoldersPath = path.join(targetDir, FOLDERS_FILE);
|
|
1082
|
+
const scanForgePath = path.join(targetDir, FORGE_FILE);
|
|
1083
|
+
const scanIndexMarkdown = buildScanIndexMarkdown({
|
|
1084
|
+
keyFiles,
|
|
1085
|
+
topLevelStats,
|
|
1086
|
+
generatedAt,
|
|
1087
|
+
includeSummaries: summaryMode !== 'titles',
|
|
1088
|
+
foldersPath: scanFoldersPath,
|
|
1089
|
+
folderScans,
|
|
1090
|
+
forgePath: scanForgePath,
|
|
1091
|
+
forgeArtifactCount: forgeArtifacts.artifactCount
|
|
1092
|
+
});
|
|
1093
|
+
let derivedArtifacts = {
|
|
1094
|
+
memoryIndexPath: null,
|
|
1095
|
+
specCurrentPath: null,
|
|
1096
|
+
specHistoryPath: null,
|
|
1097
|
+
moduleDocs: []
|
|
1098
|
+
};
|
|
1099
|
+
if (!options['dry-run']) {
|
|
1100
|
+
await ensureDir(path.dirname(scanIndexPath));
|
|
1101
|
+
await fs.writeFile(scanFoldersPath, folderMapMarkdown, 'utf8');
|
|
1102
|
+
for (const scan of folderScans) {
|
|
1103
|
+
await fs.writeFile(scan.absolutePath, scan.markdown, 'utf8');
|
|
1104
|
+
}
|
|
1105
|
+
await fs.writeFile(scanIndexPath, scanIndexMarkdown, 'utf8');
|
|
1106
|
+
await fs.writeFile(scanForgePath, forgeArtifacts.markdown, 'utf8');
|
|
1107
|
+
logger.log(t('scan_project.folders_written', { path: scanFoldersPath }));
|
|
1108
|
+
for (const scan of folderScans) {
|
|
1109
|
+
logger.log(t('scan_project.folder_written', { folder: `${scan.folder}/`, path: scan.absolutePath }));
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
if (options['dry-run']) {
|
|
1114
|
+
const output = {
|
|
1115
|
+
ok: true,
|
|
1116
|
+
dryRun: true,
|
|
1117
|
+
treeLines: entries.length,
|
|
1118
|
+
keyFiles: Object.keys(keyContents).length,
|
|
1119
|
+
provider: providerName,
|
|
1120
|
+
model,
|
|
1121
|
+
llmRequested,
|
|
1122
|
+
summaryMode,
|
|
1123
|
+
contextMode,
|
|
1124
|
+
requestedFolders,
|
|
1125
|
+
scanIndexPath,
|
|
1126
|
+
scanFoldersPath,
|
|
1127
|
+
scanFolderPaths: folderScans.map((scan) => scan.absolutePath),
|
|
1128
|
+
scanForgePath
|
|
1129
|
+
};
|
|
1130
|
+
if (options.json) return output;
|
|
1131
|
+
logger.log(t('scan_project.dry_run_done', { treeCount: entries.length, keyCount: Object.keys(keyContents).length }));
|
|
1132
|
+
return output;
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
if (!llmRequested) {
|
|
1136
|
+
derivedArtifacts = await writeDerivedContextMemory({
|
|
1137
|
+
targetDir,
|
|
1138
|
+
generatedAt,
|
|
1139
|
+
folderScans
|
|
1140
|
+
});
|
|
1141
|
+
|
|
1142
|
+
const refreshedWalk = await walkProject(targetDir);
|
|
1143
|
+
const refreshedForgeArtifacts = buildForgeArtifactsMarkdown({
|
|
1144
|
+
entries: refreshedWalk.entries,
|
|
1145
|
+
generatedAt,
|
|
1146
|
+
managedForgePaths
|
|
1147
|
+
});
|
|
1148
|
+
const refreshedScanIndexMarkdown = buildScanIndexMarkdown({
|
|
1149
|
+
keyFiles: refreshedWalk.keyFiles,
|
|
1150
|
+
topLevelStats: refreshedWalk.topLevelStats,
|
|
1151
|
+
generatedAt,
|
|
1152
|
+
includeSummaries: summaryMode !== 'titles',
|
|
1153
|
+
foldersPath: path.join(targetDir, FOLDERS_FILE),
|
|
1154
|
+
folderScans,
|
|
1155
|
+
forgePath: path.join(targetDir, FORGE_FILE),
|
|
1156
|
+
forgeArtifactCount: refreshedForgeArtifacts.artifactCount,
|
|
1157
|
+
memoryIndexPath: derivedArtifacts.memoryIndexPath,
|
|
1158
|
+
specCurrentPath: derivedArtifacts.specCurrentPath,
|
|
1159
|
+
specHistoryPath: derivedArtifacts.specHistoryPath,
|
|
1160
|
+
moduleDocs: derivedArtifacts.moduleDocs
|
|
1161
|
+
});
|
|
1162
|
+
|
|
1163
|
+
await fs.writeFile(scanIndexPath, refreshedScanIndexMarkdown, 'utf8');
|
|
1164
|
+
await fs.writeFile(scanForgePath, refreshedForgeArtifacts.markdown, 'utf8');
|
|
1165
|
+
logger.log(t('scan_project.index_written', { path: scanIndexPath, mode: summaryMode }));
|
|
1166
|
+
logger.log(t('scan_project.forge_written', { path: scanForgePath }));
|
|
1167
|
+
if (derivedArtifacts.memoryIndexPath) logger.log(t('scan_project.memory_index_written', { path: derivedArtifacts.memoryIndexPath }));
|
|
1168
|
+
if (derivedArtifacts.specCurrentPath) logger.log(t('scan_project.spec_current_written', { path: derivedArtifacts.specCurrentPath }));
|
|
1169
|
+
if (derivedArtifacts.specHistoryPath) logger.log(t('scan_project.spec_history_written', { path: derivedArtifacts.specHistoryPath }));
|
|
1170
|
+
for (const doc of derivedArtifacts.moduleDocs) {
|
|
1171
|
+
logger.log(t('scan_project.module_memory_written', { folder: `${doc.folder}/`, path: doc.absolutePath }));
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
logger.log(t('scan_project.local_done', { path: scanIndexPath }));
|
|
1175
|
+
logger.log(t('scan_project.local_missing'));
|
|
1176
|
+
logger.log(t('scan_project.architecture_note'));
|
|
1177
|
+
logger.log(t('scan_project.local_paths_title'));
|
|
1178
|
+
logger.log(t('scan_project.local_path_api'));
|
|
1179
|
+
logger.log(t('scan_project.local_next_steps', {
|
|
1180
|
+
target: targetArg,
|
|
1181
|
+
folders: requestedFolders.join(',')
|
|
1182
|
+
}));
|
|
1183
|
+
logger.log(t('scan_project.local_path_cli'));
|
|
1184
|
+
logger.log(t('scan_project.local_cli_step_analyst'));
|
|
1185
|
+
logger.log(t('scan_project.local_cli_step_prompt_codex'));
|
|
1186
|
+
logger.log(t('scan_project.local_cli_step_prompt_claude'));
|
|
1187
|
+
logger.log(t('scan_project.local_cli_step_model_hint'));
|
|
1188
|
+
logger.log(t('scan_project.local_workflow_title'));
|
|
1189
|
+
logger.log(t('scan_project.local_step_architect'));
|
|
1190
|
+
logger.log(t('scan_project.local_step_dev'));
|
|
1191
|
+
return {
|
|
1192
|
+
ok: true,
|
|
1193
|
+
targetDir,
|
|
1194
|
+
provider: null,
|
|
1195
|
+
model: null,
|
|
1196
|
+
llmRequested: false,
|
|
1197
|
+
summaryMode,
|
|
1198
|
+
contextMode,
|
|
1199
|
+
requestedFolders,
|
|
1200
|
+
scanIndexPath,
|
|
1201
|
+
scanFoldersPath,
|
|
1202
|
+
scanFolderPaths: folderScans.map((scan) => scan.absolutePath),
|
|
1203
|
+
scanForgePath,
|
|
1204
|
+
memoryIndexPath: derivedArtifacts.memoryIndexPath,
|
|
1205
|
+
specCurrentPath: derivedArtifacts.specCurrentPath,
|
|
1206
|
+
specHistoryPath: derivedArtifacts.specHistoryPath,
|
|
1207
|
+
moduleDocPaths: derivedArtifacts.moduleDocs.map((doc) => doc.absolutePath),
|
|
1208
|
+
discoveryPath: null,
|
|
1209
|
+
skeletonPath: null
|
|
1210
|
+
};
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
// Build prompt and call LLM
|
|
1214
|
+
const prompt = buildPrompt({
|
|
1215
|
+
scanIndexMarkdown,
|
|
1216
|
+
folderMapMarkdown,
|
|
1217
|
+
folderScans,
|
|
1218
|
+
forgeMapMarkdown: forgeArtifacts.markdown,
|
|
1219
|
+
keyContents,
|
|
1220
|
+
projectContext,
|
|
1221
|
+
specContent,
|
|
1222
|
+
existingDiscoveryContent,
|
|
1223
|
+
existingSkeletonContent,
|
|
1224
|
+
summaryMode
|
|
1225
|
+
});
|
|
1226
|
+
logger.log(t('scan_project.calling_llm', { provider: providerName, model }));
|
|
1227
|
+
|
|
1228
|
+
let result;
|
|
1229
|
+
try {
|
|
1230
|
+
result = await callLLM(providerName, providerCfg, prompt);
|
|
1231
|
+
} catch (err) {
|
|
1232
|
+
if (err && err.code === 'MISSING_API_KEY') {
|
|
1233
|
+
logger.error(t('scan_project.llm_missing_api_key', { provider: providerName, file: CONFIG_FILE }));
|
|
1234
|
+
} else {
|
|
1235
|
+
logger.error(t('scan_project.llm_error', { error: err.message }));
|
|
1236
|
+
}
|
|
1237
|
+
process.exitCode = 1;
|
|
1238
|
+
return { ok: false, error: err.message };
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
// Parse and write both documents
|
|
1242
|
+
const outputPath = existingDiscoveryPath;
|
|
1243
|
+
const skeletonPath = existingSkeletonPath;
|
|
1244
|
+
|
|
1245
|
+
await ensureDir(path.dirname(outputPath));
|
|
1246
|
+
|
|
1247
|
+
let discoveryContent, skeletonContent;
|
|
1248
|
+
if (result.includes(DELIMITER)) {
|
|
1249
|
+
const parts = result.split(DELIMITER);
|
|
1250
|
+
discoveryContent = parts[0].trim();
|
|
1251
|
+
skeletonContent = parts[1].trim();
|
|
1252
|
+
} else {
|
|
1253
|
+
discoveryContent = result.trim();
|
|
1254
|
+
skeletonContent = null;
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
if (!discoveryContent) {
|
|
1258
|
+
logger.error(t('scan_project.invalid_llm_output_discovery_empty'));
|
|
1259
|
+
process.exitCode = 1;
|
|
1260
|
+
return { ok: false, error: 'empty_discovery' };
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
if (result.includes(DELIMITER) && !skeletonContent) {
|
|
1264
|
+
logger.error(t('scan_project.invalid_llm_output_skeleton_empty'));
|
|
1265
|
+
process.exitCode = 1;
|
|
1266
|
+
return { ok: false, error: 'empty_skeleton' };
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
const contextFilesToBackup = [];
|
|
1270
|
+
if (await exists(existingDiscoveryPath)) contextFilesToBackup.push(OUTPUT_FILE);
|
|
1271
|
+
if (skeletonContent && await exists(existingSkeletonPath)) contextFilesToBackup.push(SKELETON_FILE);
|
|
1272
|
+
|
|
1273
|
+
if (contextFilesToBackup.length > 0) {
|
|
1274
|
+
const gitignoreChanged = await ensureGitignoreEntry(targetDir, BACKUPS_GITIGNORE_ENTRY);
|
|
1275
|
+
if (gitignoreChanged) {
|
|
1276
|
+
logger.log(t('scan_project.gitignore_backups_written', { path: path.join(targetDir, '.gitignore') }));
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
const backupResult = await backupProjectFiles(targetDir, contextFilesToBackup);
|
|
1280
|
+
if (backupResult.backedUp.length > 0) {
|
|
1281
|
+
logger.log(t('scan_project.backups_written', {
|
|
1282
|
+
count: backupResult.backedUp.length,
|
|
1283
|
+
path: backupResult.backupRoot
|
|
1284
|
+
}));
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
await fs.writeFile(outputPath, discoveryContent, 'utf8');
|
|
1289
|
+
logger.log(t('scan_project.discovery_written', { path: outputPath, chars: discoveryContent.length }));
|
|
1290
|
+
|
|
1291
|
+
if (skeletonContent) {
|
|
1292
|
+
await fs.writeFile(skeletonPath, skeletonContent, 'utf8');
|
|
1293
|
+
logger.log(t('scan_project.skeleton_written', { path: skeletonPath, chars: skeletonContent.length }));
|
|
1294
|
+
} else {
|
|
1295
|
+
logger.log(t('scan_project.skeleton_missing'));
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
derivedArtifacts = await writeDerivedContextMemory({
|
|
1299
|
+
targetDir,
|
|
1300
|
+
generatedAt,
|
|
1301
|
+
folderScans
|
|
1302
|
+
});
|
|
1303
|
+
const refreshedWalk = await walkProject(targetDir);
|
|
1304
|
+
const refreshedForgeArtifacts = buildForgeArtifactsMarkdown({
|
|
1305
|
+
entries: refreshedWalk.entries,
|
|
1306
|
+
generatedAt,
|
|
1307
|
+
managedForgePaths
|
|
1308
|
+
});
|
|
1309
|
+
const refreshedScanIndexMarkdown = buildScanIndexMarkdown({
|
|
1310
|
+
keyFiles: refreshedWalk.keyFiles,
|
|
1311
|
+
topLevelStats: refreshedWalk.topLevelStats,
|
|
1312
|
+
generatedAt,
|
|
1313
|
+
includeSummaries: summaryMode !== 'titles',
|
|
1314
|
+
foldersPath: path.join(targetDir, FOLDERS_FILE),
|
|
1315
|
+
folderScans,
|
|
1316
|
+
forgePath: path.join(targetDir, FORGE_FILE),
|
|
1317
|
+
forgeArtifactCount: refreshedForgeArtifacts.artifactCount,
|
|
1318
|
+
memoryIndexPath: derivedArtifacts.memoryIndexPath,
|
|
1319
|
+
specCurrentPath: derivedArtifacts.specCurrentPath,
|
|
1320
|
+
specHistoryPath: derivedArtifacts.specHistoryPath,
|
|
1321
|
+
moduleDocs: derivedArtifacts.moduleDocs
|
|
1322
|
+
});
|
|
1323
|
+
await fs.writeFile(scanIndexPath, refreshedScanIndexMarkdown, 'utf8');
|
|
1324
|
+
await fs.writeFile(scanForgePath, refreshedForgeArtifacts.markdown, 'utf8');
|
|
1325
|
+
logger.log(t('scan_project.index_written', { path: scanIndexPath, mode: summaryMode }));
|
|
1326
|
+
logger.log(t('scan_project.forge_written', { path: scanForgePath }));
|
|
1327
|
+
if (derivedArtifacts.memoryIndexPath) logger.log(t('scan_project.memory_index_written', { path: derivedArtifacts.memoryIndexPath }));
|
|
1328
|
+
if (derivedArtifacts.specCurrentPath) logger.log(t('scan_project.spec_current_written', { path: derivedArtifacts.specCurrentPath }));
|
|
1329
|
+
if (derivedArtifacts.specHistoryPath) logger.log(t('scan_project.spec_history_written', { path: derivedArtifacts.specHistoryPath }));
|
|
1330
|
+
for (const doc of derivedArtifacts.moduleDocs) {
|
|
1331
|
+
logger.log(t('scan_project.module_memory_written', { folder: `${doc.folder}/`, path: doc.absolutePath }));
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
logger.log(t('scan_project.architecture_note'));
|
|
1335
|
+
logger.log(t('scan_project.next_steps'));
|
|
1336
|
+
logger.log(t('scan_project.step_analyst'));
|
|
1337
|
+
logger.log(t('scan_project.step_architect'));
|
|
1338
|
+
logger.log(t('scan_project.step_dev'));
|
|
1339
|
+
|
|
1340
|
+
const output = {
|
|
1341
|
+
ok: true,
|
|
1342
|
+
targetDir,
|
|
1343
|
+
provider: providerName,
|
|
1344
|
+
model,
|
|
1345
|
+
llmRequested: true,
|
|
1346
|
+
summaryMode,
|
|
1347
|
+
contextMode,
|
|
1348
|
+
requestedFolders,
|
|
1349
|
+
scanIndexPath,
|
|
1350
|
+
scanFoldersPath,
|
|
1351
|
+
scanFolderPaths: folderScans.map((scan) => scan.absolutePath),
|
|
1352
|
+
scanForgePath,
|
|
1353
|
+
memoryIndexPath: derivedArtifacts.memoryIndexPath,
|
|
1354
|
+
specCurrentPath: derivedArtifacts.specCurrentPath,
|
|
1355
|
+
specHistoryPath: derivedArtifacts.specHistoryPath,
|
|
1356
|
+
moduleDocPaths: derivedArtifacts.moduleDocs.map((doc) => doc.absolutePath),
|
|
1357
|
+
discoveryPath: outputPath,
|
|
1358
|
+
skeletonPath: skeletonContent ? skeletonPath : null
|
|
1359
|
+
};
|
|
1360
|
+
if (options.json) return output;
|
|
1361
|
+
return output;
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
module.exports = {
|
|
1365
|
+
runScanProject,
|
|
1366
|
+
resolveSummaryMode,
|
|
1367
|
+
resolveContextMode,
|
|
1368
|
+
resolveRequestedFolders,
|
|
1369
|
+
buildScanIndexMarkdown,
|
|
1370
|
+
buildPrompt
|
|
1371
|
+
};
|