@jaimevalasek/aioson 1.6.0 → 1.7.2
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 +74 -0
- package/README.md +729 -232
- package/docs/design-previews/pt.squarespace.com-homepage.html +889 -0
- 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 +3 -0
- package/docs/pt/agentes.md +1 -0
- package/docs/pt/comandos-cli.md +888 -2
- package/docs/pt/design-hybrid-forge.md +255 -6
- 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/monitor-de-contexto.md +59 -5
- package/docs/pt/sdd-automation-scripts.md +557 -0
- package/docs/pt/site-forge.md +309 -0
- 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/cli.js +235 -1
- package/src/commands/agent-audit.js +397 -0
- package/src/commands/agent-export-skill.js +229 -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-compact.js +49 -0
- package/src/commands/context-health.js +175 -0
- package/src/commands/context-monitor.js +71 -0
- package/src/commands/context-trim.js +177 -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/hooks-emit.js +253 -0
- package/src/commands/hooks-install.js +347 -0
- package/src/commands/learning-auto-promote.js +195 -0
- package/src/commands/learning-evolve.js +18 -9
- package/src/commands/learning-export.js +103 -0
- package/src/commands/learning-rollback.js +164 -0
- package/src/commands/live.js +25 -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/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/self-implement-loop.js +256 -0
- package/src/commands/session-guard.js +218 -0
- package/src/commands/sizing.js +165 -0
- package/src/commands/skill.js +65 -0
- 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/update.js +2 -0
- package/src/commands/verify-gate.js +572 -0
- package/src/commands/workflow-execute.js +241 -0
- package/src/constants.js +22 -0
- package/src/install-profile.js +2 -2
- package/src/install-wizard.js +3 -2
- package/src/installer.js +6 -0
- 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/preflight-engine.js +443 -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 +61 -3
- 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/updater.js +4 -5
- package/src/worker-runner.js +186 -1
- package/template/.aioson/agents/analyst.md +62 -1
- package/template/.aioson/agents/architect.md +61 -1
- package/template/.aioson/agents/copywriter.md +463 -0
- package/template/.aioson/agents/design-hybrid-forge.md +14 -0
- package/template/.aioson/agents/dev.md +271 -25
- package/template/.aioson/agents/deyvin.md +67 -8
- package/template/.aioson/agents/discovery-design-doc.md +44 -0
- package/template/.aioson/agents/genome.md +14 -0
- package/template/.aioson/agents/neo.md +83 -2
- package/template/.aioson/agents/orache.md +50 -4
- package/template/.aioson/agents/orchestrator.md +197 -1
- package/template/.aioson/agents/pm.md +35 -0
- package/template/.aioson/agents/product.md +50 -5
- 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 +273 -21
- package/template/.aioson/agents/setup.md +96 -10
- package/template/.aioson/agents/sheldon.md +131 -6
- package/template/.aioson/agents/site-forge.md +1753 -0
- package/template/.aioson/agents/squad.md +352 -0
- package/template/.aioson/agents/tester.md +53 -0
- package/template/.aioson/agents/ux-ui.md +203 -4
- 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 +143 -13
- package/template/.aioson/constitution.md +33 -0
- package/template/.aioson/context/project-pulse.md +34 -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/genomes/copywriting.md +204 -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/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 +2 -0
- 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 +2 -0
- 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 +101 -18
- 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/cognitive-core-ui/references/motion.md +2 -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/marketing/references/anti-patterns.md +254 -0
- package/template/.aioson/skills/marketing/references/fascinations.md +192 -0
- package/template/.aioson/skills/marketing/references/five-acts.md +248 -0
- package/template/.aioson/skills/marketing/references/market-intelligence.md +198 -0
- package/template/.aioson/skills/marketing/references/offer-structure.md +203 -0
- package/template/.aioson/skills/marketing/references/one-belief.md +149 -0
- package/template/.aioson/skills/marketing/references/patterns.md +218 -0
- package/template/.aioson/skills/marketing/references/pms-research.md +193 -0
- package/template/.aioson/skills/marketing/vsl-craft.md +385 -0
- package/template/.aioson/skills/process/aioson-spec-driven/SKILL.md +1 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/analyst.md +30 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/architect.md +23 -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/maintenance-and-state.md +35 -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/design-hybrid-forge/SKILL.md +4 -1
- package/template/.aioson/skills/process/design-hybrid-forge/references/output-contract.md +15 -0
- package/template/.aioson/skills/process/design-hybrid-forge/references/pair-compatibility.md +32 -0
- package/template/.aioson/skills/process/design-hybrid-forge/references/quality-gates.md +20 -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/landing-page-deploy.md +192 -0
- package/template/.aioson/skills/static/landing-page-forge.md +730 -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/ui-ux-modern.md +1 -0
- package/template/.aioson/skills/static/web-research-cache.md +112 -0
- package/template/.aioson/tasks/implementation-plan.md +21 -1
- package/template/.aioson/tasks/squad-create.md +22 -0
- package/template/.aioson/tasks/squad-design.md +30 -0
- package/template/.aioson/templates/squads/digital-marketing-agency/template.json +96 -0
- 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 +55 -3
- package/template/CLAUDE.md +31 -0
- package/template/OPENCODE.md +4 -0
- package/template/researchs/.gitkeep +0 -0
- package/template/.aioson/skills/design-system/components/SKILL.md:Zone.Identifier +0 -0
- package/template/.aioson/skills/design-system/dashboards/SKILL.md:Zone.Identifier +0 -0
- package/template/.aioson/skills/design-system/foundations/SKILL.md:Zone.Identifier +0 -0
- package/template/.aioson/skills/design-system/motion/SKILL.md:Zone.Identifier +0 -0
- package/template/.aioson/skills/design-system/patterns/SKILL.md:Zone.Identifier +0 -0
|
@@ -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 };
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs/promises');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
|
|
6
|
+
function nowIso() {
|
|
7
|
+
return new Date().toISOString();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function parseFrontmatter(content) {
|
|
11
|
+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
12
|
+
if (!match) return {};
|
|
13
|
+
const result = {};
|
|
14
|
+
for (const line of match[1].split(/\r?\n/)) {
|
|
15
|
+
const colonIdx = line.indexOf(':');
|
|
16
|
+
if (colonIdx === -1) continue;
|
|
17
|
+
const key = line.slice(0, colonIdx).trim();
|
|
18
|
+
const value = line.slice(colonIdx + 1).trim().replace(/^["']|["']$/g, '');
|
|
19
|
+
if (key) result[key] = value;
|
|
20
|
+
}
|
|
21
|
+
return result;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function extractBulletValue(text, label) {
|
|
25
|
+
// Matches "**Label:**" or "- **Label:**" patterns (case-insensitive)
|
|
26
|
+
const re = new RegExp(`\\*\\*${label}[:\\s][*]*\\*\\*:?\\s*(.+)`, 'i');
|
|
27
|
+
const match = text.match(re);
|
|
28
|
+
if (match) return match[1].trim();
|
|
29
|
+
// Fallback: plain "Label:" without bold
|
|
30
|
+
const re2 = new RegExp(`^[-*]?\\s*${label}[:\\s]+(.+)`, 'im');
|
|
31
|
+
const m2 = text.match(re2);
|
|
32
|
+
return m2 ? m2[1].trim() : null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function parsePhases(content) {
|
|
36
|
+
// Match phase headers: ### Fase N — title or ## Phase N — title
|
|
37
|
+
const phasePattern = /^#{2,4}\s+(Fase|Phase)\s+(\d+)\s*[—\-–]\s*(.+)$/im;
|
|
38
|
+
const phases = [];
|
|
39
|
+
|
|
40
|
+
// Split by phase headers
|
|
41
|
+
const lines = content.split(/\r?\n/);
|
|
42
|
+
let currentPhase = null;
|
|
43
|
+
let currentLines = [];
|
|
44
|
+
|
|
45
|
+
for (const line of lines) {
|
|
46
|
+
const m = line.match(/^(#{2,4})\s+(Fase|Phase)\s+(\d+)\s*[—\-–]\s*(.+)$/i);
|
|
47
|
+
if (m) {
|
|
48
|
+
if (currentPhase) {
|
|
49
|
+
currentPhase.body = currentLines.join('\n');
|
|
50
|
+
phases.push(currentPhase);
|
|
51
|
+
}
|
|
52
|
+
currentPhase = {
|
|
53
|
+
number: parseInt(m[3], 10),
|
|
54
|
+
title: m[4].trim(),
|
|
55
|
+
body: ''
|
|
56
|
+
};
|
|
57
|
+
currentLines = [];
|
|
58
|
+
} else if (currentPhase) {
|
|
59
|
+
currentLines.push(line);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (currentPhase) {
|
|
63
|
+
currentPhase.body = currentLines.join('\n');
|
|
64
|
+
phases.push(currentPhase);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return phases.map((phase) => {
|
|
68
|
+
const what = extractBulletValue(phase.body, 'O que') ||
|
|
69
|
+
extractBulletValue(phase.body, 'What');
|
|
70
|
+
const dependsOn = extractBulletValue(phase.body, 'Depende de') ||
|
|
71
|
+
extractBulletValue(phase.body, 'Depends on');
|
|
72
|
+
const inputs = extractBulletValue(phase.body, 'Artefatos de entrada') ||
|
|
73
|
+
extractBulletValue(phase.body, 'Input artifacts') ||
|
|
74
|
+
extractBulletValue(phase.body, 'Inputs');
|
|
75
|
+
const done = extractBulletValue(phase.body, 'Critério de done') ||
|
|
76
|
+
extractBulletValue(phase.body, 'Done criterion') ||
|
|
77
|
+
extractBulletValue(phase.body, 'Done criteria') ||
|
|
78
|
+
extractBulletValue(phase.body, 'Criterio de done');
|
|
79
|
+
const checkpoint = extractBulletValue(phase.body, 'Checkpoint');
|
|
80
|
+
const parallel = /parallel:\s*true/i.test(phase.body);
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
number: phase.number,
|
|
84
|
+
title: phase.title,
|
|
85
|
+
what,
|
|
86
|
+
dependsOn,
|
|
87
|
+
inputs,
|
|
88
|
+
done,
|
|
89
|
+
checkpoint,
|
|
90
|
+
parallel
|
|
91
|
+
};
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function extractParallelNotes(content) {
|
|
96
|
+
// Find "Fases paralelas" or "Parallel" section
|
|
97
|
+
const re = /^#{1,4}\s+(Fases paralelas|Parallel phases?)[^\n]*\n([\s\S]*?)(?=^#{1,4}|\s*$)/im;
|
|
98
|
+
const m = content.match(re);
|
|
99
|
+
if (!m) return [];
|
|
100
|
+
return m[2]
|
|
101
|
+
.split(/\r?\n/)
|
|
102
|
+
.map((l) => l.replace(/^[-*]\s*/, '').trim())
|
|
103
|
+
.filter(Boolean);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function extractOpenAssumptions(content) {
|
|
107
|
+
const re = /^#{1,4}\s+(Decisões adiadas|Deferred decisions|Open assumptions)[^\n]*\n([\s\S]*?)(?=^#{1,4}|\s*$)/im;
|
|
108
|
+
const m = content.match(re);
|
|
109
|
+
if (!m) return [];
|
|
110
|
+
return m[2]
|
|
111
|
+
.split(/\r?\n/)
|
|
112
|
+
.map((l) => l.replace(/^[-*]\s*/, '').trim())
|
|
113
|
+
.filter(Boolean)
|
|
114
|
+
.slice(0, 5); // cap to avoid bloat
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function buildTasksDoc({ title, slug, planPath, phases, parallelNotes, openAssumptions, generatedAt }) {
|
|
118
|
+
const lines = [];
|
|
119
|
+
const header = title || (slug ? `tasks-${slug}` : 'tasks');
|
|
120
|
+
|
|
121
|
+
lines.push(`# Tasks — ${header}`);
|
|
122
|
+
lines.push('');
|
|
123
|
+
lines.push(`> Generated from: \`${path.basename(planPath)}\``);
|
|
124
|
+
lines.push(`> Generated at: ${generatedAt}`);
|
|
125
|
+
lines.push(`> Do not edit manually — regenerate from the plan when phases change.`);
|
|
126
|
+
lines.push('');
|
|
127
|
+
|
|
128
|
+
for (const phase of phases) {
|
|
129
|
+
lines.push(`## Phase ${phase.number} — ${phase.title}`);
|
|
130
|
+
lines.push('');
|
|
131
|
+
|
|
132
|
+
if (phase.inputs) {
|
|
133
|
+
lines.push(`- [ ] Read input artifacts: ${phase.inputs}`);
|
|
134
|
+
} else {
|
|
135
|
+
lines.push(`- [ ] Read required input artifacts`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (phase.what) {
|
|
139
|
+
lines.push(`- [ ] Execute: ${phase.what}`);
|
|
140
|
+
} else {
|
|
141
|
+
lines.push(`- [ ] Execute planned work`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (phase.done) {
|
|
145
|
+
lines.push(`- [ ] Verify done criterion: ${phase.done}`);
|
|
146
|
+
} else {
|
|
147
|
+
lines.push(`- [ ] Verify done criterion`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (phase.checkpoint) {
|
|
151
|
+
lines.push(`- [ ] Pass checkpoint gate: ${phase.checkpoint}`);
|
|
152
|
+
} else {
|
|
153
|
+
lines.push(`- [ ] Pass checkpoint gate`);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (phase.dependsOn && phase.dependsOn !== 'nada' && phase.dependsOn !== 'nothing' && phase.dependsOn !== 'none') {
|
|
157
|
+
lines.push('');
|
|
158
|
+
lines.push(`> Depends on: ${phase.dependsOn}`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (phase.parallel) {
|
|
162
|
+
lines.push(`> Can run in parallel (no shared entities with adjacent phases)`);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
lines.push('');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (parallelNotes.length > 0) {
|
|
169
|
+
lines.push('## Parallel notes');
|
|
170
|
+
lines.push('');
|
|
171
|
+
for (const note of parallelNotes) {
|
|
172
|
+
lines.push(`- ${note}`);
|
|
173
|
+
}
|
|
174
|
+
lines.push('');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (openAssumptions.length > 0) {
|
|
178
|
+
lines.push('## Deferred decisions');
|
|
179
|
+
lines.push('');
|
|
180
|
+
for (const item of openAssumptions) {
|
|
181
|
+
lines.push(`- ${item}`);
|
|
182
|
+
}
|
|
183
|
+
lines.push('');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return lines.join('\n');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async function runSpecTasks({ args, options = {}, logger }) {
|
|
190
|
+
const targetDir = path.resolve(process.cwd(), args[0] || '.');
|
|
191
|
+
const contextDir = path.join(targetDir, '.aioson', 'context');
|
|
192
|
+
|
|
193
|
+
// Resolve plan path
|
|
194
|
+
let planPath;
|
|
195
|
+
if (options.plan) {
|
|
196
|
+
// If absolute path or starts with ./ — use as-is relative to cwd
|
|
197
|
+
planPath = path.resolve(process.cwd(), options.plan);
|
|
198
|
+
} else {
|
|
199
|
+
// Default: look for implementation-plan.md in context dir
|
|
200
|
+
planPath = path.join(contextDir, 'implementation-plan.md');
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
let planContent;
|
|
204
|
+
try {
|
|
205
|
+
planContent = await fs.readFile(planPath, 'utf8');
|
|
206
|
+
} catch {
|
|
207
|
+
if (!options.json) {
|
|
208
|
+
logger.log(`Error: plan file not found: ${planPath}`);
|
|
209
|
+
logger.log('Usage: aioson spec:tasks . --plan=.aioson/context/implementation-plan-{slug}.md');
|
|
210
|
+
}
|
|
211
|
+
return { ok: false, reason: 'plan_not_found', planPath };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const fm = parseFrontmatter(planContent);
|
|
215
|
+
const featureSlug = fm.feature_slug && fm.feature_slug !== 'null' ? fm.feature_slug : null;
|
|
216
|
+
const planTitle = fm.project || null;
|
|
217
|
+
|
|
218
|
+
const phases = parsePhases(planContent);
|
|
219
|
+
|
|
220
|
+
if (phases.length === 0) {
|
|
221
|
+
if (!options.json) {
|
|
222
|
+
logger.log('Warning: no phases found in plan. Check that phases follow the format:');
|
|
223
|
+
logger.log(' ### Fase N — title');
|
|
224
|
+
}
|
|
225
|
+
return { ok: false, reason: 'no_phases', planPath };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const parallelNotes = extractParallelNotes(planContent);
|
|
229
|
+
const openAssumptions = extractOpenAssumptions(planContent);
|
|
230
|
+
const generatedAt = nowIso();
|
|
231
|
+
|
|
232
|
+
const tasksContent = buildTasksDoc({
|
|
233
|
+
title: planTitle,
|
|
234
|
+
slug: featureSlug,
|
|
235
|
+
planPath,
|
|
236
|
+
phases,
|
|
237
|
+
parallelNotes,
|
|
238
|
+
openAssumptions,
|
|
239
|
+
generatedAt
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
// Determine output path
|
|
243
|
+
const outputFilename = featureSlug ? `tasks-${featureSlug}.md` : 'tasks.md';
|
|
244
|
+
const outputPath = path.join(contextDir, outputFilename);
|
|
245
|
+
|
|
246
|
+
// Ensure context dir exists
|
|
247
|
+
try {
|
|
248
|
+
await fs.mkdir(contextDir, { recursive: true });
|
|
249
|
+
} catch { /* already exists */ }
|
|
250
|
+
|
|
251
|
+
await fs.writeFile(outputPath, tasksContent, 'utf8');
|
|
252
|
+
|
|
253
|
+
if (options.json) {
|
|
254
|
+
return {
|
|
255
|
+
ok: true,
|
|
256
|
+
planPath,
|
|
257
|
+
outputPath,
|
|
258
|
+
featureSlug,
|
|
259
|
+
phasesCount: phases.length,
|
|
260
|
+
generatedAt
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
logger.log(`spec:tasks — ${path.basename(planPath)}`);
|
|
265
|
+
logger.log('─'.repeat(50));
|
|
266
|
+
logger.log(`Phases parsed: ${phases.length}`);
|
|
267
|
+
for (const p of phases) {
|
|
268
|
+
const parallelLabel = p.parallel ? ' [parallel]' : '';
|
|
269
|
+
logger.log(` Phase ${p.number}: ${p.title}${parallelLabel}`);
|
|
270
|
+
}
|
|
271
|
+
if (parallelNotes.length > 0) logger.log(`Parallel notes: ${parallelNotes.length}`);
|
|
272
|
+
if (openAssumptions.length > 0) logger.log(`Deferred decisions: ${openAssumptions.length}`);
|
|
273
|
+
logger.log('─'.repeat(50));
|
|
274
|
+
logger.log(`Output: ${outputPath}`);
|
|
275
|
+
logger.log('');
|
|
276
|
+
logger.log(`Next: open ${outputFilename} and start executing phase by phase with @dev or @deyvin`);
|
|
277
|
+
|
|
278
|
+
return {
|
|
279
|
+
ok: true,
|
|
280
|
+
planPath,
|
|
281
|
+
outputPath,
|
|
282
|
+
featureSlug,
|
|
283
|
+
phasesCount: phases.length,
|
|
284
|
+
generatedAt
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
module.exports = { runSpecTasks };
|