@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.
- package/README.md +263 -0
- package/dist/acp/client.d.ts +159 -0
- package/dist/acp/client.d.ts.map +1 -0
- package/dist/acp/client.js +255 -0
- package/dist/acp/client.js.map +1 -0
- package/dist/acp/framing.d.ts +119 -0
- package/dist/acp/framing.d.ts.map +1 -0
- package/dist/acp/framing.js +302 -0
- package/dist/acp/framing.js.map +1 -0
- package/dist/acp/index.d.ts +14 -0
- package/dist/acp/index.d.ts.map +1 -0
- package/dist/acp/index.js +13 -0
- package/dist/acp/index.js.map +1 -0
- package/dist/acp/types.d.ts +89 -0
- package/dist/acp/types.d.ts.map +1 -0
- package/dist/acp/types.js +99 -0
- package/dist/acp/types.js.map +1 -0
- package/dist/agents/adapters.d.ts +55 -0
- package/dist/agents/adapters.d.ts.map +1 -0
- package/dist/agents/adapters.js +84 -0
- package/dist/agents/adapters.js.map +1 -0
- package/dist/agents/index.d.ts +8 -0
- package/dist/agents/index.d.ts.map +1 -0
- package/dist/agents/index.js +10 -0
- package/dist/agents/index.js.map +1 -0
- package/dist/agents/spawner.d.ts +53 -0
- package/dist/agents/spawner.d.ts.map +1 -0
- package/dist/agents/spawner.js +83 -0
- package/dist/agents/spawner.js.map +1 -0
- package/dist/cli/batch.d.ts +82 -0
- package/dist/cli/batch.d.ts.map +1 -0
- package/dist/cli/batch.js +162 -0
- package/dist/cli/batch.js.map +1 -0
- package/dist/cli/commands/clone-for-testing.d.ts +6 -0
- package/dist/cli/commands/clone-for-testing.d.ts.map +1 -0
- package/dist/cli/commands/clone-for-testing.js +176 -0
- package/dist/cli/commands/clone-for-testing.js.map +1 -0
- package/dist/cli/commands/derive.d.ts +6 -0
- package/dist/cli/commands/derive.d.ts.map +1 -0
- package/dist/cli/commands/derive.js +450 -0
- package/dist/cli/commands/derive.js.map +1 -0
- package/dist/cli/commands/help.d.ts +6 -0
- package/dist/cli/commands/help.d.ts.map +1 -0
- package/dist/cli/commands/help.js +196 -0
- package/dist/cli/commands/help.js.map +1 -0
- package/dist/cli/commands/inbox.d.ts +6 -0
- package/dist/cli/commands/inbox.d.ts.map +1 -0
- package/dist/cli/commands/inbox.js +235 -0
- package/dist/cli/commands/inbox.js.map +1 -0
- package/dist/cli/commands/index.d.ts +20 -0
- package/dist/cli/commands/index.d.ts.map +1 -0
- package/dist/cli/commands/index.js +21 -0
- package/dist/cli/commands/index.js.map +1 -0
- package/dist/cli/commands/init.d.ts +6 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +245 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/item.d.ts +6 -0
- package/dist/cli/commands/item.d.ts.map +1 -0
- package/dist/cli/commands/item.js +1311 -0
- package/dist/cli/commands/item.js.map +1 -0
- package/dist/cli/commands/link.d.ts +6 -0
- package/dist/cli/commands/link.d.ts.map +1 -0
- package/dist/cli/commands/link.js +288 -0
- package/dist/cli/commands/link.js.map +1 -0
- package/dist/cli/commands/log.d.ts +16 -0
- package/dist/cli/commands/log.d.ts.map +1 -0
- package/dist/cli/commands/log.js +291 -0
- package/dist/cli/commands/log.js.map +1 -0
- package/dist/cli/commands/meta.d.ts +15 -0
- package/dist/cli/commands/meta.d.ts.map +1 -0
- package/dist/cli/commands/meta.js +1378 -0
- package/dist/cli/commands/meta.js.map +1 -0
- package/dist/cli/commands/module.d.ts +6 -0
- package/dist/cli/commands/module.d.ts.map +1 -0
- package/dist/cli/commands/module.js +102 -0
- package/dist/cli/commands/module.js.map +1 -0
- package/dist/cli/commands/ralph.d.ts +9 -0
- package/dist/cli/commands/ralph.d.ts.map +1 -0
- package/dist/cli/commands/ralph.js +465 -0
- package/dist/cli/commands/ralph.js.map +1 -0
- package/dist/cli/commands/search.d.ts +6 -0
- package/dist/cli/commands/search.d.ts.map +1 -0
- package/dist/cli/commands/search.js +134 -0
- package/dist/cli/commands/search.js.map +1 -0
- package/dist/cli/commands/session.d.ts +164 -0
- package/dist/cli/commands/session.d.ts.map +1 -0
- package/dist/cli/commands/session.js +745 -0
- package/dist/cli/commands/session.js.map +1 -0
- package/dist/cli/commands/setup.d.ts +26 -0
- package/dist/cli/commands/setup.d.ts.map +1 -0
- package/dist/cli/commands/setup.js +586 -0
- package/dist/cli/commands/setup.js.map +1 -0
- package/dist/cli/commands/shadow.d.ts +6 -0
- package/dist/cli/commands/shadow.d.ts.map +1 -0
- package/dist/cli/commands/shadow.js +299 -0
- package/dist/cli/commands/shadow.js.map +1 -0
- package/dist/cli/commands/task.d.ts +6 -0
- package/dist/cli/commands/task.d.ts.map +1 -0
- package/dist/cli/commands/task.js +1514 -0
- package/dist/cli/commands/task.js.map +1 -0
- package/dist/cli/commands/tasks.d.ts +6 -0
- package/dist/cli/commands/tasks.d.ts.map +1 -0
- package/dist/cli/commands/tasks.js +347 -0
- package/dist/cli/commands/tasks.js.map +1 -0
- package/dist/cli/commands/trait.d.ts +10 -0
- package/dist/cli/commands/trait.d.ts.map +1 -0
- package/dist/cli/commands/trait.js +295 -0
- package/dist/cli/commands/trait.js.map +1 -0
- package/dist/cli/commands/validate.d.ts +6 -0
- package/dist/cli/commands/validate.d.ts.map +1 -0
- package/dist/cli/commands/validate.js +626 -0
- package/dist/cli/commands/validate.js.map +1 -0
- package/dist/cli/exit-codes.d.ts +62 -0
- package/dist/cli/exit-codes.d.ts.map +1 -0
- package/dist/cli/exit-codes.js +65 -0
- package/dist/cli/exit-codes.js.map +1 -0
- package/dist/cli/help/content.d.ts +35 -0
- package/dist/cli/help/content.d.ts.map +1 -0
- package/dist/cli/help/content.js +312 -0
- package/dist/cli/help/content.js.map +1 -0
- package/dist/cli/index.d.ts +5 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +85 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/introspection.d.ts +87 -0
- package/dist/cli/introspection.d.ts.map +1 -0
- package/dist/cli/introspection.js +127 -0
- package/dist/cli/introspection.js.map +1 -0
- package/dist/cli/output.d.ts +56 -0
- package/dist/cli/output.d.ts.map +1 -0
- package/dist/cli/output.js +467 -0
- package/dist/cli/output.js.map +1 -0
- package/dist/cli/suggest.d.ts +16 -0
- package/dist/cli/suggest.d.ts.map +1 -0
- package/dist/cli/suggest.js +72 -0
- package/dist/cli/suggest.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/parser/alignment.d.ts +113 -0
- package/dist/parser/alignment.d.ts.map +1 -0
- package/dist/parser/alignment.js +261 -0
- package/dist/parser/alignment.js.map +1 -0
- package/dist/parser/assess.d.ts +81 -0
- package/dist/parser/assess.d.ts.map +1 -0
- package/dist/parser/assess.js +197 -0
- package/dist/parser/assess.js.map +1 -0
- package/dist/parser/convention-validation.d.ts +48 -0
- package/dist/parser/convention-validation.d.ts.map +1 -0
- package/dist/parser/convention-validation.js +167 -0
- package/dist/parser/convention-validation.js.map +1 -0
- package/dist/parser/fix.d.ts +38 -0
- package/dist/parser/fix.d.ts.map +1 -0
- package/dist/parser/fix.js +185 -0
- package/dist/parser/fix.js.map +1 -0
- package/dist/parser/index.d.ts +12 -0
- package/dist/parser/index.d.ts.map +1 -0
- package/dist/parser/index.js +13 -0
- package/dist/parser/index.js.map +1 -0
- package/dist/parser/items.d.ts +138 -0
- package/dist/parser/items.d.ts.map +1 -0
- package/dist/parser/items.js +321 -0
- package/dist/parser/items.js.map +1 -0
- package/dist/parser/meta.d.ts +120 -0
- package/dist/parser/meta.d.ts.map +1 -0
- package/dist/parser/meta.js +441 -0
- package/dist/parser/meta.js.map +1 -0
- package/dist/parser/refs.d.ts +185 -0
- package/dist/parser/refs.d.ts.map +1 -0
- package/dist/parser/refs.js +404 -0
- package/dist/parser/refs.js.map +1 -0
- package/dist/parser/shadow.d.ts +253 -0
- package/dist/parser/shadow.d.ts.map +1 -0
- package/dist/parser/shadow.js +1053 -0
- package/dist/parser/shadow.js.map +1 -0
- package/dist/parser/traits.d.ts +72 -0
- package/dist/parser/traits.d.ts.map +1 -0
- package/dist/parser/traits.js +120 -0
- package/dist/parser/traits.js.map +1 -0
- package/dist/parser/validate.d.ts +89 -0
- package/dist/parser/validate.d.ts.map +1 -0
- package/dist/parser/validate.js +817 -0
- package/dist/parser/validate.js.map +1 -0
- package/dist/parser/yaml.d.ts +326 -0
- package/dist/parser/yaml.d.ts.map +1 -0
- package/dist/parser/yaml.js +1383 -0
- package/dist/parser/yaml.js.map +1 -0
- package/dist/ralph/cli-renderer.d.ts +20 -0
- package/dist/ralph/cli-renderer.d.ts.map +1 -0
- package/dist/ralph/cli-renderer.js +179 -0
- package/dist/ralph/cli-renderer.js.map +1 -0
- package/dist/ralph/events.d.ts +65 -0
- package/dist/ralph/events.d.ts.map +1 -0
- package/dist/ralph/events.js +397 -0
- package/dist/ralph/events.js.map +1 -0
- package/dist/ralph/index.d.ts +8 -0
- package/dist/ralph/index.d.ts.map +1 -0
- package/dist/ralph/index.js +10 -0
- package/dist/ralph/index.js.map +1 -0
- package/dist/schema/common.d.ts +46 -0
- package/dist/schema/common.d.ts.map +1 -0
- package/dist/schema/common.js +71 -0
- package/dist/schema/common.js.map +1 -0
- package/dist/schema/inbox.d.ts +90 -0
- package/dist/schema/inbox.d.ts.map +1 -0
- package/dist/schema/inbox.js +30 -0
- package/dist/schema/inbox.js.map +1 -0
- package/dist/schema/index.d.ts +6 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/index.js +7 -0
- package/dist/schema/index.js.map +1 -0
- package/dist/schema/meta.d.ts +762 -0
- package/dist/schema/meta.d.ts.map +1 -0
- package/dist/schema/meta.js +144 -0
- package/dist/schema/meta.js.map +1 -0
- package/dist/schema/spec.d.ts +912 -0
- package/dist/schema/spec.d.ts.map +1 -0
- package/dist/schema/spec.js +104 -0
- package/dist/schema/spec.js.map +1 -0
- package/dist/schema/task.d.ts +664 -0
- package/dist/schema/task.d.ts.map +1 -0
- package/dist/schema/task.js +130 -0
- package/dist/schema/task.js.map +1 -0
- package/dist/sessions/index.d.ts +11 -0
- package/dist/sessions/index.d.ts.map +1 -0
- package/dist/sessions/index.js +13 -0
- package/dist/sessions/index.js.map +1 -0
- package/dist/sessions/store.d.ts +144 -0
- package/dist/sessions/store.d.ts.map +1 -0
- package/dist/sessions/store.js +325 -0
- package/dist/sessions/store.js.map +1 -0
- package/dist/sessions/types.d.ts +157 -0
- package/dist/sessions/types.d.ts.map +1 -0
- package/dist/sessions/types.js +90 -0
- package/dist/sessions/types.js.map +1 -0
- package/dist/strings/errors.d.ts +420 -0
- package/dist/strings/errors.d.ts.map +1 -0
- package/dist/strings/errors.js +282 -0
- package/dist/strings/errors.js.map +1 -0
- package/dist/strings/guidance.d.ts +65 -0
- package/dist/strings/guidance.d.ts.map +1 -0
- package/dist/strings/guidance.js +66 -0
- package/dist/strings/guidance.js.map +1 -0
- package/dist/strings/index.d.ts +12 -0
- package/dist/strings/index.d.ts.map +1 -0
- package/dist/strings/index.js +12 -0
- package/dist/strings/index.js.map +1 -0
- package/dist/strings/labels.d.ts +74 -0
- package/dist/strings/labels.d.ts.map +1 -0
- package/dist/strings/labels.js +75 -0
- package/dist/strings/labels.js.map +1 -0
- package/dist/strings/validation.d.ts +126 -0
- package/dist/strings/validation.d.ts.map +1 -0
- package/dist/strings/validation.js +135 -0
- package/dist/strings/validation.js.map +1 -0
- package/dist/utils/commit.d.ts +23 -0
- package/dist/utils/commit.d.ts.map +1 -0
- package/dist/utils/commit.js +67 -0
- package/dist/utils/commit.js.map +1 -0
- package/dist/utils/git.d.ts +57 -0
- package/dist/utils/git.d.ts.map +1 -0
- package/dist/utils/git.js +192 -0
- package/dist/utils/git.js.map +1 -0
- package/dist/utils/grep.d.ts +28 -0
- package/dist/utils/grep.d.ts.map +1 -0
- package/dist/utils/grep.js +86 -0
- package/dist/utils/grep.js.map +1 -0
- package/dist/utils/index.d.ts +8 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +6 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/time.d.ts +18 -0
- package/dist/utils/time.d.ts.map +1 -0
- package/dist/utils/time.js +61 -0
- package/dist/utils/time.js.map +1 -0
- 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
|