@jaimevalasek/aioson 1.5.1 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +49 -0
- package/README.md +729 -226
- package/docs/design-previews/aurora-command-ui-website.html +884 -0
- package/docs/design-previews/aurora-command-ui.html +682 -0
- package/docs/design-previews/bold-editorial-ui-website.html +658 -0
- package/docs/design-previews/bold-editorial-ui.html +717 -0
- package/docs/design-previews/clean-saas-ui-website.html +1202 -0
- package/docs/design-previews/clean-saas-ui.html +549 -0
- package/docs/design-previews/cognitive-core-ui-website.html +1009 -0
- package/docs/design-previews/cognitive-core-ui.html +463 -0
- package/docs/design-previews/glassmorphism-ui-website.html +572 -0
- package/docs/design-previews/glassmorphism-ui.html +886 -0
- package/docs/design-previews/index.html +699 -0
- package/docs/design-previews/interface-design-website.html +1187 -0
- package/docs/design-previews/interface-design.html +513 -0
- package/docs/design-previews/neo-brutalist-ui-website.html +621 -0
- package/docs/design-previews/neo-brutalist-ui.html +797 -0
- package/docs/design-previews/premium-command-center-ui-website.html +1217 -0
- package/docs/design-previews/premium-command-center-ui.html +552 -0
- package/docs/design-previews/pt.squarespace.com-homepage.html +889 -0
- package/docs/design-previews/warm-craft-ui-website.html +684 -0
- package/docs/design-previews/warm-craft-ui.html +739 -0
- package/docs/en/cli-reference.md +20 -9
- package/docs/integrations/sdlc-genius-boundary.md +76 -0
- package/docs/integrations/sdlc-genius-eval-matrix.md +75 -0
- package/docs/integrations/sdlc-genius-install-checklist.md +93 -0
- package/docs/integrations/sdlc-genius-review-samples.md +86 -0
- package/docs/pt/README.md +10 -0
- package/docs/pt/agent-sharding.md +132 -0
- package/docs/pt/agentes.md +9 -2
- package/docs/pt/busca-de-contexto.md +129 -0
- package/docs/pt/cache-de-contexto.md +156 -0
- package/docs/pt/comandos-cli.md +915 -1
- package/docs/pt/design-hybrid-forge.md +356 -0
- package/docs/pt/devlog-pipeline.md +270 -0
- package/docs/pt/fluxo-artefatos.md +178 -0
- package/docs/pt/hooks-session-guard.md +454 -0
- package/docs/pt/inicio-rapido.md +54 -3
- package/docs/pt/inteligencia-adaptativa.md +324 -0
- package/docs/pt/monitor-de-contexto.md +158 -0
- package/docs/pt/recuperacao-de-sessao.md +125 -0
- package/docs/pt/sandbox.md +125 -0
- package/docs/pt/sdd-automation-scripts.md +557 -0
- package/docs/pt/site-forge.md +309 -0
- package/docs/pt/skills.md +98 -6
- package/docs/pt/spec-learnings-pipeline.md +265 -0
- package/package.json +1 -1
- package/src/a2a/client.js +165 -0
- package/src/a2a/server.js +223 -0
- package/src/agent-loader.js +280 -0
- package/src/cli.js +329 -1
- package/src/commands/agent-audit.js +397 -0
- package/src/commands/agent-export-skill.js +229 -0
- package/src/commands/agent-loader.js +85 -0
- package/src/commands/artifact-validate.js +189 -0
- package/src/commands/brief-gen.js +405 -0
- package/src/commands/brief-validate.js +65 -0
- package/src/commands/classify.js +256 -0
- package/src/commands/context-cache.js +90 -0
- package/src/commands/context-compact.js +49 -0
- package/src/commands/context-health.js +175 -0
- package/src/commands/context-monitor.js +163 -0
- package/src/commands/context-search.js +66 -0
- package/src/commands/context-trim.js +177 -0
- package/src/commands/design-hybrid-options.js +385 -0
- package/src/commands/detect-test-runner.js +55 -0
- package/src/commands/devlog-export-brains.js +27 -0
- package/src/commands/devlog-process.js +292 -0
- package/src/commands/devlog-watch.js +131 -0
- package/src/commands/feature-close.js +165 -0
- package/src/commands/gate-check.js +228 -0
- package/src/commands/health.js +214 -0
- package/src/commands/hooks-emit.js +253 -0
- package/src/commands/hooks-install.js +347 -0
- package/src/commands/init.js +54 -13
- package/src/commands/install.js +52 -13
- package/src/commands/learning-auto-promote.js +195 -0
- package/src/commands/learning-evolve.js +364 -0
- package/src/commands/learning-export.js +103 -0
- package/src/commands/learning-rollback.js +164 -0
- package/src/commands/live.js +59 -1
- package/src/commands/pattern-detect.js +33 -0
- package/src/commands/preflight-context.js +30 -0
- package/src/commands/preflight.js +208 -0
- package/src/commands/pulse-update.js +130 -0
- package/src/commands/recovery.js +43 -0
- package/src/commands/runner-daemon.js +274 -0
- package/src/commands/runner-plan.js +70 -0
- package/src/commands/runner-queue-from-plan.js +166 -0
- package/src/commands/runner-queue.js +189 -0
- package/src/commands/runner-run.js +129 -0
- package/src/commands/runtime.js +47 -1
- package/src/commands/sandbox.js +37 -0
- package/src/commands/self-implement-loop.js +256 -0
- package/src/commands/session-guard.js +218 -0
- package/src/commands/setup-context.js +22 -2
- package/src/commands/setup.js +178 -0
- package/src/commands/sizing.js +165 -0
- package/src/commands/skill.js +144 -32
- package/src/commands/spec-checkpoint.js +177 -0
- package/src/commands/spec-status.js +79 -0
- package/src/commands/spec-sync.js +190 -0
- package/src/commands/spec-tasks.js +288 -0
- package/src/commands/squad-autorun.js +1220 -0
- package/src/commands/squad-bus.js +217 -0
- package/src/commands/squad-card.js +149 -0
- package/src/commands/squad-daemon.js +134 -0
- package/src/commands/squad-dependency-graph.js +164 -0
- package/src/commands/squad-review.js +106 -0
- package/src/commands/squad-scaffold.js +55 -0
- package/src/commands/squad-tool-register.js +157 -0
- package/src/commands/state-save.js +122 -0
- package/src/commands/tool-registry-cmd.js +232 -0
- package/src/commands/update.js +9 -0
- package/src/commands/verify-gate.js +572 -0
- package/src/commands/workflow-execute.js +241 -0
- package/src/constants.js +18 -0
- package/src/context-cache.js +159 -0
- package/src/context-search.js +326 -0
- package/src/design-variation-catalog.js +503 -0
- package/src/i18n/messages/en.js +32 -2
- package/src/i18n/messages/es.js +30 -2
- package/src/i18n/messages/fr.js +30 -2
- package/src/i18n/messages/pt-BR.js +32 -2
- package/src/install-animation.js +260 -0
- package/src/install-profile.js +143 -0
- package/src/install-wizard.js +475 -0
- package/src/installer.js +44 -10
- package/src/lib/health-check.js +158 -0
- package/src/lib/hook-protocol.js +76 -0
- package/src/mcp/apps/squad-dashboard/app.js +163 -0
- package/src/mcp/apps/squad-dashboard/index.html +261 -0
- package/src/mcp/apps/squad-dashboard/mcp-manifest.json +23 -0
- package/src/mcp/resources/squad-state.js +130 -0
- package/src/parser.js +7 -1
- package/src/preflight-engine.js +443 -0
- package/src/recovery-context-session.js +154 -0
- package/src/runner/cascade.js +97 -0
- package/src/runner/cli-launcher.js +109 -0
- package/src/runner/plan-importer.js +63 -0
- package/src/runner/queue-store.js +159 -0
- package/src/runtime-store.js +158 -4
- package/src/sandbox.js +177 -0
- package/src/squad/agent-teams-adapter.js +264 -0
- package/src/squad/brief-validator.js +350 -0
- package/src/squad/bus-bridge.js +140 -0
- package/src/squad/context-compactor.js +265 -0
- package/src/squad/cross-ai-synthesizer.js +250 -0
- package/src/squad/hooks-generator.js +196 -0
- package/src/squad/inter-squad-events.js +175 -0
- package/src/squad/intra-bus.js +345 -0
- package/src/squad/learning-extractor.js +213 -0
- package/src/squad/pattern-detector.js +365 -0
- package/src/squad/preflight-context.js +296 -0
- package/src/squad/recovery-context.js +242 -71
- package/src/squad/reflection.js +365 -0
- package/src/squad/squad-scaffold.js +177 -0
- package/src/squad/state-manager.js +310 -0
- package/src/squad/task-decomposer.js +652 -0
- package/src/squad/verify-gate.js +303 -0
- package/src/tool-executor.js +94 -0
- package/src/updater.js +10 -3
- package/src/worker-runner.js +186 -1
- package/template/.aioson/agents/analyst.md +119 -3
- package/template/.aioson/agents/architect.md +98 -0
- package/template/.aioson/agents/design-hybrid-forge.md +141 -0
- package/template/.aioson/agents/dev.md +335 -14
- package/template/.aioson/agents/deyvin.md +117 -2
- package/template/.aioson/agents/discovery-design-doc.md +44 -0
- package/template/.aioson/agents/genome.md +14 -0
- package/template/.aioson/agents/neo.md +78 -1
- package/template/.aioson/agents/orache.md +50 -4
- package/template/.aioson/agents/orchestrator.md +197 -1
- package/template/.aioson/agents/pm.md +93 -0
- package/template/.aioson/agents/product.md +77 -4
- package/template/.aioson/agents/profiler-enricher.md +14 -0
- package/template/.aioson/agents/profiler-forge.md +14 -0
- package/template/.aioson/agents/profiler-researcher.md +14 -0
- package/template/.aioson/agents/qa.md +249 -19
- package/template/.aioson/agents/setup.md +144 -12
- package/template/.aioson/agents/sheldon.md +237 -11
- package/template/.aioson/agents/site-forge.md +1753 -0
- package/template/.aioson/agents/squad.md +162 -0
- package/template/.aioson/agents/tester.md +209 -0
- package/template/.aioson/agents/ux-ui.md +34 -1
- package/template/.aioson/brains/README.md +128 -0
- package/template/.aioson/brains/_index.json +16 -0
- package/template/.aioson/brains/scripts/query.js +103 -0
- package/template/.aioson/brains/site-forge/visual-patterns.brain.json +205 -0
- package/template/.aioson/config.md +158 -13
- package/template/.aioson/constitution.md +33 -0
- package/template/.aioson/context/forensics/.gitkeep +0 -0
- package/template/.aioson/context/project-pulse.md +34 -0
- package/template/.aioson/context/seeds/seed-example.md +27 -0
- package/template/.aioson/context/user-profile.md +42 -0
- package/template/.aioson/docs/LAYERS.md +79 -0
- package/template/.aioson/docs/README.md +76 -0
- package/template/.aioson/docs/example-external-api-context.md +72 -0
- package/template/.aioson/locales/en/agents/architect.md +17 -0
- package/template/.aioson/locales/en/agents/dev.md +79 -13
- package/template/.aioson/locales/en/agents/orache.md +6 -0
- package/template/.aioson/locales/en/agents/orchestrator.md +24 -0
- package/template/.aioson/locales/en/agents/product.md +50 -0
- package/template/.aioson/locales/en/agents/setup.md +33 -1
- package/template/.aioson/locales/en/agents/sheldon.md +115 -0
- package/template/.aioson/locales/en/agents/squad.md +14 -0
- package/template/.aioson/locales/en/agents/tester.md +6 -0
- package/template/.aioson/locales/es/agents/analyst.md +2 -0
- package/template/.aioson/locales/es/agents/architect.md +19 -0
- package/template/.aioson/locales/es/agents/dev.md +64 -4
- package/template/.aioson/locales/es/agents/deyvin.md +2 -0
- package/template/.aioson/locales/es/agents/discovery-design-doc.md +2 -0
- package/template/.aioson/locales/es/agents/genome.md +2 -0
- package/template/.aioson/locales/es/agents/neo.md +2 -0
- package/template/.aioson/locales/es/agents/orache.md +2 -0
- package/template/.aioson/locales/es/agents/orchestrator.md +26 -0
- package/template/.aioson/locales/es/agents/pair.md +2 -0
- package/template/.aioson/locales/es/agents/pm.md +2 -0
- package/template/.aioson/locales/es/agents/product.md +52 -0
- package/template/.aioson/locales/es/agents/profiler-enricher.md +2 -0
- package/template/.aioson/locales/es/agents/profiler-forge.md +2 -0
- package/template/.aioson/locales/es/agents/profiler-researcher.md +2 -0
- package/template/.aioson/locales/es/agents/qa.md +2 -0
- package/template/.aioson/locales/es/agents/setup.md +35 -1
- package/template/.aioson/locales/es/agents/sheldon.md +117 -0
- package/template/.aioson/locales/es/agents/squad.md +16 -0
- package/template/.aioson/locales/es/agents/tester.md +9 -0
- package/template/.aioson/locales/es/agents/ux-ui.md +2 -0
- package/template/.aioson/locales/fr/agents/analyst.md +2 -0
- package/template/.aioson/locales/fr/agents/architect.md +19 -0
- package/template/.aioson/locales/fr/agents/dev.md +64 -4
- package/template/.aioson/locales/fr/agents/deyvin.md +2 -0
- package/template/.aioson/locales/fr/agents/discovery-design-doc.md +2 -0
- package/template/.aioson/locales/fr/agents/genome.md +2 -0
- package/template/.aioson/locales/fr/agents/neo.md +2 -0
- package/template/.aioson/locales/fr/agents/orache.md +2 -0
- package/template/.aioson/locales/fr/agents/orchestrator.md +26 -0
- package/template/.aioson/locales/fr/agents/pair.md +2 -0
- package/template/.aioson/locales/fr/agents/pm.md +2 -0
- package/template/.aioson/locales/fr/agents/product.md +52 -0
- package/template/.aioson/locales/fr/agents/profiler-enricher.md +2 -0
- package/template/.aioson/locales/fr/agents/profiler-forge.md +2 -0
- package/template/.aioson/locales/fr/agents/profiler-researcher.md +2 -0
- package/template/.aioson/locales/fr/agents/qa.md +2 -0
- package/template/.aioson/locales/fr/agents/setup.md +35 -1
- package/template/.aioson/locales/fr/agents/sheldon.md +117 -0
- package/template/.aioson/locales/fr/agents/squad.md +16 -0
- package/template/.aioson/locales/fr/agents/tester.md +9 -0
- package/template/.aioson/locales/fr/agents/ux-ui.md +2 -0
- package/template/.aioson/locales/pt-BR/agents/analyst.md +64 -3
- package/template/.aioson/locales/pt-BR/agents/architect.md +42 -0
- package/template/.aioson/locales/pt-BR/agents/dev.md +147 -14
- package/template/.aioson/locales/pt-BR/agents/deyvin.md +47 -0
- package/template/.aioson/locales/pt-BR/agents/neo.md +62 -1
- package/template/.aioson/locales/pt-BR/agents/orchestrator.md +158 -2
- package/template/.aioson/locales/pt-BR/agents/pm.md +95 -1
- package/template/.aioson/locales/pt-BR/agents/product.md +145 -18
- package/template/.aioson/locales/pt-BR/agents/qa.md +16 -0
- package/template/.aioson/locales/pt-BR/agents/setup.md +134 -19
- package/template/.aioson/locales/pt-BR/agents/sheldon.md +132 -1
- package/template/.aioson/locales/pt-BR/agents/squad.md +14 -0
- package/template/.aioson/locales/pt-BR/agents/tester.md +449 -0
- package/template/.aioson/rules/README.md +69 -0
- package/template/.aioson/rules/data-format-convention.md +136 -0
- package/template/.aioson/rules/example-monetary-values.md +30 -0
- package/template/.aioson/schemas/squad-manifest.schema.json +124 -3
- package/template/.aioson/skills/design/aurora-command-ui/SKILL.md +243 -0
- package/template/.aioson/skills/design/aurora-command-ui/references/art-direction.md +293 -0
- package/template/.aioson/skills/design/aurora-command-ui/references/components.md +827 -0
- package/template/.aioson/skills/design/aurora-command-ui/references/dashboards.md +250 -0
- package/template/.aioson/skills/design/aurora-command-ui/references/design-tokens.md +585 -0
- package/template/.aioson/skills/design/aurora-command-ui/references/motion.md +365 -0
- package/template/.aioson/skills/design/aurora-command-ui/references/patterns.md +482 -0
- package/template/.aioson/skills/design/aurora-command-ui/references/websites.md +387 -0
- package/template/.aioson/skills/design/glassmorphism-ui/SKILL.md +222 -0
- package/template/.aioson/skills/design/glassmorphism-ui/references/art-direction.md +159 -0
- package/template/.aioson/skills/design/glassmorphism-ui/references/components.md +498 -0
- package/template/.aioson/skills/design/glassmorphism-ui/references/dashboards.md +236 -0
- package/template/.aioson/skills/design/glassmorphism-ui/references/design-tokens.md +274 -0
- package/template/.aioson/skills/design/glassmorphism-ui/references/motion.md +355 -0
- package/template/.aioson/skills/design/glassmorphism-ui/references/patterns.md +198 -0
- package/template/.aioson/skills/design/glassmorphism-ui/references/websites.md +307 -0
- package/template/.aioson/skills/design/neo-brutalist-ui/SKILL.md +213 -0
- package/template/.aioson/skills/design/neo-brutalist-ui/references/art-direction.md +228 -0
- package/template/.aioson/skills/design/neo-brutalist-ui/references/components.md +855 -0
- package/template/.aioson/skills/design/neo-brutalist-ui/references/dashboards.md +334 -0
- package/template/.aioson/skills/design/neo-brutalist-ui/references/design-tokens.md +342 -0
- package/template/.aioson/skills/design/neo-brutalist-ui/references/motion.md +286 -0
- package/template/.aioson/skills/design/neo-brutalist-ui/references/patterns.md +458 -0
- package/template/.aioson/skills/design/neo-brutalist-ui/references/websites.md +723 -0
- package/template/.aioson/skills/design/pt.squarespace.com/.skill-meta.json +31 -0
- package/template/.aioson/skills/design/pt.squarespace.com/SKILL.md +66 -0
- package/template/.aioson/skills/design/pt.squarespace.com/references/components.md +368 -0
- package/template/.aioson/skills/design/pt.squarespace.com/references/design-tokens.md +150 -0
- package/template/.aioson/skills/design/pt.squarespace.com/references/motion.md +270 -0
- package/template/.aioson/skills/design/pt.squarespace.com/references/patterns.md +189 -0
- package/template/.aioson/skills/design/pt.squarespace.com/references/websites.md +165 -0
- package/template/.aioson/skills/process/aioson-spec-driven/SKILL.md +46 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/analyst.md +30 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/approval-gates.md +109 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/architect.md +23 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/artifact-map.md +44 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/classification-map.md +37 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/dev.md +47 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/deyvin.md +27 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/hardening-lane.md +49 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/maintenance-and-state.md +101 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/product.md +25 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/qa.md +30 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/sheldon.md +25 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/ui-language.md +75 -0
- package/template/.aioson/skills/process/design-hybrid-forge/SKILL.md +147 -0
- package/template/.aioson/skills/process/design-hybrid-forge/references/crossover-protocol.md +221 -0
- package/template/.aioson/skills/process/design-hybrid-forge/references/naming-registry.md +88 -0
- package/template/.aioson/skills/process/design-hybrid-forge/references/output-contract.md +306 -0
- package/template/.aioson/skills/process/design-hybrid-forge/references/pair-compatibility.md +149 -0
- package/template/.aioson/skills/process/design-hybrid-forge/references/quality-gates.md +208 -0
- package/template/.aioson/skills/process/design-hybrid-forge/references/variation-library.md +125 -0
- package/template/.aioson/skills/process/simplify/SKILL.md +173 -0
- package/template/.aioson/skills/static/context-budget-guide.md +46 -0
- package/template/.aioson/skills/static/harness-sensors.md +74 -0
- package/template/.aioson/skills/static/multi-agent-patterns.md +43 -0
- package/template/.aioson/skills/static/react-motion-patterns.md +22 -0
- package/template/.aioson/skills/static/static-html-patterns/checklists.md +43 -0
- package/template/.aioson/skills/static/static-html-patterns/css-tokens.md +609 -0
- package/template/.aioson/skills/static/static-html-patterns/motion.md +193 -0
- package/template/.aioson/skills/static/static-html-patterns/premium.md +711 -0
- package/template/.aioson/skills/static/static-html-patterns/structure.md +209 -0
- package/template/.aioson/skills/static/static-html-patterns/utilities.md +190 -0
- package/template/.aioson/skills/static/static-html-patterns.md +58 -1913
- package/template/.aioson/skills/static/threejs-patterns.md +929 -0
- package/template/.aioson/skills/static/web-research-cache.md +112 -0
- package/template/.aioson/tasks/implementation-plan.md +21 -1
- package/template/.claude/commands/aioson/agent/design-hybrid-forge.md +5 -0
- package/template/.claude/commands/aioson/agent/orache.md +5 -0
- package/template/.claude/commands/aioson/agent/sheldon.md +5 -0
- package/template/.claude/commands/aioson/agent/site-forge.md +5 -0
- package/template/AGENTS.md +75 -1
- package/template/CLAUDE.md +31 -0
- package/template/OPENCODE.md +4 -0
- package/template/researchs/.gitkeep +0 -0
package/src/commands/skill.js
CHANGED
|
@@ -16,6 +16,47 @@ function resolveTargetDir(args) {
|
|
|
16
16
|
return path.resolve(process.cwd(), args[0] || '.');
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
async function copyRecursive(src, dest) {
|
|
20
|
+
const stat = await fs.stat(src);
|
|
21
|
+
if (stat.isDirectory()) {
|
|
22
|
+
await ensureDir(dest);
|
|
23
|
+
const entries = await fs.readdir(src);
|
|
24
|
+
for (const entry of entries) {
|
|
25
|
+
await copyRecursive(path.join(src, entry), path.join(dest, entry));
|
|
26
|
+
}
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
await ensureDir(path.dirname(dest));
|
|
31
|
+
await fs.copyFile(src, dest);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function replaceDirectory(srcDir, destDir) {
|
|
35
|
+
await fs.rm(destDir, { recursive: true, force: true });
|
|
36
|
+
await copyRecursive(srcDir, destDir);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function readJsonIfExists(filePath) {
|
|
40
|
+
if (!(await exists(filePath))) return null;
|
|
41
|
+
try {
|
|
42
|
+
return JSON.parse(await fs.readFile(filePath, 'utf8'));
|
|
43
|
+
} catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function writeSkillMeta(destDir, patch) {
|
|
49
|
+
const metaPath = path.join(destDir, '.skill-meta.json');
|
|
50
|
+
const existing = await readJsonIfExists(metaPath) || {};
|
|
51
|
+
const merged = {
|
|
52
|
+
...existing,
|
|
53
|
+
...patch
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
await fs.writeFile(metaPath, JSON.stringify(merged, null, 2), 'utf8');
|
|
57
|
+
return merged;
|
|
58
|
+
}
|
|
59
|
+
|
|
19
60
|
/**
|
|
20
61
|
* Parse YAML frontmatter from a SKILL.md file.
|
|
21
62
|
*/
|
|
@@ -44,17 +85,7 @@ async function distributeToTool(targetDir, slug, skillDir) {
|
|
|
44
85
|
for (const toolPath of TOOL_TARGETS) {
|
|
45
86
|
const toolSkillDir = path.join(targetDir, toolPath, slug);
|
|
46
87
|
try {
|
|
47
|
-
await
|
|
48
|
-
// Copy all files from the installed skill dir
|
|
49
|
-
const entries = await fs.readdir(skillDir);
|
|
50
|
-
for (const entry of entries) {
|
|
51
|
-
const src = path.join(skillDir, entry);
|
|
52
|
-
const dest = path.join(toolSkillDir, entry);
|
|
53
|
-
const stat = await fs.stat(src);
|
|
54
|
-
if (stat.isFile()) {
|
|
55
|
-
await fs.copyFile(src, dest);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
88
|
+
await replaceDirectory(skillDir, toolSkillDir);
|
|
58
89
|
results.push({ tool: toolPath, ok: true });
|
|
59
90
|
} catch (err) {
|
|
60
91
|
results.push({ tool: toolPath, ok: false, error: err.message });
|
|
@@ -187,12 +218,13 @@ async function installFromNpm(targetDir, slug, options, logger) {
|
|
|
187
218
|
|
|
188
219
|
// Copy to .aioson/installed-skills/{slug}/
|
|
189
220
|
const destDir = path.join(targetDir, INSTALLED_SKILLS_DIR, slug);
|
|
190
|
-
await
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
221
|
+
await replaceDirectory(sourceDir, destDir);
|
|
222
|
+
await writeSkillMeta(destDir, {
|
|
223
|
+
source: 'npm',
|
|
224
|
+
sourcePackage: '@tech-leads-club/agent-skills',
|
|
225
|
+
sourcePath: path.relative(targetDir, sourceDir),
|
|
226
|
+
installedAt: new Date().toISOString()
|
|
227
|
+
});
|
|
196
228
|
|
|
197
229
|
resolve({ ok: true, sourceDir, destDir });
|
|
198
230
|
});
|
|
@@ -248,15 +280,16 @@ async function installFromCloud(targetDir, slug, options, logger) {
|
|
|
248
280
|
].join('\n');
|
|
249
281
|
|
|
250
282
|
const destDir = path.join(targetDir, INSTALLED_SKILLS_DIR, slug);
|
|
283
|
+
await fs.rm(destDir, { recursive: true, force: true });
|
|
251
284
|
await ensureDir(destDir);
|
|
252
285
|
await fs.writeFile(path.join(destDir, 'SKILL.md'), fm, 'utf8');
|
|
253
286
|
|
|
254
287
|
// Write meta
|
|
255
|
-
await
|
|
288
|
+
await writeSkillMeta(destDir, {
|
|
256
289
|
source: 'cloud',
|
|
257
290
|
cloudSlug: snapshot.skill.slug,
|
|
258
291
|
installedAt: new Date().toISOString()
|
|
259
|
-
}
|
|
292
|
+
});
|
|
260
293
|
|
|
261
294
|
return { ok: true, destDir };
|
|
262
295
|
}
|
|
@@ -271,30 +304,32 @@ async function installFromLocal(targetDir, slug, filePath, logger) {
|
|
|
271
304
|
}
|
|
272
305
|
|
|
273
306
|
const destDir = path.join(targetDir, INSTALLED_SKILLS_DIR, slug);
|
|
274
|
-
await ensureDir(destDir);
|
|
275
|
-
|
|
276
307
|
const stat = await fs.stat(absPath);
|
|
308
|
+
const samePath = path.resolve(absPath) === path.resolve(destDir);
|
|
309
|
+
|
|
310
|
+
if (!samePath) {
|
|
311
|
+
await fs.rm(destDir, { recursive: true, force: true });
|
|
312
|
+
await ensureDir(destDir);
|
|
313
|
+
} else if (!stat.isDirectory()) {
|
|
314
|
+
return { ok: false, error: 'Local self-install only supports skill directories' };
|
|
315
|
+
}
|
|
316
|
+
|
|
277
317
|
if (stat.isDirectory()) {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
for (const entry of entries) {
|
|
281
|
-
const src = path.join(absPath, entry);
|
|
282
|
-
const srcStat = await fs.stat(src);
|
|
283
|
-
if (srcStat.isFile()) {
|
|
284
|
-
await fs.copyFile(src, path.join(destDir, entry));
|
|
285
|
-
}
|
|
318
|
+
if (!samePath) {
|
|
319
|
+
await replaceDirectory(absPath, destDir);
|
|
286
320
|
}
|
|
287
321
|
} else {
|
|
288
322
|
// Single file — copy as SKILL.md
|
|
323
|
+
await ensureDir(destDir);
|
|
289
324
|
await fs.copyFile(absPath, path.join(destDir, 'SKILL.md'));
|
|
290
325
|
}
|
|
291
326
|
|
|
292
327
|
// Write meta
|
|
293
|
-
await
|
|
328
|
+
await writeSkillMeta(destDir, {
|
|
294
329
|
source: 'local',
|
|
295
330
|
sourcePath: filePath,
|
|
296
331
|
installedAt: new Date().toISOString()
|
|
297
|
-
}
|
|
332
|
+
});
|
|
298
333
|
|
|
299
334
|
return { ok: true, destDir };
|
|
300
335
|
}
|
|
@@ -362,6 +397,58 @@ async function runSkillInstall({ args, options = {}, logger, t }) {
|
|
|
362
397
|
};
|
|
363
398
|
}
|
|
364
399
|
|
|
400
|
+
/**
|
|
401
|
+
* Parse a minimal extension.yml manifest if present in a skill directory.
|
|
402
|
+
* Reads only top-level scalar fields and one level of nesting.
|
|
403
|
+
* Returns null if the file is absent or unreadable.
|
|
404
|
+
*/
|
|
405
|
+
async function parseExtensionManifest(skillDir) {
|
|
406
|
+
const manifestPath = path.join(skillDir, 'extension.yml');
|
|
407
|
+
let raw;
|
|
408
|
+
try {
|
|
409
|
+
raw = await fs.readFile(manifestPath, 'utf8');
|
|
410
|
+
} catch {
|
|
411
|
+
return null; // file not present — this is the normal case for old skills
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const manifest = {};
|
|
415
|
+
let currentSection = null;
|
|
416
|
+
|
|
417
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
418
|
+
const trimmed = line.trim();
|
|
419
|
+
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
420
|
+
|
|
421
|
+
// Detect top-level section (no leading spaces, ends with colon, no value)
|
|
422
|
+
if (/^\w[\w-]*:$/.test(trimmed)) {
|
|
423
|
+
currentSection = trimmed.slice(0, -1);
|
|
424
|
+
manifest[currentSection] = {};
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Nested key: value (indented with spaces or tab)
|
|
429
|
+
if (currentSection && /^\s+/.test(line)) {
|
|
430
|
+
const idx = trimmed.indexOf(':');
|
|
431
|
+
if (idx === -1) continue;
|
|
432
|
+
const key = trimmed.slice(0, idx).trim();
|
|
433
|
+
const val = trimmed.slice(idx + 1).trim().replace(/^["']|["']$/g, '');
|
|
434
|
+
if (key) manifest[currentSection][key] = val;
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Top-level scalar key: value
|
|
439
|
+
const idx = trimmed.indexOf(':');
|
|
440
|
+
if (idx === -1) continue;
|
|
441
|
+
const key = trimmed.slice(0, idx).trim();
|
|
442
|
+
const val = trimmed.slice(idx + 1).trim().replace(/^["']|["']$/g, '');
|
|
443
|
+
if (key) {
|
|
444
|
+
currentSection = null;
|
|
445
|
+
manifest[key] = val;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
return manifest;
|
|
450
|
+
}
|
|
451
|
+
|
|
365
452
|
/**
|
|
366
453
|
* Scan a directory for .md files with SKILL.md or frontmatter descriptions.
|
|
367
454
|
* Returns array of { slug, name, description, type, path }.
|
|
@@ -429,17 +516,31 @@ async function runSkillList({ args, options = {}, logger, t }) {
|
|
|
429
516
|
const fm = parseSkillFrontmatter(raw);
|
|
430
517
|
|
|
431
518
|
let source = 'unknown';
|
|
519
|
+
let meta = null;
|
|
432
520
|
try {
|
|
433
521
|
const metaRaw = await fs.readFile(path.join(skillsDir, slug, '.skill-meta.json'), 'utf8');
|
|
434
|
-
|
|
522
|
+
meta = JSON.parse(metaRaw);
|
|
435
523
|
source = meta.source || 'unknown';
|
|
436
524
|
} catch { /* no meta */ }
|
|
437
525
|
|
|
526
|
+
const author = meta?.author?.name || meta?.author_name || null;
|
|
527
|
+
const model =
|
|
528
|
+
meta?.generator?.model ||
|
|
529
|
+
meta?.generation?.model ||
|
|
530
|
+
meta?.generated_by_model ||
|
|
531
|
+
null;
|
|
532
|
+
|
|
533
|
+
// Read optional extension.yml manifest — additive, does not affect existing behavior
|
|
534
|
+
const extManifest = showAll ? await parseExtensionManifest(path.join(skillsDir, slug)) : null;
|
|
535
|
+
|
|
438
536
|
installed.push({
|
|
439
537
|
slug,
|
|
440
538
|
name: fm.name || slug,
|
|
441
539
|
description: fm.description || '',
|
|
442
540
|
source,
|
|
541
|
+
author,
|
|
542
|
+
model,
|
|
543
|
+
manifest: extManifest,
|
|
443
544
|
path: path.relative(targetDir, path.join(skillsDir, slug))
|
|
444
545
|
});
|
|
445
546
|
}
|
|
@@ -472,6 +573,17 @@ async function runSkillList({ args, options = {}, logger, t }) {
|
|
|
472
573
|
if (s.description) {
|
|
473
574
|
logger.log(` ${s.description.slice(0, 100)}`);
|
|
474
575
|
}
|
|
576
|
+
if (s.author) logger.log(` author: ${s.author}`);
|
|
577
|
+
if (s.model) logger.log(` model: ${s.model}`);
|
|
578
|
+
if (showAll && s.manifest) {
|
|
579
|
+
const ext = s.manifest.extension || {};
|
|
580
|
+
if (ext.version) logger.log(` version: ${ext.version}`);
|
|
581
|
+
const hooks = s.manifest.hooks;
|
|
582
|
+
if (hooks && typeof hooks === 'object') {
|
|
583
|
+
const hookNames = Object.keys(hooks).filter(h => hooks[h]?.enabled !== 'false' && hooks[h]?.enabled !== false);
|
|
584
|
+
if (hookNames.length > 0) logger.log(` hooks declared: ${hookNames.join(', ')}`);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
475
587
|
logger.log(` ${s.path}/SKILL.md`);
|
|
476
588
|
logger.log('');
|
|
477
589
|
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs/promises');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const { openRuntimeDb, appendRunEvent } = require('../runtime-store');
|
|
6
|
+
|
|
7
|
+
function nowIso() {
|
|
8
|
+
return new Date().toISOString();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function parseFrontmatter(content) {
|
|
12
|
+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
13
|
+
if (!match) return {};
|
|
14
|
+
const result = {};
|
|
15
|
+
for (const line of match[1].split(/\r?\n/)) {
|
|
16
|
+
const colonIdx = line.indexOf(':');
|
|
17
|
+
if (colonIdx === -1) continue;
|
|
18
|
+
const key = line.slice(0, colonIdx).trim();
|
|
19
|
+
const value = line.slice(colonIdx + 1).trim().replace(/^["']|["']$/g, '');
|
|
20
|
+
if (key) result[key] = value;
|
|
21
|
+
}
|
|
22
|
+
return result;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function extractLastCheckpoint(content) {
|
|
26
|
+
// Try frontmatter first
|
|
27
|
+
const fmMatch = content.match(/^---[\s\S]*?last_checkpoint:\s*(.+)/m);
|
|
28
|
+
if (fmMatch) return fmMatch[1].trim().replace(/^["']|["']$/g, '');
|
|
29
|
+
// Try section header
|
|
30
|
+
const sectionMatch = content.match(/##\s+last_checkpoint[^\n]*\n([\s\S]*?)(?=\n##|\s*$)/i);
|
|
31
|
+
if (sectionMatch) return sectionMatch[1].replace(/^[-*]\s*/, '').trim();
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function extractPhaseGates(content) {
|
|
36
|
+
const fm = parseFrontmatter(content);
|
|
37
|
+
if (!fm.phase_gates) return null;
|
|
38
|
+
try {
|
|
39
|
+
return JSON.parse(fm.phase_gates.replace(/'/g, '"'));
|
|
40
|
+
} catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function runSpecCheckpoint({ args, options = {}, logger }) {
|
|
46
|
+
const targetDir = path.resolve(process.cwd(), args[0] || '.');
|
|
47
|
+
const featureSlug = options.feature ? String(options.feature).trim() : null;
|
|
48
|
+
const agentName = options.agent ? String(options.agent).trim() : 'dev';
|
|
49
|
+
const contextDir = path.join(targetDir, '.aioson', 'context');
|
|
50
|
+
|
|
51
|
+
if (!featureSlug) {
|
|
52
|
+
if (!options.json) logger.log('Error: --feature=<slug> is required.');
|
|
53
|
+
return { ok: false, reason: 'missing_feature' };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Find spec file
|
|
57
|
+
const candidates = [
|
|
58
|
+
path.join(contextDir, `spec-${featureSlug}.md`),
|
|
59
|
+
path.join(contextDir, 'spec.md')
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
let specPath = null;
|
|
63
|
+
let specContent = null;
|
|
64
|
+
for (const candidate of candidates) {
|
|
65
|
+
try {
|
|
66
|
+
specContent = await fs.readFile(candidate, 'utf8');
|
|
67
|
+
specPath = candidate;
|
|
68
|
+
break;
|
|
69
|
+
} catch { /* try next */ }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (!specContent) {
|
|
73
|
+
if (!options.json) logger.log(`No spec file found for feature: ${featureSlug}`);
|
|
74
|
+
return { ok: false, reason: 'no_spec_file', featureSlug };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const lastCheckpoint = extractLastCheckpoint(specContent);
|
|
78
|
+
const phaseGates = extractPhaseGates(specContent);
|
|
79
|
+
|
|
80
|
+
if (!lastCheckpoint) {
|
|
81
|
+
if (!options.json) logger.log(`No last_checkpoint found in ${path.basename(specPath)}.`);
|
|
82
|
+
return { ok: false, reason: 'no_checkpoint', specPath };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const { db, dbPath } = await openRuntimeDb(targetDir, { mustExist: true });
|
|
86
|
+
|
|
87
|
+
if (!db) {
|
|
88
|
+
if (!options.json) logger.log('No runtime database found. Run aioson agent:done first.');
|
|
89
|
+
return { ok: false, reason: 'no_db' };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
// Find latest agent run for this feature
|
|
94
|
+
const run = db.prepare(`
|
|
95
|
+
SELECT r.run_key, r.status, r.summary, r.updated_at
|
|
96
|
+
FROM agent_runs r
|
|
97
|
+
WHERE r.agent_name = ?
|
|
98
|
+
AND (r.run_key LIKE ? OR r.task_key LIKE ?)
|
|
99
|
+
ORDER BY r.updated_at DESC
|
|
100
|
+
LIMIT 1
|
|
101
|
+
`).get(agentName, `%${featureSlug}%`, `%${featureSlug}%`);
|
|
102
|
+
|
|
103
|
+
// If no run with feature slug, try latest run by agent name
|
|
104
|
+
const activeRun = run || db.prepare(`
|
|
105
|
+
SELECT run_key, status, summary, updated_at
|
|
106
|
+
FROM agent_runs
|
|
107
|
+
WHERE agent_name = ? AND status IN ('running', 'completed')
|
|
108
|
+
ORDER BY updated_at DESC
|
|
109
|
+
LIMIT 1
|
|
110
|
+
`).get(agentName);
|
|
111
|
+
|
|
112
|
+
if (!activeRun) {
|
|
113
|
+
if (!options.json) {
|
|
114
|
+
logger.log(`No active run found for agent @${agentName}.`);
|
|
115
|
+
logger.log('Tip: run aioson runtime:start first, or specify --agent=<name>.');
|
|
116
|
+
}
|
|
117
|
+
return { ok: false, reason: 'no_run', featureSlug, agentName };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Append checkpoint event
|
|
121
|
+
appendRunEvent(db, {
|
|
122
|
+
runKey: activeRun.run_key,
|
|
123
|
+
eventType: 'plan_checkpoint',
|
|
124
|
+
phase: 'spec',
|
|
125
|
+
status: 'in_progress',
|
|
126
|
+
message: lastCheckpoint,
|
|
127
|
+
payload: phaseGates ? { phase_gates: phaseGates } : null,
|
|
128
|
+
createdAt: nowIso()
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Update run summary to last_checkpoint if run is still in_progress
|
|
132
|
+
if (activeRun.status !== 'completed') {
|
|
133
|
+
db.prepare(
|
|
134
|
+
'UPDATE agent_runs SET summary = ?, updated_at = ? WHERE run_key = ?'
|
|
135
|
+
).run(lastCheckpoint, nowIso(), activeRun.run_key);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (options.json) {
|
|
139
|
+
return {
|
|
140
|
+
ok: true,
|
|
141
|
+
featureSlug,
|
|
142
|
+
specPath,
|
|
143
|
+
lastCheckpoint,
|
|
144
|
+
phaseGates,
|
|
145
|
+
runKey: activeRun.run_key,
|
|
146
|
+
dbPath
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
logger.log(`Reading ${path.basename(specPath)}...`);
|
|
151
|
+
logger.log(`last_checkpoint: "${lastCheckpoint}"`);
|
|
152
|
+
if (phaseGates) {
|
|
153
|
+
logger.log(`phase_gates: ${JSON.stringify(phaseGates)}`);
|
|
154
|
+
}
|
|
155
|
+
logger.log('');
|
|
156
|
+
logger.log('Checkpoint registered:');
|
|
157
|
+
logger.log(` run_key: ${activeRun.run_key}`);
|
|
158
|
+
logger.log(` summary: "${lastCheckpoint}"`);
|
|
159
|
+
logger.log(` status: in_progress (checkpoint only — use agent:done to close)`);
|
|
160
|
+
logger.log('');
|
|
161
|
+
logger.log(`Next: continue with /${agentName} — start from last_checkpoint`);
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
ok: true,
|
|
165
|
+
featureSlug,
|
|
166
|
+
specPath,
|
|
167
|
+
lastCheckpoint,
|
|
168
|
+
phaseGates,
|
|
169
|
+
runKey: activeRun.run_key,
|
|
170
|
+
dbPath
|
|
171
|
+
};
|
|
172
|
+
} finally {
|
|
173
|
+
db.close();
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
module.exports = { runSpecCheckpoint };
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs/promises');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const { openRuntimeDb } = require('../runtime-store');
|
|
6
|
+
|
|
7
|
+
async function runSpecStatus({ args, options = {}, logger }) {
|
|
8
|
+
const targetDir = path.resolve(process.cwd(), args[0] || '.');
|
|
9
|
+
const { db, dbPath } = await openRuntimeDb(targetDir, { mustExist: true });
|
|
10
|
+
|
|
11
|
+
if (!db) {
|
|
12
|
+
if (!options.json) logger.log('No runtime database found. Run aioson agent:done first.');
|
|
13
|
+
return { ok: false, reason: 'no_db' };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
const plans = db.prepare(`
|
|
18
|
+
SELECT plan_id, feature_slug, status, phases_total, phases_completed, created_at, updated_at
|
|
19
|
+
FROM implementation_plans
|
|
20
|
+
WHERE status != 'archived'
|
|
21
|
+
ORDER BY updated_at DESC
|
|
22
|
+
`).all();
|
|
23
|
+
|
|
24
|
+
const rows = [];
|
|
25
|
+
for (const plan of plans) {
|
|
26
|
+
const lastRun = db.prepare(`
|
|
27
|
+
SELECT r.agent_name, r.summary, r.updated_at
|
|
28
|
+
FROM agent_runs r
|
|
29
|
+
JOIN tasks t ON r.task_key = t.task_key
|
|
30
|
+
WHERE r.status IN ('running', 'completed')
|
|
31
|
+
AND (t.session_key LIKE ? OR r.agent_name IS NOT NULL)
|
|
32
|
+
ORDER BY r.updated_at DESC
|
|
33
|
+
LIMIT 1
|
|
34
|
+
`).get(`%${plan.feature_slug || ''}%`);
|
|
35
|
+
|
|
36
|
+
rows.push({
|
|
37
|
+
feature: plan.feature_slug || '(project)',
|
|
38
|
+
phase: `${plan.phases_completed}/${plan.phases_total}`,
|
|
39
|
+
status: plan.status,
|
|
40
|
+
lastAgent: lastRun?.agent_name || '—',
|
|
41
|
+
checkpoint: lastRun?.summary ? lastRun.summary.slice(0, 60) : '—'
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const totalLearnings = db.prepare(
|
|
46
|
+
"SELECT COUNT(*) as cnt FROM project_learnings WHERE status = 'active'"
|
|
47
|
+
).get()?.cnt || 0;
|
|
48
|
+
|
|
49
|
+
const promotable = db.prepare(
|
|
50
|
+
"SELECT COUNT(*) as cnt FROM project_learnings WHERE status = 'active' AND frequency >= 3"
|
|
51
|
+
).get()?.cnt || 0;
|
|
52
|
+
|
|
53
|
+
if (options.json) {
|
|
54
|
+
return { ok: true, features: rows, totalLearnings, promotable, dbPath };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
logger.log(`Project Status — ${targetDir}`);
|
|
58
|
+
logger.log('─'.repeat(80));
|
|
59
|
+
logger.log('Feature'.padEnd(20) + 'Phase'.padEnd(10) + 'Status'.padEnd(16) + 'Last Agent'.padEnd(16) + 'Checkpoint');
|
|
60
|
+
logger.log('─'.repeat(80));
|
|
61
|
+
for (const r of rows) {
|
|
62
|
+
logger.log(
|
|
63
|
+
r.feature.padEnd(20) +
|
|
64
|
+
r.phase.padEnd(10) +
|
|
65
|
+
r.status.padEnd(16) +
|
|
66
|
+
r.lastAgent.padEnd(16) +
|
|
67
|
+
r.checkpoint
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
logger.log('─'.repeat(80));
|
|
71
|
+
logger.log(`Active learnings: ${totalLearnings} | Promotable (freq≥3): ${promotable}`);
|
|
72
|
+
|
|
73
|
+
return { ok: true, features: rows, totalLearnings, promotable, dbPath };
|
|
74
|
+
} finally {
|
|
75
|
+
db.close();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
module.exports = { runSpecStatus };
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs/promises');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const { openRuntimeDb } = require('../runtime-store');
|
|
6
|
+
|
|
7
|
+
function nowIso() {
|
|
8
|
+
return new Date().toISOString();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function createLearningId() {
|
|
12
|
+
return `learning-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function parseFrontmatter(content) {
|
|
16
|
+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
17
|
+
if (!match) return {};
|
|
18
|
+
const result = {};
|
|
19
|
+
for (const line of match[1].split(/\r?\n/)) {
|
|
20
|
+
const colonIdx = line.indexOf(':');
|
|
21
|
+
if (colonIdx === -1) continue;
|
|
22
|
+
const key = line.slice(0, colonIdx).trim();
|
|
23
|
+
const value = line.slice(colonIdx + 1).trim().replace(/^["']|["']$/g, '');
|
|
24
|
+
if (key) result[key] = value;
|
|
25
|
+
}
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function extractSection(content, sectionName) {
|
|
30
|
+
const re = new RegExp(`^#{1,4}\\s+${sectionName}[\\s\\S]*?(?=^#{1,4}\\s|$)`, 'im');
|
|
31
|
+
const match = content.match(re);
|
|
32
|
+
if (!match) return '';
|
|
33
|
+
return match[0].replace(/^#{1,4}\s+\S.*\n/, '').trim();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function extractLearnings(content) {
|
|
37
|
+
const section = extractSection(content, 'Session Learnings');
|
|
38
|
+
const learnings = [];
|
|
39
|
+
for (const line of section.split(/\r?\n/)) {
|
|
40
|
+
const trimmed = line.replace(/^[-*]\s*/, '').trim();
|
|
41
|
+
if (!trimmed) continue;
|
|
42
|
+
const typeMatch = trimmed.match(/^\[(process|domain|quality|preference)\]\s+(.+)/i);
|
|
43
|
+
if (typeMatch) {
|
|
44
|
+
learnings.push({ type: typeMatch[1].toLowerCase(), title: typeMatch[2].trim() });
|
|
45
|
+
} else if (trimmed.length > 5) {
|
|
46
|
+
learnings.push({ type: 'process', title: trimmed });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return learnings;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function extractLastCheckpoint(content) {
|
|
53
|
+
const section = extractSection(content, 'last_checkpoint');
|
|
54
|
+
if (section) return section.replace(/^.*:\s*/, '').trim();
|
|
55
|
+
const fmMatch = content.match(/^---[\s\S]*?last_checkpoint:\s*(.+)/m);
|
|
56
|
+
return fmMatch ? fmMatch[1].trim().replace(/^["']|["']$/g, '') : null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function upsertProjectLearning(db, { title, type, featureSlug, evidence, sourceSession }) {
|
|
60
|
+
const existing = db.prepare(
|
|
61
|
+
'SELECT learning_id, frequency FROM project_learnings WHERE title = ? AND feature_slug = ?'
|
|
62
|
+
).get(title, featureSlug || null);
|
|
63
|
+
|
|
64
|
+
if (existing) {
|
|
65
|
+
db.prepare(
|
|
66
|
+
'UPDATE project_learnings SET frequency = ?, last_reinforced = ?, updated_at = ? WHERE learning_id = ?'
|
|
67
|
+
).run(existing.frequency + 1, nowIso(), nowIso(), existing.learning_id);
|
|
68
|
+
return { action: 'updated', learningId: existing.learning_id };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const learningId = createLearningId();
|
|
72
|
+
db.prepare(`
|
|
73
|
+
INSERT INTO project_learnings
|
|
74
|
+
(learning_id, feature_slug, type, title, confidence, frequency, last_reinforced,
|
|
75
|
+
applies_to, source_session, evidence, status, created_at, updated_at)
|
|
76
|
+
VALUES (?, ?, ?, ?, 'medium', 1, ?, 'project', ?, ?, 'active', ?, ?)
|
|
77
|
+
`).run(learningId, featureSlug || null, type, title, nowIso(), sourceSession || null, evidence || null, nowIso(), nowIso());
|
|
78
|
+
return { action: 'inserted', learningId };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function syncPlanPhases(db, featureSlug, phaseGates) {
|
|
82
|
+
if (!phaseGates || typeof phaseGates !== 'object') return 0;
|
|
83
|
+
const plan = db.prepare(
|
|
84
|
+
"SELECT plan_id FROM implementation_plans WHERE feature_slug = ? AND status != 'archived' ORDER BY created_at DESC LIMIT 1"
|
|
85
|
+
).get(featureSlug);
|
|
86
|
+
if (!plan) return 0;
|
|
87
|
+
|
|
88
|
+
let updated = 0;
|
|
89
|
+
const gateMap = { plan: 1, requirements: 2, design: 3 };
|
|
90
|
+
for (const [gate, status] of Object.entries(phaseGates)) {
|
|
91
|
+
const phaseNum = gateMap[gate];
|
|
92
|
+
if (!phaseNum) continue;
|
|
93
|
+
const phase = db.prepare(
|
|
94
|
+
'SELECT phase_number, status FROM plan_phases WHERE plan_id = ? AND phase_number = ?'
|
|
95
|
+
).get(plan.plan_id, phaseNum);
|
|
96
|
+
if (!phase) continue;
|
|
97
|
+
|
|
98
|
+
const newStatus = status === 'approved' ? 'completed' : (status === 'pending' ? 'pending' : phase.status);
|
|
99
|
+
if (newStatus !== phase.status) {
|
|
100
|
+
db.prepare(
|
|
101
|
+
'UPDATE plan_phases SET status = ?, completed_at = ? WHERE plan_id = ? AND phase_number = ?'
|
|
102
|
+
).run(newStatus, newStatus === 'completed' ? nowIso() : null, plan.plan_id, phaseNum);
|
|
103
|
+
updated++;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return updated;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function syncSpecFile(db, specPath, { verbose = false } = {}) {
|
|
110
|
+
let content;
|
|
111
|
+
try {
|
|
112
|
+
content = await fs.readFile(specPath, 'utf8');
|
|
113
|
+
} catch {
|
|
114
|
+
return { skipped: true, reason: 'not_found' };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const filename = path.basename(specPath, '.md');
|
|
118
|
+
const featureSlug = filename.startsWith('spec-') ? filename.slice(5) : null;
|
|
119
|
+
const fm = parseFrontmatter(content);
|
|
120
|
+
const phaseGates = fm.phase_gates ? JSON.parse(fm.phase_gates.replace(/'/g, '"')).catch?.() || null : null;
|
|
121
|
+
|
|
122
|
+
const learnings = extractLearnings(content);
|
|
123
|
+
const lastCheckpoint = extractLastCheckpoint(content);
|
|
124
|
+
|
|
125
|
+
let learningsSynced = 0;
|
|
126
|
+
for (const { type, title } of learnings) {
|
|
127
|
+
upsertProjectLearning(db, { title, type, featureSlug, sourceSession: filename });
|
|
128
|
+
learningsSynced++;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
let phasesSynced = 0;
|
|
132
|
+
if (featureSlug && fm.phase_gates) {
|
|
133
|
+
try {
|
|
134
|
+
const gates = JSON.parse(fm.phase_gates.replace(/'/g, '"'));
|
|
135
|
+
phasesSynced = syncPlanPhases(db, featureSlug, gates);
|
|
136
|
+
} catch { /* malformed phase_gates — skip */ }
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return { featureSlug, learningsSynced, phasesSynced, lastCheckpoint };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function runSpecSync({ args, options = {}, logger }) {
|
|
143
|
+
const targetDir = path.resolve(process.cwd(), args[0] || '.');
|
|
144
|
+
const contextDir = path.join(targetDir, '.aioson', 'context');
|
|
145
|
+
|
|
146
|
+
let files;
|
|
147
|
+
try {
|
|
148
|
+
const entries = await fs.readdir(contextDir);
|
|
149
|
+
files = entries.filter((f) => f.startsWith('spec') && f.endsWith('.md'));
|
|
150
|
+
} catch {
|
|
151
|
+
if (!options.json) logger.log('No .aioson/context/ directory found.');
|
|
152
|
+
return { ok: false, reason: 'no_context_dir' };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const { db, dbPath } = await openRuntimeDb(targetDir);
|
|
156
|
+
const results = [];
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
for (const file of files) {
|
|
160
|
+
const result = await syncSpecFile(db, path.join(contextDir, file), { verbose: options.verbose });
|
|
161
|
+
if (!result.skipped) {
|
|
162
|
+
results.push({ file, ...result });
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
} finally {
|
|
166
|
+
db.close();
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const totalLearnings = results.reduce((s, r) => s + (r.learningsSynced || 0), 0);
|
|
170
|
+
const totalPhases = results.reduce((s, r) => s + (r.phasesSynced || 0), 0);
|
|
171
|
+
|
|
172
|
+
if (options.json) {
|
|
173
|
+
return { ok: true, files: results, totalLearnings, totalPhases, dbPath };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
logger.log(`Spec Sync — ${targetDir}`);
|
|
177
|
+
logger.log('─'.repeat(50));
|
|
178
|
+
for (const r of results) {
|
|
179
|
+
logger.log(`${r.file}`);
|
|
180
|
+
if (r.learningsSynced > 0) logger.log(` Learnings synced: ${r.learningsSynced}`);
|
|
181
|
+
if (r.phasesSynced > 0) logger.log(` Plan phases updated: ${r.phasesSynced}`);
|
|
182
|
+
if (r.lastCheckpoint) logger.log(` last_checkpoint: "${r.lastCheckpoint}"`);
|
|
183
|
+
}
|
|
184
|
+
logger.log('─'.repeat(50));
|
|
185
|
+
logger.log(`Summary: ${totalLearnings} learnings synced, ${totalPhases} plan phases updated`);
|
|
186
|
+
|
|
187
|
+
return { ok: true, files: results, totalLearnings, totalPhases, dbPath };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
module.exports = { runSpecSync };
|