@jaimevalasek/aioson 1.5.1 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +49 -0
- package/README.md +729 -226
- package/docs/design-previews/aurora-command-ui-website.html +884 -0
- package/docs/design-previews/aurora-command-ui.html +682 -0
- package/docs/design-previews/bold-editorial-ui-website.html +658 -0
- package/docs/design-previews/bold-editorial-ui.html +717 -0
- package/docs/design-previews/clean-saas-ui-website.html +1202 -0
- package/docs/design-previews/clean-saas-ui.html +549 -0
- package/docs/design-previews/cognitive-core-ui-website.html +1009 -0
- package/docs/design-previews/cognitive-core-ui.html +463 -0
- package/docs/design-previews/glassmorphism-ui-website.html +572 -0
- package/docs/design-previews/glassmorphism-ui.html +886 -0
- package/docs/design-previews/index.html +699 -0
- package/docs/design-previews/interface-design-website.html +1187 -0
- package/docs/design-previews/interface-design.html +513 -0
- package/docs/design-previews/neo-brutalist-ui-website.html +621 -0
- package/docs/design-previews/neo-brutalist-ui.html +797 -0
- package/docs/design-previews/premium-command-center-ui-website.html +1217 -0
- package/docs/design-previews/premium-command-center-ui.html +552 -0
- package/docs/design-previews/pt.squarespace.com-homepage.html +889 -0
- package/docs/design-previews/warm-craft-ui-website.html +684 -0
- package/docs/design-previews/warm-craft-ui.html +739 -0
- package/docs/en/cli-reference.md +20 -9
- package/docs/integrations/sdlc-genius-boundary.md +76 -0
- package/docs/integrations/sdlc-genius-eval-matrix.md +75 -0
- package/docs/integrations/sdlc-genius-install-checklist.md +93 -0
- package/docs/integrations/sdlc-genius-review-samples.md +86 -0
- package/docs/pt/README.md +10 -0
- package/docs/pt/agent-sharding.md +132 -0
- package/docs/pt/agentes.md +9 -2
- package/docs/pt/busca-de-contexto.md +129 -0
- package/docs/pt/cache-de-contexto.md +156 -0
- package/docs/pt/comandos-cli.md +915 -1
- package/docs/pt/design-hybrid-forge.md +356 -0
- package/docs/pt/devlog-pipeline.md +270 -0
- package/docs/pt/fluxo-artefatos.md +178 -0
- package/docs/pt/hooks-session-guard.md +454 -0
- package/docs/pt/inicio-rapido.md +54 -3
- package/docs/pt/inteligencia-adaptativa.md +324 -0
- package/docs/pt/monitor-de-contexto.md +158 -0
- package/docs/pt/recuperacao-de-sessao.md +125 -0
- package/docs/pt/sandbox.md +125 -0
- package/docs/pt/sdd-automation-scripts.md +557 -0
- package/docs/pt/site-forge.md +309 -0
- package/docs/pt/skills.md +98 -6
- package/docs/pt/spec-learnings-pipeline.md +265 -0
- package/package.json +1 -1
- package/src/a2a/client.js +165 -0
- package/src/a2a/server.js +223 -0
- package/src/agent-loader.js +280 -0
- package/src/cli.js +329 -1
- package/src/commands/agent-audit.js +397 -0
- package/src/commands/agent-export-skill.js +229 -0
- package/src/commands/agent-loader.js +85 -0
- package/src/commands/artifact-validate.js +189 -0
- package/src/commands/brief-gen.js +405 -0
- package/src/commands/brief-validate.js +65 -0
- package/src/commands/classify.js +256 -0
- package/src/commands/context-cache.js +90 -0
- package/src/commands/context-compact.js +49 -0
- package/src/commands/context-health.js +175 -0
- package/src/commands/context-monitor.js +163 -0
- package/src/commands/context-search.js +66 -0
- package/src/commands/context-trim.js +177 -0
- package/src/commands/design-hybrid-options.js +385 -0
- package/src/commands/detect-test-runner.js +55 -0
- package/src/commands/devlog-export-brains.js +27 -0
- package/src/commands/devlog-process.js +292 -0
- package/src/commands/devlog-watch.js +131 -0
- package/src/commands/feature-close.js +165 -0
- package/src/commands/gate-check.js +228 -0
- package/src/commands/health.js +214 -0
- package/src/commands/hooks-emit.js +253 -0
- package/src/commands/hooks-install.js +347 -0
- package/src/commands/init.js +54 -13
- package/src/commands/install.js +52 -13
- package/src/commands/learning-auto-promote.js +195 -0
- package/src/commands/learning-evolve.js +364 -0
- package/src/commands/learning-export.js +103 -0
- package/src/commands/learning-rollback.js +164 -0
- package/src/commands/live.js +59 -1
- package/src/commands/pattern-detect.js +33 -0
- package/src/commands/preflight-context.js +30 -0
- package/src/commands/preflight.js +208 -0
- package/src/commands/pulse-update.js +130 -0
- package/src/commands/recovery.js +43 -0
- package/src/commands/runner-daemon.js +274 -0
- package/src/commands/runner-plan.js +70 -0
- package/src/commands/runner-queue-from-plan.js +166 -0
- package/src/commands/runner-queue.js +189 -0
- package/src/commands/runner-run.js +129 -0
- package/src/commands/runtime.js +47 -1
- package/src/commands/sandbox.js +37 -0
- package/src/commands/self-implement-loop.js +256 -0
- package/src/commands/session-guard.js +218 -0
- package/src/commands/setup-context.js +22 -2
- package/src/commands/setup.js +178 -0
- package/src/commands/sizing.js +165 -0
- package/src/commands/skill.js +144 -32
- package/src/commands/spec-checkpoint.js +177 -0
- package/src/commands/spec-status.js +79 -0
- package/src/commands/spec-sync.js +190 -0
- package/src/commands/spec-tasks.js +288 -0
- package/src/commands/squad-autorun.js +1220 -0
- package/src/commands/squad-bus.js +217 -0
- package/src/commands/squad-card.js +149 -0
- package/src/commands/squad-daemon.js +134 -0
- package/src/commands/squad-dependency-graph.js +164 -0
- package/src/commands/squad-review.js +106 -0
- package/src/commands/squad-scaffold.js +55 -0
- package/src/commands/squad-tool-register.js +157 -0
- package/src/commands/state-save.js +122 -0
- package/src/commands/tool-registry-cmd.js +232 -0
- package/src/commands/update.js +9 -0
- package/src/commands/verify-gate.js +572 -0
- package/src/commands/workflow-execute.js +241 -0
- package/src/constants.js +18 -0
- package/src/context-cache.js +159 -0
- package/src/context-search.js +326 -0
- package/src/design-variation-catalog.js +503 -0
- package/src/i18n/messages/en.js +32 -2
- package/src/i18n/messages/es.js +30 -2
- package/src/i18n/messages/fr.js +30 -2
- package/src/i18n/messages/pt-BR.js +32 -2
- package/src/install-animation.js +260 -0
- package/src/install-profile.js +143 -0
- package/src/install-wizard.js +475 -0
- package/src/installer.js +44 -10
- package/src/lib/health-check.js +158 -0
- package/src/lib/hook-protocol.js +76 -0
- package/src/mcp/apps/squad-dashboard/app.js +163 -0
- package/src/mcp/apps/squad-dashboard/index.html +261 -0
- package/src/mcp/apps/squad-dashboard/mcp-manifest.json +23 -0
- package/src/mcp/resources/squad-state.js +130 -0
- package/src/parser.js +7 -1
- package/src/preflight-engine.js +443 -0
- package/src/recovery-context-session.js +154 -0
- package/src/runner/cascade.js +97 -0
- package/src/runner/cli-launcher.js +109 -0
- package/src/runner/plan-importer.js +63 -0
- package/src/runner/queue-store.js +159 -0
- package/src/runtime-store.js +158 -4
- package/src/sandbox.js +177 -0
- package/src/squad/agent-teams-adapter.js +264 -0
- package/src/squad/brief-validator.js +350 -0
- package/src/squad/bus-bridge.js +140 -0
- package/src/squad/context-compactor.js +265 -0
- package/src/squad/cross-ai-synthesizer.js +250 -0
- package/src/squad/hooks-generator.js +196 -0
- package/src/squad/inter-squad-events.js +175 -0
- package/src/squad/intra-bus.js +345 -0
- package/src/squad/learning-extractor.js +213 -0
- package/src/squad/pattern-detector.js +365 -0
- package/src/squad/preflight-context.js +296 -0
- package/src/squad/recovery-context.js +242 -71
- package/src/squad/reflection.js +365 -0
- package/src/squad/squad-scaffold.js +177 -0
- package/src/squad/state-manager.js +310 -0
- package/src/squad/task-decomposer.js +652 -0
- package/src/squad/verify-gate.js +303 -0
- package/src/tool-executor.js +94 -0
- package/src/updater.js +10 -3
- package/src/worker-runner.js +186 -1
- package/template/.aioson/agents/analyst.md +119 -3
- package/template/.aioson/agents/architect.md +98 -0
- package/template/.aioson/agents/design-hybrid-forge.md +141 -0
- package/template/.aioson/agents/dev.md +335 -14
- package/template/.aioson/agents/deyvin.md +117 -2
- package/template/.aioson/agents/discovery-design-doc.md +44 -0
- package/template/.aioson/agents/genome.md +14 -0
- package/template/.aioson/agents/neo.md +78 -1
- package/template/.aioson/agents/orache.md +50 -4
- package/template/.aioson/agents/orchestrator.md +197 -1
- package/template/.aioson/agents/pm.md +93 -0
- package/template/.aioson/agents/product.md +77 -4
- package/template/.aioson/agents/profiler-enricher.md +14 -0
- package/template/.aioson/agents/profiler-forge.md +14 -0
- package/template/.aioson/agents/profiler-researcher.md +14 -0
- package/template/.aioson/agents/qa.md +249 -19
- package/template/.aioson/agents/setup.md +144 -12
- package/template/.aioson/agents/sheldon.md +237 -11
- package/template/.aioson/agents/site-forge.md +1753 -0
- package/template/.aioson/agents/squad.md +162 -0
- package/template/.aioson/agents/tester.md +209 -0
- package/template/.aioson/agents/ux-ui.md +34 -1
- package/template/.aioson/brains/README.md +128 -0
- package/template/.aioson/brains/_index.json +16 -0
- package/template/.aioson/brains/scripts/query.js +103 -0
- package/template/.aioson/brains/site-forge/visual-patterns.brain.json +205 -0
- package/template/.aioson/config.md +158 -13
- package/template/.aioson/constitution.md +33 -0
- package/template/.aioson/context/forensics/.gitkeep +0 -0
- package/template/.aioson/context/project-pulse.md +34 -0
- package/template/.aioson/context/seeds/seed-example.md +27 -0
- package/template/.aioson/context/user-profile.md +42 -0
- package/template/.aioson/docs/LAYERS.md +79 -0
- package/template/.aioson/docs/README.md +76 -0
- package/template/.aioson/docs/example-external-api-context.md +72 -0
- package/template/.aioson/locales/en/agents/architect.md +17 -0
- package/template/.aioson/locales/en/agents/dev.md +79 -13
- package/template/.aioson/locales/en/agents/orache.md +6 -0
- package/template/.aioson/locales/en/agents/orchestrator.md +24 -0
- package/template/.aioson/locales/en/agents/product.md +50 -0
- package/template/.aioson/locales/en/agents/setup.md +33 -1
- package/template/.aioson/locales/en/agents/sheldon.md +115 -0
- package/template/.aioson/locales/en/agents/squad.md +14 -0
- package/template/.aioson/locales/en/agents/tester.md +6 -0
- package/template/.aioson/locales/es/agents/analyst.md +2 -0
- package/template/.aioson/locales/es/agents/architect.md +19 -0
- package/template/.aioson/locales/es/agents/dev.md +64 -4
- package/template/.aioson/locales/es/agents/deyvin.md +2 -0
- package/template/.aioson/locales/es/agents/discovery-design-doc.md +2 -0
- package/template/.aioson/locales/es/agents/genome.md +2 -0
- package/template/.aioson/locales/es/agents/neo.md +2 -0
- package/template/.aioson/locales/es/agents/orache.md +2 -0
- package/template/.aioson/locales/es/agents/orchestrator.md +26 -0
- package/template/.aioson/locales/es/agents/pair.md +2 -0
- package/template/.aioson/locales/es/agents/pm.md +2 -0
- package/template/.aioson/locales/es/agents/product.md +52 -0
- package/template/.aioson/locales/es/agents/profiler-enricher.md +2 -0
- package/template/.aioson/locales/es/agents/profiler-forge.md +2 -0
- package/template/.aioson/locales/es/agents/profiler-researcher.md +2 -0
- package/template/.aioson/locales/es/agents/qa.md +2 -0
- package/template/.aioson/locales/es/agents/setup.md +35 -1
- package/template/.aioson/locales/es/agents/sheldon.md +117 -0
- package/template/.aioson/locales/es/agents/squad.md +16 -0
- package/template/.aioson/locales/es/agents/tester.md +9 -0
- package/template/.aioson/locales/es/agents/ux-ui.md +2 -0
- package/template/.aioson/locales/fr/agents/analyst.md +2 -0
- package/template/.aioson/locales/fr/agents/architect.md +19 -0
- package/template/.aioson/locales/fr/agents/dev.md +64 -4
- package/template/.aioson/locales/fr/agents/deyvin.md +2 -0
- package/template/.aioson/locales/fr/agents/discovery-design-doc.md +2 -0
- package/template/.aioson/locales/fr/agents/genome.md +2 -0
- package/template/.aioson/locales/fr/agents/neo.md +2 -0
- package/template/.aioson/locales/fr/agents/orache.md +2 -0
- package/template/.aioson/locales/fr/agents/orchestrator.md +26 -0
- package/template/.aioson/locales/fr/agents/pair.md +2 -0
- package/template/.aioson/locales/fr/agents/pm.md +2 -0
- package/template/.aioson/locales/fr/agents/product.md +52 -0
- package/template/.aioson/locales/fr/agents/profiler-enricher.md +2 -0
- package/template/.aioson/locales/fr/agents/profiler-forge.md +2 -0
- package/template/.aioson/locales/fr/agents/profiler-researcher.md +2 -0
- package/template/.aioson/locales/fr/agents/qa.md +2 -0
- package/template/.aioson/locales/fr/agents/setup.md +35 -1
- package/template/.aioson/locales/fr/agents/sheldon.md +117 -0
- package/template/.aioson/locales/fr/agents/squad.md +16 -0
- package/template/.aioson/locales/fr/agents/tester.md +9 -0
- package/template/.aioson/locales/fr/agents/ux-ui.md +2 -0
- package/template/.aioson/locales/pt-BR/agents/analyst.md +64 -3
- package/template/.aioson/locales/pt-BR/agents/architect.md +42 -0
- package/template/.aioson/locales/pt-BR/agents/dev.md +147 -14
- package/template/.aioson/locales/pt-BR/agents/deyvin.md +47 -0
- package/template/.aioson/locales/pt-BR/agents/neo.md +62 -1
- package/template/.aioson/locales/pt-BR/agents/orchestrator.md +158 -2
- package/template/.aioson/locales/pt-BR/agents/pm.md +95 -1
- package/template/.aioson/locales/pt-BR/agents/product.md +145 -18
- package/template/.aioson/locales/pt-BR/agents/qa.md +16 -0
- package/template/.aioson/locales/pt-BR/agents/setup.md +134 -19
- package/template/.aioson/locales/pt-BR/agents/sheldon.md +132 -1
- package/template/.aioson/locales/pt-BR/agents/squad.md +14 -0
- package/template/.aioson/locales/pt-BR/agents/tester.md +449 -0
- package/template/.aioson/rules/README.md +69 -0
- package/template/.aioson/rules/data-format-convention.md +136 -0
- package/template/.aioson/rules/example-monetary-values.md +30 -0
- package/template/.aioson/schemas/squad-manifest.schema.json +124 -3
- package/template/.aioson/skills/design/aurora-command-ui/SKILL.md +243 -0
- package/template/.aioson/skills/design/aurora-command-ui/references/art-direction.md +293 -0
- package/template/.aioson/skills/design/aurora-command-ui/references/components.md +827 -0
- package/template/.aioson/skills/design/aurora-command-ui/references/dashboards.md +250 -0
- package/template/.aioson/skills/design/aurora-command-ui/references/design-tokens.md +585 -0
- package/template/.aioson/skills/design/aurora-command-ui/references/motion.md +365 -0
- package/template/.aioson/skills/design/aurora-command-ui/references/patterns.md +482 -0
- package/template/.aioson/skills/design/aurora-command-ui/references/websites.md +387 -0
- package/template/.aioson/skills/design/glassmorphism-ui/SKILL.md +222 -0
- package/template/.aioson/skills/design/glassmorphism-ui/references/art-direction.md +159 -0
- package/template/.aioson/skills/design/glassmorphism-ui/references/components.md +498 -0
- package/template/.aioson/skills/design/glassmorphism-ui/references/dashboards.md +236 -0
- package/template/.aioson/skills/design/glassmorphism-ui/references/design-tokens.md +274 -0
- package/template/.aioson/skills/design/glassmorphism-ui/references/motion.md +355 -0
- package/template/.aioson/skills/design/glassmorphism-ui/references/patterns.md +198 -0
- package/template/.aioson/skills/design/glassmorphism-ui/references/websites.md +307 -0
- package/template/.aioson/skills/design/neo-brutalist-ui/SKILL.md +213 -0
- package/template/.aioson/skills/design/neo-brutalist-ui/references/art-direction.md +228 -0
- package/template/.aioson/skills/design/neo-brutalist-ui/references/components.md +855 -0
- package/template/.aioson/skills/design/neo-brutalist-ui/references/dashboards.md +334 -0
- package/template/.aioson/skills/design/neo-brutalist-ui/references/design-tokens.md +342 -0
- package/template/.aioson/skills/design/neo-brutalist-ui/references/motion.md +286 -0
- package/template/.aioson/skills/design/neo-brutalist-ui/references/patterns.md +458 -0
- package/template/.aioson/skills/design/neo-brutalist-ui/references/websites.md +723 -0
- package/template/.aioson/skills/design/pt.squarespace.com/.skill-meta.json +31 -0
- package/template/.aioson/skills/design/pt.squarespace.com/SKILL.md +66 -0
- package/template/.aioson/skills/design/pt.squarespace.com/references/components.md +368 -0
- package/template/.aioson/skills/design/pt.squarespace.com/references/design-tokens.md +150 -0
- package/template/.aioson/skills/design/pt.squarespace.com/references/motion.md +270 -0
- package/template/.aioson/skills/design/pt.squarespace.com/references/patterns.md +189 -0
- package/template/.aioson/skills/design/pt.squarespace.com/references/websites.md +165 -0
- package/template/.aioson/skills/process/aioson-spec-driven/SKILL.md +46 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/analyst.md +30 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/approval-gates.md +109 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/architect.md +23 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/artifact-map.md +44 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/classification-map.md +37 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/dev.md +47 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/deyvin.md +27 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/hardening-lane.md +49 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/maintenance-and-state.md +101 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/product.md +25 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/qa.md +30 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/sheldon.md +25 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/ui-language.md +75 -0
- package/template/.aioson/skills/process/design-hybrid-forge/SKILL.md +147 -0
- package/template/.aioson/skills/process/design-hybrid-forge/references/crossover-protocol.md +221 -0
- package/template/.aioson/skills/process/design-hybrid-forge/references/naming-registry.md +88 -0
- package/template/.aioson/skills/process/design-hybrid-forge/references/output-contract.md +306 -0
- package/template/.aioson/skills/process/design-hybrid-forge/references/pair-compatibility.md +149 -0
- package/template/.aioson/skills/process/design-hybrid-forge/references/quality-gates.md +208 -0
- package/template/.aioson/skills/process/design-hybrid-forge/references/variation-library.md +125 -0
- package/template/.aioson/skills/process/simplify/SKILL.md +173 -0
- package/template/.aioson/skills/static/context-budget-guide.md +46 -0
- package/template/.aioson/skills/static/harness-sensors.md +74 -0
- package/template/.aioson/skills/static/multi-agent-patterns.md +43 -0
- package/template/.aioson/skills/static/react-motion-patterns.md +22 -0
- package/template/.aioson/skills/static/static-html-patterns/checklists.md +43 -0
- package/template/.aioson/skills/static/static-html-patterns/css-tokens.md +609 -0
- package/template/.aioson/skills/static/static-html-patterns/motion.md +193 -0
- package/template/.aioson/skills/static/static-html-patterns/premium.md +711 -0
- package/template/.aioson/skills/static/static-html-patterns/structure.md +209 -0
- package/template/.aioson/skills/static/static-html-patterns/utilities.md +190 -0
- package/template/.aioson/skills/static/static-html-patterns.md +58 -1913
- package/template/.aioson/skills/static/threejs-patterns.md +929 -0
- package/template/.aioson/skills/static/web-research-cache.md +112 -0
- package/template/.aioson/tasks/implementation-plan.md +21 -1
- package/template/.claude/commands/aioson/agent/design-hybrid-forge.md +5 -0
- package/template/.claude/commands/aioson/agent/orache.md +5 -0
- package/template/.claude/commands/aioson/agent/sheldon.md +5 -0
- package/template/.claude/commands/aioson/agent/site-forge.md +5 -0
- package/template/AGENTS.md +75 -1
- package/template/CLAUDE.md +31 -0
- package/template/OPENCODE.md +4 -0
- package/template/researchs/.gitkeep +0 -0
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs/promises');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const { openRuntimeDb, startTask, startRun, updateRun, updateTask, appendRunEvent, attachArtifact } = 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 null;
|
|
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 === 'null' ? null : 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|\\Z)`, 'im');
|
|
31
|
+
const match = content.match(re);
|
|
32
|
+
if (!match) return '';
|
|
33
|
+
return match[0].replace(/^#{1,4}\s+\S[^\n]*\n/, '').trim();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function extractListItems(content, sectionName) {
|
|
37
|
+
const section = extractSection(content, sectionName);
|
|
38
|
+
const items = [];
|
|
39
|
+
for (const line of section.split(/\r?\n/)) {
|
|
40
|
+
const trimmed = line.replace(/^[-*]\s*/, '').trim();
|
|
41
|
+
if (trimmed && trimmed.length > 2) items.push(trimmed);
|
|
42
|
+
}
|
|
43
|
+
return items;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function extractTaggedLearnings(content) {
|
|
47
|
+
const section = extractSection(content, 'Learnings');
|
|
48
|
+
const learnings = [];
|
|
49
|
+
for (const line of section.split(/\r?\n/)) {
|
|
50
|
+
const trimmed = line.replace(/^[-*]\s*/, '').trim();
|
|
51
|
+
if (!trimmed) continue;
|
|
52
|
+
const typeMatch = trimmed.match(/^\[(process|domain|quality|preference)\]\s+(.+)/i);
|
|
53
|
+
if (typeMatch) {
|
|
54
|
+
learnings.push({ type: typeMatch[1].toLowerCase(), title: typeMatch[2].trim() });
|
|
55
|
+
} else if (trimmed.length > 5) {
|
|
56
|
+
learnings.push({ type: 'process', title: trimmed });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return learnings;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function extractSummary(content) {
|
|
63
|
+
const section = extractSection(content, 'Summary');
|
|
64
|
+
if (section) return section.split(/\r?\n/)[0].trim();
|
|
65
|
+
// Fallback: first non-empty line of body after frontmatter
|
|
66
|
+
const body = content.replace(/^---[\s\S]*?---\r?\n/, '');
|
|
67
|
+
const firstHeading = body.match(/^#\s+(.+)/m);
|
|
68
|
+
return firstHeading ? firstHeading[1].trim() : null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function upsertProjectLearning(db, { title, type, featureSlug, evidence, sourceSession }) {
|
|
72
|
+
const existing = db.prepare(
|
|
73
|
+
'SELECT learning_id, frequency FROM project_learnings WHERE title = ? AND (feature_slug = ? OR (feature_slug IS NULL AND ? IS NULL))'
|
|
74
|
+
).get(title, featureSlug || null, featureSlug || null);
|
|
75
|
+
|
|
76
|
+
if (existing) {
|
|
77
|
+
db.prepare(
|
|
78
|
+
'UPDATE project_learnings SET frequency = ?, last_reinforced = ?, updated_at = ? WHERE learning_id = ?'
|
|
79
|
+
).run(existing.frequency + 1, nowIso(), nowIso(), existing.learning_id);
|
|
80
|
+
return { action: 'updated', learningId: existing.learning_id };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const learningId = createLearningId();
|
|
84
|
+
db.prepare(`
|
|
85
|
+
INSERT INTO project_learnings
|
|
86
|
+
(learning_id, feature_slug, type, title, confidence, frequency, last_reinforced,
|
|
87
|
+
applies_to, source_session, evidence, status, created_at, updated_at)
|
|
88
|
+
VALUES (?, ?, ?, ?, 'medium', 1, ?, 'project', ?, ?, 'active', ?, ?)
|
|
89
|
+
`).run(learningId, featureSlug || null, type, title, nowIso(), sourceSession || null, evidence || null, nowIso(), nowIso());
|
|
90
|
+
return { action: 'inserted', learningId };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function markAsProcessed(filePath, processedAt) {
|
|
94
|
+
const content = await fs.readFile(filePath, 'utf8');
|
|
95
|
+
const ts = processedAt || nowIso();
|
|
96
|
+
|
|
97
|
+
// If file already has processed_at, skip
|
|
98
|
+
if (/^processed_at:/m.test(content)) return;
|
|
99
|
+
|
|
100
|
+
// Inject processed_at into frontmatter
|
|
101
|
+
const updated = content.replace(/^(---\r?\n[\s\S]*?)(---)/m, `$1processed_at: ${ts}\n$2`);
|
|
102
|
+
await fs.writeFile(filePath, updated, 'utf8');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function processDevlogFile(db, filePath) {
|
|
106
|
+
const content = await fs.readFile(filePath, 'utf8');
|
|
107
|
+
const fm = parseFrontmatter(content);
|
|
108
|
+
|
|
109
|
+
if (!fm || !fm.agent) {
|
|
110
|
+
return { status: 'malformed', file: path.basename(filePath), reason: 'missing frontmatter or agent field' };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Skip already-processed devlogs
|
|
114
|
+
if (fm.processed_at) {
|
|
115
|
+
return { status: 'skipped', file: path.basename(filePath), reason: 'already processed' };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const body = content.replace(/^---[\s\S]*?---\r?\n?/, '');
|
|
119
|
+
const summary = extractSummary(body) || `@${fm.agent} devlog`;
|
|
120
|
+
const featureSlug = fm.feature && fm.feature !== 'project' ? fm.feature : null;
|
|
121
|
+
const sessionKey = fm.session_key || null;
|
|
122
|
+
const startedAt = fm.started_at || fm.session_start || nowIso();
|
|
123
|
+
const finishedAt = fm.finished_at || fm.session_end || nowIso();
|
|
124
|
+
const status = fm.status === 'partial' ? 'running' : 'completed';
|
|
125
|
+
const verdict = fm.verdict ? String(fm.verdict).trim().toUpperCase() : null;
|
|
126
|
+
const planStepId = fm.plan_step || null;
|
|
127
|
+
|
|
128
|
+
// Create task + run
|
|
129
|
+
const taskKey = startTask(db, {
|
|
130
|
+
title: `devlog: ${summary}`,
|
|
131
|
+
squadSlug: null,
|
|
132
|
+
sessionKey: sessionKey || undefined,
|
|
133
|
+
status,
|
|
134
|
+
createdBy: fm.agent
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const runKey = startRun(db, {
|
|
138
|
+
taskKey,
|
|
139
|
+
agentName: fm.agent,
|
|
140
|
+
agentKind: 'devlog',
|
|
141
|
+
squadSlug: null,
|
|
142
|
+
title: `@${fm.agent} devlog`,
|
|
143
|
+
message: summary
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// Register artifacts
|
|
147
|
+
const artifactPaths = extractListItems(body, 'Artifacts');
|
|
148
|
+
for (const filePath_ of artifactPaths) {
|
|
149
|
+
// Only register file-like entries (containing a slash or dot)
|
|
150
|
+
if (/[/.]/.test(filePath_)) {
|
|
151
|
+
attachArtifact(db, { runKey, agentName: fm.agent, kind: 'output', filePath: filePath_ });
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Register decisions as execution events
|
|
156
|
+
const decisions = extractListItems(body, 'Decisions');
|
|
157
|
+
for (const decision of decisions) {
|
|
158
|
+
appendRunEvent(db, {
|
|
159
|
+
runKey,
|
|
160
|
+
eventType: 'decision',
|
|
161
|
+
phase: 'devlog',
|
|
162
|
+
status: 'completed',
|
|
163
|
+
message: decision,
|
|
164
|
+
createdAt: finishedAt
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Upsert learnings
|
|
169
|
+
const learnings = extractTaggedLearnings(body);
|
|
170
|
+
for (const { type, title } of learnings) {
|
|
171
|
+
upsertProjectLearning(db, { title, type, featureSlug, sourceSession: sessionKey || path.basename(filePath) });
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Log verdict if present
|
|
175
|
+
if (verdict && verdict !== 'NULL') {
|
|
176
|
+
appendRunEvent(db, {
|
|
177
|
+
runKey,
|
|
178
|
+
eventType: 'qa_verdict',
|
|
179
|
+
phase: 'devlog',
|
|
180
|
+
status: 'completed',
|
|
181
|
+
message: `QA VERDICT: ${verdict}`,
|
|
182
|
+
verdict,
|
|
183
|
+
planStepId,
|
|
184
|
+
createdAt: finishedAt
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Close run
|
|
189
|
+
updateRun(db, runKey, {
|
|
190
|
+
status,
|
|
191
|
+
summary,
|
|
192
|
+
finishedAt
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
if (status === 'completed') {
|
|
196
|
+
updateTask(db, taskKey, { status: 'completed', finishedAt });
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Mark devlog as processed
|
|
200
|
+
await markAsProcessed(filePath, nowIso());
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
status: 'ok',
|
|
204
|
+
file: path.basename(filePath),
|
|
205
|
+
runKey,
|
|
206
|
+
taskKey,
|
|
207
|
+
featureSlug,
|
|
208
|
+
artifactsCount: artifactPaths.filter((p) => /[/.]/.test(p)).length,
|
|
209
|
+
decisionsCount: decisions.length,
|
|
210
|
+
learningsCount: learnings.length,
|
|
211
|
+
verdict: verdict && verdict !== 'NULL' ? verdict : null
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async function runDevlogProcess({ args, options = {}, logger }) {
|
|
216
|
+
const targetDir = path.resolve(process.cwd(), args[0] || '.');
|
|
217
|
+
const logsDir = path.join(targetDir, 'aioson-logs');
|
|
218
|
+
|
|
219
|
+
let entries;
|
|
220
|
+
try {
|
|
221
|
+
entries = await fs.readdir(logsDir);
|
|
222
|
+
} catch {
|
|
223
|
+
if (!options.json) logger.log('No aioson-logs/ directory found — nothing to process.');
|
|
224
|
+
return { ok: true, processed: 0, skipped: 0, malformed: 0 };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const devlogFiles = entries
|
|
228
|
+
.filter((f) => f.startsWith('devlog-') && f.endsWith('.md'))
|
|
229
|
+
.sort();
|
|
230
|
+
|
|
231
|
+
if (devlogFiles.length === 0) {
|
|
232
|
+
if (!options.json) logger.log('No devlog files found.');
|
|
233
|
+
return { ok: true, processed: 0, skipped: 0, malformed: 0 };
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const { db, dbPath } = await openRuntimeDb(targetDir);
|
|
237
|
+
const results = [];
|
|
238
|
+
|
|
239
|
+
try {
|
|
240
|
+
for (const file of devlogFiles) {
|
|
241
|
+
const result = await processDevlogFile(db, path.join(logsDir, file));
|
|
242
|
+
results.push(result);
|
|
243
|
+
}
|
|
244
|
+
} finally {
|
|
245
|
+
db.close();
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const processed = results.filter((r) => r.status === 'ok');
|
|
249
|
+
const skipped = results.filter((r) => r.status === 'skipped');
|
|
250
|
+
const malformed = results.filter((r) => r.status === 'malformed');
|
|
251
|
+
|
|
252
|
+
const totalArtifacts = processed.reduce((s, r) => s + (r.artifactsCount || 0), 0);
|
|
253
|
+
const totalLearnings = processed.reduce((s, r) => s + (r.learningsCount || 0), 0);
|
|
254
|
+
|
|
255
|
+
if (options.json) {
|
|
256
|
+
return { ok: true, results, processed: processed.length, skipped: skipped.length, malformed: malformed.length, totalArtifacts, totalLearnings, dbPath };
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
logger.log(`Devlog Processing — ${path.basename(targetDir)}`);
|
|
260
|
+
logger.log('─'.repeat(50));
|
|
261
|
+
|
|
262
|
+
if (results.length === 0) {
|
|
263
|
+
logger.log('No devlogs to process.');
|
|
264
|
+
} else {
|
|
265
|
+
logger.log(`Found ${devlogFiles.length} devlog(s):`);
|
|
266
|
+
logger.log('');
|
|
267
|
+
for (const r of results) {
|
|
268
|
+
if (r.status === 'ok') {
|
|
269
|
+
logger.log(`${r.file}`);
|
|
270
|
+
logger.log(` Agent: @${r.featureSlug ? `${r.featureSlug}` : 'project'} | run: ${r.runKey}`);
|
|
271
|
+
if (r.artifactsCount > 0) logger.log(` Artifacts: ${r.artifactsCount} registered ✓`);
|
|
272
|
+
if (r.decisionsCount > 0) logger.log(` Decisions: ${r.decisionsCount} logged ✓`);
|
|
273
|
+
if (r.learningsCount > 0) logger.log(` Learnings: ${r.learningsCount} upserted ✓`);
|
|
274
|
+
if (r.verdict) logger.log(` Verdict: ${r.verdict} ✓`);
|
|
275
|
+
} else if (r.status === 'skipped') {
|
|
276
|
+
logger.log(`${r.file} — skipped (${r.reason})`);
|
|
277
|
+
} else {
|
|
278
|
+
logger.log(`${r.file} — ⚠ ${r.reason}. Fix frontmatter and re-run.`);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
logger.log('─'.repeat(50));
|
|
284
|
+
logger.log(`Processed: ${processed.length}/${devlogFiles.length} devlogs`);
|
|
285
|
+
if (totalLearnings > 0) logger.log(`New learnings: ${totalLearnings} (queued for brains export)`);
|
|
286
|
+
if (totalArtifacts > 0) logger.log(`Artifacts registered: ${totalArtifacts}`);
|
|
287
|
+
if (malformed.length > 0) logger.log(`Malformed (skipped): ${malformed.length}`);
|
|
288
|
+
|
|
289
|
+
return { ok: true, results, processed: processed.length, skipped: skipped.length, malformed: malformed.length, totalArtifacts, totalLearnings, dbPath };
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
module.exports = { runDevlogProcess, processDevlogFile };
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs/promises');
|
|
4
|
+
const fsSync = require('node:fs');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
const { openRuntimeDb } = require('../runtime-store');
|
|
7
|
+
const { processDevlogFile } = require('./devlog-process');
|
|
8
|
+
|
|
9
|
+
const POLL_INTERVAL_MS = 5000;
|
|
10
|
+
const WSL_VERSION_PATH = '/proc/version';
|
|
11
|
+
|
|
12
|
+
async function isWsl2() {
|
|
13
|
+
try {
|
|
14
|
+
const version = await fs.readFile(WSL_VERSION_PATH, 'utf8');
|
|
15
|
+
return version.toLowerCase().includes('microsoft');
|
|
16
|
+
} catch {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function processNewDevlog(targetDir, filePath, logger) {
|
|
22
|
+
const { db, dbPath } = await openRuntimeDb(targetDir).catch(() => ({ db: null }));
|
|
23
|
+
if (!db) {
|
|
24
|
+
logger.log(`[DEVLOG WATCHER] No database available — skipping ${path.basename(filePath)}`);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
const result = await processDevlogFile(db, filePath);
|
|
29
|
+
if (result.status === 'ok') {
|
|
30
|
+
const parts = [];
|
|
31
|
+
if (result.learningsCount > 0) parts.push(`${result.learningsCount} learnings`);
|
|
32
|
+
if (result.artifactsCount > 0) parts.push(`${result.artifactsCount} artifacts`);
|
|
33
|
+
if (result.verdict) parts.push(`VERDICT: ${result.verdict}`);
|
|
34
|
+
const detail = parts.length > 0 ? ` → ${parts.join(', ')} → SQLite ✓` : ' → SQLite ✓';
|
|
35
|
+
logger.log(`[${new Date().toISOString().slice(11, 19)}] Processed: ${result.file}${detail}`);
|
|
36
|
+
} else if (result.status === 'skipped') {
|
|
37
|
+
// silently skip already-processed files
|
|
38
|
+
} else {
|
|
39
|
+
logger.log(`[${new Date().toISOString().slice(11, 19)}] ⚠ ${result.file}: ${result.reason}`);
|
|
40
|
+
}
|
|
41
|
+
} finally {
|
|
42
|
+
db.close();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function watchWithFsWatch(logsDir, targetDir, logger) {
|
|
47
|
+
logger.log(`[DEVLOG WATCHER] Using fs.watch on ${logsDir}`);
|
|
48
|
+
|
|
49
|
+
return new Promise((resolve) => {
|
|
50
|
+
const watcher = fsSync.watch(logsDir, { persistent: true }, async (eventType, filename) => {
|
|
51
|
+
if (!filename || !filename.startsWith('devlog-') || !filename.endsWith('.md')) return;
|
|
52
|
+
if (eventType !== 'rename' && eventType !== 'change') return;
|
|
53
|
+
|
|
54
|
+
const filePath = path.join(logsDir, filename);
|
|
55
|
+
// Small delay to ensure file is fully written
|
|
56
|
+
setTimeout(async () => {
|
|
57
|
+
try {
|
|
58
|
+
await fs.access(filePath);
|
|
59
|
+
logger.log(`[${new Date().toISOString().slice(11, 19)}] New: ${filename} → processing...`);
|
|
60
|
+
await processNewDevlog(targetDir, filePath, logger);
|
|
61
|
+
} catch { /* file may have been removed */ }
|
|
62
|
+
}, 200);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
process.on('SIGINT', () => { watcher.close(); resolve(); });
|
|
66
|
+
process.on('SIGTERM', () => { watcher.close(); resolve(); });
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function watchWithPolling(logsDir, targetDir, logger) {
|
|
71
|
+
logger.log(`[DEVLOG WATCHER] WSL2 detected — using polling (${POLL_INTERVAL_MS / 1000}s interval)`);
|
|
72
|
+
|
|
73
|
+
const seen = new Set();
|
|
74
|
+
|
|
75
|
+
// Seed with already-existing files so we don't reprocess them
|
|
76
|
+
try {
|
|
77
|
+
const entries = await fs.readdir(logsDir);
|
|
78
|
+
for (const f of entries) {
|
|
79
|
+
if (f.startsWith('devlog-') && f.endsWith('.md')) seen.add(f);
|
|
80
|
+
}
|
|
81
|
+
} catch { /* logsDir not yet created */ }
|
|
82
|
+
|
|
83
|
+
return new Promise((resolve) => {
|
|
84
|
+
const timer = setInterval(async () => {
|
|
85
|
+
try {
|
|
86
|
+
const entries = await fs.readdir(logsDir);
|
|
87
|
+
const devlogs = entries.filter((f) => f.startsWith('devlog-') && f.endsWith('.md'));
|
|
88
|
+
for (const filename of devlogs) {
|
|
89
|
+
if (!seen.has(filename)) {
|
|
90
|
+
seen.add(filename);
|
|
91
|
+
const filePath = path.join(logsDir, filename);
|
|
92
|
+
logger.log(`[${new Date().toISOString().slice(11, 19)}] New: ${filename} → processing...`);
|
|
93
|
+
await processNewDevlog(targetDir, filePath, logger);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
} catch { /* directory may not exist yet */ }
|
|
97
|
+
}, POLL_INTERVAL_MS);
|
|
98
|
+
|
|
99
|
+
process.on('SIGINT', () => { clearInterval(timer); resolve(); });
|
|
100
|
+
process.on('SIGTERM', () => { clearInterval(timer); resolve(); });
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function runDevlogWatch({ args, options = {}, logger }) {
|
|
105
|
+
const targetDir = path.resolve(process.cwd(), args[0] || '.');
|
|
106
|
+
const logsDir = path.join(targetDir, 'aioson-logs');
|
|
107
|
+
const usePolling = options.poll || await isWsl2();
|
|
108
|
+
|
|
109
|
+
// Ensure the directory exists
|
|
110
|
+
await fs.mkdir(logsDir, { recursive: true });
|
|
111
|
+
|
|
112
|
+
logger.log(`[DEVLOG WATCHER] Watching ${logsDir} for new devlogs...`);
|
|
113
|
+
logger.log('[DEVLOG WATCHER] Press Ctrl+C to stop.');
|
|
114
|
+
|
|
115
|
+
if (usePolling) {
|
|
116
|
+
await watchWithPolling(logsDir, targetDir, logger);
|
|
117
|
+
} else {
|
|
118
|
+
try {
|
|
119
|
+
await watchWithFsWatch(logsDir, targetDir, logger);
|
|
120
|
+
} catch (err) {
|
|
121
|
+
// Fall back to polling if fs.watch fails (can happen in some environments)
|
|
122
|
+
logger.log(`[DEVLOG WATCHER] fs.watch failed (${err.message}) — falling back to polling`);
|
|
123
|
+
await watchWithPolling(logsDir, targetDir, logger);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
logger.log('[DEVLOG WATCHER] Stopped.');
|
|
128
|
+
return { ok: true };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
module.exports = { runDevlogWatch };
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* aioson feature:close — close a feature after QA sign-off.
|
|
5
|
+
*
|
|
6
|
+
* Updates spec-{slug}.md (adds QA sign-off block), features.md (sets status to done),
|
|
7
|
+
* and project-pulse.md (removes from active work).
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* aioson feature:close . --feature=checkout --verdict=PASS
|
|
11
|
+
* aioson feature:close . --feature=checkout --verdict=PASS --residual="Email delivery not tested E2E"
|
|
12
|
+
* aioson feature:close . --feature=checkout --verdict=FAIL --notes="Auth edge case missing"
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const fs = require('node:fs/promises');
|
|
16
|
+
const path = require('node:path');
|
|
17
|
+
const { contextDir, readFileSafe, parseFrontmatter } = require('../preflight-engine');
|
|
18
|
+
|
|
19
|
+
function nowDate() {
|
|
20
|
+
return new Date().toISOString().slice(0, 10);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function updateSpecFile(specPath, verdict, residual, date) {
|
|
24
|
+
const content = await readFileSafe(specPath);
|
|
25
|
+
if (!content) return false;
|
|
26
|
+
|
|
27
|
+
const signOff = [
|
|
28
|
+
'',
|
|
29
|
+
'## QA Sign-off',
|
|
30
|
+
'',
|
|
31
|
+
`- **Date:** ${date}`,
|
|
32
|
+
`- **Verdict:** ${verdict}`,
|
|
33
|
+
residual ? `- **Residual:** ${residual}` : null,
|
|
34
|
+
`- **Gate D (execution):** ${verdict === 'PASS' ? 'approved' : 'rejected'}`,
|
|
35
|
+
''
|
|
36
|
+
].filter((l) => l !== null).join('\n');
|
|
37
|
+
|
|
38
|
+
// Update gate_execution in frontmatter first (on original content)
|
|
39
|
+
const newStatus = verdict === 'PASS' ? 'approved' : 'rejected';
|
|
40
|
+
const fm = parseFrontmatter(content);
|
|
41
|
+
let baseContent = content;
|
|
42
|
+
if (Object.keys(fm).length > 0) {
|
|
43
|
+
baseContent = content.replace(
|
|
44
|
+
/^---\r?\n[\s\S]*?\r?\n---/,
|
|
45
|
+
(block) => {
|
|
46
|
+
if (block.includes('gate_execution')) {
|
|
47
|
+
return block.replace(/gate_execution:\s*.+/, `gate_execution: ${newStatus}`);
|
|
48
|
+
}
|
|
49
|
+
return block.replace(/^---\r?\n/, `---\ngate_execution: ${newStatus}\n`);
|
|
50
|
+
}
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Now apply QA sign-off on top of the frontmatter-updated content
|
|
55
|
+
if (baseContent.includes('## QA Sign-off')) {
|
|
56
|
+
const updated = baseContent.replace(
|
|
57
|
+
/## QA Sign-off[\s\S]*?(?=\n##|\s*$)/,
|
|
58
|
+
signOff.trimStart()
|
|
59
|
+
);
|
|
60
|
+
await fs.writeFile(specPath, updated, 'utf8');
|
|
61
|
+
} else {
|
|
62
|
+
await fs.writeFile(specPath, baseContent + signOff, 'utf8');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function updateFeaturesFile(featuresPath, slug, verdict, date) {
|
|
69
|
+
const content = await readFileSafe(featuresPath);
|
|
70
|
+
if (!content) return false;
|
|
71
|
+
|
|
72
|
+
const status = verdict === 'PASS' ? 'done' : 'qa_failed';
|
|
73
|
+
|
|
74
|
+
// Try to find and update the feature row
|
|
75
|
+
const updated = content.replace(
|
|
76
|
+
new RegExp(`(\\|[^|]*${slug}[^|]*\\|[^|]*\\|)[^|]*(\\|)`, 'g'),
|
|
77
|
+
(match, before, after) => `${before} ${status} (${date}) ${after}`
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
if (updated !== content) {
|
|
81
|
+
await fs.writeFile(featuresPath, updated, 'utf8');
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Append if not found
|
|
86
|
+
const line = `| ${slug} | ${verdict === 'PASS' ? 'done' : 'qa_failed'} | ${date} | QA ${verdict} |`;
|
|
87
|
+
await fs.appendFile(featuresPath, `\n${line}\n`, 'utf8');
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function runFeatureClose({ args, options = {}, logger }) {
|
|
92
|
+
const targetDir = path.resolve(process.cwd(), args[0] || '.');
|
|
93
|
+
const slug = options.feature ? String(options.feature) : null;
|
|
94
|
+
const verdict = options.verdict ? String(options.verdict).toUpperCase() : null;
|
|
95
|
+
const residual = options.residual ? String(options.residual) : null;
|
|
96
|
+
const notes = options.notes ? String(options.notes) : null;
|
|
97
|
+
|
|
98
|
+
if (!slug) {
|
|
99
|
+
if (options.json) return { ok: false, reason: 'missing_feature' };
|
|
100
|
+
logger.log('--feature=<slug> is required.');
|
|
101
|
+
return { ok: false };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (!verdict || !['PASS', 'FAIL'].includes(verdict)) {
|
|
105
|
+
if (options.json) return { ok: false, reason: 'invalid_verdict' };
|
|
106
|
+
logger.log('--verdict=PASS or --verdict=FAIL is required.');
|
|
107
|
+
return { ok: false };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const today = nowDate();
|
|
111
|
+
const dir = contextDir(targetDir);
|
|
112
|
+
const updates = [];
|
|
113
|
+
|
|
114
|
+
// 1. Update spec file
|
|
115
|
+
const specPath = path.join(dir, `spec-${slug}.md`);
|
|
116
|
+
const specUpdated = await updateSpecFile(specPath, verdict, residual || notes, today);
|
|
117
|
+
if (specUpdated) {
|
|
118
|
+
updates.push(`spec-${slug}.md: added QA sign-off (${today}, ${verdict})`);
|
|
119
|
+
} else {
|
|
120
|
+
updates.push(`spec-${slug}.md: not found (skipped)`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// 2. Update features.md
|
|
124
|
+
const featuresPath = path.join(dir, 'features.md');
|
|
125
|
+
const featuresContent = await readFileSafe(featuresPath);
|
|
126
|
+
if (featuresContent) {
|
|
127
|
+
await updateFeaturesFile(featuresPath, slug, verdict, today);
|
|
128
|
+
updates.push(`features.md: ${slug} → ${verdict === 'PASS' ? 'done' : 'qa_failed'} (${today})`);
|
|
129
|
+
} else {
|
|
130
|
+
updates.push('features.md: not found (skipped)');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// 3. Update project-pulse.md
|
|
134
|
+
const pulsePath = path.join(dir, 'project-pulse.md');
|
|
135
|
+
const pulseContent = await readFileSafe(pulsePath);
|
|
136
|
+
if (pulseContent) {
|
|
137
|
+
const fm = parseFrontmatter(pulseContent);
|
|
138
|
+
const status = verdict === 'PASS' ? 'closed' : 'qa_failed';
|
|
139
|
+
const updatedPulse = pulseContent
|
|
140
|
+
.replace(/active_feature:\s*.+/, `active_feature: (none)`)
|
|
141
|
+
.replace(/active_work:\s*".+"/, `active_work: ""`)
|
|
142
|
+
.replace(/last_agent:\s*.+/, `last_agent: qa`)
|
|
143
|
+
.replace(/last_gate:\s*.+/, `last_gate: Gate D: ${verdict === 'PASS' ? 'approved' : 'rejected'}`);
|
|
144
|
+
await fs.writeFile(pulsePath, updatedPulse, 'utf8');
|
|
145
|
+
updates.push('project-pulse.md: updated active work');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const result = {
|
|
149
|
+
ok: true,
|
|
150
|
+
feature: slug,
|
|
151
|
+
verdict,
|
|
152
|
+
date: today,
|
|
153
|
+
residual: residual || notes || null,
|
|
154
|
+
updates
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
if (options.json) return result;
|
|
158
|
+
|
|
159
|
+
logger.log(`Feature closure — ${slug}:`);
|
|
160
|
+
for (const u of updates) logger.log(` ${u}`);
|
|
161
|
+
|
|
162
|
+
return result;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
module.exports = { runFeatureClose };
|