@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,176 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import * as path from 'node:path';
3
+ import * as os from 'node:os';
4
+ import * as fs from 'node:fs';
5
+ import { ulid } from 'ulid';
6
+ import { output, error as outputError } from '../output.js';
7
+ import { EXIT_CODES } from '../exit-codes.js';
8
+ // AC: @cmd-clone-for-testing ac-1
9
+ /**
10
+ * Creates isolated repo copy for testing
11
+ * - Clones source repo to destination
12
+ * - Removes remote origin for isolation
13
+ * - Preserves all branches including kspec-meta
14
+ */
15
+ function cloneRepo(source, dest) {
16
+ // Clone with --mirror to get all branches
17
+ const cloneResult = spawnSync('git', [
18
+ 'clone',
19
+ '--mirror',
20
+ source,
21
+ path.join(dest, '.git')
22
+ ], { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'] });
23
+ if (cloneResult.status !== 0) {
24
+ return { success: false, error: cloneResult.stderr };
25
+ }
26
+ // Convert bare mirror repo to normal repo
27
+ const configResult = spawnSync('git', [
28
+ 'config',
29
+ '--bool',
30
+ 'core.bare',
31
+ 'false'
32
+ ], { cwd: dest, encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'] });
33
+ if (configResult.status !== 0) {
34
+ return { success: false, error: configResult.stderr };
35
+ }
36
+ // Reset working tree to populate files
37
+ const resetResult = spawnSync('git', ['reset', '--hard'], {
38
+ cwd: dest,
39
+ encoding: 'utf-8',
40
+ stdio: ['ignore', 'pipe', 'pipe']
41
+ });
42
+ if (resetResult.status !== 0) {
43
+ return { success: false, error: resetResult.stderr };
44
+ }
45
+ // Remove remote references for true isolation
46
+ const remoteResult = spawnSync('git', ['remote', 'remove', 'origin'], {
47
+ cwd: dest,
48
+ encoding: 'utf-8',
49
+ stdio: ['ignore', 'pipe', 'pipe']
50
+ });
51
+ // Don't fail if no remote exists
52
+ // remoteResult.status will be non-zero if origin doesn't exist, which is fine
53
+ // Clean up worktree metadata from source repo to prevent conflicts
54
+ // The mirror clone copies .git/worktrees/ which causes "already in use" errors
55
+ const worktreesPath = path.join(dest, '.git', 'worktrees');
56
+ if (fs.existsSync(worktreesPath)) {
57
+ fs.rmSync(worktreesPath, { recursive: true, force: true });
58
+ }
59
+ return { success: true };
60
+ }
61
+ // AC: @cmd-clone-for-testing ac-2
62
+ /**
63
+ * Sets up .kspec worktree if kspec-meta branch exists
64
+ */
65
+ function setupWorktree(repoPath) {
66
+ // Check for kspec-meta branch
67
+ const branchCheck = spawnSync('git', [
68
+ 'branch', '--list', 'kspec-meta'
69
+ ], { cwd: repoPath, encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'] });
70
+ if (!branchCheck.stdout.trim()) {
71
+ // No kspec-meta branch, nothing to do
72
+ return { success: true };
73
+ }
74
+ // Create .kspec worktree
75
+ const worktreePath = path.join(repoPath, '.kspec');
76
+ const worktreeResult = spawnSync('git', [
77
+ 'worktree', 'add', '.kspec', 'kspec-meta'
78
+ ], { cwd: repoPath, encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'] });
79
+ if (worktreeResult.status !== 0) {
80
+ return { success: false, error: worktreeResult.stderr };
81
+ }
82
+ return { success: true };
83
+ }
84
+ // AC: @cmd-clone-for-testing ac-3
85
+ /**
86
+ * Checkout specified branch
87
+ */
88
+ function checkoutBranch(repoPath, branch) {
89
+ const checkoutResult = spawnSync('git', ['checkout', branch], {
90
+ cwd: repoPath,
91
+ encoding: 'utf-8',
92
+ stdio: ['ignore', 'pipe', 'pipe']
93
+ });
94
+ if (checkoutResult.status !== 0) {
95
+ return { success: false, error: checkoutResult.stderr, currentBranch: '' };
96
+ }
97
+ // Get current branch name
98
+ const branchResult = spawnSync('git', ['branch', '--show-current'], {
99
+ cwd: repoPath,
100
+ encoding: 'utf-8',
101
+ stdio: ['ignore', 'pipe', 'pipe']
102
+ });
103
+ return { success: true, currentBranch: branchResult.stdout.trim() };
104
+ }
105
+ /**
106
+ * Get current branch name
107
+ */
108
+ function getCurrentBranch(repoPath) {
109
+ const branchResult = spawnSync('git', ['branch', '--show-current'], {
110
+ cwd: repoPath,
111
+ encoding: 'utf-8',
112
+ stdio: ['ignore', 'pipe', 'pipe']
113
+ });
114
+ return branchResult.stdout.trim() || 'main';
115
+ }
116
+ /**
117
+ * Register the 'clone-for-testing' command
118
+ */
119
+ export function registerCloneForTestingCommand(program) {
120
+ program
121
+ .command('clone-for-testing [dest] [source]')
122
+ .description('Create isolated repo copy for testing')
123
+ .option('--branch <name>', 'Branch to checkout after cloning')
124
+ .action(async (dest, source, options) => {
125
+ // Default source to current directory
126
+ if (!source) {
127
+ source = process.cwd();
128
+ }
129
+ try {
130
+ // AC: @cmd-clone-for-testing ac-4
131
+ // Generate temp dest if not provided
132
+ if (!dest) {
133
+ dest = path.join(os.tmpdir(), `kspec-test-${ulid().slice(0, 8).toLowerCase()}`);
134
+ }
135
+ // Create destination directory
136
+ if (!fs.existsSync(dest)) {
137
+ fs.mkdirSync(dest, { recursive: true });
138
+ }
139
+ // Clone the repo
140
+ const cloneResult = cloneRepo(source, dest);
141
+ if (!cloneResult.success) {
142
+ outputError(`Failed to clone repository: ${cloneResult.error}`);
143
+ process.exit(EXIT_CODES.ERROR);
144
+ }
145
+ // Setup worktree if kspec-meta exists
146
+ const worktreeResult = setupWorktree(dest);
147
+ if (!worktreeResult.success) {
148
+ outputError(`Failed to setup worktree: ${worktreeResult.error}`);
149
+ process.exit(EXIT_CODES.ERROR);
150
+ }
151
+ // Checkout specified branch if provided
152
+ let currentBranch = getCurrentBranch(dest);
153
+ if (options.branch) {
154
+ const checkoutResult = checkoutBranch(dest, options.branch);
155
+ if (!checkoutResult.success) {
156
+ outputError(`Failed to checkout branch '${options.branch}': ${checkoutResult.error}`);
157
+ process.exit(EXIT_CODES.ERROR);
158
+ }
159
+ currentBranch = checkoutResult.currentBranch;
160
+ }
161
+ // AC: @cmd-clone-for-testing ac-5
162
+ // Output result (respects global --json flag)
163
+ output({ path: dest, branch: currentBranch }, () => {
164
+ console.log(`Created test repo at: ${dest}`);
165
+ if (options.branch) {
166
+ console.log(`Checked out branch: ${currentBranch}`);
167
+ }
168
+ });
169
+ }
170
+ catch (err) {
171
+ outputError(`Unexpected error: ${err instanceof Error ? err.message : String(err)}`);
172
+ process.exit(EXIT_CODES.ERROR);
173
+ }
174
+ });
175
+ }
176
+ //# sourceMappingURL=clone-for-testing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clone-for-testing.js","sourceRoot":"","sources":["../../../src/cli/commands/clone-for-testing.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import { Command } from 'commander';
2
+ /**
3
+ * Register the 'derive' command
4
+ */
5
+ export declare function registerDeriveCommand(program: Command): void;
6
+ //# sourceMappingURL=derive.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"derive.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/derive.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA2WpC;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAyM5D"}
@@ -0,0 +1,450 @@
1
+ import { initContext, loadAllTasks, loadAllItems, saveTask, createTask, createNote, ReferenceIndex, AlignmentIndex, } from '../../parser/index.js';
2
+ import { commitIfShadow } from '../../parser/shadow.js';
3
+ import { output, error, info, isJsonMode } from '../output.js';
4
+ import { errors } from '../../strings/index.js';
5
+ import { EXIT_CODES } from '../exit-codes.js';
6
+ /**
7
+ * Fields that contain nested spec items (mirrors yaml.ts)
8
+ */
9
+ const NESTED_ITEM_FIELDS = ['modules', 'features', 'requirements', 'constraints', 'decisions'];
10
+ /**
11
+ * Get the parent path from a child's _path.
12
+ * e.g., "features[0].requirements[1]" -> "features[0]"
13
+ * Returns empty string for top-level items.
14
+ */
15
+ function getParentPath(childPath) {
16
+ if (!childPath)
17
+ return '';
18
+ const lastDotIndex = childPath.lastIndexOf('.');
19
+ if (lastDotIndex === -1)
20
+ return '';
21
+ return childPath.slice(0, lastDotIndex);
22
+ }
23
+ /**
24
+ * Check if an item is a direct child of another item based on _path.
25
+ * Direct children have a path that extends the parent's path by exactly one field[index].
26
+ */
27
+ function isDirectChildOf(child, parent) {
28
+ const childPath = child._path || '';
29
+ const parentPath = parent._path || '';
30
+ // If paths are equal, not a child
31
+ if (childPath === parentPath)
32
+ return false;
33
+ // Child path must start with parent path
34
+ if (parentPath && !childPath.startsWith(parentPath + '.'))
35
+ return false;
36
+ // For root parent (empty path), child must be a top-level path like "features[0]"
37
+ if (!parentPath) {
38
+ // Direct child of root has no '.' in its path
39
+ return !childPath.includes('.');
40
+ }
41
+ // Get the remaining path after parent
42
+ const remaining = childPath.slice(parentPath.length + 1);
43
+ // Direct child has no additional '.' (e.g., "requirements[0]" not "requirements[0].something")
44
+ return !remaining.includes('.');
45
+ }
46
+ /**
47
+ * Find the parent spec item of a given item.
48
+ * Returns undefined for root-level items.
49
+ */
50
+ function findParentItem(item, allItems) {
51
+ const parentPath = getParentPath(item._path);
52
+ // Root-level item or no path
53
+ if (!parentPath && !item._path)
54
+ return undefined;
55
+ if (!parentPath)
56
+ return undefined;
57
+ // Find item with matching path in the same source file
58
+ return allItems.find(i => i._path === parentPath && i._sourceFile === item._sourceFile);
59
+ }
60
+ /**
61
+ * Get direct children of a spec item.
62
+ * Only returns immediate children, not grandchildren.
63
+ */
64
+ function getDirectChildren(parent, allItems) {
65
+ return allItems.filter(item => item._sourceFile === parent._sourceFile && isDirectChildOf(item, parent));
66
+ }
67
+ /**
68
+ * Collect an item and all its descendants in topological order (parent first).
69
+ * This ensures parent tasks are created before child tasks.
70
+ */
71
+ function collectItemsRecursively(root, allItems) {
72
+ const result = [root];
73
+ const children = getDirectChildren(root, allItems);
74
+ for (const child of children) {
75
+ const descendants = collectItemsRecursively(child, allItems);
76
+ result.push(...descendants);
77
+ }
78
+ return result;
79
+ }
80
+ /**
81
+ * Resolve a spec item reference.
82
+ * Returns the spec item or exits with error.
83
+ */
84
+ function resolveSpecRef(ref, items, tasks, index) {
85
+ const result = index.resolve(ref);
86
+ if (!result.ok) {
87
+ switch (result.error) {
88
+ case 'not_found':
89
+ error(errors.reference.specNotFound(ref));
90
+ break;
91
+ case 'ambiguous':
92
+ error(errors.reference.ambiguous(ref));
93
+ for (const candidate of result.candidates) {
94
+ const item = items.find(i => i._ulid === candidate);
95
+ const slug = item?.slugs[0] || '';
96
+ console.error(` - ${index.shortUlid(candidate)} ${slug ? `(${slug})` : ''}`);
97
+ }
98
+ break;
99
+ case 'duplicate_slug':
100
+ error(errors.reference.slugMapsToMultiple(ref));
101
+ for (const candidate of result.candidates) {
102
+ console.error(` - ${index.shortUlid(candidate)}`);
103
+ }
104
+ break;
105
+ }
106
+ process.exit(EXIT_CODES.NOT_FOUND);
107
+ }
108
+ // Check if it's actually a spec item (not a task)
109
+ const item = items.find(i => i._ulid === result.ulid);
110
+ if (!item) {
111
+ // Check if it's a task
112
+ const task = tasks.find(t => t._ulid === result.ulid);
113
+ if (task) {
114
+ error(errors.reference.notSpecItem(ref));
115
+ }
116
+ else {
117
+ error(errors.reference.specNotFound(ref));
118
+ }
119
+ process.exit(EXIT_CODES.NOT_FOUND);
120
+ }
121
+ return item;
122
+ }
123
+ /**
124
+ * Generate a slug from a spec item title.
125
+ * Converts "My Feature Title" -> "task-my-feature-title"
126
+ */
127
+ function generateSlugFromTitle(title) {
128
+ return ('task-' +
129
+ title
130
+ .toLowerCase()
131
+ .replace(/[^a-z0-9]+/g, '-')
132
+ .replace(/^-|-$/g, '')
133
+ .slice(0, 50));
134
+ }
135
+ /**
136
+ * Convert spec priority to task priority (number).
137
+ * Spec can use 'high', 'medium', 'low' or numeric 1-5.
138
+ */
139
+ function normalizePriority(priority) {
140
+ if (priority === undefined)
141
+ return 3;
142
+ if (typeof priority === 'number')
143
+ return priority;
144
+ switch (priority) {
145
+ case 'high':
146
+ return 1;
147
+ case 'medium':
148
+ return 3;
149
+ case 'low':
150
+ return 5;
151
+ default:
152
+ return 3;
153
+ }
154
+ }
155
+ /**
156
+ * Generate implementation notes from spec item for newly derived task.
157
+ * Includes description and acceptance criteria summary.
158
+ */
159
+ function generateImplementationNotes(specItem) {
160
+ const parts = [];
161
+ // Add description if present
162
+ if (specItem.description) {
163
+ parts.push(specItem.description.trim());
164
+ }
165
+ // Add acceptance criteria summary if present
166
+ if (specItem.acceptance_criteria && specItem.acceptance_criteria.length > 0) {
167
+ const acSection = ['', 'Acceptance Criteria:'];
168
+ for (const ac of specItem.acceptance_criteria) {
169
+ const summary = `${ac.given ? 'Given ' + ac.given + ', ' : ''}when ${ac.when}, then ${ac.then}`;
170
+ acSection.push(`- ${ac.id}: ${summary}`);
171
+ }
172
+ parts.push(acSection.join('\n'));
173
+ }
174
+ // Return combined content, or undefined if nothing to add
175
+ return parts.length > 0 ? parts.join('\n\n') : undefined;
176
+ }
177
+ /**
178
+ * Derive a task from a spec item.
179
+ * Returns result describing what happened.
180
+ *
181
+ * @param dependsOn - Task references to add as dependencies (for hierarchy-based deps)
182
+ * @param priority - Priority override (1-5), if not provided uses spec's priority
183
+ */
184
+ async function deriveTaskFromSpec(ctx, specItem, existingTasks, items, index, alignmentIndex, options) {
185
+ // Check if a task already exists for this spec
186
+ const linkedTasks = alignmentIndex.getTasksForSpec(specItem._ulid);
187
+ if (linkedTasks.length > 0 && !options.force) {
188
+ const taskRef = linkedTasks[0].slugs[0]
189
+ ? `@${linkedTasks[0].slugs[0]}`
190
+ : `@${index.shortUlid(linkedTasks[0]._ulid)}`;
191
+ return {
192
+ specItem,
193
+ action: 'skipped',
194
+ task: linkedTasks[0],
195
+ reason: `task exists: ${taskRef}`,
196
+ };
197
+ }
198
+ // Check if slug would collide with existing task
199
+ const baseSlug = generateSlugFromTitle(specItem.title);
200
+ let slug = baseSlug;
201
+ let slugSuffix = 1;
202
+ // Find unique slug if needed
203
+ while (existingTasks.some(t => t.slugs.includes(slug))) {
204
+ slug = `${baseSlug}-${slugSuffix}`;
205
+ slugSuffix++;
206
+ }
207
+ // Generate implementation notes from spec
208
+ const noteContent = generateImplementationNotes(specItem);
209
+ const initialNotes = noteContent
210
+ ? [createNote(`Implementation notes (auto-generated from spec):\n\n${noteContent}`, '@kspec-derive')]
211
+ : [];
212
+ // Build task input with depends_on and initial notes
213
+ const taskInput = {
214
+ title: `Implement: ${specItem.title}`,
215
+ type: 'task',
216
+ spec_ref: `@${specItem.slugs[0] || specItem._ulid}`,
217
+ derivation: 'auto',
218
+ priority: options.priority ?? normalizePriority(specItem.priority),
219
+ slugs: [slug],
220
+ tags: [...(specItem.tags || [])],
221
+ depends_on: options.dependsOn || [],
222
+ notes: initialNotes,
223
+ };
224
+ // Dry run - don't actually create
225
+ if (options.dryRun) {
226
+ const previewTask = createTask(taskInput);
227
+ return {
228
+ specItem,
229
+ action: 'would_create',
230
+ task: previewTask,
231
+ dependsOn: options.dependsOn,
232
+ };
233
+ }
234
+ // Create and save the task
235
+ const newTask = createTask(taskInput);
236
+ await saveTask(ctx, newTask);
237
+ const specSlug = specItem.slugs[0] || specItem._ulid.slice(0, 8);
238
+ await commitIfShadow(ctx.shadow, 'derive', specSlug);
239
+ // Add to existing tasks list for slug collision checks
240
+ existingTasks.push(newTask);
241
+ return {
242
+ specItem,
243
+ action: 'created',
244
+ task: newTask,
245
+ dependsOn: options.dependsOn,
246
+ };
247
+ }
248
+ /**
249
+ * Get a task reference string for use in depends_on.
250
+ * Prefers slug over ULID for readability.
251
+ */
252
+ function getTaskRef(task, index) {
253
+ return task.slugs[0] ? `@${task.slugs[0]}` : `@${index.shortUlid(task._ulid)}`;
254
+ }
255
+ /**
256
+ * Find or get the task for a parent spec item.
257
+ * Looks in:
258
+ * 1. Tasks created in this derive session (specToTaskMap)
259
+ * 2. Existing tasks linked to the parent spec (alignmentIndex)
260
+ */
261
+ function getParentTaskRef(parentSpec, specToTaskMap, alignmentIndex, index) {
262
+ // Check if we created a task for this parent in this session
263
+ const sessionTask = specToTaskMap.get(parentSpec._ulid);
264
+ if (sessionTask) {
265
+ return getTaskRef(sessionTask, index);
266
+ }
267
+ // Check if an existing task is linked to this parent spec
268
+ const linkedTasks = alignmentIndex.getTasksForSpec(parentSpec._ulid);
269
+ if (linkedTasks.length > 0) {
270
+ return getTaskRef(linkedTasks[0], index);
271
+ }
272
+ return undefined;
273
+ }
274
+ /**
275
+ * Register the 'derive' command
276
+ */
277
+ export function registerDeriveCommand(program) {
278
+ program
279
+ .command('derive [ref]')
280
+ .description('Create task(s) from spec item(s)')
281
+ .option('--all', 'Derive tasks for all spec items without linked tasks')
282
+ .option('--flat', 'Only derive for the specified item, not children (default: recursive)')
283
+ .option('--force', 'Create task even if one already exists for the spec')
284
+ .option('--dry-run', 'Show what would be created without making changes')
285
+ .option('--priority <n>', 'Set priority for created task(s) (1-5)', parseInt)
286
+ .action(async (ref, options) => {
287
+ try {
288
+ // Validate arguments
289
+ if (!ref && !options.all) {
290
+ error(errors.usage.deriveNoRef);
291
+ console.error('Usage:');
292
+ console.error(' kspec derive @spec-ref');
293
+ console.error(' kspec derive @spec-ref --flat');
294
+ console.error(' kspec derive --all');
295
+ process.exit(EXIT_CODES.USAGE_ERROR);
296
+ }
297
+ if (ref && options.all) {
298
+ error(errors.usage.deriveRefAndAll);
299
+ process.exit(EXIT_CODES.USAGE_ERROR);
300
+ }
301
+ // Validate priority if provided
302
+ if (options.priority !== undefined) {
303
+ if (isNaN(options.priority) || options.priority < 1 || options.priority > 5) {
304
+ error('Priority must be a number between 1 and 5');
305
+ process.exit(EXIT_CODES.USAGE_ERROR);
306
+ }
307
+ }
308
+ const ctx = await initContext();
309
+ const tasks = await loadAllTasks(ctx);
310
+ const items = await loadAllItems(ctx);
311
+ const index = new ReferenceIndex(tasks, items);
312
+ // Build alignment index
313
+ const alignmentIndex = new AlignmentIndex(tasks, items);
314
+ alignmentIndex.buildLinks(index);
315
+ // Collect spec items to process
316
+ let specsToDerive;
317
+ if (options.all) {
318
+ // Get all spec items without linked tasks
319
+ specsToDerive = items.filter(item => {
320
+ const linkedTasks = alignmentIndex.getTasksForSpec(item._ulid);
321
+ return linkedTasks.length === 0 || options.force;
322
+ });
323
+ if (specsToDerive.length === 0) {
324
+ if (isJsonMode()) {
325
+ console.log(JSON.stringify([]));
326
+ }
327
+ else {
328
+ info('Nothing to derive (all items have tasks)');
329
+ }
330
+ return;
331
+ }
332
+ }
333
+ else {
334
+ // Single spec item - recursive by default, flat if --flat
335
+ const specItem = resolveSpecRef(ref, items, tasks, index);
336
+ if (options.flat) {
337
+ specsToDerive = [specItem];
338
+ }
339
+ else {
340
+ // Recursive: collect item and all descendants
341
+ specsToDerive = collectItemsRecursively(specItem, items);
342
+ }
343
+ }
344
+ // Track spec ULID -> created task for dependency resolution
345
+ const specToTaskMap = new Map();
346
+ // Process each spec item in order (parents before children due to topological sort)
347
+ const results = [];
348
+ for (const specItem of specsToDerive) {
349
+ // Determine depends_on based on parent spec's task
350
+ let dependsOn;
351
+ if (!options.flat && !options.all) {
352
+ // Find the parent spec item
353
+ const parentSpec = findParentItem(specItem, items);
354
+ if (parentSpec) {
355
+ const parentTaskRef = getParentTaskRef(parentSpec, specToTaskMap, alignmentIndex, index);
356
+ if (parentTaskRef) {
357
+ dependsOn = [parentTaskRef];
358
+ }
359
+ }
360
+ }
361
+ const result = await deriveTaskFromSpec(ctx, specItem, tasks, items, index, alignmentIndex, {
362
+ force: options.force || false,
363
+ dryRun: options.dryRun || false,
364
+ dependsOn,
365
+ priority: options.priority,
366
+ });
367
+ // Track created/would_create tasks for dependency resolution
368
+ if (result.task && (result.action === 'created' || result.action === 'would_create')) {
369
+ specToTaskMap.set(specItem._ulid, result.task);
370
+ }
371
+ // Also track skipped tasks (existing) for dependency resolution
372
+ if (result.action === 'skipped' && result.task) {
373
+ specToTaskMap.set(specItem._ulid, result.task);
374
+ }
375
+ results.push(result);
376
+ }
377
+ // Output results
378
+ if (isJsonMode()) {
379
+ // JSON output format - simplified per AC
380
+ const jsonOutput = results.map(r => ({
381
+ ulid: r.task?._ulid || null,
382
+ slug: r.task?.slugs[0] || null,
383
+ spec_ref: `@${r.specItem.slugs[0] || r.specItem._ulid}`,
384
+ depends_on: r.task?.depends_on || [],
385
+ action: r.action,
386
+ }));
387
+ console.log(JSON.stringify(jsonOutput, null, 2));
388
+ return; // Don't call output() which would output full results in global JSON mode
389
+ }
390
+ else {
391
+ // Human-readable output
392
+ output(results, () => {
393
+ const created = results.filter(r => r.action === 'created');
394
+ const skipped = results.filter(r => r.action === 'skipped');
395
+ const wouldCreate = results.filter(r => r.action === 'would_create');
396
+ if (options.dryRun) {
397
+ console.log('Would create:');
398
+ for (const r of wouldCreate) {
399
+ const taskSlug = r.task?.slugs[0] || '';
400
+ const deps = r.dependsOn?.length ? ` (depends: ${r.dependsOn.join(', ')})` : '';
401
+ console.log(` + ${r.specItem.title}`);
402
+ console.log(` -> ${taskSlug}${deps}`);
403
+ }
404
+ if (skipped.length > 0) {
405
+ console.log('\nSkipped:');
406
+ for (const r of skipped) {
407
+ const specRef = r.specItem.slugs[0] ? `@${r.specItem.slugs[0]}` : `@${index.shortUlid(r.specItem._ulid)}`;
408
+ console.log(` - ${specRef} (${r.reason})`);
409
+ }
410
+ }
411
+ console.log(`\nWould create ${wouldCreate.length} task(s)`);
412
+ if (skipped.length > 0) {
413
+ console.log(`Skipped ${skipped.length} (already have tasks)`);
414
+ }
415
+ return;
416
+ }
417
+ if (created.length > 0) {
418
+ for (const r of created) {
419
+ const taskSlug = r.task?.slugs[0] || '';
420
+ const deps = r.dependsOn?.length ? ` (depends: ${r.dependsOn.join(', ')})` : '';
421
+ console.log(`OK Created task: ${taskSlug}${deps}`);
422
+ }
423
+ }
424
+ if (skipped.length > 0 && !options.all) {
425
+ // Show skipped for explicit derive (not --all)
426
+ for (const r of skipped) {
427
+ const specRef = r.specItem.slugs[0] ? `@${r.specItem.slugs[0]}` : `@${index.shortUlid(r.specItem._ulid)}`;
428
+ console.log(`Skipped ${specRef} (${r.reason})`);
429
+ }
430
+ }
431
+ // Summary
432
+ if (created.length > 0 || skipped.length > 0) {
433
+ console.log('');
434
+ if (created.length > 0) {
435
+ console.log(`Created ${created.length} task(s)`);
436
+ }
437
+ if (skipped.length > 0) {
438
+ console.log(`Skipped ${skipped.length} (already have tasks)`);
439
+ }
440
+ }
441
+ });
442
+ }
443
+ }
444
+ catch (err) {
445
+ error(errors.failures.deriveTasks, err);
446
+ process.exit(EXIT_CODES.ERROR);
447
+ }
448
+ });
449
+ }
450
+ //# sourceMappingURL=derive.js.map