@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.
Files changed (213) hide show
  1. package/README.md +19 -2
  2. package/docs/pt/README.md +62 -2
  3. package/docs/pt/advisor-spec.md +5 -5
  4. package/docs/pt/agentes-customizados.md +670 -0
  5. package/docs/pt/agentes.md +111 -13
  6. package/docs/pt/automacao-squads.md +407 -0
  7. package/docs/pt/cenarios.md +3 -3
  8. package/docs/pt/clientes-ai.md +62 -0
  9. package/docs/pt/comandos-cli.md +167 -17
  10. package/docs/pt/deyvin.md +115 -0
  11. package/docs/pt/genome-3.0-spec.md +11 -11
  12. package/docs/pt/inicio-rapido.md +45 -0
  13. package/docs/pt/memoria-contexto.md +255 -0
  14. package/docs/pt/output-strategy-delivery.md +655 -0
  15. package/docs/pt/profiler-system.md +17 -17
  16. package/docs/pt/runtime-observability.md +5 -1
  17. package/docs/pt/skills.md +175 -0
  18. package/docs/pt/{squad-genoma.md → squad-genome.md} +81 -75
  19. package/docs/testing/genome-2.0-rollout.md +1 -1
  20. package/package.json +3 -3
  21. package/src/agents.js +21 -5
  22. package/src/backup-provider.js +303 -0
  23. package/src/cli.js +178 -2
  24. package/src/commands/agents.js +22 -4
  25. package/src/commands/backup.js +533 -0
  26. package/src/commands/cloud.js +17 -17
  27. package/src/commands/context-pack.js +45 -0
  28. package/src/commands/implementation-plan.js +340 -0
  29. package/src/commands/learning.js +134 -0
  30. package/src/commands/live.js +1583 -0
  31. package/src/commands/runtime.js +833 -2
  32. package/src/commands/scan-project.js +288 -24
  33. package/src/commands/setup-context.js +23 -0
  34. package/src/commands/skill.js +558 -0
  35. package/src/commands/squad-agent-create.js +788 -0
  36. package/src/commands/squad-doctor.js +51 -1
  37. package/src/commands/squad-investigate.js +261 -0
  38. package/src/commands/squad-learning.js +209 -0
  39. package/src/commands/squad-pipeline.js +247 -1
  40. package/src/commands/squad-plan.js +329 -0
  41. package/src/commands/squad-status.js +1 -1
  42. package/src/commands/squad-validate.js +57 -1
  43. package/src/commands/test-agents.js +6 -1
  44. package/src/commands/workflow-next.js +8 -1
  45. package/src/commands/workflow-status.js +250 -0
  46. package/src/constants.js +80 -16
  47. package/src/context-memory.js +837 -0
  48. package/src/context-writer.js +2 -0
  49. package/src/delivery-runner.js +319 -0
  50. package/src/genome-files.js +1 -1
  51. package/src/genome-format.js +1 -1
  52. package/src/i18n/messages/en.js +206 -7
  53. package/src/i18n/messages/es.js +123 -6
  54. package/src/i18n/messages/fr.js +122 -5
  55. package/src/i18n/messages/pt-BR.js +205 -12
  56. package/src/installer.js +30 -2
  57. package/src/lib/genomes/compat.js +1 -1
  58. package/src/runtime-store.js +780 -42
  59. package/src/session-handoff.js +77 -0
  60. package/template/.aioson/agents/analyst.md +36 -9
  61. package/template/.aioson/agents/architect.md +20 -5
  62. package/template/.aioson/agents/dev.md +135 -15
  63. package/template/.aioson/agents/deyvin.md +166 -0
  64. package/template/.aioson/agents/discovery-design-doc.md +25 -1
  65. package/template/.aioson/agents/{genoma.md → genome.md} +20 -20
  66. package/template/.aioson/agents/orache.md +371 -0
  67. package/template/.aioson/agents/orchestrator.md +37 -2
  68. package/template/.aioson/agents/pair.md +5 -0
  69. package/template/.aioson/agents/pm.md +17 -5
  70. package/template/.aioson/agents/product.md +58 -22
  71. package/template/.aioson/agents/profiler-enricher.md +1 -1
  72. package/template/.aioson/agents/profiler-forge.md +9 -9
  73. package/template/.aioson/agents/profiler-researcher.md +1 -1
  74. package/template/.aioson/agents/qa.md +17 -5
  75. package/template/.aioson/agents/setup.md +81 -5
  76. package/template/.aioson/agents/squad.md +675 -28
  77. package/template/.aioson/agents/ux-ui.md +277 -34
  78. package/template/.aioson/config.md +175 -0
  79. package/template/.aioson/context/spec.md.template +17 -0
  80. package/template/.aioson/genomes/.gitkeep +0 -0
  81. package/template/.aioson/installed-skills/.gitkeep +0 -0
  82. package/template/.aioson/locales/en/agents/analyst.md +26 -4
  83. package/template/.aioson/locales/en/agents/architect.md +10 -0
  84. package/template/.aioson/locales/en/agents/dev.md +89 -4
  85. package/template/.aioson/locales/en/agents/deyvin.md +129 -0
  86. package/template/.aioson/locales/en/agents/{genoma.md → genome.md} +14 -14
  87. package/template/.aioson/locales/en/agents/orchestrator.md +36 -2
  88. package/template/.aioson/locales/en/agents/pair.md +5 -0
  89. package/template/.aioson/locales/en/agents/pm.md +7 -0
  90. package/template/.aioson/locales/en/agents/product.md +35 -17
  91. package/template/.aioson/locales/en/agents/qa.md +7 -0
  92. package/template/.aioson/locales/en/agents/setup.md +51 -5
  93. package/template/.aioson/locales/en/agents/squad.md +203 -15
  94. package/template/.aioson/locales/en/agents/ux-ui.md +375 -35
  95. package/template/.aioson/locales/es/agents/analyst.md +16 -4
  96. package/template/.aioson/locales/es/agents/architect.md +10 -0
  97. package/template/.aioson/locales/es/agents/dev.md +70 -2
  98. package/template/.aioson/locales/es/agents/deyvin.md +89 -0
  99. package/template/.aioson/locales/es/agents/{genoma.md → genome.md} +13 -13
  100. package/template/.aioson/locales/es/agents/orache.md +103 -0
  101. package/template/.aioson/locales/es/agents/orchestrator.md +36 -2
  102. package/template/.aioson/locales/es/agents/pair.md +5 -0
  103. package/template/.aioson/locales/es/agents/pm.md +7 -0
  104. package/template/.aioson/locales/es/agents/product.md +13 -3
  105. package/template/.aioson/locales/es/agents/qa.md +7 -0
  106. package/template/.aioson/locales/es/agents/setup.md +28 -5
  107. package/template/.aioson/locales/es/agents/squad.md +221 -15
  108. package/template/.aioson/locales/es/agents/ux-ui.md +26 -25
  109. package/template/.aioson/locales/fr/agents/analyst.md +16 -4
  110. package/template/.aioson/locales/fr/agents/architect.md +10 -0
  111. package/template/.aioson/locales/fr/agents/dev.md +70 -2
  112. package/template/.aioson/locales/fr/agents/deyvin.md +89 -0
  113. package/template/.aioson/locales/fr/agents/{genoma.md → genome.md} +7 -7
  114. package/template/.aioson/locales/fr/agents/orache.md +104 -0
  115. package/template/.aioson/locales/fr/agents/orchestrator.md +36 -2
  116. package/template/.aioson/locales/fr/agents/pair.md +5 -0
  117. package/template/.aioson/locales/fr/agents/pm.md +7 -0
  118. package/template/.aioson/locales/fr/agents/product.md +13 -3
  119. package/template/.aioson/locales/fr/agents/qa.md +7 -0
  120. package/template/.aioson/locales/fr/agents/setup.md +28 -5
  121. package/template/.aioson/locales/fr/agents/squad.md +216 -10
  122. package/template/.aioson/locales/fr/agents/ux-ui.md +26 -25
  123. package/template/.aioson/locales/pt-BR/agents/analyst.md +26 -4
  124. package/template/.aioson/locales/pt-BR/agents/architect.md +10 -0
  125. package/template/.aioson/locales/pt-BR/agents/dev.md +93 -4
  126. package/template/.aioson/locales/pt-BR/agents/deyvin.md +129 -0
  127. package/template/.aioson/locales/pt-BR/agents/{genoma.md → genome.md} +49 -49
  128. package/template/.aioson/locales/pt-BR/agents/orache.md +137 -0
  129. package/template/.aioson/locales/pt-BR/agents/orchestrator.md +36 -2
  130. package/template/.aioson/locales/pt-BR/agents/pair.md +5 -0
  131. package/template/.aioson/locales/pt-BR/agents/pm.md +7 -0
  132. package/template/.aioson/locales/pt-BR/agents/product.md +35 -17
  133. package/template/.aioson/locales/pt-BR/agents/qa.md +7 -0
  134. package/template/.aioson/locales/pt-BR/agents/setup.md +51 -5
  135. package/template/.aioson/locales/pt-BR/agents/squad.md +486 -47
  136. package/template/.aioson/locales/pt-BR/agents/ux-ui.md +361 -22
  137. package/template/.aioson/my-agents/.gitkeep +0 -0
  138. package/template/.aioson/rules/.gitkeep +0 -0
  139. package/template/.aioson/rules/squad/.gitkeep +0 -0
  140. package/template/.aioson/rules/squad/README.md +50 -0
  141. package/template/.aioson/schemas/genome-meta.schema.json +1 -1
  142. package/template/.aioson/schemas/genome.schema.json +1 -1
  143. package/template/.aioson/schemas/squad-blueprint.schema.json +11 -0
  144. package/template/.aioson/schemas/squad-manifest.schema.json +257 -1
  145. package/template/.aioson/skills/design/cognitive-core-ui/SKILL.md +157 -0
  146. package/template/.aioson/skills/design/cognitive-core-ui/references/components.md +407 -0
  147. package/template/.aioson/skills/design/cognitive-core-ui/references/dashboards.md +172 -0
  148. package/template/.aioson/skills/design/cognitive-core-ui/references/design-tokens.md +490 -0
  149. package/template/.aioson/skills/design/cognitive-core-ui/references/motion.md +237 -0
  150. package/template/.aioson/skills/design/cognitive-core-ui/references/patterns.md +289 -0
  151. package/template/.aioson/skills/design/cognitive-core-ui/references/websites.md +350 -0
  152. package/template/.aioson/skills/design/interface-design/SKILL.md +47 -0
  153. package/template/.aioson/skills/design/interface-design/references/components-and-states.md +105 -0
  154. package/template/.aioson/skills/design/interface-design/references/design-directions.md +101 -0
  155. package/template/.aioson/skills/design/interface-design/references/handoff-and-quality.md +71 -0
  156. package/template/.aioson/skills/design/interface-design/references/intent-and-domain.md +74 -0
  157. package/template/.aioson/skills/design/interface-design/references/tokens-and-depth.md +173 -0
  158. package/template/.aioson/skills/design/premium-command-center-ui/SKILL.md +62 -0
  159. package/template/.aioson/skills/design/premium-command-center-ui/references/operations.md +74 -0
  160. package/template/.aioson/skills/design/premium-command-center-ui/references/patterns.md +116 -0
  161. package/template/.aioson/skills/design/premium-command-center-ui/references/validation.md +47 -0
  162. package/template/.aioson/skills/design/premium-command-center-ui/references/visual-system.md +215 -0
  163. package/template/.aioson/skills/design-system/SKILL.md +92 -0
  164. package/template/.aioson/skills/design-system/cognitive-core-ui.skill +0 -0
  165. package/template/.aioson/skills/design-system/components/SKILL.md +274 -0
  166. package/template/.aioson/skills/design-system/components/SKILL.md:Zone.Identifier +0 -0
  167. package/template/.aioson/skills/design-system/dashboards/SKILL.md +184 -0
  168. package/template/.aioson/skills/design-system/dashboards/SKILL.md:Zone.Identifier +0 -0
  169. package/template/.aioson/skills/design-system/foundations/SKILL.md +250 -0
  170. package/template/.aioson/skills/design-system/foundations/SKILL.md:Zone.Identifier +0 -0
  171. package/template/.aioson/skills/design-system/motion/SKILL.md +197 -0
  172. package/template/.aioson/skills/design-system/motion/SKILL.md:Zone.Identifier +0 -0
  173. package/template/.aioson/skills/design-system/patterns/SKILL.md +231 -0
  174. package/template/.aioson/skills/design-system/patterns/SKILL.md:Zone.Identifier +0 -0
  175. package/template/.aioson/skills/squad/SKILL.md +58 -0
  176. package/template/.aioson/skills/squad/domains/.gitkeep +0 -0
  177. package/template/.aioson/skills/squad/formats/.gitkeep +0 -0
  178. package/template/.aioson/skills/squad/patterns/.gitkeep +0 -0
  179. package/template/.aioson/skills/squad/references/.gitkeep +0 -0
  180. package/template/.aioson/tasks/implementation-plan.md +288 -0
  181. package/template/.aioson/tasks/squad-create.md +1 -1
  182. package/template/.aioson/tasks/squad-execution-plan.md +279 -0
  183. package/template/.aioson/tasks/squad-export.md +1 -1
  184. package/template/.aioson/tasks/squad-investigate.md +44 -0
  185. package/template/.aioson/tasks/squad-learning-review.md +44 -0
  186. package/template/.aioson/tasks/squad-output-config.md +177 -0
  187. package/template/.aioson/tasks/squad-validate.md +1 -1
  188. package/template/.claude/commands/aioson/agent/deyvin.md +5 -0
  189. package/template/.claude/commands/aioson/agent/discovery-design-doc.md +5 -0
  190. package/template/.claude/commands/aioson/agent/genome.md +5 -0
  191. package/template/.claude/commands/aioson/agent/product.md +5 -0
  192. package/template/.claude/commands/aioson/agent/profiler-enricher.md +5 -0
  193. package/template/.claude/commands/aioson/agent/profiler-forge.md +5 -0
  194. package/template/.claude/commands/aioson/agent/profiler-researcher.md +5 -0
  195. package/template/.claude/commands/aioson/agent/squad.md +5 -0
  196. package/template/.gemini/GEMINI.md +2 -0
  197. package/template/.gemini/commands/aios-deyvin.toml +6 -0
  198. package/template/.gemini/commands/aios-pair.toml +6 -0
  199. package/template/AGENTS.md +34 -6
  200. package/template/CLAUDE.md +31 -4
  201. package/template/OPENCODE.md +6 -2
  202. package/template/squad-searches/.gitkeep +0 -0
  203. package/template/.aioson/skills/static/interface-design.md +0 -372
  204. package/template/.aioson/skills/static/premium-command-center-ui.md +0 -190
  205. /package/template/.aioson/{genomas → docs}/.gitkeep +0 -0
  206. /package/template/.claude/commands/aioson/{analyst.md → agent/analyst.md} +0 -0
  207. /package/template/.claude/commands/aioson/{architect.md → agent/architect.md} +0 -0
  208. /package/template/.claude/commands/aioson/{dev.md → agent/dev.md} +0 -0
  209. /package/template/.claude/commands/aioson/{orchestrator.md → agent/orchestrator.md} +0 -0
  210. /package/template/.claude/commands/aioson/{pm.md → agent/pm.md} +0 -0
  211. /package/template/.claude/commands/aioson/{qa.md → agent/qa.md} +0 -0
  212. /package/template/.claude/commands/aioson/{setup.md → agent/setup.md} +0 -0
  213. /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 };