@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,1378 @@
1
+ /**
2
+ * Meta CLI commands for interacting with meta-spec.
3
+ *
4
+ * AC-meta-manifest-1: kspec meta show outputs summary
5
+ * AC-meta-manifest-2: kspec validate includes meta line
6
+ * AC-meta-manifest-3: kspec validate shows meta errors with prefix
7
+ * AC-agent-1: kspec meta agents outputs table
8
+ * AC-agent-2: kspec meta agents --json outputs JSON
9
+ */
10
+ import chalk from 'chalk';
11
+ import Table from 'cli-table3';
12
+ import { ulid } from 'ulid';
13
+ import { initContext, loadMetaContext, getMetaStats, createObservation, saveObservation, saveMetaItem, deleteMetaItem, createTask, saveTask, loadAllTasks, loadAllItems, ReferenceIndex, loadSessionContext, saveSessionContext, loadInboxItems, findInboxItemByRef, deleteInboxItem, } from '../../parser/index.js';
14
+ import { output, error, success, isJsonMode } from '../output.js';
15
+ import { errors } from '../../strings/errors.js';
16
+ import { commitIfShadow } from '../../parser/shadow.js';
17
+ import { EXIT_CODES } from '../exit-codes.js';
18
+ /**
19
+ * Resolve a meta reference to its ULID
20
+ * Handles semantic IDs (agent.id, workflow.id, convention.domain) and ULID prefixes
21
+ */
22
+ function resolveMetaRefToUlid(ref, metaCtx) {
23
+ const normalizedRef = ref.startsWith('@') ? ref.substring(1) : ref;
24
+ // Check agents
25
+ const agent = (metaCtx.agents || []).find((a) => a.id === normalizedRef || a._ulid.startsWith(normalizedRef));
26
+ if (agent)
27
+ return { ulid: agent._ulid, type: 'agent' };
28
+ // Check workflows
29
+ const workflow = (metaCtx.workflows || []).find((w) => w.id === normalizedRef || w._ulid.startsWith(normalizedRef));
30
+ if (workflow)
31
+ return { ulid: workflow._ulid, type: 'workflow' };
32
+ // Check conventions
33
+ const convention = (metaCtx.conventions || []).find((c) => c.domain === normalizedRef || c._ulid.startsWith(normalizedRef));
34
+ if (convention)
35
+ return { ulid: convention._ulid, type: 'convention' };
36
+ // Check observations
37
+ const observation = (metaCtx.observations || []).find((o) => o._ulid.startsWith(normalizedRef));
38
+ if (observation)
39
+ return { ulid: observation._ulid, type: 'observation' };
40
+ return null;
41
+ }
42
+ /**
43
+ * Format meta show output
44
+ */
45
+ function formatMetaShow(meta) {
46
+ const stats = getMetaStats(meta);
47
+ if (!meta.manifest) {
48
+ console.log(chalk.yellow('No meta manifest found (kynetic.meta.yaml)'));
49
+ console.log(chalk.gray('Create one to define agents, workflows, conventions, and observations'));
50
+ return;
51
+ }
52
+ console.log(chalk.bold('Meta-Spec Summary'));
53
+ console.log(chalk.gray('─'.repeat(40)));
54
+ console.log(`Agents: ${stats.agents}`);
55
+ console.log(`Workflows: ${stats.workflows}`);
56
+ console.log(`Conventions: ${stats.conventions}`);
57
+ console.log(`Observations: ${stats.observations} (${stats.unresolvedObservations} unresolved)`);
58
+ }
59
+ /**
60
+ * Format agents table output
61
+ * AC-agent-1: outputs table with columns: ID, Name, Capabilities
62
+ */
63
+ function formatAgents(agents) {
64
+ if (agents.length === 0) {
65
+ console.log(chalk.yellow('No agents defined'));
66
+ return;
67
+ }
68
+ const table = new Table({
69
+ head: [chalk.bold('ID'), chalk.bold('Name'), chalk.bold('Capabilities')],
70
+ style: {
71
+ head: [],
72
+ border: [],
73
+ },
74
+ });
75
+ for (const agent of agents) {
76
+ table.push([
77
+ agent.id,
78
+ agent.name,
79
+ agent.capabilities.join(', '),
80
+ ]);
81
+ }
82
+ console.log(table.toString());
83
+ }
84
+ /**
85
+ * Format workflows table output
86
+ * AC-workflow-1: outputs table with columns: ID, Trigger, Steps (count)
87
+ */
88
+ function formatWorkflows(workflows) {
89
+ if (workflows.length === 0) {
90
+ console.log(chalk.yellow('No workflows defined'));
91
+ return;
92
+ }
93
+ const table = new Table({
94
+ head: [chalk.bold('ID'), chalk.bold('Trigger'), chalk.bold('Steps')],
95
+ style: {
96
+ head: [],
97
+ border: [],
98
+ },
99
+ });
100
+ for (const workflow of workflows) {
101
+ table.push([
102
+ workflow.id,
103
+ workflow.trigger,
104
+ workflow.steps.length.toString(),
105
+ ]);
106
+ }
107
+ console.log(table.toString());
108
+ }
109
+ /**
110
+ * Format workflows verbose output
111
+ * AC-workflow-2: outputs each workflow with full step list
112
+ */
113
+ function formatWorkflowsVerbose(workflows) {
114
+ if (workflows.length === 0) {
115
+ console.log(chalk.yellow('No workflows defined'));
116
+ return;
117
+ }
118
+ for (const workflow of workflows) {
119
+ console.log(chalk.bold(`${workflow.id} - ${workflow.trigger}`));
120
+ if (workflow.description) {
121
+ console.log(chalk.gray(workflow.description));
122
+ }
123
+ console.log(chalk.gray('─'.repeat(60)));
124
+ for (const step of workflow.steps) {
125
+ const prefix = {
126
+ check: chalk.yellow('[check]'),
127
+ action: chalk.blue('[action]'),
128
+ decision: chalk.magenta('[decision]'),
129
+ }[step.type];
130
+ console.log(`${prefix} ${step.content}`);
131
+ if (step.on_fail) {
132
+ console.log(chalk.gray(` → on fail: ${step.on_fail}`));
133
+ }
134
+ if (step.options && step.options.length > 0) {
135
+ for (const option of step.options) {
136
+ console.log(chalk.gray(` • ${option}`));
137
+ }
138
+ }
139
+ }
140
+ console.log('');
141
+ }
142
+ }
143
+ /**
144
+ * Format conventions table output
145
+ * AC-conv-1: outputs table with columns: Domain, Rules (count), Validation (yes/no)
146
+ */
147
+ function formatConventions(conventions) {
148
+ if (conventions.length === 0) {
149
+ console.log(chalk.yellow('No conventions defined'));
150
+ return;
151
+ }
152
+ const table = new Table({
153
+ head: [chalk.bold('Domain'), chalk.bold('Rules'), chalk.bold('Validation')],
154
+ style: {
155
+ head: [],
156
+ border: [],
157
+ },
158
+ });
159
+ for (const convention of conventions) {
160
+ table.push([
161
+ convention.domain,
162
+ convention.rules.length.toString(),
163
+ convention.validation ? 'yes' : 'no',
164
+ ]);
165
+ }
166
+ console.log(table.toString());
167
+ }
168
+ /**
169
+ * Format convention detail output
170
+ * AC-conv-2: outputs full rules list and examples
171
+ */
172
+ function formatConventionDetail(convention) {
173
+ console.log(chalk.bold(`${convention.domain} Convention`));
174
+ console.log(chalk.gray('─'.repeat(60)));
175
+ console.log(chalk.bold('\nRules:'));
176
+ for (const rule of convention.rules) {
177
+ console.log(` • ${rule}`);
178
+ }
179
+ if (convention.examples && convention.examples.length > 0) {
180
+ console.log(chalk.bold('\nExamples:'));
181
+ for (const example of convention.examples) {
182
+ console.log(chalk.green(` ✓ ${example.good}`));
183
+ console.log(chalk.red(` ✗ ${example.bad}`));
184
+ }
185
+ }
186
+ if (convention.validation) {
187
+ console.log(chalk.bold('\nValidation:'));
188
+ console.log(` Type: ${convention.validation.type}`);
189
+ if (convention.validation.pattern) {
190
+ console.log(` Pattern: ${convention.validation.pattern}`);
191
+ }
192
+ if (convention.validation.message) {
193
+ console.log(` Message: ${convention.validation.message}`);
194
+ }
195
+ }
196
+ console.log('');
197
+ }
198
+ /**
199
+ * Format observations table output
200
+ * AC-obs-2: outputs table with columns: ID, Type, Workflow, Created, Content (truncated)
201
+ */
202
+ function formatObservations(observations, showResolved) {
203
+ const filtered = showResolved ? observations : observations.filter(o => !o.resolved);
204
+ if (filtered.length === 0) {
205
+ console.log(chalk.yellow(showResolved ? 'No observations found' : 'No unresolved observations'));
206
+ return;
207
+ }
208
+ const table = new Table({
209
+ head: [
210
+ chalk.bold('ID'),
211
+ chalk.bold('Type'),
212
+ chalk.bold('Workflow'),
213
+ chalk.bold('Created'),
214
+ chalk.bold('Content'),
215
+ ],
216
+ style: {
217
+ head: [],
218
+ border: [],
219
+ },
220
+ colWidths: [10, 10, 20, 12, 50],
221
+ wordWrap: true,
222
+ });
223
+ for (const obs of filtered) {
224
+ const id = obs._ulid.substring(0, 8);
225
+ const workflow = obs.workflow_ref || '-';
226
+ const created = new Date(obs.created_at).toISOString().split('T')[0];
227
+ const content = obs.content.length > 47 ? obs.content.substring(0, 47) + '...' : obs.content;
228
+ table.push([id, obs.type, workflow, created, content]);
229
+ }
230
+ console.log(table.toString());
231
+ }
232
+ /**
233
+ * Register meta commands
234
+ */
235
+ export function registerMetaCommands(program) {
236
+ const meta = program
237
+ .command('meta')
238
+ .description('Meta-spec commands (agents, workflows, conventions, observations)');
239
+ // AC-meta-manifest-1: kspec meta show outputs summary with counts
240
+ meta
241
+ .command('show')
242
+ .description('Display meta-spec summary')
243
+ .action(async () => {
244
+ try {
245
+ const ctx = await initContext();
246
+ if (!ctx.manifestPath) {
247
+ error(errors.project.noKspecProject);
248
+ process.exit(EXIT_CODES.ERROR);
249
+ }
250
+ const metaCtx = await loadMetaContext(ctx);
251
+ const stats = getMetaStats(metaCtx);
252
+ output({
253
+ manifest: metaCtx.manifestPath,
254
+ stats,
255
+ }, () => formatMetaShow(metaCtx));
256
+ }
257
+ catch (err) {
258
+ error(errors.failures.showMeta, err);
259
+ process.exit(EXIT_CODES.ERROR);
260
+ }
261
+ });
262
+ // AC-agent-1, AC-agent-2: kspec meta agents
263
+ meta
264
+ .command('agents')
265
+ .description('List agents defined in meta-spec')
266
+ .action(async () => {
267
+ try {
268
+ const ctx = await initContext();
269
+ if (!ctx.manifestPath) {
270
+ error(errors.project.noKspecProject);
271
+ process.exit(EXIT_CODES.ERROR);
272
+ }
273
+ const metaCtx = await loadMetaContext(ctx);
274
+ const agents = metaCtx.agents || [];
275
+ // AC-agent-2: JSON output includes full agent details
276
+ output(agents.map((agent) => ({
277
+ id: agent.id,
278
+ name: agent.name,
279
+ description: agent.description,
280
+ capabilities: agent.capabilities,
281
+ tools: agent.tools,
282
+ session_protocol: agent.session_protocol,
283
+ conventions: agent.conventions,
284
+ })),
285
+ // AC-agent-1: Table output with ID, Name, Capabilities
286
+ () => formatAgents(agents));
287
+ }
288
+ catch (err) {
289
+ error(errors.failures.listAgents, err);
290
+ process.exit(EXIT_CODES.ERROR);
291
+ }
292
+ });
293
+ // AC-workflow-1, AC-workflow-2, AC-workflow-4: kspec meta workflows
294
+ meta
295
+ .command('workflows')
296
+ .description('List workflows defined in meta-spec')
297
+ .option('--verbose', 'Show full workflow details with all steps')
298
+ .action(async (options) => {
299
+ try {
300
+ const ctx = await initContext();
301
+ if (!ctx.manifestPath) {
302
+ error(errors.project.noKspecProject);
303
+ process.exit(EXIT_CODES.ERROR);
304
+ }
305
+ const metaCtx = await loadMetaContext(ctx);
306
+ const workflows = metaCtx.workflows || [];
307
+ // AC-workflow-4: JSON output includes full workflow details
308
+ output(workflows.map((workflow) => ({
309
+ id: workflow.id,
310
+ trigger: workflow.trigger,
311
+ description: workflow.description,
312
+ steps: workflow.steps,
313
+ })),
314
+ // AC-workflow-1 (table) or AC-workflow-2 (verbose)
315
+ () => {
316
+ if (options.verbose) {
317
+ formatWorkflowsVerbose(workflows);
318
+ }
319
+ else {
320
+ formatWorkflows(workflows);
321
+ }
322
+ });
323
+ }
324
+ catch (err) {
325
+ error(errors.failures.listWorkflows, err);
326
+ process.exit(EXIT_CODES.ERROR);
327
+ }
328
+ });
329
+ // AC-conv-1, AC-conv-2, AC-conv-5: kspec meta conventions
330
+ meta
331
+ .command('conventions')
332
+ .description('List conventions defined in meta-spec')
333
+ .option('--domain <domain>', 'Filter by specific domain')
334
+ .action(async (options) => {
335
+ try {
336
+ const ctx = await initContext();
337
+ if (!ctx.manifestPath) {
338
+ error(errors.project.noKspecProject);
339
+ process.exit(EXIT_CODES.ERROR);
340
+ }
341
+ const metaCtx = await loadMetaContext(ctx);
342
+ const conventions = metaCtx.conventions || [];
343
+ // AC-conv-2: Filter by domain if specified
344
+ const filtered = options.domain
345
+ ? conventions.filter((c) => c.domain === options.domain)
346
+ : conventions;
347
+ // AC-conv-5: JSON output includes full convention details
348
+ output(filtered.map((convention) => ({
349
+ domain: convention.domain,
350
+ rules: convention.rules,
351
+ examples: convention.examples,
352
+ validation: convention.validation,
353
+ })),
354
+ // AC-conv-1 (table) or AC-conv-2 (detail for single domain)
355
+ () => {
356
+ if (options.domain && filtered.length === 1) {
357
+ formatConventionDetail(filtered[0]);
358
+ }
359
+ else {
360
+ formatConventions(filtered);
361
+ }
362
+ });
363
+ }
364
+ catch (err) {
365
+ error(errors.failures.listConventions, err);
366
+ process.exit(EXIT_CODES.ERROR);
367
+ }
368
+ });
369
+ // meta-get-cmd: kspec meta get <ref>
370
+ meta
371
+ .command('get <ref>')
372
+ .description('Get a meta item by reference (agent, workflow, convention, or observation)')
373
+ .action(async (ref) => {
374
+ try {
375
+ const ctx = await initContext();
376
+ if (!ctx.manifestPath) {
377
+ error(errors.project.noKspecProject);
378
+ process.exit(EXIT_CODES.ERROR);
379
+ }
380
+ const metaCtx = await loadMetaContext(ctx);
381
+ // Normalize reference
382
+ const normalizedRef = ref.startsWith('@') ? ref.substring(1) : ref;
383
+ // Search in all meta item types
384
+ const agents = metaCtx.agents || [];
385
+ const workflows = metaCtx.workflows || [];
386
+ const conventions = metaCtx.conventions || [];
387
+ const observations = metaCtx.observations || [];
388
+ // Try to find by ID or ULID prefix
389
+ let found = null;
390
+ let itemType = '';
391
+ // Check agents (by id or ULID)
392
+ const agent = agents.find((a) => a.id === normalizedRef || a._ulid.startsWith(normalizedRef));
393
+ if (agent) {
394
+ found = agent;
395
+ itemType = 'agent';
396
+ }
397
+ // Check workflows (by id or ULID)
398
+ if (!found) {
399
+ const workflow = workflows.find((w) => w.id === normalizedRef || w._ulid.startsWith(normalizedRef));
400
+ if (workflow) {
401
+ found = workflow;
402
+ itemType = 'workflow';
403
+ }
404
+ }
405
+ // Check conventions (by domain or ULID)
406
+ if (!found) {
407
+ const convention = conventions.find((c) => c.domain === normalizedRef || c._ulid.startsWith(normalizedRef));
408
+ if (convention) {
409
+ found = convention;
410
+ itemType = 'convention';
411
+ }
412
+ }
413
+ // Check observations (by ULID)
414
+ if (!found) {
415
+ const observation = observations.find((o) => o._ulid.startsWith(normalizedRef));
416
+ if (observation) {
417
+ found = observation;
418
+ itemType = 'observation';
419
+ }
420
+ }
421
+ if (!found) {
422
+ error(errors.reference.metaNotFound(ref));
423
+ process.exit(EXIT_CODES.ERROR);
424
+ }
425
+ // Output the item
426
+ output(found, () => {
427
+ console.log(chalk.bold(`${itemType.charAt(0).toUpperCase() + itemType.slice(1)}: ${ref}`));
428
+ console.log(chalk.gray('─'.repeat(60)));
429
+ console.log(JSON.stringify(found, null, 2));
430
+ });
431
+ }
432
+ catch (err) {
433
+ error(errors.failures.getMetaItem, err);
434
+ process.exit(EXIT_CODES.ERROR);
435
+ }
436
+ });
437
+ // meta-list-cmd: kspec meta list
438
+ meta
439
+ .command('list')
440
+ .description('List all meta items')
441
+ .option('--type <type>', 'Filter by type (agent, workflow, convention, observation)')
442
+ .action(async (options) => {
443
+ try {
444
+ const ctx = await initContext();
445
+ if (!ctx.manifestPath) {
446
+ error(errors.project.noKspecProject);
447
+ process.exit(EXIT_CODES.ERROR);
448
+ }
449
+ const metaCtx = await loadMetaContext(ctx);
450
+ const items = [];
451
+ // Add agents
452
+ if (!options.type || options.type === 'agent') {
453
+ for (const agent of metaCtx.agents || []) {
454
+ items.push({
455
+ id: agent.id,
456
+ type: 'agent',
457
+ context: agent.name,
458
+ ulid: agent._ulid,
459
+ });
460
+ }
461
+ }
462
+ // Add workflows
463
+ if (!options.type || options.type === 'workflow') {
464
+ for (const workflow of metaCtx.workflows || []) {
465
+ items.push({
466
+ id: workflow.id,
467
+ type: 'workflow',
468
+ context: workflow.trigger,
469
+ ulid: workflow._ulid,
470
+ });
471
+ }
472
+ }
473
+ // Add conventions
474
+ if (!options.type || options.type === 'convention') {
475
+ for (const convention of metaCtx.conventions || []) {
476
+ items.push({
477
+ id: convention.domain,
478
+ type: 'convention',
479
+ context: `${convention.rules.length} rules`,
480
+ ulid: convention._ulid,
481
+ });
482
+ }
483
+ }
484
+ // Add observations
485
+ if (!options.type || options.type === 'observation') {
486
+ for (const observation of metaCtx.observations || []) {
487
+ const ulidPrefix = observation._ulid.substring(0, 8);
488
+ items.push({
489
+ id: ulidPrefix,
490
+ type: 'observation',
491
+ context: `${observation.type} ${observation.resolved ? '(resolved)' : ''}`,
492
+ ulid: observation._ulid,
493
+ });
494
+ }
495
+ }
496
+ // Output
497
+ output(items, () => {
498
+ if (items.length === 0) {
499
+ console.log(chalk.yellow('No meta items found'));
500
+ return;
501
+ }
502
+ const table = new Table({
503
+ head: [chalk.bold('ID'), chalk.bold('Type'), chalk.bold('Context')],
504
+ style: {
505
+ head: [],
506
+ border: [],
507
+ },
508
+ });
509
+ for (const item of items) {
510
+ table.push([item.id, item.type, item.context]);
511
+ }
512
+ console.log(table.toString());
513
+ });
514
+ }
515
+ catch (err) {
516
+ error(errors.failures.listMetaItems, err);
517
+ process.exit(EXIT_CODES.ERROR);
518
+ }
519
+ });
520
+ // AC-obs-1: kspec meta observe <type> <content>
521
+ // AC: @meta-observe-cmd from-inbox-conversion
522
+ meta
523
+ .command('observe [type] [content]')
524
+ .description('Create an observation (friction, success, question, idea)')
525
+ .option('--workflow <ref>', 'Reference to workflow this observation relates to')
526
+ .option('--author <author>', 'Author of the observation')
527
+ .option('--from-inbox <ref>', 'Convert inbox item to observation')
528
+ .option('--type <type>', 'Override type when using --from-inbox (defaults to idea)')
529
+ .action(async (type, content, options) => {
530
+ try {
531
+ const ctx = await initContext();
532
+ if (!ctx.manifestPath) {
533
+ error(errors.project.noKspecProject);
534
+ process.exit(EXIT_CODES.ERROR);
535
+ }
536
+ // AC: @meta-observe-cmd from-inbox-conversion
537
+ // Handle --from-inbox flag
538
+ if (options.fromInbox) {
539
+ // Load inbox items
540
+ const inboxItems = await loadInboxItems(ctx);
541
+ const item = findInboxItemByRef(inboxItems, options.fromInbox);
542
+ if (!item) {
543
+ error(errors.reference.inboxNotFound(options.fromInbox));
544
+ process.exit(EXIT_CODES.NOT_FOUND);
545
+ }
546
+ // Use inbox item content
547
+ const observationContent = item.text;
548
+ // Type defaults to 'idea' but can be overridden with --type flag
549
+ const observationType = (options.type || 'idea');
550
+ // Validate observation type
551
+ const validTypes = ['friction', 'success', 'question', 'idea'];
552
+ if (!validTypes.includes(observationType)) {
553
+ error(errors.validation.invalidObservationType(observationType));
554
+ console.log(`Valid types: ${validTypes.join(', ')}`);
555
+ process.exit(EXIT_CODES.ERROR);
556
+ }
557
+ // Create observation
558
+ const observation = createObservation(observationType, observationContent, {
559
+ workflow_ref: options.workflow,
560
+ author: options.author,
561
+ });
562
+ // Save observation
563
+ await saveObservation(ctx, observation);
564
+ // Delete inbox item
565
+ const deleted = await deleteInboxItem(ctx, item._ulid);
566
+ if (!deleted) {
567
+ error('Failed to delete inbox item after creating observation');
568
+ process.exit(EXIT_CODES.ERROR);
569
+ }
570
+ await commitIfShadow(ctx.shadow, 'meta-observe-from-inbox', observation._ulid.substring(0, 8), `Convert inbox item to ${observationType} observation`);
571
+ // Return observation ref
572
+ output(observation, () => success(`Created observation: ${observation._ulid.substring(0, 8)}`));
573
+ return;
574
+ }
575
+ // Standard observe flow (without --from-inbox)
576
+ if (!type || !content) {
577
+ error('Type and content are required when not using --from-inbox');
578
+ process.exit(EXIT_CODES.ERROR);
579
+ }
580
+ // Validate observation type
581
+ const validTypes = ['friction', 'success', 'question', 'idea'];
582
+ if (!validTypes.includes(type)) {
583
+ error(errors.validation.invalidObservationType(type));
584
+ console.log(`Valid types: ${validTypes.join(', ')}`);
585
+ process.exit(EXIT_CODES.ERROR);
586
+ }
587
+ // Create observation
588
+ const observation = createObservation(type, content, {
589
+ workflow_ref: options.workflow,
590
+ author: options.author,
591
+ });
592
+ // Save to manifest
593
+ await saveObservation(ctx, observation);
594
+ // AC-obs-1: outputs "OK Created observation: <ULID-prefix>"
595
+ // In JSON mode, return the created observation object
596
+ output(observation, () => success(`Created observation: ${observation._ulid.substring(0, 8)}`));
597
+ }
598
+ catch (err) {
599
+ error(errors.failures.createObservation, err);
600
+ process.exit(EXIT_CODES.ERROR);
601
+ }
602
+ });
603
+ // AC-obs-2, AC-obs-5: kspec meta observations
604
+ meta
605
+ .command('observations')
606
+ .description('List observations (shows unresolved by default)')
607
+ .option('--type <type>', 'Filter by observation type (friction/success/question/idea)')
608
+ .option('--workflow <ref>', 'Filter by workflow reference')
609
+ .option('--all', 'Include resolved observations')
610
+ .option('--promoted', 'Show only observations promoted to tasks')
611
+ .option('--pending-resolution', 'Show observations with completed tasks awaiting resolution')
612
+ .action(async (options) => {
613
+ try {
614
+ const ctx = await initContext();
615
+ if (!ctx.manifestPath) {
616
+ error(errors.project.noKspecProject);
617
+ process.exit(EXIT_CODES.ERROR);
618
+ }
619
+ const metaCtx = await loadMetaContext(ctx);
620
+ let observations = metaCtx.observations || [];
621
+ // Apply filters
622
+ if (options.type) {
623
+ observations = observations.filter((obs) => obs.type === options.type);
624
+ }
625
+ if (options.workflow) {
626
+ observations = observations.filter((obs) => obs.workflow_ref === options.workflow);
627
+ }
628
+ if (options.promoted) {
629
+ observations = observations.filter((obs) => obs.promoted_to !== undefined);
630
+ }
631
+ if (options.pendingResolution) {
632
+ // Load tasks to check if promoted tasks are completed
633
+ const tasks = await loadAllTasks(ctx);
634
+ const items = await loadAllItems(ctx);
635
+ const index = new ReferenceIndex(tasks, items);
636
+ observations = observations.filter((obs) => {
637
+ if (!obs.promoted_to || obs.resolved)
638
+ return false;
639
+ const taskResult = index.resolve(obs.promoted_to);
640
+ if (!taskResult.ok)
641
+ return false;
642
+ const item = taskResult.item;
643
+ // Type guard: check if item is a task (has status and depends_on properties)
644
+ return 'status' in item && 'depends_on' in item && item.status === 'completed';
645
+ });
646
+ }
647
+ // AC-obs-5: JSON output includes full observation objects
648
+ output(observations.map((obs) => ({
649
+ _ulid: obs._ulid,
650
+ type: obs.type,
651
+ content: obs.content,
652
+ workflow_ref: obs.workflow_ref ?? null,
653
+ created_at: obs.created_at,
654
+ author: obs.author ?? null,
655
+ resolved: obs.resolved,
656
+ resolution: obs.resolution ?? null,
657
+ resolved_at: obs.resolved_at ?? null,
658
+ resolved_by: obs.resolved_by ?? null,
659
+ promoted_to: obs.promoted_to ?? null,
660
+ })),
661
+ // AC-obs-2: Table output with ID, Type, Workflow, Created, Content
662
+ () => formatObservations(observations, options.all));
663
+ }
664
+ catch (err) {
665
+ error(errors.failures.listObservations, err);
666
+ process.exit(EXIT_CODES.ERROR);
667
+ }
668
+ });
669
+ // AC-obs-3, AC-obs-6, AC-obs-8: kspec meta promote
670
+ meta
671
+ .command('promote <ref>')
672
+ .description('Promote observation to a task')
673
+ .requiredOption('--title <title>', 'Task title')
674
+ .option('--priority <priority>', 'Task priority (1-5)', '2')
675
+ .option('--force', 'Force promotion even if observation is resolved')
676
+ .action(async (ref, options) => {
677
+ try {
678
+ const ctx = await initContext();
679
+ if (!ctx.manifestPath) {
680
+ error(errors.project.noKspecProject);
681
+ process.exit(EXIT_CODES.ERROR);
682
+ }
683
+ const metaCtx = await loadMetaContext(ctx);
684
+ const observations = metaCtx.observations || [];
685
+ // Find observation
686
+ const normalizedRef = ref.startsWith('@') ? ref.substring(1) : ref;
687
+ const observation = observations.find((o) => o._ulid.startsWith(normalizedRef));
688
+ if (!observation) {
689
+ error(errors.reference.observationNotFound(ref));
690
+ process.exit(EXIT_CODES.ERROR);
691
+ }
692
+ // AC-obs-6: Check if already promoted
693
+ if (observation.promoted_to) {
694
+ error(errors.conflict.observationAlreadyPromoted(observation.promoted_to));
695
+ process.exit(EXIT_CODES.CONFLICT);
696
+ }
697
+ // AC-obs-8: Check if resolved
698
+ if (observation.resolved && !options.force) {
699
+ error(errors.operation.cannotPromoteResolved);
700
+ process.exit(EXIT_CODES.ERROR);
701
+ }
702
+ // AC-obs-3: Create task with title, description from observation, meta_ref, and origin
703
+ const task = createTask({
704
+ title: options.title,
705
+ description: observation.content,
706
+ priority: Number.parseInt(options.priority, 10),
707
+ meta_ref: observation.workflow_ref,
708
+ origin: 'observation_promotion',
709
+ });
710
+ // Save task
711
+ await saveTask(ctx, task);
712
+ await commitIfShadow(ctx.shadow, 'task-add', task.slugs[0] || task._ulid.slice(0, 8), task.title);
713
+ const taskRef = `@${task._ulid.substring(0, 8)}`;
714
+ // Update observation with promoted_to field
715
+ observation.promoted_to = taskRef;
716
+ await saveObservation(ctx, observation);
717
+ // AC-obs-3: outputs "OK Created task: <ULID-prefix>"
718
+ // In JSON mode, return the created task object
719
+ output(task, () => success(`Created task: ${taskRef.substring(0, 9)}`));
720
+ }
721
+ catch (err) {
722
+ error(errors.failures.promoteObservation, err);
723
+ process.exit(EXIT_CODES.ERROR);
724
+ }
725
+ });
726
+ // AC-obs-4, AC-obs-7, AC-obs-9: kspec meta resolve
727
+ meta
728
+ .command('resolve <ref> [resolution]')
729
+ .description('Resolve an observation')
730
+ .action(async (ref, resolution) => {
731
+ try {
732
+ const ctx = await initContext();
733
+ if (!ctx.manifestPath) {
734
+ error(errors.project.noKspecProject);
735
+ process.exit(EXIT_CODES.ERROR);
736
+ }
737
+ const metaCtx = await loadMetaContext(ctx);
738
+ const observations = metaCtx.observations || [];
739
+ // Find observation
740
+ const normalizedRef = ref.startsWith('@') ? ref.substring(1) : ref;
741
+ const observation = observations.find((o) => o._ulid.startsWith(normalizedRef));
742
+ if (!observation) {
743
+ error(errors.reference.observationNotFound(ref));
744
+ process.exit(EXIT_CODES.ERROR);
745
+ }
746
+ // AC-obs-7: Check if already resolved
747
+ if (observation.resolved) {
748
+ const resolvedDate = new Date(observation.resolved_at).toISOString().split('T')[0];
749
+ const resolutionText = observation.resolution || '';
750
+ const truncated = resolutionText.length > 50
751
+ ? resolutionText.substring(0, 50) + '...'
752
+ : resolutionText;
753
+ error(errors.conflict.observationAlreadyResolved(resolvedDate, truncated));
754
+ process.exit(EXIT_CODES.CONFLICT);
755
+ }
756
+ // AC-obs-9: Auto-populate resolution from task completion if promoted
757
+ let finalResolution = resolution;
758
+ if (!finalResolution && observation.promoted_to) {
759
+ // Fetch task to get completion reason
760
+ const tasks = await loadAllTasks(ctx);
761
+ const items = await loadAllItems(ctx);
762
+ const index = new ReferenceIndex(tasks, items);
763
+ const taskResult = index.resolve(observation.promoted_to);
764
+ if (taskResult.ok) {
765
+ const item = taskResult.item;
766
+ // Type guard: ensure this is a task (has status and depends_on properties)
767
+ if ('status' in item && 'depends_on' in item) {
768
+ const task = item;
769
+ if (task.status === 'completed' && task.closed_reason) {
770
+ finalResolution = `Resolved via task ${observation.promoted_to}: ${task.closed_reason}`;
771
+ }
772
+ else if (task.status === 'completed') {
773
+ finalResolution = `Resolved via task ${observation.promoted_to}`;
774
+ }
775
+ else {
776
+ error(`Task ${observation.promoted_to} is not completed yet`);
777
+ process.exit(EXIT_CODES.ERROR);
778
+ }
779
+ }
780
+ else {
781
+ error(`Reference ${observation.promoted_to} is not a task`);
782
+ process.exit(EXIT_CODES.ERROR);
783
+ }
784
+ }
785
+ else {
786
+ error(`Task ${observation.promoted_to} not found`);
787
+ process.exit(EXIT_CODES.ERROR);
788
+ }
789
+ }
790
+ if (!finalResolution) {
791
+ error(errors.validation.resolutionRequired);
792
+ process.exit(EXIT_CODES.ERROR);
793
+ }
794
+ // AC-obs-4: Update observation
795
+ observation.resolved = true;
796
+ observation.resolution = finalResolution;
797
+ observation.resolved_at = new Date().toISOString();
798
+ observation.resolved_by = observation.author; // Use same author
799
+ await saveObservation(ctx, observation);
800
+ // AC-obs-4: outputs "OK Resolved: <ULID-prefix>"
801
+ success(`Resolved: ${observation._ulid.substring(0, 8)}`);
802
+ }
803
+ catch (err) {
804
+ error(errors.failures.resolveObservation, err);
805
+ process.exit(EXIT_CODES.ERROR);
806
+ }
807
+ });
808
+ // Meta add command - create new meta items
809
+ meta
810
+ .command('add <type>')
811
+ .description('Create a new meta item (agent, workflow, or convention)')
812
+ .option('--id <id>', 'Semantic ID (required for agents and workflows)')
813
+ .option('--domain <domain>', 'Domain (required for conventions)')
814
+ .option('--name <name>', 'Name (for agents)')
815
+ .option('--trigger <trigger>', 'Trigger (for workflows)')
816
+ .option('--description <desc>', 'Description')
817
+ .option('--capability <cap...>', 'Capabilities (for agents)')
818
+ .option('--tool <tool...>', 'Tools (for agents)')
819
+ .option('--convention <conv...>', 'Convention references (for agents)')
820
+ .option('--rule <rule...>', 'Rules (for conventions)')
821
+ .action(async (type, options) => {
822
+ try {
823
+ const ctx = await initContext();
824
+ // Validate type
825
+ const validTypes = ['agent', 'workflow', 'convention'];
826
+ if (!validTypes.includes(type)) {
827
+ error(errors.validation.invalidType(type, validTypes));
828
+ process.exit(EXIT_CODES.ERROR);
829
+ }
830
+ // Generate ULID
831
+ const itemUlid = ulid();
832
+ // Create the item based on type
833
+ let item;
834
+ if (type === 'agent') {
835
+ // Validate required fields
836
+ if (!options.id) {
837
+ error(errors.validation.agentRequiresId);
838
+ process.exit(EXIT_CODES.ERROR);
839
+ }
840
+ if (!options.name) {
841
+ error(errors.validation.agentRequiresName);
842
+ process.exit(EXIT_CODES.ERROR);
843
+ }
844
+ item = {
845
+ _ulid: itemUlid,
846
+ id: options.id,
847
+ name: options.name,
848
+ description: options.description || '',
849
+ capabilities: options.capability || [],
850
+ tools: options.tool || [],
851
+ conventions: options.convention || [],
852
+ };
853
+ }
854
+ else if (type === 'workflow') {
855
+ // Validate required fields
856
+ if (!options.id) {
857
+ error(errors.validation.workflowRequiresId);
858
+ process.exit(EXIT_CODES.ERROR);
859
+ }
860
+ if (!options.trigger) {
861
+ error(errors.validation.workflowRequiresTrigger);
862
+ process.exit(EXIT_CODES.ERROR);
863
+ }
864
+ item = {
865
+ _ulid: itemUlid,
866
+ id: options.id,
867
+ trigger: options.trigger,
868
+ description: options.description || '',
869
+ steps: [],
870
+ };
871
+ }
872
+ else {
873
+ // convention
874
+ if (!options.domain) {
875
+ error(errors.validation.conventionRequiresDomain);
876
+ process.exit(EXIT_CODES.ERROR);
877
+ }
878
+ item = {
879
+ _ulid: itemUlid,
880
+ domain: options.domain,
881
+ rules: options.rule || [],
882
+ examples: [],
883
+ };
884
+ }
885
+ // Save the item
886
+ await saveMetaItem(ctx, item, type);
887
+ if (isJsonMode()) {
888
+ // In JSON mode, output the item data directly
889
+ console.log(JSON.stringify(item, null, 2));
890
+ }
891
+ else {
892
+ const idOrDomain = 'id' in item ? item.id : 'domain' in item ? item.domain : itemUlid;
893
+ success(`Created ${type}: ${idOrDomain} (@${itemUlid.substring(0, 8)})`);
894
+ }
895
+ }
896
+ catch (err) {
897
+ error(errors.failures.createMeta(type), err);
898
+ process.exit(EXIT_CODES.ERROR);
899
+ }
900
+ });
901
+ // Meta set command - update existing meta items
902
+ meta
903
+ .command('set <ref>')
904
+ .description('Update an existing meta item')
905
+ .option('--name <name>', 'Update name (for agents)')
906
+ .option('--description <desc>', 'Update description')
907
+ .option('--trigger <trigger>', 'Update trigger (for workflows)')
908
+ .option('--add-capability <cap>', 'Add capability (for agents)')
909
+ .option('--add-tool <tool>', 'Add tool (for agents)')
910
+ .option('--add-convention <conv>', 'Add convention reference (for agents)')
911
+ .option('--add-rule <rule>', 'Add rule (for conventions)')
912
+ .action(async (ref, options) => {
913
+ try {
914
+ const ctx = await initContext();
915
+ const metaCtx = await loadMetaContext(ctx);
916
+ // Find the item using unified lookup
917
+ const normalizedRef = ref.startsWith('@') ? ref.substring(1) : ref;
918
+ let found = null;
919
+ let itemType = null;
920
+ // Search in agents
921
+ const agents = metaCtx.manifest?.agents || [];
922
+ const agent = agents.find((a) => a.id === normalizedRef || a._ulid.startsWith(normalizedRef));
923
+ if (agent) {
924
+ found = agent;
925
+ itemType = 'agent';
926
+ }
927
+ // Search in workflows
928
+ if (!found) {
929
+ const workflows = metaCtx.manifest?.workflows || [];
930
+ const workflow = workflows.find((w) => w.id === normalizedRef || w._ulid.startsWith(normalizedRef));
931
+ if (workflow) {
932
+ found = workflow;
933
+ itemType = 'workflow';
934
+ }
935
+ }
936
+ // Search in conventions
937
+ if (!found) {
938
+ const conventions = metaCtx.manifest?.conventions || [];
939
+ const convention = conventions.find((c) => c.domain === normalizedRef || c._ulid.startsWith(normalizedRef));
940
+ if (convention) {
941
+ found = convention;
942
+ itemType = 'convention';
943
+ }
944
+ }
945
+ if (!found || !itemType) {
946
+ error(errors.reference.metaNotFound(ref));
947
+ process.exit(EXIT_CODES.ERROR);
948
+ }
949
+ // Update fields based on type
950
+ if (itemType === 'agent') {
951
+ const item = found;
952
+ if (options.name)
953
+ item.name = options.name;
954
+ if (options.description !== undefined)
955
+ item.description = options.description;
956
+ if (options.addCapability) {
957
+ if (!item.capabilities.includes(options.addCapability)) {
958
+ item.capabilities.push(options.addCapability);
959
+ }
960
+ }
961
+ if (options.addTool) {
962
+ if (!item.tools.includes(options.addTool)) {
963
+ item.tools.push(options.addTool);
964
+ }
965
+ }
966
+ if (options.addConvention) {
967
+ if (!item.conventions.includes(options.addConvention)) {
968
+ item.conventions.push(options.addConvention);
969
+ }
970
+ }
971
+ }
972
+ else if (itemType === 'workflow') {
973
+ const item = found;
974
+ if (options.trigger)
975
+ item.trigger = options.trigger;
976
+ if (options.description !== undefined)
977
+ item.description = options.description;
978
+ }
979
+ else {
980
+ const item = found;
981
+ // Convention doesn't have a description field
982
+ if (options.addRule) {
983
+ if (!item.rules.includes(options.addRule)) {
984
+ item.rules.push(options.addRule);
985
+ }
986
+ }
987
+ }
988
+ // Save the updated item
989
+ await saveMetaItem(ctx, found, itemType);
990
+ if (isJsonMode()) {
991
+ // In JSON mode, output the item data directly
992
+ console.log(JSON.stringify(found, null, 2));
993
+ }
994
+ else {
995
+ const idOrDomain = itemType === 'agent'
996
+ ? found.id
997
+ : itemType === 'workflow'
998
+ ? found.id
999
+ : found.domain;
1000
+ success(`Updated ${itemType}: ${idOrDomain}`);
1001
+ }
1002
+ }
1003
+ catch (err) {
1004
+ error(errors.failures.updateMetaItem, err);
1005
+ process.exit(EXIT_CODES.ERROR);
1006
+ }
1007
+ });
1008
+ // Meta delete command - delete meta items
1009
+ meta
1010
+ .command('delete <ref>')
1011
+ .description('Delete a meta item')
1012
+ .option('--confirm', 'Skip confirmation prompt')
1013
+ .action(async (ref, options) => {
1014
+ try {
1015
+ const ctx = await initContext();
1016
+ const metaCtx = await loadMetaContext(ctx);
1017
+ // Find the item to determine type
1018
+ const normalizedRef = ref.startsWith('@') ? ref.substring(1) : ref;
1019
+ let itemType = null;
1020
+ let itemUlid = null;
1021
+ let itemLabel = null;
1022
+ // Search in agents
1023
+ const agents = metaCtx.manifest?.agents || [];
1024
+ const agent = agents.find((a) => a.id === normalizedRef || a._ulid.startsWith(normalizedRef));
1025
+ if (agent) {
1026
+ itemType = 'agent';
1027
+ itemUlid = agent._ulid;
1028
+ itemLabel = `agent ${agent.id}`;
1029
+ }
1030
+ // Search in workflows
1031
+ if (!itemType) {
1032
+ const workflows = metaCtx.manifest?.workflows || [];
1033
+ const workflow = workflows.find((w) => w.id === normalizedRef || w._ulid.startsWith(normalizedRef));
1034
+ if (workflow) {
1035
+ itemType = 'workflow';
1036
+ itemUlid = workflow._ulid;
1037
+ itemLabel = `workflow ${workflow.id}`;
1038
+ }
1039
+ }
1040
+ // Search in conventions
1041
+ if (!itemType) {
1042
+ const conventions = metaCtx.manifest?.conventions || [];
1043
+ const convention = conventions.find((c) => c.domain === normalizedRef || c._ulid.startsWith(normalizedRef));
1044
+ if (convention) {
1045
+ itemType = 'convention';
1046
+ itemUlid = convention._ulid;
1047
+ itemLabel = `convention ${convention.domain}`;
1048
+ }
1049
+ }
1050
+ // Search in observations
1051
+ if (!itemType) {
1052
+ const observations = metaCtx.observations || [];
1053
+ const observation = observations.find((o) => o._ulid.startsWith(normalizedRef));
1054
+ if (observation) {
1055
+ itemType = 'observation';
1056
+ itemUlid = observation._ulid;
1057
+ itemLabel = `observation ${observation._ulid.substring(0, 8)}`;
1058
+ }
1059
+ }
1060
+ if (!itemType || !itemUlid || !itemLabel) {
1061
+ error(errors.reference.metaNotFound(ref));
1062
+ process.exit(EXIT_CODES.ERROR);
1063
+ }
1064
+ // Check for dangling references (unless --confirm is used to override)
1065
+ if (!options.confirm) {
1066
+ // Check tasks with meta_ref
1067
+ const tasks = await loadAllTasks(ctx);
1068
+ const referencingTasks = tasks.filter((t) => {
1069
+ if (!t.meta_ref)
1070
+ return false;
1071
+ // Resolve the task's meta_ref to a ULID
1072
+ const taskMetaRef = resolveMetaRefToUlid(t.meta_ref, metaCtx);
1073
+ // Compare ULIDs to handle both semantic IDs and ULID prefixes
1074
+ return taskMetaRef && taskMetaRef.ulid === itemUlid;
1075
+ });
1076
+ if (referencingTasks.length > 0) {
1077
+ const taskRefs = referencingTasks
1078
+ .map((t) => `@${t.slugs?.[0] || t._ulid.substring(0, 8)}`)
1079
+ .join(', ');
1080
+ error(errors.operation.cannotDeleteReferencedByTasks(itemLabel, referencingTasks.length, taskRefs));
1081
+ process.exit(EXIT_CODES.ERROR);
1082
+ }
1083
+ // Check observations with workflow_ref (only for workflows)
1084
+ if (itemType === 'workflow') {
1085
+ const observations = metaCtx.observations || [];
1086
+ const referencingObservations = observations.filter((o) => {
1087
+ if (!o.workflow_ref)
1088
+ return false;
1089
+ // Resolve the observation's workflow_ref to a ULID
1090
+ const obsWorkflowRef = resolveMetaRefToUlid(o.workflow_ref, metaCtx);
1091
+ // Compare ULIDs to handle both semantic IDs and ULID prefixes
1092
+ return obsWorkflowRef && obsWorkflowRef.ulid === itemUlid;
1093
+ });
1094
+ if (referencingObservations.length > 0) {
1095
+ const obsRefs = referencingObservations
1096
+ .map((o) => `@${o._ulid.substring(0, 8)}`)
1097
+ .join(', ');
1098
+ error(errors.operation.cannotDeleteReferencedByObservations(itemLabel, referencingObservations.length, obsRefs));
1099
+ process.exit(EXIT_CODES.ERROR);
1100
+ }
1101
+ }
1102
+ // Show confirmation prompt even if no references found
1103
+ error(errors.operation.confirmRequired(itemLabel));
1104
+ process.exit(EXIT_CODES.ERROR);
1105
+ }
1106
+ // Delete the item
1107
+ const deleted = await deleteMetaItem(ctx, itemUlid, itemType);
1108
+ if (!deleted) {
1109
+ error(errors.operation.deleteItemFailed(itemLabel));
1110
+ process.exit(EXIT_CODES.ERROR);
1111
+ }
1112
+ success(`Deleted ${itemLabel}`);
1113
+ }
1114
+ catch (err) {
1115
+ error(errors.failures.deleteMetaItem, err);
1116
+ process.exit(EXIT_CODES.ERROR);
1117
+ }
1118
+ });
1119
+ // meta-focus-cmd: kspec meta focus [ref]
1120
+ meta
1121
+ .command('focus [ref]')
1122
+ .description('Get or set session focus')
1123
+ .option('--clear', 'Clear current focus')
1124
+ .action(async (ref, options) => {
1125
+ try {
1126
+ const ctx = await initContext();
1127
+ if (!ctx.manifestPath) {
1128
+ error(errors.project.noKspecProject);
1129
+ process.exit(EXIT_CODES.ERROR);
1130
+ }
1131
+ const sessionCtx = await loadSessionContext(ctx);
1132
+ // Clear focus
1133
+ if (options.clear) {
1134
+ sessionCtx.focus = null;
1135
+ await saveSessionContext(ctx, sessionCtx);
1136
+ output({ focus: null }, () => success('Cleared session focus'));
1137
+ return;
1138
+ }
1139
+ // Show current focus
1140
+ if (!ref) {
1141
+ output({ focus: sessionCtx.focus }, () => {
1142
+ if (sessionCtx.focus) {
1143
+ console.log(`Current focus: ${sessionCtx.focus}`);
1144
+ }
1145
+ else {
1146
+ console.log(chalk.yellow('No focus set'));
1147
+ }
1148
+ });
1149
+ return;
1150
+ }
1151
+ // Set focus to ref
1152
+ sessionCtx.focus = ref.startsWith('@') ? ref : `@${ref}`;
1153
+ await saveSessionContext(ctx, sessionCtx);
1154
+ output({ focus: sessionCtx.focus }, () => success(`Set focus to: ${sessionCtx.focus}`));
1155
+ }
1156
+ catch (err) {
1157
+ error(errors.failures.updateSessionContext, err);
1158
+ process.exit(EXIT_CODES.ERROR);
1159
+ }
1160
+ });
1161
+ // meta-thread-cmd: kspec meta thread <action> [text]
1162
+ meta
1163
+ .command('thread <action> [text]')
1164
+ .description('Manage active threads')
1165
+ .action(async (action, text) => {
1166
+ try {
1167
+ const ctx = await initContext();
1168
+ if (!ctx.manifestPath) {
1169
+ error(errors.project.noKspecProject);
1170
+ process.exit(EXIT_CODES.ERROR);
1171
+ }
1172
+ const sessionCtx = await loadSessionContext(ctx);
1173
+ // List threads
1174
+ if (action === 'list') {
1175
+ output({ threads: sessionCtx.threads }, () => {
1176
+ if (sessionCtx.threads.length === 0) {
1177
+ console.log(chalk.yellow('No active threads'));
1178
+ }
1179
+ else {
1180
+ console.log('Active threads:');
1181
+ sessionCtx.threads.forEach((thread, idx) => {
1182
+ console.log(` ${idx + 1}. ${thread}`);
1183
+ });
1184
+ }
1185
+ });
1186
+ return;
1187
+ }
1188
+ // Clear all threads
1189
+ if (action === 'clear') {
1190
+ sessionCtx.threads = [];
1191
+ await saveSessionContext(ctx, sessionCtx);
1192
+ output({ threads: [] }, () => success('Cleared all threads'));
1193
+ return;
1194
+ }
1195
+ // Add thread
1196
+ if (action === 'add') {
1197
+ if (!text) {
1198
+ error('Thread text is required for add action');
1199
+ process.exit(EXIT_CODES.ERROR);
1200
+ }
1201
+ sessionCtx.threads.push(text);
1202
+ await saveSessionContext(ctx, sessionCtx);
1203
+ output({ threads: sessionCtx.threads, added: text }, () => success(`Added thread: ${text}`));
1204
+ return;
1205
+ }
1206
+ // Remove thread by index (1-based)
1207
+ if (action === 'remove') {
1208
+ if (!text) {
1209
+ error('Index is required for remove action');
1210
+ process.exit(EXIT_CODES.ERROR);
1211
+ }
1212
+ const index = parseInt(text, 10);
1213
+ if (isNaN(index) || index < 1 || index > sessionCtx.threads.length) {
1214
+ error(`Invalid index: ${text}. Must be between 1 and ${sessionCtx.threads.length}`);
1215
+ process.exit(EXIT_CODES.ERROR);
1216
+ }
1217
+ const removed = sessionCtx.threads.splice(index - 1, 1)[0];
1218
+ await saveSessionContext(ctx, sessionCtx);
1219
+ output({ threads: sessionCtx.threads, removed }, () => success(`Removed thread: ${removed}`));
1220
+ return;
1221
+ }
1222
+ // Unknown action
1223
+ error(`Unknown action: ${action}. Use add, remove, list, or clear`);
1224
+ process.exit(EXIT_CODES.ERROR);
1225
+ }
1226
+ catch (err) {
1227
+ error(errors.failures.updateSessionContext, err);
1228
+ process.exit(EXIT_CODES.ERROR);
1229
+ }
1230
+ });
1231
+ // meta-question-cmd: kspec meta question <action> [text]
1232
+ meta
1233
+ .command('question <action> [text]')
1234
+ .description('Manage open questions')
1235
+ .action(async (action, text) => {
1236
+ try {
1237
+ const ctx = await initContext();
1238
+ if (!ctx.manifestPath) {
1239
+ error(errors.project.noKspecProject);
1240
+ process.exit(EXIT_CODES.ERROR);
1241
+ }
1242
+ const sessionCtx = await loadSessionContext(ctx);
1243
+ // List questions
1244
+ if (action === 'list') {
1245
+ output({ questions: sessionCtx.open_questions }, () => {
1246
+ if (sessionCtx.open_questions.length === 0) {
1247
+ console.log(chalk.yellow('No open questions'));
1248
+ }
1249
+ else {
1250
+ console.log('Open questions:');
1251
+ sessionCtx.open_questions.forEach((question, idx) => {
1252
+ console.log(` ${idx + 1}. ${question}`);
1253
+ });
1254
+ }
1255
+ });
1256
+ return;
1257
+ }
1258
+ // Clear all questions
1259
+ if (action === 'clear') {
1260
+ sessionCtx.open_questions = [];
1261
+ await saveSessionContext(ctx, sessionCtx);
1262
+ output({ questions: [] }, () => success('Cleared all questions'));
1263
+ return;
1264
+ }
1265
+ // Add question
1266
+ if (action === 'add') {
1267
+ if (!text) {
1268
+ error('Question text is required for add action');
1269
+ process.exit(EXIT_CODES.ERROR);
1270
+ }
1271
+ sessionCtx.open_questions.push(text);
1272
+ await saveSessionContext(ctx, sessionCtx);
1273
+ output({ questions: sessionCtx.open_questions, added: text }, () => success(`Added question: ${text}`));
1274
+ return;
1275
+ }
1276
+ // Remove question by index (1-based)
1277
+ if (action === 'remove') {
1278
+ if (!text) {
1279
+ error('Index is required for remove action');
1280
+ process.exit(EXIT_CODES.ERROR);
1281
+ }
1282
+ const index = parseInt(text, 10);
1283
+ if (isNaN(index) || index < 1 || index > sessionCtx.open_questions.length) {
1284
+ error(`Invalid index: ${text}. Must be between 1 and ${sessionCtx.open_questions.length}`);
1285
+ process.exit(EXIT_CODES.ERROR);
1286
+ }
1287
+ const removed = sessionCtx.open_questions.splice(index - 1, 1)[0];
1288
+ await saveSessionContext(ctx, sessionCtx);
1289
+ output({ questions: sessionCtx.open_questions, removed }, () => success(`Removed question: ${removed}`));
1290
+ return;
1291
+ }
1292
+ // Unknown action
1293
+ error(`Unknown action: ${action}. Use add, remove, list, or clear`);
1294
+ process.exit(EXIT_CODES.ERROR);
1295
+ }
1296
+ catch (err) {
1297
+ error(errors.failures.updateSessionContext, err);
1298
+ process.exit(EXIT_CODES.ERROR);
1299
+ }
1300
+ });
1301
+ // meta-context-cmd: kspec meta context
1302
+ meta
1303
+ .command('context')
1304
+ .description('Show full session context')
1305
+ .option('--clear', 'Clear all session context')
1306
+ .action(async (options) => {
1307
+ try {
1308
+ const ctx = await initContext();
1309
+ if (!ctx.manifestPath) {
1310
+ error(errors.project.noKspecProject);
1311
+ process.exit(EXIT_CODES.ERROR);
1312
+ }
1313
+ const sessionCtx = await loadSessionContext(ctx);
1314
+ // Clear all context
1315
+ if (options.clear) {
1316
+ sessionCtx.focus = null;
1317
+ sessionCtx.threads = [];
1318
+ sessionCtx.open_questions = [];
1319
+ await saveSessionContext(ctx, sessionCtx);
1320
+ output({
1321
+ focus: null,
1322
+ threads: [],
1323
+ open_questions: [],
1324
+ updated_at: sessionCtx.updated_at,
1325
+ }, () => success('Cleared all session context'));
1326
+ return;
1327
+ }
1328
+ // Show full session context
1329
+ output({
1330
+ focus: sessionCtx.focus,
1331
+ threads: sessionCtx.threads,
1332
+ open_questions: sessionCtx.open_questions,
1333
+ updated_at: sessionCtx.updated_at,
1334
+ }, () => {
1335
+ console.log(chalk.bold('Session Context'));
1336
+ console.log(chalk.gray('─'.repeat(60)));
1337
+ // Focus
1338
+ console.log(chalk.bold('\nFocus:'));
1339
+ if (sessionCtx.focus) {
1340
+ console.log(` ${sessionCtx.focus}`);
1341
+ }
1342
+ else {
1343
+ console.log(chalk.gray(' (none)'));
1344
+ }
1345
+ // Active threads
1346
+ console.log(chalk.bold('\nActive Threads:'));
1347
+ if (sessionCtx.threads.length > 0) {
1348
+ sessionCtx.threads.forEach((thread, idx) => {
1349
+ console.log(` ${idx + 1}. ${thread}`);
1350
+ });
1351
+ }
1352
+ else {
1353
+ console.log(chalk.gray(' (none)'));
1354
+ }
1355
+ // Open questions
1356
+ console.log(chalk.bold('\nOpen Questions:'));
1357
+ if (sessionCtx.open_questions.length > 0) {
1358
+ sessionCtx.open_questions.forEach((question, idx) => {
1359
+ console.log(` ${idx + 1}. ${question}`);
1360
+ });
1361
+ }
1362
+ else {
1363
+ console.log(chalk.gray(' (none)'));
1364
+ }
1365
+ // Last updated
1366
+ console.log(chalk.bold('\nLast Updated:'));
1367
+ const updatedDate = new Date(sessionCtx.updated_at);
1368
+ console.log(` ${updatedDate.toISOString()}`);
1369
+ console.log(chalk.gray(` (${updatedDate.toLocaleString()})`));
1370
+ });
1371
+ }
1372
+ catch (err) {
1373
+ error(errors.failures.updateSessionContext, err);
1374
+ process.exit(EXIT_CODES.ERROR);
1375
+ }
1376
+ });
1377
+ }
1378
+ //# sourceMappingURL=meta.js.map