@jaimevalasek/aioson 1.3.0 → 1.4.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/README.md +19 -2
- package/docs/pt/README.md +62 -2
- package/docs/pt/advisor-spec.md +5 -5
- package/docs/pt/agentes-customizados.md +670 -0
- package/docs/pt/agentes.md +111 -13
- package/docs/pt/automacao-squads.md +407 -0
- package/docs/pt/cenarios.md +3 -3
- package/docs/pt/clientes-ai.md +62 -0
- package/docs/pt/comandos-cli.md +167 -17
- package/docs/pt/deyvin.md +115 -0
- package/docs/pt/genome-3.0-spec.md +11 -11
- package/docs/pt/inicio-rapido.md +45 -0
- package/docs/pt/memoria-contexto.md +255 -0
- package/docs/pt/output-strategy-delivery.md +655 -0
- package/docs/pt/profiler-system.md +17 -17
- package/docs/pt/runtime-observability.md +5 -1
- package/docs/pt/skills.md +175 -0
- package/docs/pt/{squad-genoma.md → squad-genome.md} +81 -75
- package/docs/testing/genome-2.0-rollout.md +1 -1
- package/package.json +3 -3
- package/src/agents.js +21 -5
- package/src/backup-provider.js +303 -0
- package/src/cli.js +178 -2
- package/src/commands/agents.js +22 -4
- package/src/commands/backup.js +533 -0
- package/src/commands/cloud.js +17 -17
- package/src/commands/context-pack.js +45 -0
- package/src/commands/implementation-plan.js +340 -0
- package/src/commands/learning.js +134 -0
- package/src/commands/live.js +1583 -0
- package/src/commands/runtime.js +833 -2
- package/src/commands/scan-project.js +288 -24
- package/src/commands/setup-context.js +23 -0
- package/src/commands/skill.js +558 -0
- package/src/commands/squad-agent-create.js +788 -0
- package/src/commands/squad-doctor.js +51 -1
- package/src/commands/squad-investigate.js +261 -0
- package/src/commands/squad-learning.js +209 -0
- package/src/commands/squad-pipeline.js +247 -1
- package/src/commands/squad-plan.js +329 -0
- package/src/commands/squad-status.js +1 -1
- package/src/commands/squad-validate.js +57 -1
- package/src/commands/test-agents.js +6 -1
- package/src/commands/workflow-next.js +8 -1
- package/src/commands/workflow-status.js +250 -0
- package/src/constants.js +80 -16
- package/src/context-memory.js +837 -0
- package/src/context-writer.js +2 -0
- package/src/delivery-runner.js +319 -0
- package/src/genome-files.js +1 -1
- package/src/genome-format.js +1 -1
- package/src/i18n/messages/en.js +206 -7
- package/src/i18n/messages/es.js +123 -6
- package/src/i18n/messages/fr.js +122 -5
- package/src/i18n/messages/pt-BR.js +205 -12
- package/src/installer.js +30 -2
- package/src/lib/genomes/compat.js +1 -1
- package/src/runtime-store.js +780 -42
- package/src/session-handoff.js +77 -0
- package/template/.aioson/agents/analyst.md +36 -9
- package/template/.aioson/agents/architect.md +20 -5
- package/template/.aioson/agents/dev.md +135 -15
- package/template/.aioson/agents/deyvin.md +166 -0
- package/template/.aioson/agents/discovery-design-doc.md +25 -1
- package/template/.aioson/agents/{genoma.md → genome.md} +20 -20
- package/template/.aioson/agents/orache.md +371 -0
- package/template/.aioson/agents/orchestrator.md +37 -2
- package/template/.aioson/agents/pair.md +5 -0
- package/template/.aioson/agents/pm.md +17 -5
- package/template/.aioson/agents/product.md +58 -22
- package/template/.aioson/agents/profiler-enricher.md +1 -1
- package/template/.aioson/agents/profiler-forge.md +9 -9
- package/template/.aioson/agents/profiler-researcher.md +1 -1
- package/template/.aioson/agents/qa.md +17 -5
- package/template/.aioson/agents/setup.md +81 -5
- package/template/.aioson/agents/squad.md +675 -28
- package/template/.aioson/agents/ux-ui.md +277 -34
- package/template/.aioson/config.md +175 -0
- package/template/.aioson/context/spec.md.template +17 -0
- package/template/.aioson/genomes/.gitkeep +0 -0
- package/template/.aioson/installed-skills/.gitkeep +0 -0
- package/template/.aioson/locales/en/agents/analyst.md +26 -4
- package/template/.aioson/locales/en/agents/architect.md +10 -0
- package/template/.aioson/locales/en/agents/dev.md +89 -4
- package/template/.aioson/locales/en/agents/deyvin.md +129 -0
- package/template/.aioson/locales/en/agents/{genoma.md → genome.md} +14 -14
- package/template/.aioson/locales/en/agents/orchestrator.md +36 -2
- package/template/.aioson/locales/en/agents/pair.md +5 -0
- package/template/.aioson/locales/en/agents/pm.md +7 -0
- package/template/.aioson/locales/en/agents/product.md +35 -17
- package/template/.aioson/locales/en/agents/qa.md +7 -0
- package/template/.aioson/locales/en/agents/setup.md +51 -5
- package/template/.aioson/locales/en/agents/squad.md +203 -15
- package/template/.aioson/locales/en/agents/ux-ui.md +375 -35
- package/template/.aioson/locales/es/agents/analyst.md +16 -4
- package/template/.aioson/locales/es/agents/architect.md +10 -0
- package/template/.aioson/locales/es/agents/dev.md +70 -2
- package/template/.aioson/locales/es/agents/deyvin.md +89 -0
- package/template/.aioson/locales/es/agents/{genoma.md → genome.md} +13 -13
- package/template/.aioson/locales/es/agents/orache.md +103 -0
- package/template/.aioson/locales/es/agents/orchestrator.md +36 -2
- package/template/.aioson/locales/es/agents/pair.md +5 -0
- package/template/.aioson/locales/es/agents/pm.md +7 -0
- package/template/.aioson/locales/es/agents/product.md +13 -3
- package/template/.aioson/locales/es/agents/qa.md +7 -0
- package/template/.aioson/locales/es/agents/setup.md +28 -5
- package/template/.aioson/locales/es/agents/squad.md +221 -15
- package/template/.aioson/locales/es/agents/ux-ui.md +26 -25
- package/template/.aioson/locales/fr/agents/analyst.md +16 -4
- package/template/.aioson/locales/fr/agents/architect.md +10 -0
- package/template/.aioson/locales/fr/agents/dev.md +70 -2
- package/template/.aioson/locales/fr/agents/deyvin.md +89 -0
- package/template/.aioson/locales/fr/agents/{genoma.md → genome.md} +7 -7
- package/template/.aioson/locales/fr/agents/orache.md +104 -0
- package/template/.aioson/locales/fr/agents/orchestrator.md +36 -2
- package/template/.aioson/locales/fr/agents/pair.md +5 -0
- package/template/.aioson/locales/fr/agents/pm.md +7 -0
- package/template/.aioson/locales/fr/agents/product.md +13 -3
- package/template/.aioson/locales/fr/agents/qa.md +7 -0
- package/template/.aioson/locales/fr/agents/setup.md +28 -5
- package/template/.aioson/locales/fr/agents/squad.md +216 -10
- package/template/.aioson/locales/fr/agents/ux-ui.md +26 -25
- package/template/.aioson/locales/pt-BR/agents/analyst.md +26 -4
- package/template/.aioson/locales/pt-BR/agents/architect.md +10 -0
- package/template/.aioson/locales/pt-BR/agents/dev.md +93 -4
- package/template/.aioson/locales/pt-BR/agents/deyvin.md +129 -0
- package/template/.aioson/locales/pt-BR/agents/{genoma.md → genome.md} +49 -49
- package/template/.aioson/locales/pt-BR/agents/orache.md +137 -0
- package/template/.aioson/locales/pt-BR/agents/orchestrator.md +36 -2
- package/template/.aioson/locales/pt-BR/agents/pair.md +5 -0
- package/template/.aioson/locales/pt-BR/agents/pm.md +7 -0
- package/template/.aioson/locales/pt-BR/agents/product.md +35 -17
- package/template/.aioson/locales/pt-BR/agents/qa.md +7 -0
- package/template/.aioson/locales/pt-BR/agents/setup.md +51 -5
- package/template/.aioson/locales/pt-BR/agents/squad.md +486 -47
- package/template/.aioson/locales/pt-BR/agents/ux-ui.md +361 -22
- package/template/.aioson/my-agents/.gitkeep +0 -0
- package/template/.aioson/rules/.gitkeep +0 -0
- package/template/.aioson/rules/squad/.gitkeep +0 -0
- package/template/.aioson/rules/squad/README.md +50 -0
- package/template/.aioson/schemas/genome-meta.schema.json +1 -1
- package/template/.aioson/schemas/genome.schema.json +1 -1
- package/template/.aioson/schemas/squad-blueprint.schema.json +11 -0
- package/template/.aioson/schemas/squad-manifest.schema.json +257 -1
- package/template/.aioson/skills/design/cognitive-core-ui/SKILL.md +157 -0
- package/template/.aioson/skills/design/cognitive-core-ui/references/components.md +407 -0
- package/template/.aioson/skills/design/cognitive-core-ui/references/dashboards.md +172 -0
- package/template/.aioson/skills/design/cognitive-core-ui/references/design-tokens.md +490 -0
- package/template/.aioson/skills/design/cognitive-core-ui/references/motion.md +237 -0
- package/template/.aioson/skills/design/cognitive-core-ui/references/patterns.md +289 -0
- package/template/.aioson/skills/design/cognitive-core-ui/references/websites.md +350 -0
- package/template/.aioson/skills/design/interface-design/SKILL.md +47 -0
- package/template/.aioson/skills/design/interface-design/references/components-and-states.md +105 -0
- package/template/.aioson/skills/design/interface-design/references/design-directions.md +101 -0
- package/template/.aioson/skills/design/interface-design/references/handoff-and-quality.md +71 -0
- package/template/.aioson/skills/design/interface-design/references/intent-and-domain.md +74 -0
- package/template/.aioson/skills/design/interface-design/references/tokens-and-depth.md +173 -0
- package/template/.aioson/skills/design/premium-command-center-ui/SKILL.md +62 -0
- package/template/.aioson/skills/design/premium-command-center-ui/references/operations.md +74 -0
- package/template/.aioson/skills/design/premium-command-center-ui/references/patterns.md +116 -0
- package/template/.aioson/skills/design/premium-command-center-ui/references/validation.md +47 -0
- package/template/.aioson/skills/design/premium-command-center-ui/references/visual-system.md +215 -0
- package/template/.aioson/skills/design-system/SKILL.md +92 -0
- package/template/.aioson/skills/design-system/cognitive-core-ui.skill +0 -0
- package/template/.aioson/skills/design-system/components/SKILL.md +274 -0
- package/template/.aioson/skills/design-system/components/SKILL.md:Zone.Identifier +0 -0
- package/template/.aioson/skills/design-system/dashboards/SKILL.md +184 -0
- package/template/.aioson/skills/design-system/dashboards/SKILL.md:Zone.Identifier +0 -0
- package/template/.aioson/skills/design-system/foundations/SKILL.md +250 -0
- package/template/.aioson/skills/design-system/foundations/SKILL.md:Zone.Identifier +0 -0
- package/template/.aioson/skills/design-system/motion/SKILL.md +197 -0
- package/template/.aioson/skills/design-system/motion/SKILL.md:Zone.Identifier +0 -0
- package/template/.aioson/skills/design-system/patterns/SKILL.md +231 -0
- package/template/.aioson/skills/design-system/patterns/SKILL.md:Zone.Identifier +0 -0
- package/template/.aioson/skills/squad/SKILL.md +58 -0
- package/template/.aioson/skills/squad/domains/.gitkeep +0 -0
- package/template/.aioson/skills/squad/formats/.gitkeep +0 -0
- package/template/.aioson/skills/squad/patterns/.gitkeep +0 -0
- package/template/.aioson/skills/squad/references/.gitkeep +0 -0
- package/template/.aioson/tasks/implementation-plan.md +288 -0
- package/template/.aioson/tasks/squad-create.md +1 -1
- package/template/.aioson/tasks/squad-execution-plan.md +279 -0
- package/template/.aioson/tasks/squad-export.md +1 -1
- package/template/.aioson/tasks/squad-investigate.md +44 -0
- package/template/.aioson/tasks/squad-learning-review.md +44 -0
- package/template/.aioson/tasks/squad-output-config.md +177 -0
- package/template/.aioson/tasks/squad-validate.md +1 -1
- package/template/.claude/commands/aioson/agent/deyvin.md +5 -0
- package/template/.claude/commands/aioson/agent/discovery-design-doc.md +5 -0
- package/template/.claude/commands/aioson/agent/genome.md +5 -0
- package/template/.claude/commands/aioson/agent/product.md +5 -0
- package/template/.claude/commands/aioson/agent/profiler-enricher.md +5 -0
- package/template/.claude/commands/aioson/agent/profiler-forge.md +5 -0
- package/template/.claude/commands/aioson/agent/profiler-researcher.md +5 -0
- package/template/.claude/commands/aioson/agent/squad.md +5 -0
- package/template/.gemini/GEMINI.md +2 -0
- package/template/.gemini/commands/aios-deyvin.toml +6 -0
- package/template/.gemini/commands/aios-pair.toml +6 -0
- package/template/AGENTS.md +34 -6
- package/template/CLAUDE.md +31 -4
- package/template/OPENCODE.md +6 -2
- package/template/squad-searches/.gitkeep +0 -0
- package/template/.aioson/skills/static/interface-design.md +0 -372
- package/template/.aioson/skills/static/premium-command-center-ui.md +0 -190
- /package/template/.aioson/{genomas → docs}/.gitkeep +0 -0
- /package/template/.claude/commands/aioson/{analyst.md → agent/analyst.md} +0 -0
- /package/template/.claude/commands/aioson/{architect.md → agent/architect.md} +0 -0
- /package/template/.claude/commands/aioson/{dev.md → agent/dev.md} +0 -0
- /package/template/.claude/commands/aioson/{orchestrator.md → agent/orchestrator.md} +0 -0
- /package/template/.claude/commands/aioson/{pm.md → agent/pm.md} +0 -0
- /package/template/.claude/commands/aioson/{qa.md → agent/qa.md} +0 -0
- /package/template/.claude/commands/aioson/{setup.md → agent/setup.md} +0 -0
- /package/template/.claude/commands/aioson/{ux-ui.md → agent/ux-ui.md} +0 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
const { createContextPack } = require('../context-memory');
|
|
5
|
+
|
|
6
|
+
async function runContextPack({ args, options = {}, logger, t }) {
|
|
7
|
+
const targetDir = path.resolve(process.cwd(), args[0] || '.');
|
|
8
|
+
const agent = String(options.agent || '').trim();
|
|
9
|
+
const goal = String(options.goal || '').trim();
|
|
10
|
+
const module = String(options.module || options.folder || '').trim();
|
|
11
|
+
|
|
12
|
+
const output = await createContextPack({
|
|
13
|
+
targetDir,
|
|
14
|
+
agent,
|
|
15
|
+
goal,
|
|
16
|
+
module,
|
|
17
|
+
maxFiles: options['max-files']
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
if (options.json) {
|
|
21
|
+
return output;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
logger.log(t('context_pack.generated', { path: output.packPath }));
|
|
25
|
+
if (output.selectedFiles.length === 0) {
|
|
26
|
+
logger.log(t('context_pack.no_matches'));
|
|
27
|
+
logger.log(t('context_pack.hint_use', { path: output.packPath }));
|
|
28
|
+
return output;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
logger.log(t('context_pack.selected_title'));
|
|
32
|
+
output.selectedFiles.forEach((file, index) => {
|
|
33
|
+
logger.log(t('context_pack.selected_line', {
|
|
34
|
+
index: index + 1,
|
|
35
|
+
path: file.path,
|
|
36
|
+
reason: file.reason
|
|
37
|
+
}));
|
|
38
|
+
});
|
|
39
|
+
logger.log(t('context_pack.hint_use', { path: output.packPath }));
|
|
40
|
+
return output;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = {
|
|
44
|
+
runContextPack
|
|
45
|
+
};
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs/promises');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const crypto = require('node:crypto');
|
|
6
|
+
const {
|
|
7
|
+
openRuntimeDb,
|
|
8
|
+
upsertImplementationPlan,
|
|
9
|
+
getImplementationPlan,
|
|
10
|
+
listImplementationPlans,
|
|
11
|
+
updateImplementationPlanStatus,
|
|
12
|
+
upsertPlanPhase,
|
|
13
|
+
updatePlanPhaseStatus,
|
|
14
|
+
getPlanPhases
|
|
15
|
+
} = require('../runtime-store');
|
|
16
|
+
|
|
17
|
+
const CONTEXT_DIR = path.join('.aioson', 'context');
|
|
18
|
+
|
|
19
|
+
async function pathExists(targetPath) {
|
|
20
|
+
try {
|
|
21
|
+
await fs.access(targetPath);
|
|
22
|
+
return true;
|
|
23
|
+
} catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Compute a simple hash of an array of files for staleness detection.
|
|
30
|
+
*/
|
|
31
|
+
async function computeSourceHash(projectDir, filePaths) {
|
|
32
|
+
const hash = crypto.createHash('sha256');
|
|
33
|
+
for (const fp of filePaths) {
|
|
34
|
+
const abs = path.resolve(projectDir, fp);
|
|
35
|
+
try {
|
|
36
|
+
const stat = await fs.stat(abs);
|
|
37
|
+
hash.update(`${fp}:${stat.mtimeMs}`);
|
|
38
|
+
} catch {
|
|
39
|
+
hash.update(`${fp}:missing`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return hash.digest('hex').slice(0, 16);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Detect plan files in the context directory.
|
|
47
|
+
*/
|
|
48
|
+
async function detectPlanFiles(projectDir) {
|
|
49
|
+
const contextDir = path.resolve(projectDir, CONTEXT_DIR);
|
|
50
|
+
const plans = [];
|
|
51
|
+
try {
|
|
52
|
+
const files = await fs.readdir(contextDir);
|
|
53
|
+
for (const f of files) {
|
|
54
|
+
if (f.startsWith('implementation-plan') && f.endsWith('.md')) {
|
|
55
|
+
const slug = f === 'implementation-plan.md'
|
|
56
|
+
? null
|
|
57
|
+
: f.replace('implementation-plan-', '').replace('.md', '');
|
|
58
|
+
plans.push({ file: f, featureSlug: slug, path: path.join(CONTEXT_DIR, f) });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
} catch {
|
|
62
|
+
// context dir may not exist
|
|
63
|
+
}
|
|
64
|
+
return plans;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Parse plan frontmatter to extract status and metadata.
|
|
69
|
+
*/
|
|
70
|
+
function parsePlanFrontmatter(content) {
|
|
71
|
+
const text = String(content || '');
|
|
72
|
+
const match = text.match(/^---\n([\s\S]*?)\n---/);
|
|
73
|
+
if (!match) return {};
|
|
74
|
+
const meta = {};
|
|
75
|
+
for (const line of match[1].split('\n')) {
|
|
76
|
+
const [key, ...rest] = line.split(':');
|
|
77
|
+
if (key && rest.length) {
|
|
78
|
+
meta[key.trim()] = rest.join(':').trim().replace(/^"(.*)"$/, '$1');
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return meta;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Count phases in an implementation plan markdown.
|
|
86
|
+
*/
|
|
87
|
+
function countPhases(content) {
|
|
88
|
+
const text = String(content || '');
|
|
89
|
+
const matches = text.match(/^### Fase \d+/gm);
|
|
90
|
+
return matches ? matches.length : 0;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Subcommand: show [slug]
|
|
95
|
+
* Shows the current implementation plan.
|
|
96
|
+
*/
|
|
97
|
+
async function handleShow(projectDir, featureSlug, { logger, t }) {
|
|
98
|
+
const fileName = featureSlug
|
|
99
|
+
? `implementation-plan-${featureSlug}.md`
|
|
100
|
+
: 'implementation-plan.md';
|
|
101
|
+
const planPath = path.resolve(projectDir, CONTEXT_DIR, fileName);
|
|
102
|
+
|
|
103
|
+
if (!(await pathExists(planPath))) {
|
|
104
|
+
logger.error(t('implementation_plan.not_found', { file: fileName }));
|
|
105
|
+
return { found: false };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const content = await fs.readFile(planPath, 'utf8');
|
|
109
|
+
const meta = parsePlanFrontmatter(content);
|
|
110
|
+
const phases = countPhases(content);
|
|
111
|
+
|
|
112
|
+
logger.log(`Plan: ${fileName}`);
|
|
113
|
+
logger.log(`Status: ${meta.status || 'unknown'}`);
|
|
114
|
+
logger.log(`Classification: ${meta.classification || 'unknown'}`);
|
|
115
|
+
logger.log(`Phases: ${phases}`);
|
|
116
|
+
logger.log('');
|
|
117
|
+
logger.log(content);
|
|
118
|
+
|
|
119
|
+
return { found: true, meta, phases };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Subcommand: status [slug]
|
|
124
|
+
* Shows progress of the implementation plan from SQLite.
|
|
125
|
+
*/
|
|
126
|
+
async function handleStatus(projectDir, featureSlug, { logger, t }) {
|
|
127
|
+
const handle = await openRuntimeDb(projectDir, { mustExist: true });
|
|
128
|
+
if (!handle) {
|
|
129
|
+
logger.error(t('implementation_plan.no_runtime'));
|
|
130
|
+
return { found: false };
|
|
131
|
+
}
|
|
132
|
+
const { db } = handle;
|
|
133
|
+
try {
|
|
134
|
+
const rows = listImplementationPlans(db);
|
|
135
|
+
const match = featureSlug
|
|
136
|
+
? rows.find(r => r.feature_slug === featureSlug)
|
|
137
|
+
: rows.find(r => r.scope === 'project') || rows[0];
|
|
138
|
+
|
|
139
|
+
if (!match) {
|
|
140
|
+
logger.error(t('implementation_plan.no_plans'));
|
|
141
|
+
return { found: false };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const phases = getPlanPhases(db, match.plan_id);
|
|
145
|
+
logger.log(`Plan: ${match.plan_id}`);
|
|
146
|
+
logger.log(`Status: ${match.status}`);
|
|
147
|
+
logger.log(`Progress: ${match.phases_completed}/${match.phases_total}`);
|
|
148
|
+
logger.log('');
|
|
149
|
+
for (const ph of phases) {
|
|
150
|
+
const icon = ph.status === 'completed' ? '✓' : ph.status === 'in_progress' ? '▸' : '○';
|
|
151
|
+
logger.log(` ${icon} Phase ${ph.phase_number}: ${ph.title} [${ph.status}]`);
|
|
152
|
+
}
|
|
153
|
+
return { found: true, plan: match, phases };
|
|
154
|
+
} finally {
|
|
155
|
+
db.close();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Subcommand: checkpoint [slug] <phase-number>
|
|
161
|
+
* Marks a phase as completed.
|
|
162
|
+
*/
|
|
163
|
+
async function handleCheckpoint(projectDir, featureSlug, phaseNumber, { logger, t }) {
|
|
164
|
+
if (!phaseNumber || isNaN(Number(phaseNumber))) {
|
|
165
|
+
logger.error(t('implementation_plan.checkpoint_usage'));
|
|
166
|
+
return { updated: false };
|
|
167
|
+
}
|
|
168
|
+
const handle = await openRuntimeDb(projectDir, { mustExist: true });
|
|
169
|
+
if (!handle) {
|
|
170
|
+
logger.error(t('implementation_plan.no_runtime'));
|
|
171
|
+
return { updated: false };
|
|
172
|
+
}
|
|
173
|
+
const { db } = handle;
|
|
174
|
+
try {
|
|
175
|
+
const rows = listImplementationPlans(db);
|
|
176
|
+
const match = featureSlug
|
|
177
|
+
? rows.find(r => r.feature_slug === featureSlug)
|
|
178
|
+
: rows.find(r => r.scope === 'project') || rows[0];
|
|
179
|
+
|
|
180
|
+
if (!match) {
|
|
181
|
+
logger.error(t('implementation_plan.no_plans'));
|
|
182
|
+
return { updated: false };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const updated = updatePlanPhaseStatus(db, match.plan_id, Number(phaseNumber), 'completed');
|
|
186
|
+
if (updated) {
|
|
187
|
+
logger.log(t('implementation_plan.phase_completed', { phase: phaseNumber }));
|
|
188
|
+
} else {
|
|
189
|
+
logger.error(t('implementation_plan.phase_not_found', { phase: phaseNumber }));
|
|
190
|
+
}
|
|
191
|
+
return { updated };
|
|
192
|
+
} finally {
|
|
193
|
+
db.close();
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Subcommand: stale [slug]
|
|
199
|
+
* Checks if source artifacts changed after the plan was created.
|
|
200
|
+
*/
|
|
201
|
+
async function handleStale(projectDir, featureSlug, { logger, t }) {
|
|
202
|
+
const fileName = featureSlug
|
|
203
|
+
? `implementation-plan-${featureSlug}.md`
|
|
204
|
+
: 'implementation-plan.md';
|
|
205
|
+
const planPath = path.resolve(projectDir, CONTEXT_DIR, fileName);
|
|
206
|
+
|
|
207
|
+
if (!(await pathExists(planPath))) {
|
|
208
|
+
logger.error(t('implementation_plan.not_found', { file: fileName }));
|
|
209
|
+
return { found: false, stale: false };
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const content = await fs.readFile(planPath, 'utf8');
|
|
213
|
+
const meta = parsePlanFrontmatter(content);
|
|
214
|
+
if (!meta.created) {
|
|
215
|
+
logger.log(t('implementation_plan.no_created_date'));
|
|
216
|
+
return { found: true, stale: false };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const planDate = new Date(meta.created);
|
|
220
|
+
const contextDir = path.resolve(projectDir, CONTEXT_DIR);
|
|
221
|
+
const sourceFiles = ['project.context.md', 'architecture.md', 'prd.md', 'discovery.md', 'ui-spec.md'];
|
|
222
|
+
let stale = false;
|
|
223
|
+
|
|
224
|
+
for (const sf of sourceFiles) {
|
|
225
|
+
const sfPath = path.join(contextDir, sf);
|
|
226
|
+
try {
|
|
227
|
+
const stat = await fs.stat(sfPath);
|
|
228
|
+
if (stat.mtime > planDate) {
|
|
229
|
+
logger.log(` ⚠ ${sf} modified after plan was created`);
|
|
230
|
+
stale = true;
|
|
231
|
+
}
|
|
232
|
+
} catch {
|
|
233
|
+
// file doesn't exist, skip
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (stale) {
|
|
238
|
+
logger.log(t('implementation_plan.is_stale'));
|
|
239
|
+
} else {
|
|
240
|
+
logger.log(t('implementation_plan.is_fresh'));
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return { found: true, stale };
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Subcommand: register
|
|
248
|
+
* Registers an existing plan file into the runtime SQLite.
|
|
249
|
+
*/
|
|
250
|
+
async function handleRegister(projectDir, featureSlug, { logger, t }) {
|
|
251
|
+
const fileName = featureSlug
|
|
252
|
+
? `implementation-plan-${featureSlug}.md`
|
|
253
|
+
: 'implementation-plan.md';
|
|
254
|
+
const planPath = path.resolve(projectDir, CONTEXT_DIR, fileName);
|
|
255
|
+
|
|
256
|
+
if (!(await pathExists(planPath))) {
|
|
257
|
+
logger.error(t('implementation_plan.not_found', { file: fileName }));
|
|
258
|
+
return { registered: false };
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const content = await fs.readFile(planPath, 'utf8');
|
|
262
|
+
const meta = parsePlanFrontmatter(content);
|
|
263
|
+
const phases = countPhases(content);
|
|
264
|
+
|
|
265
|
+
const contextDir = path.resolve(projectDir, CONTEXT_DIR);
|
|
266
|
+
const sourceFiles = ['project.context.md', 'architecture.md', 'prd.md'];
|
|
267
|
+
const existingSources = [];
|
|
268
|
+
for (const sf of sourceFiles) {
|
|
269
|
+
if (await pathExists(path.join(contextDir, sf))) existingSources.push(sf);
|
|
270
|
+
}
|
|
271
|
+
const hash = await computeSourceHash(projectDir, existingSources.map(s => path.join(CONTEXT_DIR, s)));
|
|
272
|
+
|
|
273
|
+
const handle = await openRuntimeDb(projectDir);
|
|
274
|
+
const { db } = handle;
|
|
275
|
+
try {
|
|
276
|
+
const planId = upsertImplementationPlan(db, {
|
|
277
|
+
projectName: meta.project || path.basename(projectDir),
|
|
278
|
+
scope: meta.scope || 'project',
|
|
279
|
+
featureSlug: meta.feature_slug || featureSlug || null,
|
|
280
|
+
status: meta.status || 'draft',
|
|
281
|
+
classification: meta.classification || null,
|
|
282
|
+
phasesTotal: phases,
|
|
283
|
+
phasesCompleted: 0,
|
|
284
|
+
sourceArtifacts: existingSources,
|
|
285
|
+
sourceHash: hash
|
|
286
|
+
});
|
|
287
|
+
logger.log(t('implementation_plan.registered', { planId, phases }));
|
|
288
|
+
return { registered: true, planId };
|
|
289
|
+
} finally {
|
|
290
|
+
db.close();
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Main router for implementation-plan subcommands.
|
|
296
|
+
*/
|
|
297
|
+
async function run(projectDir, args, context) {
|
|
298
|
+
const sub = args[0] || 'show';
|
|
299
|
+
const rest = args.slice(1);
|
|
300
|
+
|
|
301
|
+
switch (sub) {
|
|
302
|
+
case 'show':
|
|
303
|
+
return handleShow(projectDir, rest[0] || null, context);
|
|
304
|
+
case 'status':
|
|
305
|
+
return handleStatus(projectDir, rest[0] || null, context);
|
|
306
|
+
case 'checkpoint':
|
|
307
|
+
return handleCheckpoint(projectDir, rest[0] || null, rest[1], context);
|
|
308
|
+
case 'stale':
|
|
309
|
+
return handleStale(projectDir, rest[0] || null, context);
|
|
310
|
+
case 'register':
|
|
311
|
+
return handleRegister(projectDir, rest[0] || null, context);
|
|
312
|
+
default:
|
|
313
|
+
context.logger.error(`Unknown subcommand: ${sub}. Available: show, status, checkpoint, stale, register`);
|
|
314
|
+
return { error: true };
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Entry point for CLI integration (same signature as other commands).
|
|
320
|
+
*/
|
|
321
|
+
async function runImplementationPlan({ args = [], options = {}, logger = console, t = (k) => k } = {}) {
|
|
322
|
+
const projectDir = path.resolve(process.cwd(), args[0] || '.');
|
|
323
|
+
const sub = options.sub || args[1] || 'show';
|
|
324
|
+
const slug = options.feature || options.slug || args[2] || null;
|
|
325
|
+
const context = { logger, t };
|
|
326
|
+
|
|
327
|
+
if (sub === 'show') return handleShow(projectDir, slug, context);
|
|
328
|
+
if (sub === 'status') return handleStatus(projectDir, slug, context);
|
|
329
|
+
if (sub === 'checkpoint') {
|
|
330
|
+
const phase = args[3] || options.phase;
|
|
331
|
+
return handleCheckpoint(projectDir, slug, phase, context);
|
|
332
|
+
}
|
|
333
|
+
if (sub === 'stale') return handleStale(projectDir, slug, context);
|
|
334
|
+
if (sub === 'register') return handleRegister(projectDir, slug, context);
|
|
335
|
+
|
|
336
|
+
logger.error(`Unknown subcommand: ${sub}. Available: show, status, checkpoint, stale, register`);
|
|
337
|
+
return { error: true };
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
module.exports = { run, runImplementationPlan, handleShow, handleStatus, handleCheckpoint, handleStale, handleRegister };
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
const {
|
|
5
|
+
openRuntimeDb,
|
|
6
|
+
listProjectLearnings,
|
|
7
|
+
getProjectLearning,
|
|
8
|
+
promoteProjectLearning,
|
|
9
|
+
getProjectLearningStats
|
|
10
|
+
} = require('../runtime-store');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Subcommand: list [--status=active|stale|archived|promoted]
|
|
14
|
+
* Lists project-level learnings.
|
|
15
|
+
*/
|
|
16
|
+
async function handleList(projectDir, statusFilter, { logger, t }) {
|
|
17
|
+
const handle = await openRuntimeDb(projectDir, { mustExist: true });
|
|
18
|
+
if (!handle) {
|
|
19
|
+
logger.error(t('learning.no_runtime'));
|
|
20
|
+
return { found: false };
|
|
21
|
+
}
|
|
22
|
+
const { db } = handle;
|
|
23
|
+
try {
|
|
24
|
+
const rows = listProjectLearnings(db, statusFilter || null);
|
|
25
|
+
if (rows.length === 0) {
|
|
26
|
+
logger.log(t('learning.no_learnings'));
|
|
27
|
+
return { found: true, learnings: [] };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
logger.log(`Project learnings (${rows.length})`);
|
|
31
|
+
logger.log('');
|
|
32
|
+
for (const row of rows) {
|
|
33
|
+
const icon = row.status === 'active' ? '●' : row.status === 'promoted' ? '★' : row.status === 'stale' ? '○' : '▪';
|
|
34
|
+
const scope = row.feature_slug ? `feature:${row.feature_slug}` : 'project';
|
|
35
|
+
logger.log(` ${icon} [${row.type}] ${row.title} (freq: ${row.frequency}, ${scope}) [${row.status}]`);
|
|
36
|
+
logger.log(` id: ${row.learning_id}`);
|
|
37
|
+
}
|
|
38
|
+
return { found: true, learnings: rows };
|
|
39
|
+
} finally {
|
|
40
|
+
db.close();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Subcommand: stats
|
|
46
|
+
* Shows statistics for project learnings.
|
|
47
|
+
*/
|
|
48
|
+
async function handleStats(projectDir, { logger, t }) {
|
|
49
|
+
const handle = await openRuntimeDb(projectDir, { mustExist: true });
|
|
50
|
+
if (!handle) {
|
|
51
|
+
logger.error(t('learning.no_runtime'));
|
|
52
|
+
return { found: false };
|
|
53
|
+
}
|
|
54
|
+
const { db } = handle;
|
|
55
|
+
try {
|
|
56
|
+
const stats = getProjectLearningStats(db);
|
|
57
|
+
if (stats.length === 0) {
|
|
58
|
+
logger.log(t('learning.no_learnings'));
|
|
59
|
+
return { found: true, stats: [] };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
logger.log('Project learning stats');
|
|
63
|
+
logger.log('');
|
|
64
|
+
let total = 0;
|
|
65
|
+
for (const row of stats) {
|
|
66
|
+
logger.log(` ${row.type} / ${row.status}: ${row.count}`);
|
|
67
|
+
total += row.count;
|
|
68
|
+
}
|
|
69
|
+
logger.log('');
|
|
70
|
+
logger.log(` Total: ${total}`);
|
|
71
|
+
return { found: true, stats, total };
|
|
72
|
+
} finally {
|
|
73
|
+
db.close();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Subcommand: promote <learning-id> --to=<rule-path>
|
|
79
|
+
* Promotes a learning to a project rule.
|
|
80
|
+
*/
|
|
81
|
+
async function handlePromote(projectDir, learningId, promotedTo, { logger, t }) {
|
|
82
|
+
if (!learningId) {
|
|
83
|
+
logger.error(t('learning.promote_usage'));
|
|
84
|
+
return { promoted: false };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const handle = await openRuntimeDb(projectDir, { mustExist: true });
|
|
88
|
+
if (!handle) {
|
|
89
|
+
logger.error(t('learning.no_runtime'));
|
|
90
|
+
return { promoted: false };
|
|
91
|
+
}
|
|
92
|
+
const { db } = handle;
|
|
93
|
+
try {
|
|
94
|
+
const learning = getProjectLearning(db, learningId);
|
|
95
|
+
if (!learning) {
|
|
96
|
+
logger.error(t('learning.not_found', { id: learningId }));
|
|
97
|
+
return { promoted: false };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const rulePath = promotedTo || path.join('.aioson', 'rules', `${learning.type}-${Date.now()}.md`);
|
|
101
|
+
const updated = promoteProjectLearning(db, learningId, rulePath);
|
|
102
|
+
if (updated) {
|
|
103
|
+
logger.log(t('learning.promoted', { id: learningId, path: rulePath }));
|
|
104
|
+
}
|
|
105
|
+
return { promoted: updated, rulePath };
|
|
106
|
+
} finally {
|
|
107
|
+
db.close();
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Entry point for CLI integration.
|
|
113
|
+
*/
|
|
114
|
+
async function runLearning({ args = [], options = {}, logger = console, t = (k) => k } = {}) {
|
|
115
|
+
const projectDir = path.resolve(process.cwd(), args[0] || '.');
|
|
116
|
+
const sub = options.sub || args[1] || 'list';
|
|
117
|
+
const context = { logger, t };
|
|
118
|
+
|
|
119
|
+
if (sub === 'list') {
|
|
120
|
+
return handleList(projectDir, options.status || null, context);
|
|
121
|
+
}
|
|
122
|
+
if (sub === 'stats') {
|
|
123
|
+
return handleStats(projectDir, context);
|
|
124
|
+
}
|
|
125
|
+
if (sub === 'promote') {
|
|
126
|
+
const learningId = args[2] || options.id;
|
|
127
|
+
return handlePromote(projectDir, learningId, options.to || null, context);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
logger.error(`Unknown subcommand: ${sub}. Available: list, stats, promote`);
|
|
131
|
+
return { error: true };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
module.exports = { runLearning, handleList, handleStats, handlePromote };
|