@kynetic-ai/spec 0.1.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 (278) hide show
  1. package/README.md +263 -0
  2. package/dist/acp/client.d.ts +159 -0
  3. package/dist/acp/client.d.ts.map +1 -0
  4. package/dist/acp/client.js +255 -0
  5. package/dist/acp/client.js.map +1 -0
  6. package/dist/acp/framing.d.ts +119 -0
  7. package/dist/acp/framing.d.ts.map +1 -0
  8. package/dist/acp/framing.js +302 -0
  9. package/dist/acp/framing.js.map +1 -0
  10. package/dist/acp/index.d.ts +14 -0
  11. package/dist/acp/index.d.ts.map +1 -0
  12. package/dist/acp/index.js +13 -0
  13. package/dist/acp/index.js.map +1 -0
  14. package/dist/acp/types.d.ts +89 -0
  15. package/dist/acp/types.d.ts.map +1 -0
  16. package/dist/acp/types.js +99 -0
  17. package/dist/acp/types.js.map +1 -0
  18. package/dist/agents/adapters.d.ts +55 -0
  19. package/dist/agents/adapters.d.ts.map +1 -0
  20. package/dist/agents/adapters.js +84 -0
  21. package/dist/agents/adapters.js.map +1 -0
  22. package/dist/agents/index.d.ts +8 -0
  23. package/dist/agents/index.d.ts.map +1 -0
  24. package/dist/agents/index.js +10 -0
  25. package/dist/agents/index.js.map +1 -0
  26. package/dist/agents/spawner.d.ts +53 -0
  27. package/dist/agents/spawner.d.ts.map +1 -0
  28. package/dist/agents/spawner.js +83 -0
  29. package/dist/agents/spawner.js.map +1 -0
  30. package/dist/cli/batch.d.ts +82 -0
  31. package/dist/cli/batch.d.ts.map +1 -0
  32. package/dist/cli/batch.js +162 -0
  33. package/dist/cli/batch.js.map +1 -0
  34. package/dist/cli/commands/clone-for-testing.d.ts +6 -0
  35. package/dist/cli/commands/clone-for-testing.d.ts.map +1 -0
  36. package/dist/cli/commands/clone-for-testing.js +176 -0
  37. package/dist/cli/commands/clone-for-testing.js.map +1 -0
  38. package/dist/cli/commands/derive.d.ts +6 -0
  39. package/dist/cli/commands/derive.d.ts.map +1 -0
  40. package/dist/cli/commands/derive.js +450 -0
  41. package/dist/cli/commands/derive.js.map +1 -0
  42. package/dist/cli/commands/help.d.ts +6 -0
  43. package/dist/cli/commands/help.d.ts.map +1 -0
  44. package/dist/cli/commands/help.js +196 -0
  45. package/dist/cli/commands/help.js.map +1 -0
  46. package/dist/cli/commands/inbox.d.ts +6 -0
  47. package/dist/cli/commands/inbox.d.ts.map +1 -0
  48. package/dist/cli/commands/inbox.js +235 -0
  49. package/dist/cli/commands/inbox.js.map +1 -0
  50. package/dist/cli/commands/index.d.ts +20 -0
  51. package/dist/cli/commands/index.d.ts.map +1 -0
  52. package/dist/cli/commands/index.js +21 -0
  53. package/dist/cli/commands/index.js.map +1 -0
  54. package/dist/cli/commands/init.d.ts +6 -0
  55. package/dist/cli/commands/init.d.ts.map +1 -0
  56. package/dist/cli/commands/init.js +245 -0
  57. package/dist/cli/commands/init.js.map +1 -0
  58. package/dist/cli/commands/item.d.ts +6 -0
  59. package/dist/cli/commands/item.d.ts.map +1 -0
  60. package/dist/cli/commands/item.js +1311 -0
  61. package/dist/cli/commands/item.js.map +1 -0
  62. package/dist/cli/commands/link.d.ts +6 -0
  63. package/dist/cli/commands/link.d.ts.map +1 -0
  64. package/dist/cli/commands/link.js +288 -0
  65. package/dist/cli/commands/link.js.map +1 -0
  66. package/dist/cli/commands/log.d.ts +16 -0
  67. package/dist/cli/commands/log.d.ts.map +1 -0
  68. package/dist/cli/commands/log.js +291 -0
  69. package/dist/cli/commands/log.js.map +1 -0
  70. package/dist/cli/commands/meta.d.ts +15 -0
  71. package/dist/cli/commands/meta.d.ts.map +1 -0
  72. package/dist/cli/commands/meta.js +1378 -0
  73. package/dist/cli/commands/meta.js.map +1 -0
  74. package/dist/cli/commands/module.d.ts +6 -0
  75. package/dist/cli/commands/module.d.ts.map +1 -0
  76. package/dist/cli/commands/module.js +102 -0
  77. package/dist/cli/commands/module.js.map +1 -0
  78. package/dist/cli/commands/ralph.d.ts +9 -0
  79. package/dist/cli/commands/ralph.d.ts.map +1 -0
  80. package/dist/cli/commands/ralph.js +465 -0
  81. package/dist/cli/commands/ralph.js.map +1 -0
  82. package/dist/cli/commands/search.d.ts +6 -0
  83. package/dist/cli/commands/search.d.ts.map +1 -0
  84. package/dist/cli/commands/search.js +134 -0
  85. package/dist/cli/commands/search.js.map +1 -0
  86. package/dist/cli/commands/session.d.ts +164 -0
  87. package/dist/cli/commands/session.d.ts.map +1 -0
  88. package/dist/cli/commands/session.js +745 -0
  89. package/dist/cli/commands/session.js.map +1 -0
  90. package/dist/cli/commands/setup.d.ts +26 -0
  91. package/dist/cli/commands/setup.d.ts.map +1 -0
  92. package/dist/cli/commands/setup.js +586 -0
  93. package/dist/cli/commands/setup.js.map +1 -0
  94. package/dist/cli/commands/shadow.d.ts +6 -0
  95. package/dist/cli/commands/shadow.d.ts.map +1 -0
  96. package/dist/cli/commands/shadow.js +299 -0
  97. package/dist/cli/commands/shadow.js.map +1 -0
  98. package/dist/cli/commands/task.d.ts +6 -0
  99. package/dist/cli/commands/task.d.ts.map +1 -0
  100. package/dist/cli/commands/task.js +1514 -0
  101. package/dist/cli/commands/task.js.map +1 -0
  102. package/dist/cli/commands/tasks.d.ts +6 -0
  103. package/dist/cli/commands/tasks.d.ts.map +1 -0
  104. package/dist/cli/commands/tasks.js +347 -0
  105. package/dist/cli/commands/tasks.js.map +1 -0
  106. package/dist/cli/commands/trait.d.ts +10 -0
  107. package/dist/cli/commands/trait.d.ts.map +1 -0
  108. package/dist/cli/commands/trait.js +295 -0
  109. package/dist/cli/commands/trait.js.map +1 -0
  110. package/dist/cli/commands/validate.d.ts +6 -0
  111. package/dist/cli/commands/validate.d.ts.map +1 -0
  112. package/dist/cli/commands/validate.js +626 -0
  113. package/dist/cli/commands/validate.js.map +1 -0
  114. package/dist/cli/exit-codes.d.ts +62 -0
  115. package/dist/cli/exit-codes.d.ts.map +1 -0
  116. package/dist/cli/exit-codes.js +65 -0
  117. package/dist/cli/exit-codes.js.map +1 -0
  118. package/dist/cli/help/content.d.ts +35 -0
  119. package/dist/cli/help/content.d.ts.map +1 -0
  120. package/dist/cli/help/content.js +312 -0
  121. package/dist/cli/help/content.js.map +1 -0
  122. package/dist/cli/index.d.ts +5 -0
  123. package/dist/cli/index.d.ts.map +1 -0
  124. package/dist/cli/index.js +85 -0
  125. package/dist/cli/index.js.map +1 -0
  126. package/dist/cli/introspection.d.ts +87 -0
  127. package/dist/cli/introspection.d.ts.map +1 -0
  128. package/dist/cli/introspection.js +127 -0
  129. package/dist/cli/introspection.js.map +1 -0
  130. package/dist/cli/output.d.ts +56 -0
  131. package/dist/cli/output.d.ts.map +1 -0
  132. package/dist/cli/output.js +467 -0
  133. package/dist/cli/output.js.map +1 -0
  134. package/dist/cli/suggest.d.ts +16 -0
  135. package/dist/cli/suggest.d.ts.map +1 -0
  136. package/dist/cli/suggest.js +72 -0
  137. package/dist/cli/suggest.js.map +1 -0
  138. package/dist/index.d.ts +3 -0
  139. package/dist/index.d.ts.map +1 -0
  140. package/dist/index.js +5 -0
  141. package/dist/index.js.map +1 -0
  142. package/dist/parser/alignment.d.ts +113 -0
  143. package/dist/parser/alignment.d.ts.map +1 -0
  144. package/dist/parser/alignment.js +261 -0
  145. package/dist/parser/alignment.js.map +1 -0
  146. package/dist/parser/assess.d.ts +81 -0
  147. package/dist/parser/assess.d.ts.map +1 -0
  148. package/dist/parser/assess.js +197 -0
  149. package/dist/parser/assess.js.map +1 -0
  150. package/dist/parser/convention-validation.d.ts +48 -0
  151. package/dist/parser/convention-validation.d.ts.map +1 -0
  152. package/dist/parser/convention-validation.js +167 -0
  153. package/dist/parser/convention-validation.js.map +1 -0
  154. package/dist/parser/fix.d.ts +38 -0
  155. package/dist/parser/fix.d.ts.map +1 -0
  156. package/dist/parser/fix.js +185 -0
  157. package/dist/parser/fix.js.map +1 -0
  158. package/dist/parser/index.d.ts +12 -0
  159. package/dist/parser/index.d.ts.map +1 -0
  160. package/dist/parser/index.js +13 -0
  161. package/dist/parser/index.js.map +1 -0
  162. package/dist/parser/items.d.ts +138 -0
  163. package/dist/parser/items.d.ts.map +1 -0
  164. package/dist/parser/items.js +321 -0
  165. package/dist/parser/items.js.map +1 -0
  166. package/dist/parser/meta.d.ts +120 -0
  167. package/dist/parser/meta.d.ts.map +1 -0
  168. package/dist/parser/meta.js +441 -0
  169. package/dist/parser/meta.js.map +1 -0
  170. package/dist/parser/refs.d.ts +185 -0
  171. package/dist/parser/refs.d.ts.map +1 -0
  172. package/dist/parser/refs.js +404 -0
  173. package/dist/parser/refs.js.map +1 -0
  174. package/dist/parser/shadow.d.ts +253 -0
  175. package/dist/parser/shadow.d.ts.map +1 -0
  176. package/dist/parser/shadow.js +1053 -0
  177. package/dist/parser/shadow.js.map +1 -0
  178. package/dist/parser/traits.d.ts +72 -0
  179. package/dist/parser/traits.d.ts.map +1 -0
  180. package/dist/parser/traits.js +120 -0
  181. package/dist/parser/traits.js.map +1 -0
  182. package/dist/parser/validate.d.ts +89 -0
  183. package/dist/parser/validate.d.ts.map +1 -0
  184. package/dist/parser/validate.js +817 -0
  185. package/dist/parser/validate.js.map +1 -0
  186. package/dist/parser/yaml.d.ts +326 -0
  187. package/dist/parser/yaml.d.ts.map +1 -0
  188. package/dist/parser/yaml.js +1383 -0
  189. package/dist/parser/yaml.js.map +1 -0
  190. package/dist/ralph/cli-renderer.d.ts +20 -0
  191. package/dist/ralph/cli-renderer.d.ts.map +1 -0
  192. package/dist/ralph/cli-renderer.js +179 -0
  193. package/dist/ralph/cli-renderer.js.map +1 -0
  194. package/dist/ralph/events.d.ts +65 -0
  195. package/dist/ralph/events.d.ts.map +1 -0
  196. package/dist/ralph/events.js +397 -0
  197. package/dist/ralph/events.js.map +1 -0
  198. package/dist/ralph/index.d.ts +8 -0
  199. package/dist/ralph/index.d.ts.map +1 -0
  200. package/dist/ralph/index.js +10 -0
  201. package/dist/ralph/index.js.map +1 -0
  202. package/dist/schema/common.d.ts +46 -0
  203. package/dist/schema/common.d.ts.map +1 -0
  204. package/dist/schema/common.js +71 -0
  205. package/dist/schema/common.js.map +1 -0
  206. package/dist/schema/inbox.d.ts +90 -0
  207. package/dist/schema/inbox.d.ts.map +1 -0
  208. package/dist/schema/inbox.js +30 -0
  209. package/dist/schema/inbox.js.map +1 -0
  210. package/dist/schema/index.d.ts +6 -0
  211. package/dist/schema/index.d.ts.map +1 -0
  212. package/dist/schema/index.js +7 -0
  213. package/dist/schema/index.js.map +1 -0
  214. package/dist/schema/meta.d.ts +762 -0
  215. package/dist/schema/meta.d.ts.map +1 -0
  216. package/dist/schema/meta.js +144 -0
  217. package/dist/schema/meta.js.map +1 -0
  218. package/dist/schema/spec.d.ts +912 -0
  219. package/dist/schema/spec.d.ts.map +1 -0
  220. package/dist/schema/spec.js +104 -0
  221. package/dist/schema/spec.js.map +1 -0
  222. package/dist/schema/task.d.ts +664 -0
  223. package/dist/schema/task.d.ts.map +1 -0
  224. package/dist/schema/task.js +130 -0
  225. package/dist/schema/task.js.map +1 -0
  226. package/dist/sessions/index.d.ts +11 -0
  227. package/dist/sessions/index.d.ts.map +1 -0
  228. package/dist/sessions/index.js +13 -0
  229. package/dist/sessions/index.js.map +1 -0
  230. package/dist/sessions/store.d.ts +144 -0
  231. package/dist/sessions/store.d.ts.map +1 -0
  232. package/dist/sessions/store.js +325 -0
  233. package/dist/sessions/store.js.map +1 -0
  234. package/dist/sessions/types.d.ts +157 -0
  235. package/dist/sessions/types.d.ts.map +1 -0
  236. package/dist/sessions/types.js +90 -0
  237. package/dist/sessions/types.js.map +1 -0
  238. package/dist/strings/errors.d.ts +420 -0
  239. package/dist/strings/errors.d.ts.map +1 -0
  240. package/dist/strings/errors.js +282 -0
  241. package/dist/strings/errors.js.map +1 -0
  242. package/dist/strings/guidance.d.ts +65 -0
  243. package/dist/strings/guidance.d.ts.map +1 -0
  244. package/dist/strings/guidance.js +66 -0
  245. package/dist/strings/guidance.js.map +1 -0
  246. package/dist/strings/index.d.ts +12 -0
  247. package/dist/strings/index.d.ts.map +1 -0
  248. package/dist/strings/index.js +12 -0
  249. package/dist/strings/index.js.map +1 -0
  250. package/dist/strings/labels.d.ts +74 -0
  251. package/dist/strings/labels.d.ts.map +1 -0
  252. package/dist/strings/labels.js +75 -0
  253. package/dist/strings/labels.js.map +1 -0
  254. package/dist/strings/validation.d.ts +126 -0
  255. package/dist/strings/validation.d.ts.map +1 -0
  256. package/dist/strings/validation.js +135 -0
  257. package/dist/strings/validation.js.map +1 -0
  258. package/dist/utils/commit.d.ts +23 -0
  259. package/dist/utils/commit.d.ts.map +1 -0
  260. package/dist/utils/commit.js +67 -0
  261. package/dist/utils/commit.js.map +1 -0
  262. package/dist/utils/git.d.ts +57 -0
  263. package/dist/utils/git.d.ts.map +1 -0
  264. package/dist/utils/git.js +192 -0
  265. package/dist/utils/git.js.map +1 -0
  266. package/dist/utils/grep.d.ts +28 -0
  267. package/dist/utils/grep.d.ts.map +1 -0
  268. package/dist/utils/grep.js +86 -0
  269. package/dist/utils/grep.js.map +1 -0
  270. package/dist/utils/index.d.ts +8 -0
  271. package/dist/utils/index.d.ts.map +1 -0
  272. package/dist/utils/index.js +6 -0
  273. package/dist/utils/index.js.map +1 -0
  274. package/dist/utils/time.d.ts +18 -0
  275. package/dist/utils/time.d.ts.map +1 -0
  276. package/dist/utils/time.js +61 -0
  277. package/dist/utils/time.js.map +1 -0
  278. package/package.json +62 -0
@@ -0,0 +1,626 @@
1
+ import * as path from 'node:path';
2
+ import chalk from 'chalk';
3
+ import { initContext, validate, loadAllTasks, loadAllItems, AlignmentIndex, ReferenceIndex, fixFiles, findTaskFiles, expandIncludePattern, loadMetaContext, validateConventions, } from '../../parser/index.js';
4
+ import { output, error } from '../output.js';
5
+ import { validation as validationStrings } from '../../strings/index.js';
6
+ import { EXIT_CODES } from '../exit-codes.js';
7
+ /**
8
+ * Check for stale status mismatches between specs and tasks
9
+ * AC: @stale-status-detection ac-1, ac-2, ac-3
10
+ */
11
+ function checkStaleness(items, tasks, refIndex) {
12
+ const warnings = [];
13
+ // AC: @stale-status-detection ac-1 (parent-pending-children-done)
14
+ // Check if task with dependencies is pending but all dependencies are completed
15
+ for (const task of tasks) {
16
+ // Only check pending/in_progress tasks with dependencies
17
+ if (task.status !== 'pending' && task.status !== 'in_progress')
18
+ continue;
19
+ if (!task.depends_on || task.depends_on.length === 0)
20
+ continue;
21
+ // Resolve all dependency tasks
22
+ const depTasks = task.depends_on
23
+ .map(depRef => {
24
+ const result = refIndex.resolve(depRef);
25
+ if (!result.ok)
26
+ return null;
27
+ return tasks.find(t => t._ulid === result.ulid);
28
+ })
29
+ .filter((t) => t !== null);
30
+ if (depTasks.length === 0)
31
+ continue;
32
+ // Check if all dependencies are completed and their linked specs are implemented
33
+ const allDepsDone = depTasks.every(depTask => {
34
+ if (depTask.status !== 'completed')
35
+ return false;
36
+ // If the dep task has a spec_ref, check if that spec is implemented
37
+ if (depTask.spec_ref) {
38
+ const result = refIndex.resolve(depTask.spec_ref);
39
+ if (!result.ok)
40
+ return true; // Missing spec ref doesn't block
41
+ const spec = items.find(item => item._ulid === result.ulid);
42
+ return spec?.status?.implementation === 'implemented';
43
+ }
44
+ return true;
45
+ });
46
+ if (allDepsDone) {
47
+ const taskRef = task.slugs[0] || refIndex.shortUlid(task._ulid);
48
+ warnings.push({
49
+ type: 'parent-pending-children-done',
50
+ message: `Task @${taskRef} is ${task.status} but all dependencies are completed. Consider completing or reviewing.`,
51
+ refs: [task._ulid],
52
+ });
53
+ }
54
+ }
55
+ // AC: @stale-status-detection ac-2 (spec-implemented-no-task)
56
+ // Check if spec is implemented but has no completed tasks
57
+ for (const item of items) {
58
+ if (item.status?.implementation !== 'implemented')
59
+ continue;
60
+ // Find completed tasks that reference this spec
61
+ const completedTasks = tasks.filter(task => {
62
+ if (task.status !== 'completed' || !task.spec_ref)
63
+ return false;
64
+ const result = refIndex.resolve(task.spec_ref);
65
+ return result.ok && result.ulid === item._ulid;
66
+ });
67
+ if (completedTasks.length === 0) {
68
+ const specRef = item.slugs[0] || refIndex.shortUlid(item._ulid);
69
+ warnings.push({
70
+ type: 'spec-implemented-no-task',
71
+ message: `Spec @${specRef} is implemented but has no completed tasks. Verify implementation or link existing task.`,
72
+ refs: [item._ulid],
73
+ });
74
+ }
75
+ }
76
+ // AC: @stale-status-detection ac-3 (task-done-spec-not-started)
77
+ // Check if task is completed but spec is still not_started
78
+ for (const task of tasks) {
79
+ if (task.status !== 'completed')
80
+ continue;
81
+ if (!task.spec_ref)
82
+ continue;
83
+ // Resolve spec reference
84
+ const result = refIndex.resolve(task.spec_ref);
85
+ if (!result.ok)
86
+ continue;
87
+ const spec = items.find(item => item._ulid === result.ulid);
88
+ if (!spec)
89
+ continue;
90
+ if (spec.status?.implementation === 'not_started') {
91
+ const taskRef = task.slugs[0] || refIndex.shortUlid(task._ulid);
92
+ const specRef = spec.slugs[0] || refIndex.shortUlid(spec._ulid);
93
+ warnings.push({
94
+ type: 'task-done-spec-not-started',
95
+ message: `Task @${taskRef} completed but spec @${specRef} is not_started. Update spec status.`,
96
+ refs: [task._ulid, spec._ulid],
97
+ });
98
+ }
99
+ }
100
+ return warnings;
101
+ }
102
+ /**
103
+ * Format staleness warnings for display
104
+ * AC: @stale-status-detection ac-4
105
+ */
106
+ function formatStalenessWarnings(warnings, verbose) {
107
+ if (warnings.length === 0) {
108
+ console.log(chalk.green('Staleness: OK'));
109
+ return;
110
+ }
111
+ console.log(chalk.yellow(`\nStaleness warnings: ${warnings.length}`));
112
+ // Group by type
113
+ const parentPending = warnings.filter(w => w.type === 'parent-pending-children-done');
114
+ const specNoTask = warnings.filter(w => w.type === 'spec-implemented-no-task');
115
+ const taskDoneSpecNot = warnings.filter(w => w.type === 'task-done-spec-not-started');
116
+ if (parentPending.length > 0) {
117
+ console.log(chalk.yellow(` Parent pending, children done: ${parentPending.length}`));
118
+ const shown = verbose ? parentPending : parentPending.slice(0, 3);
119
+ for (const w of shown) {
120
+ console.log(chalk.yellow(` ! ${w.message}`));
121
+ }
122
+ if (!verbose && parentPending.length > 3) {
123
+ console.log(chalk.gray(` ... and ${parentPending.length - 3} more`));
124
+ }
125
+ }
126
+ if (specNoTask.length > 0) {
127
+ console.log(chalk.yellow(` Spec implemented, no task: ${specNoTask.length}`));
128
+ const shown = verbose ? specNoTask : specNoTask.slice(0, 3);
129
+ for (const w of shown) {
130
+ console.log(chalk.yellow(` ! ${w.message}`));
131
+ }
132
+ if (!verbose && specNoTask.length > 3) {
133
+ console.log(chalk.gray(` ... and ${specNoTask.length - 3} more`));
134
+ }
135
+ }
136
+ if (taskDoneSpecNot.length > 0) {
137
+ console.log(chalk.yellow(` Task done, spec not started: ${taskDoneSpecNot.length}`));
138
+ const shown = verbose ? taskDoneSpecNot : taskDoneSpecNot.slice(0, 3);
139
+ for (const w of shown) {
140
+ console.log(chalk.yellow(` ! ${w.message}`));
141
+ }
142
+ if (!verbose && taskDoneSpecNot.length > 3) {
143
+ console.log(chalk.gray(` ... and ${taskDoneSpecNot.length - 3} more`));
144
+ }
145
+ }
146
+ }
147
+ /**
148
+ * Format convention validation results for display
149
+ * AC: @convention-definitions ac-3, ac-4
150
+ */
151
+ function formatConventionValidationResult(result) {
152
+ if (result.valid && result.skipped.length === 0) {
153
+ console.log(chalk.green('Conventions: OK'));
154
+ return;
155
+ }
156
+ // AC: @convention-definitions ac-4
157
+ // Skipped prose conventions
158
+ if (result.skipped.length > 0) {
159
+ for (const domain of result.skipped) {
160
+ console.log(chalk.gray(`ℹ Skipping prose convention: ${domain}`));
161
+ }
162
+ }
163
+ // AC: @convention-definitions ac-3
164
+ // Validation errors
165
+ if (result.errors.length > 0) {
166
+ console.log(chalk.red(`\nConvention violations: ${result.errors.length}`));
167
+ for (const err of result.errors) {
168
+ console.log(chalk.red(` ✗ ${err.domain}`));
169
+ console.log(chalk.gray(` ${err.message}`));
170
+ if (err.expected) {
171
+ console.log(chalk.gray(` Expected: ${err.expected}`));
172
+ }
173
+ if (err.location) {
174
+ console.log(chalk.gray(` Location: ${err.location}`));
175
+ }
176
+ }
177
+ }
178
+ else {
179
+ console.log(chalk.green('\nConventions: OK'));
180
+ }
181
+ // Stats
182
+ console.log(chalk.gray(`\nConventions checked: ${result.stats.conventionsChecked}`));
183
+ console.log(chalk.gray(`Conventions skipped: ${result.stats.conventionsSkipped}`));
184
+ }
185
+ /**
186
+ * Format completeness warnings for display
187
+ * AC: @spec-completeness ac-4
188
+ */
189
+ function formatCompletenessWarnings(warnings, verbose) {
190
+ if (warnings.length === 0) {
191
+ console.log(chalk.green('Completeness: OK'));
192
+ return;
193
+ }
194
+ console.log(chalk.yellow(`\nCompleteness warnings: ${warnings.length}`));
195
+ // Group by type
196
+ const missingAC = warnings.filter(w => w.type === 'missing_acceptance_criteria');
197
+ const missingDesc = warnings.filter(w => w.type === 'missing_description');
198
+ const statusMismatch = warnings.filter(w => w.type === 'status_inconsistency');
199
+ const missingTestCoverage = warnings.filter(w => w.type === 'missing_test_coverage');
200
+ const automationNoSpec = warnings.filter(w => w.type === 'automation_eligible_no_spec');
201
+ // AC: @spec-completeness ac-4
202
+ // Show summary with counts by issue type
203
+ if (missingAC.length > 0) {
204
+ console.log(chalk.yellow(` Missing acceptance criteria: ${missingAC.length}`));
205
+ const shown = verbose ? missingAC : missingAC.slice(0, 3);
206
+ for (const w of shown) {
207
+ console.log(chalk.gray(` ○ ${w.itemRef} - ${w.itemTitle}`));
208
+ }
209
+ if (!verbose && missingAC.length > 3) {
210
+ console.log(chalk.gray(` ... and ${missingAC.length - 3} more`));
211
+ }
212
+ }
213
+ if (missingDesc.length > 0) {
214
+ console.log(chalk.yellow(` Missing descriptions: ${missingDesc.length}`));
215
+ const shown = verbose ? missingDesc : missingDesc.slice(0, 3);
216
+ for (const w of shown) {
217
+ console.log(chalk.gray(` ○ ${w.itemRef} - ${w.itemTitle}`));
218
+ }
219
+ if (!verbose && missingDesc.length > 3) {
220
+ console.log(chalk.gray(` ... and ${missingDesc.length - 3} more`));
221
+ }
222
+ }
223
+ if (statusMismatch.length > 0) {
224
+ console.log(chalk.yellow(` Status inconsistencies: ${statusMismatch.length}`));
225
+ for (const w of statusMismatch) {
226
+ console.log(chalk.yellow(` ! ${w.message}`));
227
+ if (w.details) {
228
+ console.log(chalk.gray(` ${w.details}`));
229
+ }
230
+ }
231
+ }
232
+ if (missingTestCoverage.length > 0) {
233
+ console.log(chalk.yellow(` Missing test coverage: ${missingTestCoverage.length}`));
234
+ const shown = verbose ? missingTestCoverage : missingTestCoverage.slice(0, 3);
235
+ for (const w of shown) {
236
+ console.log(chalk.yellow(` ! ${w.itemRef} - ${w.itemTitle}`));
237
+ if (w.details) {
238
+ console.log(chalk.gray(` ${w.details}`));
239
+ }
240
+ }
241
+ if (!verbose && missingTestCoverage.length > 3) {
242
+ console.log(chalk.gray(` ... and ${missingTestCoverage.length - 3} more`));
243
+ }
244
+ }
245
+ // AC: @task-automation-eligibility ac-21, ac-23
246
+ if (automationNoSpec.length > 0) {
247
+ console.log(chalk.yellow(` Automation without spec: ${automationNoSpec.length}`));
248
+ const shown = verbose ? automationNoSpec : automationNoSpec.slice(0, 3);
249
+ for (const w of shown) {
250
+ console.log(chalk.yellow(` ! ${w.itemRef} - ${w.itemTitle}`));
251
+ console.log(chalk.gray(` ${w.message}`));
252
+ }
253
+ if (!verbose && automationNoSpec.length > 3) {
254
+ console.log(chalk.gray(` ... and ${automationNoSpec.length - 3} more`));
255
+ }
256
+ }
257
+ }
258
+ /**
259
+ * Format alignment warnings for display
260
+ */
261
+ function formatAlignmentWarnings(warnings, verbose) {
262
+ if (warnings.length === 0) {
263
+ console.log(chalk.green('Alignment: OK'));
264
+ return;
265
+ }
266
+ console.log(chalk.yellow(`\nAlignment warnings: ${warnings.length}`));
267
+ // Group by type
268
+ const orphaned = warnings.filter(w => w.type === 'orphaned_spec');
269
+ const mismatches = warnings.filter(w => w.type === 'status_mismatch');
270
+ const stale = warnings.filter(w => w.type === 'stale_implementation');
271
+ if (orphaned.length > 0) {
272
+ console.log(chalk.yellow(` Orphaned specs (no tasks): ${orphaned.length}`));
273
+ const shown = verbose ? orphaned : orphaned.slice(0, 3);
274
+ for (const w of shown) {
275
+ console.log(chalk.gray(` ○ ${w.specTitle}`));
276
+ }
277
+ if (!verbose && orphaned.length > 3) {
278
+ console.log(chalk.gray(` ... and ${orphaned.length - 3} more`));
279
+ }
280
+ }
281
+ if (mismatches.length > 0) {
282
+ console.log(chalk.yellow(` Status mismatches: ${mismatches.length}`));
283
+ for (const w of mismatches) {
284
+ console.log(chalk.yellow(` ! ${w.specTitle}`));
285
+ console.log(chalk.gray(` ${w.message}`));
286
+ }
287
+ }
288
+ if (stale.length > 0) {
289
+ console.log(chalk.yellow(` Stale implementation status: ${stale.length}`));
290
+ for (const w of stale) {
291
+ console.log(chalk.yellow(` ! ${w.message}`));
292
+ }
293
+ }
294
+ }
295
+ /**
296
+ * Format fix results for display
297
+ */
298
+ function formatFixResult(result) {
299
+ if (result.fixesApplied.length === 0) {
300
+ console.log(chalk.gray('\nNo auto-fixable issues found.'));
301
+ return;
302
+ }
303
+ console.log(chalk.cyan(`\n✓ Applied ${result.fixesApplied.length} fix(es) to ${result.filesModified} file(s):`));
304
+ for (const fix of result.fixesApplied) {
305
+ const typeLabel = {
306
+ ulid_regenerated: 'ULID regenerated',
307
+ timestamp_added: 'Timestamp added',
308
+ status_added: 'Status added',
309
+ }[fix.type];
310
+ const shortFile = path.basename(fix.file);
311
+ console.log(chalk.cyan(` ✓ ${shortFile}:${fix.path} - ${typeLabel}`));
312
+ }
313
+ if (result.errors.length > 0) {
314
+ console.log(chalk.yellow(`\nFix errors: ${result.errors.length}`));
315
+ for (const err of result.errors) {
316
+ console.log(chalk.yellow(` ! ${err.file}: ${err.message}`));
317
+ }
318
+ }
319
+ }
320
+ /**
321
+ * Collect all files that can be fixed
322
+ */
323
+ async function collectFixableFiles(ctx) {
324
+ const files = [];
325
+ // Task files (exclude test fixtures)
326
+ const taskFiles = await findTaskFiles(ctx.rootDir);
327
+ const specTaskFiles = await findTaskFiles(path.join(ctx.rootDir, 'spec'));
328
+ const allTaskFiles = [...new Set([...taskFiles, ...specTaskFiles])];
329
+ files.push(...allTaskFiles.filter(f => !f.includes('fixtures') && !f.includes('test')));
330
+ // Spec files from includes
331
+ if (ctx.manifest && ctx.manifestPath) {
332
+ const manifestDir = path.dirname(ctx.manifestPath);
333
+ const includes = ctx.manifest.includes || [];
334
+ for (const include of includes) {
335
+ const expandedPaths = await expandIncludePattern(include, manifestDir);
336
+ files.push(...expandedPaths);
337
+ }
338
+ }
339
+ // Inbox file
340
+ const inboxPath = path.join(ctx.rootDir, 'spec', 'kynetic.inbox.yaml');
341
+ try {
342
+ await import('node:fs/promises').then(fs => fs.access(inboxPath));
343
+ files.push(inboxPath);
344
+ }
345
+ catch {
346
+ // Inbox file doesn't exist, skip
347
+ }
348
+ return [...new Set(files)];
349
+ }
350
+ /**
351
+ * Format validation result for display
352
+ */
353
+ function formatValidationResult(result, verbose) {
354
+ // Header
355
+ if (result.valid) {
356
+ console.log(chalk.green.bold('✓ Validation passed'));
357
+ }
358
+ else {
359
+ console.log(chalk.red.bold('✗ Validation failed'));
360
+ }
361
+ console.log(chalk.gray('─'.repeat(40)));
362
+ console.log(`Files checked: ${result.stats.filesChecked}`);
363
+ console.log(`Items checked: ${result.stats.itemsChecked}`);
364
+ console.log(`Tasks checked: ${result.stats.tasksChecked}`);
365
+ // AC-meta-manifest-2: Display meta summary line
366
+ if (result.metaStats) {
367
+ console.log(`Meta: ${result.metaStats.agents} agents, ${result.metaStats.workflows} workflows, ${result.metaStats.conventions} conventions`);
368
+ }
369
+ // Schema errors
370
+ if (result.schemaErrors.length > 0) {
371
+ console.log(chalk.red(`\nSchema errors: ${result.schemaErrors.length}`));
372
+ for (const err of result.schemaErrors) {
373
+ const location = err.path ? `${err.file}:${err.path}` : err.file;
374
+ console.log(chalk.red(` ✗ ${location}`));
375
+ console.log(chalk.gray(` ${err.message}`));
376
+ if (verbose && err.details) {
377
+ console.log(chalk.gray(` ${JSON.stringify(err.details)}`));
378
+ }
379
+ }
380
+ }
381
+ else {
382
+ console.log(chalk.green('\nSchema: OK'));
383
+ }
384
+ // Reference errors
385
+ if (result.refErrors.length > 0) {
386
+ console.log(chalk.red(`\nReference errors: ${result.refErrors.length}`));
387
+ for (const err of result.refErrors) {
388
+ const location = err.sourceFile
389
+ ? `${err.sourceFile} (${err.field})`
390
+ : `${err.sourceUlid?.slice(0, 8)} (${err.field})`;
391
+ console.log(chalk.red(` ✗ ${err.ref}`));
392
+ console.log(chalk.gray(` ${err.message}`));
393
+ console.log(chalk.gray(` in: ${location}`));
394
+ }
395
+ }
396
+ else {
397
+ console.log(chalk.green('References: OK'));
398
+ }
399
+ // Reference warnings (deprecated targets)
400
+ if (result.refWarnings.length > 0) {
401
+ console.log(chalk.yellow(`\nReference warnings: ${result.refWarnings.length}`));
402
+ const shown = verbose ? result.refWarnings : result.refWarnings.slice(0, 5);
403
+ for (const warn of shown) {
404
+ const location = warn.sourceFile
405
+ ? `${warn.sourceFile} (${warn.field})`
406
+ : `${warn.sourceUlid?.slice(0, 8)} (${warn.field})`;
407
+ console.log(chalk.yellow(` ⚠ ${warn.ref}`));
408
+ console.log(chalk.gray(` ${warn.message}`));
409
+ console.log(chalk.gray(` in: ${location}`));
410
+ }
411
+ if (!verbose && result.refWarnings.length > 5) {
412
+ console.log(chalk.gray(` ... and ${result.refWarnings.length - 5} more (use -v to see all)`));
413
+ }
414
+ }
415
+ // AC: @trait-edge-cases ac-2
416
+ // Trait cycle errors
417
+ if (result.traitCycleErrors.length > 0) {
418
+ console.log(chalk.red(`\nTrait cycle errors: ${result.traitCycleErrors.length}`));
419
+ for (const err of result.traitCycleErrors) {
420
+ console.log(chalk.red(` ✗ ${err.traitRef} - ${err.traitTitle}`));
421
+ console.log(chalk.gray(` ${err.message}`));
422
+ }
423
+ }
424
+ // Orphans (warnings, not errors)
425
+ if (result.orphans.length > 0) {
426
+ console.log(chalk.yellow(`\nOrphans (not referenced): ${result.orphans.length}`));
427
+ if (verbose) {
428
+ for (const orphan of result.orphans) {
429
+ console.log(chalk.yellow(` ○ ${orphan.ulid.slice(0, 8)} [${orphan.type}] ${orphan.title}`));
430
+ }
431
+ }
432
+ else {
433
+ // Show first few
434
+ const shown = result.orphans.slice(0, 5);
435
+ for (const orphan of shown) {
436
+ console.log(chalk.yellow(` ○ ${orphan.ulid.slice(0, 8)} [${orphan.type}] ${orphan.title}`));
437
+ }
438
+ if (result.orphans.length > 5) {
439
+ console.log(chalk.gray(` ... and ${result.orphans.length - 5} more (use -v to see all)`));
440
+ }
441
+ }
442
+ }
443
+ }
444
+ /**
445
+ * Register validate command
446
+ */
447
+ export function registerValidateCommand(program) {
448
+ program
449
+ .command('validate')
450
+ .description('Validate spec files')
451
+ .option('--schema', 'Check schema conformance only')
452
+ .option('--refs', 'Check reference resolution only')
453
+ .option('--orphans', 'Find orphaned items only')
454
+ .option('--alignment', 'Check spec-task alignment')
455
+ .option('--completeness', 'Check spec completeness (missing AC, descriptions, status inconsistencies)')
456
+ .option('--conventions', 'Validate conventions')
457
+ .option('--staleness', 'Check for stale status mismatches between specs and tasks')
458
+ .option('--fix', 'Auto-fix issues where possible (invalid ULIDs, missing timestamps)')
459
+ .option('-v, --verbose', 'Show detailed output')
460
+ .option('--strict', 'Treat orphans and staleness warnings as errors')
461
+ .action(async (options) => {
462
+ try {
463
+ const ctx = await initContext();
464
+ if (!ctx.manifestPath) {
465
+ error(validationStrings.noManifest);
466
+ console.log(validationStrings.initHint);
467
+ process.exit(EXIT_CODES.ERROR);
468
+ }
469
+ // Determine which checks to run
470
+ const runAll = !options.schema && !options.refs && !options.orphans && !options.alignment && !options.completeness && !options.conventions;
471
+ const validateOptions = {
472
+ schema: runAll || options.schema,
473
+ refs: runAll || options.refs,
474
+ orphans: runAll || options.orphans,
475
+ completeness: runAll || options.completeness,
476
+ };
477
+ const result = await validate(ctx, validateOptions);
478
+ // In strict mode, orphans are errors
479
+ if (options.strict && result.orphans.length > 0) {
480
+ result.valid = false;
481
+ }
482
+ output(result, () => formatValidationResult(result, options.verbose));
483
+ // Run auto-fix if requested
484
+ if (options.fix) {
485
+ const filesToFix = await collectFixableFiles(ctx);
486
+ const fixResult = await fixFiles(filesToFix);
487
+ formatFixResult(fixResult);
488
+ // Re-run validation after fixes to show updated status
489
+ if (fixResult.fixesApplied.length > 0) {
490
+ console.log(validationStrings.revalidating);
491
+ const revalidateResult = await validate(ctx, validateOptions);
492
+ if (revalidateResult.valid) {
493
+ console.log(validationStrings.nowPasses);
494
+ }
495
+ else {
496
+ console.log(validationStrings.issuesRemain);
497
+ }
498
+ // Update result for exit code
499
+ result.valid = revalidateResult.valid;
500
+ result.schemaErrors = revalidateResult.schemaErrors;
501
+ result.refErrors = revalidateResult.refErrors;
502
+ }
503
+ }
504
+ // Run alignment check if requested or running all checks
505
+ if (options.alignment || runAll) {
506
+ const tasks = await loadAllTasks(ctx);
507
+ const items = await loadAllItems(ctx);
508
+ const refIndex = new ReferenceIndex(tasks, items);
509
+ const alignmentIndex = new AlignmentIndex(tasks, items);
510
+ alignmentIndex.buildLinks(refIndex);
511
+ const alignmentWarnings = alignmentIndex.findAlignmentWarnings();
512
+ formatAlignmentWarnings(alignmentWarnings, options.verbose);
513
+ // Show alignment stats
514
+ const stats = alignmentIndex.getStats();
515
+ console.log(validationStrings.alignmentStats(stats.specsWithTasks, stats.totalSpecs, stats.alignedSpecs));
516
+ }
517
+ // Show completeness warnings if any
518
+ // AC: @spec-completeness ac-4
519
+ if (result.completenessWarnings.length > 0) {
520
+ formatCompletenessWarnings(result.completenessWarnings, options.verbose);
521
+ }
522
+ // Run convention validation if requested
523
+ // AC: @convention-definitions ac-3, ac-4
524
+ if (options.conventions) {
525
+ try {
526
+ const metaCtx = await loadMetaContext(ctx);
527
+ if (metaCtx && metaCtx.conventions.length > 0) {
528
+ // For now, we just validate that conventions are well-formed
529
+ // Full validation against actual content (commits, notes, etc.)
530
+ // would require additional content gathering logic
531
+ const conventionResult = validateConventions(metaCtx.conventions, {});
532
+ formatConventionValidationResult(conventionResult);
533
+ if (!conventionResult.valid) {
534
+ result.valid = false;
535
+ }
536
+ }
537
+ else {
538
+ console.log(chalk.gray('No conventions defined in meta manifest'));
539
+ }
540
+ }
541
+ catch (err) {
542
+ console.log(chalk.yellow('Warning: Could not load meta manifest for convention validation'));
543
+ }
544
+ }
545
+ // Run staleness checks if requested
546
+ // AC: @stale-status-detection ac-4, ac-5
547
+ if (options.staleness) {
548
+ const tasks = await loadAllTasks(ctx);
549
+ const items = await loadAllItems(ctx);
550
+ const refIndex = new ReferenceIndex(tasks, items);
551
+ const stalenessWarnings = checkStaleness(items, tasks, refIndex);
552
+ formatStalenessWarnings(stalenessWarnings, options.verbose);
553
+ // AC: @stale-status-detection ac-5 (staleness-exit-code)
554
+ // With --strict, staleness warnings cause validation failure
555
+ if (options.strict && stalenessWarnings.length > 0) {
556
+ process.exit(EXIT_CODES.VALIDATION_FAILED);
557
+ }
558
+ }
559
+ if (!result.valid) {
560
+ process.exit(EXIT_CODES.ERROR);
561
+ }
562
+ }
563
+ catch (err) {
564
+ error(validationStrings.failed, err);
565
+ process.exit(EXIT_CODES.ERROR);
566
+ }
567
+ });
568
+ // Alias: kspec lint
569
+ program
570
+ .command('lint')
571
+ .description('Alias for validate with style checks')
572
+ .option('--schema', 'Check schema conformance only')
573
+ .option('--refs', 'Check reference resolution only')
574
+ .option('--orphans', 'Find orphaned items only')
575
+ .option('--completeness', 'Check spec completeness (missing AC, descriptions, status inconsistencies)')
576
+ .option('--fix', 'Auto-fix issues where possible (invalid ULIDs, missing timestamps)')
577
+ .option('-v, --verbose', 'Show detailed output')
578
+ .option('--strict', 'Treat orphans as errors')
579
+ .action(async (options) => {
580
+ try {
581
+ const ctx = await initContext();
582
+ if (!ctx.manifestPath) {
583
+ error(validationStrings.noManifest);
584
+ process.exit(EXIT_CODES.ERROR);
585
+ }
586
+ const runAll = !options.schema && !options.refs && !options.orphans && !options.completeness;
587
+ const validateOptions = {
588
+ schema: runAll || options.schema,
589
+ refs: runAll || options.refs,
590
+ orphans: runAll || options.orphans,
591
+ completeness: runAll || options.completeness,
592
+ };
593
+ const result = await validate(ctx, validateOptions);
594
+ if (options.strict && result.orphans.length > 0) {
595
+ result.valid = false;
596
+ }
597
+ output(result, () => formatValidationResult(result, options.verbose));
598
+ // Run auto-fix if requested
599
+ if (options.fix) {
600
+ const filesToFix = await collectFixableFiles(ctx);
601
+ const fixResult = await fixFiles(filesToFix);
602
+ formatFixResult(fixResult);
603
+ // Re-run validation after fixes
604
+ if (fixResult.fixesApplied.length > 0) {
605
+ console.log(validationStrings.revalidating);
606
+ const revalidateResult = await validate(ctx, validateOptions);
607
+ if (revalidateResult.valid) {
608
+ console.log(validationStrings.nowPasses);
609
+ }
610
+ else {
611
+ console.log(validationStrings.issuesRemain);
612
+ }
613
+ result.valid = revalidateResult.valid;
614
+ }
615
+ }
616
+ if (!result.valid) {
617
+ process.exit(EXIT_CODES.ERROR);
618
+ }
619
+ }
620
+ catch (err) {
621
+ error(validationStrings.lintFailed, err);
622
+ process.exit(EXIT_CODES.ERROR);
623
+ }
624
+ });
625
+ }
626
+ //# sourceMappingURL=validate.js.map