@zq-silk/yui 0.0.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 (95) hide show
  1. package/ARCHITECTURE.md +141 -0
  2. package/LICENSE +21 -0
  3. package/README.md +211 -0
  4. package/dist/agent/adapterCatalog.js +10 -0
  5. package/dist/agent/agent.js +89 -0
  6. package/dist/agent/agentRegistry.js +10 -0
  7. package/dist/agent/argumentPolicy.js +80 -0
  8. package/dist/brief/taskBrief.js +37 -0
  9. package/dist/cli/commandCatalog.js +647 -0
  10. package/dist/cli/completion.js +111 -0
  11. package/dist/cli/completionWizard.js +143 -0
  12. package/dist/cli/dynamicCompletion.js +48 -0
  13. package/dist/cli/helpRenderer.js +32 -0
  14. package/dist/cli/interactionCandidates.js +139 -0
  15. package/dist/cli/interactionPolicy.js +389 -0
  16. package/dist/cli/interactiveSelection.js +185 -0
  17. package/dist/cli/invocationRouter.js +51 -0
  18. package/dist/cli/roleOptionCatalog.js +67 -0
  19. package/dist/cli/roleWizard.js +546 -0
  20. package/dist/cli/selectionPorts.js +1 -0
  21. package/dist/cli/updateCommand.js +22 -0
  22. package/dist/cli.js +402 -0
  23. package/dist/commands/agentCommands.js +196 -0
  24. package/dist/commands/globalRoleCommands.js +367 -0
  25. package/dist/commands/jobCommands.js +100 -0
  26. package/dist/commands/operatorCommands.js +38 -0
  27. package/dist/commands/repositoryCommands.js +86 -0
  28. package/dist/commands/roleConfiguration.js +201 -0
  29. package/dist/commands/taskCommands.js +1344 -0
  30. package/dist/commands/taskContextCommand.js +215 -0
  31. package/dist/commands/taskInputCommands.js +423 -0
  32. package/dist/commands/taskRoleRuntimeStatus.js +152 -0
  33. package/dist/completion/completionInstaller.js +168 -0
  34. package/dist/completion/completionPort.js +1 -0
  35. package/dist/completion/completionState.js +137 -0
  36. package/dist/completion/completionWizard.js +125 -0
  37. package/dist/completion/fileCompletionManager.js +51 -0
  38. package/dist/config/yuiConfig.js +17 -0
  39. package/dist/context/dispatchContext.js +74 -0
  40. package/dist/controller/clientRuntime.js +215 -0
  41. package/dist/controller/controller.js +158 -0
  42. package/dist/controller/controllerMain.js +37 -0
  43. package/dist/controller/fileSchedulerStoreAdapter.js +322 -0
  44. package/dist/controller/runtime.js +31 -0
  45. package/dist/controller/sessionNotify.js +136 -0
  46. package/dist/core/controllerClient.js +127 -0
  47. package/dist/core/controllerServer.js +269 -0
  48. package/dist/core/protocol.js +169 -0
  49. package/dist/decision/decision.js +42 -0
  50. package/dist/doctor/doctor.js +229 -0
  51. package/dist/errors/cliError.js +38 -0
  52. package/dist/event/taskEvent.js +44 -0
  53. package/dist/executor/agentAdapter.js +338 -0
  54. package/dist/executor/agentExecutor.js +144 -0
  55. package/dist/executor/executorRegistry.js +101 -0
  56. package/dist/executor/fileRoleLaunchPlanner.js +156 -0
  57. package/dist/executor/launchPlan.js +16 -0
  58. package/dist/input/inputRequest.js +326 -0
  59. package/dist/message/message.js +69 -0
  60. package/dist/milestone/milestone.js +27 -0
  61. package/dist/operator/operatorContext.js +66 -0
  62. package/dist/output/rolePresentation.js +82 -0
  63. package/dist/output/table.js +77 -0
  64. package/dist/output/terminal.js +198 -0
  65. package/dist/repository/gitWorkspace.js +210 -0
  66. package/dist/repository/repository.js +55 -0
  67. package/dist/repository/taskWorkspacePreparer.js +256 -0
  68. package/dist/role/role.js +246 -0
  69. package/dist/role/systemRoles.js +20 -0
  70. package/dist/run/agentRun.js +102 -0
  71. package/dist/scheduler/activeRoleRunDelivery.js +94 -0
  72. package/dist/scheduler/archivedTaskRuntime.js +12 -0
  73. package/dist/scheduler/leaderFailure.js +18 -0
  74. package/dist/scheduler/leaderWakeupProcessor.js +143 -0
  75. package/dist/scheduler/operatorInputNotificationProcessor.js +85 -0
  76. package/dist/scheduler/operatorNotification.js +17 -0
  77. package/dist/scheduler/pendingWakeup.js +33 -0
  78. package/dist/scheduler/ports.js +1 -0
  79. package/dist/scheduler/roleRunLiveness.js +41 -0
  80. package/dist/scheduler/wakeupQueue.js +13 -0
  81. package/dist/setup/setupCommand.js +317 -0
  82. package/dist/storage/durableFile.js +38 -0
  83. package/dist/storage/storageSchema.js +259 -0
  84. package/dist/storage/taskStore.js +1032 -0
  85. package/dist/task/task.js +216 -0
  86. package/dist/tmux/commandExecutor.js +69 -0
  87. package/dist/tmux/terminalHandoff.js +17 -0
  88. package/dist/tmux/tmuxManager.js +408 -0
  89. package/dist/workItem/workItem.js +45 -0
  90. package/dist/worktree/roleWorkspace.js +62 -0
  91. package/i18n/README.zh-CN.md +205 -0
  92. package/package.json +47 -0
  93. package/skills/yui-leader/SKILL.md +72 -0
  94. package/skills/yui-operator/SKILL.md +57 -0
  95. package/skills/yui-worker/SKILL.md +31 -0
@@ -0,0 +1,647 @@
1
+ import { supportedAgentAdapterIds } from "../agent/adapterCatalog.js";
2
+ function buildNode(input, parentPath = []) {
3
+ const path = [...parentPath, input.name];
4
+ const children = (input.children ?? []).map((child) => buildNode(child, path));
5
+ const executable = input.executable ?? children.length === 0;
6
+ const usage = input.usage === undefined
7
+ ? [`${path.join(" ")}${children.length > 0 && !executable ? " <command>" : ""}`]
8
+ : typeof input.usage === "string" ? [input.usage] : [...input.usage];
9
+ return Object.freeze({
10
+ name: input.name,
11
+ path: Object.freeze(path),
12
+ summary: input.summary,
13
+ kind: children.length === 0 ? "leaf" : executable ? "hybrid" : "group",
14
+ usage: Object.freeze(usage),
15
+ sections: Object.freeze((input.sections ?? []).map((section) => Object.freeze({
16
+ ...section,
17
+ entries: Object.freeze([...section.entries])
18
+ }))),
19
+ children: Object.freeze(children),
20
+ hidden: input.hidden ?? false,
21
+ options: Object.freeze([...(input.options ?? [])]),
22
+ hiddenOptions: Object.freeze([...(input.hiddenOptions ?? [])]),
23
+ values: Object.freeze((input.values ?? []).map((value) => Object.freeze(typeof value === "string" ? { name: value, summary: value } : { ...value }))),
24
+ optionValues: freezeRecord(input.optionValues),
25
+ argumentValues: freezeRecord(input.argumentValues),
26
+ fileOptions: Object.freeze([...(input.fileOptions ?? [])]),
27
+ workspaceMapOptions: Object.freeze([...(input.workspaceMapOptions ?? [])]),
28
+ fileArguments: Object.freeze([...(input.fileArguments ?? [])]),
29
+ executableOptions: Object.freeze([...(input.executableOptions ?? [])]),
30
+ ...(input.completionProvider === undefined ? {} : { completionProvider: input.completionProvider }),
31
+ completionUsesDefaultAgent: input.completionUsesDefaultAgent ?? false,
32
+ commandPathArguments: input.commandPathArguments ?? false,
33
+ acceptsArguments: input.acceptsArguments ?? executable
34
+ });
35
+ }
36
+ function freezeRecord(record) {
37
+ return Object.freeze(Object.fromEntries(Object.entries(record ?? {}).map(([key, values]) => [key, Object.freeze([...values])])));
38
+ }
39
+ const agentChildren = [
40
+ {
41
+ name: "add",
42
+ summary: "Add a configured native Agent CLI.",
43
+ usage: "yui agent add <id> [--adapter <adapter>] --command <command> [--arg <arg> ...] [--env TARGET=PROCESS_NAME ...]",
44
+ options: ["--adapter", "--command", "--arg", "--env"],
45
+ optionValues: { "--adapter": supportedAgentAdapterIds() },
46
+ executableOptions: ["--command"]
47
+ },
48
+ { name: "list", summary: "List configured Agents." },
49
+ { name: "show", summary: "Show one configured Agent.", usage: "yui agent show <id>" },
50
+ {
51
+ name: "update",
52
+ summary: "Update a configured Agent.",
53
+ usage: "yui agent update <id> [--adapter <adapter>] [--command <command>] [--arg <arg> ... | --clear-args] [--env TARGET=PROCESS_NAME ... | --clear-env]",
54
+ options: ["--adapter", "--command", "--arg", "--clear-args", "--env", "--clear-env"],
55
+ optionValues: { "--adapter": supportedAgentAdapterIds() },
56
+ executableOptions: ["--command"]
57
+ },
58
+ { name: "remove", summary: "Remove a configured Agent.", usage: "yui agent remove <id>" }
59
+ ];
60
+ const roleProfileOptions = [
61
+ "--description", "--responsibility", "--constraint",
62
+ "--expected-output", "--system-prompt", "--skill"
63
+ ];
64
+ const roleAgentOptions = [
65
+ "--model", "--effort", "--sandbox", "--approval", "--permission-mode", "--search"
66
+ ];
67
+ const roleProfileClearOptions = [
68
+ "--clear-description", "--clear-responsibilities", "--clear-constraints",
69
+ "--clear-expected-output", "--clear-system-prompt", "--clear-skills"
70
+ ];
71
+ const roleAgentClearOptions = [
72
+ "--clear-model", "--clear-effort", "--clear-sandbox", "--clear-approval",
73
+ "--clear-permission-mode", "--clear-search", "--clear-agent-config"
74
+ ];
75
+ const roleAgentOptionValues = {
76
+ "--sandbox": ["read-only", "workspace-write", "danger-full-access"],
77
+ "--approval": ["untrusted", "on-request", "never"],
78
+ "--search": ["true"]
79
+ };
80
+ const roleChildren = [
81
+ {
82
+ name: "add",
83
+ summary: "Add a reusable global Role.",
84
+ usage: "yui role add <name> --agent <id> [Role and Agent settings]",
85
+ options: ["--agent", "--workspace", ...roleProfileOptions, ...roleAgentOptions],
86
+ optionValues: roleAgentOptionValues,
87
+ fileOptions: ["--workspace"]
88
+ },
89
+ { name: "list", summary: "List global Roles." },
90
+ { name: "show", summary: "Show one global Role.", usage: "yui role show <name>" },
91
+ {
92
+ name: "update",
93
+ summary: "Update a global Role.",
94
+ usage: "yui role update <name> [profile options] [clear options]",
95
+ options: ["--agent", "--workspace", ...roleProfileOptions, ...roleAgentOptions,
96
+ ...roleProfileClearOptions, ...roleAgentClearOptions],
97
+ optionValues: roleAgentOptionValues,
98
+ fileOptions: ["--workspace"]
99
+ },
100
+ { name: "remove", summary: "Remove a global Role.", usage: "yui role remove <name>" },
101
+ { name: "bind", summary: "Bind and activate an Agent for a global Role.", usage: "yui role bind <role> <agent-id>" },
102
+ { name: "enter", summary: "Enter a global Role's native session.", usage: "yui role enter <role>" },
103
+ {
104
+ name: "session",
105
+ summary: "Manage native session IDs for a global Role.",
106
+ sections: [{ id: "manage", title: "Commands", entries: ["record", "replace"] }],
107
+ children: [
108
+ {
109
+ name: "record",
110
+ summary: "Record the active Agent's native session ID.",
111
+ usage: "yui role session record <role> --native-id <id>",
112
+ options: ["--native-id"]
113
+ },
114
+ {
115
+ name: "replace",
116
+ summary: "Explicitly replace the active Agent's native session ID.",
117
+ usage: "yui role session replace <role> --native-id <id> --reason <text>",
118
+ options: ["--native-id", "--reason"]
119
+ }
120
+ ]
121
+ }
122
+ ];
123
+ const taskChildren = [
124
+ {
125
+ name: "create",
126
+ summary: "Create a Draft Task.",
127
+ usage: "yui task create <title> [--repository <id>] [--base <ref>]",
128
+ options: ["--repository", "--base"]
129
+ },
130
+ {
131
+ name: "update",
132
+ summary: "Update Task metadata.",
133
+ usage: "yui task update <id> [--title <text>] [--description <text>|--clear-description] [--priority <low|medium|high|urgent>|--clear-priority] [--tags <comma-separated>|--clear-tags] [--due-at <RFC3339>|--clear-due-at]",
134
+ options: [
135
+ "--title", "--description", "--priority", "--tags", "--due-at",
136
+ "--clear-description", "--clear-priority", "--clear-tags", "--clear-due-at"
137
+ ],
138
+ optionValues: { "--priority": ["low", "medium", "high", "urgent"] }
139
+ },
140
+ { name: "activate", summary: "Activate a Draft Task.", usage: "yui task activate <id>" },
141
+ {
142
+ name: "complete",
143
+ summary: "Complete an active Task and stop automatic wakeups.",
144
+ usage: "yui task complete <id> --summary <text>",
145
+ options: ["--summary"]
146
+ },
147
+ { name: "reopen", summary: "Reopen a completed Task.", usage: "yui task reopen <id>" },
148
+ { name: "list", summary: "List Tasks." },
149
+ { name: "show", summary: "Show a Task.", usage: "yui task show <id>" },
150
+ {
151
+ name: "context",
152
+ summary: "Show consolidated working context for a Task.",
153
+ usage: "yui task context <task>"
154
+ },
155
+ { name: "archive", summary: "Archive a Task.", usage: "yui task archive <id>" },
156
+ { name: "reconcile", summary: "Run one immediate Controller reconciliation.", usage: "yui task reconcile <id>" },
157
+ {
158
+ name: "message",
159
+ summary: "Manage durable Task messages.",
160
+ sections: [{ id: "manage", title: "Commands", entries: ["send", "list"] }],
161
+ children: [
162
+ { name: "send", summary: "Send a Task message.", usage: "yui task message send <id> <body>" },
163
+ { name: "list", summary: "List Task messages.", usage: "yui task message list <id>" }
164
+ ]
165
+ },
166
+ {
167
+ name: "input",
168
+ summary: "Manage durable Task-owned input requests.",
169
+ sections: [{ id: "manage", title: "Commands", entries: ["request", "list", "show", "answer", "cancel"] }],
170
+ children: [
171
+ {
172
+ name: "request",
173
+ summary: "Pause the active Leader Run and request user input.",
174
+ usage: "yui task input request <task> --question <text> [--choice <key=label> ...] [--blocks <work-item:id|run:id> ...] [--recommend <key> --timeout-seconds <seconds>]",
175
+ options: ["--question", "--choice", "--blocks", "--recommend", "--timeout-seconds"]
176
+ },
177
+ {
178
+ name: "list",
179
+ summary: "List the global Inbox or one Task's input requests.",
180
+ usage: "yui task input list [task] [--all]",
181
+ options: ["--all"]
182
+ },
183
+ {
184
+ name: "show",
185
+ summary: "Show one input request.",
186
+ usage: "yui task input show <input> [--task <task>]",
187
+ options: ["--task"]
188
+ },
189
+ {
190
+ name: "answer",
191
+ summary: "Answer one open input request.",
192
+ usage: "yui task input answer <input> [--task <task>] (--choice <key> | --text <text>)",
193
+ options: ["--task", "--choice", "--text"]
194
+ },
195
+ {
196
+ name: "cancel",
197
+ summary: "Cancel an open request from its originating Leader.",
198
+ usage: "yui task input cancel <task> <input> --reason <text>",
199
+ options: ["--reason"]
200
+ }
201
+ ]
202
+ },
203
+ {
204
+ name: "role",
205
+ summary: "Manage Roles within a Task.",
206
+ sections: [{ id: "manage", title: "Commands", entries: [
207
+ "add", "list", "status", "show", "update", "remove", "bind", "enter"
208
+ ] }],
209
+ children: [
210
+ {
211
+ name: "add",
212
+ summary: "Add a Role to a Task.",
213
+ usage: "yui task role add <task> <name> [--agent <id>] [Role and Agent settings]",
214
+ options: ["--agent", ...roleProfileOptions, ...roleAgentOptions],
215
+ optionValues: roleAgentOptionValues
216
+ },
217
+ { name: "list", summary: "List Task Roles.", usage: "yui task role list <task>" },
218
+ {
219
+ name: "status",
220
+ summary: "Show persisted and live runtime state for one Task Role.",
221
+ usage: "yui task role status <task> <role>"
222
+ },
223
+ { name: "show", summary: "Show one Task Role.", usage: "yui task role show <task> <role>" },
224
+ {
225
+ name: "update",
226
+ summary: "Update a Task Role.",
227
+ usage: "yui task role update <task> <role> [Role and Agent settings]",
228
+ options: ["--agent", ...roleProfileOptions, ...roleAgentOptions,
229
+ ...roleProfileClearOptions, ...roleAgentClearOptions],
230
+ optionValues: roleAgentOptionValues
231
+ },
232
+ { name: "remove", summary: "Remove a Task Role.", usage: "yui task role remove <task> <role>" },
233
+ { name: "bind", summary: "Bind and activate an Agent for a Task Role.", usage: "yui task role bind <task> <role> <agent-id>" },
234
+ { name: "enter", summary: "Enter a Task Role's native session.", usage: "yui task role enter <task> <role>" }
235
+ ]
236
+ },
237
+ {
238
+ name: "work",
239
+ summary: "Manage finite Task work items.",
240
+ sections: [{ id: "manage", title: "Commands", entries: ["create", "list", "update", "dispatch"] }],
241
+ children: [
242
+ {
243
+ name: "create",
244
+ summary: "Create a work item.",
245
+ usage: "yui task work create <task> <title> [--role <name>]",
246
+ options: ["--role"]
247
+ },
248
+ { name: "list", summary: "List work items for a Task.", usage: "yui task work list <task>" },
249
+ {
250
+ name: "update",
251
+ summary: "Update a work item's state.",
252
+ usage: "yui task work update <id> <todo|running|done|failed> [--summary <text>]",
253
+ options: ["--summary"],
254
+ argumentValues: { 1: ["todo", "running", "done", "failed"] }
255
+ },
256
+ {
257
+ name: "dispatch",
258
+ summary: "Dispatch a work item to its Role.",
259
+ usage: "yui task work dispatch <id> [--input <text>]",
260
+ options: ["--input"]
261
+ }
262
+ ]
263
+ },
264
+ {
265
+ name: "run",
266
+ summary: "Inspect and control Agent Runs.",
267
+ sections: [{ id: "manage", title: "Commands", entries: ["list", "retry", "yield"] }],
268
+ children: [
269
+ { name: "list", summary: "List Runs for a work item.", usage: "yui task run list <work>" },
270
+ { name: "retry", summary: "Retry a failed Run.", usage: "yui task run retry <run>" },
271
+ {
272
+ name: "yield",
273
+ summary: "Complete an active Run and wake the Leader.",
274
+ usage: "yui task run yield <run> --summary <text>",
275
+ options: ["--summary"]
276
+ }
277
+ ]
278
+ },
279
+ {
280
+ name: "brief",
281
+ summary: "Manage the Task Brief, the authoritative summary of current task state.",
282
+ sections: [{ id: "manage", title: "Commands", entries: ["show", "update"] }],
283
+ children: [
284
+ { name: "show", summary: "Show the Task Brief.", usage: "yui task brief show <task>" },
285
+ {
286
+ name: "update",
287
+ summary: "Create or update the Task Brief.",
288
+ usage: "yui task brief update <task> [--objective <text>] [--boundary <text> ...] [--focus <text>] [--leader-summary <text>]",
289
+ options: ["--objective", "--boundary", "--focus", "--leader-summary"]
290
+ }
291
+ ]
292
+ },
293
+ {
294
+ name: "decision",
295
+ summary: "Record and supersede durable Task decisions.",
296
+ sections: [{ id: "manage", title: "Commands", entries: ["record", "list", "show", "supersede"] }],
297
+ children: [
298
+ {
299
+ name: "record",
300
+ summary: "Record a new active Decision.",
301
+ usage: "yui task decision record <task> --title <text> --rationale <text>",
302
+ options: ["--title", "--rationale"]
303
+ },
304
+ {
305
+ name: "list",
306
+ summary: "List Decisions for a Task.",
307
+ usage: "yui task decision list <task> [--status active|superseded]",
308
+ options: ["--status"],
309
+ optionValues: { "--status": ["active", "superseded"] }
310
+ },
311
+ { name: "show", summary: "Show one Decision.", usage: "yui task decision show <task> <decision>" },
312
+ {
313
+ name: "supersede",
314
+ summary: "Mark a Decision as superseded.",
315
+ usage: "yui task decision supersede <task> <decision> --reason <text>",
316
+ options: ["--reason"]
317
+ }
318
+ ]
319
+ },
320
+ {
321
+ name: "milestone",
322
+ summary: "Append immutable Milestone records for completed progress.",
323
+ sections: [{ id: "manage", title: "Commands", entries: ["add", "list", "show"] }],
324
+ children: [
325
+ {
326
+ name: "add",
327
+ summary: "Append a Milestone to a Task.",
328
+ usage: "yui task milestone add <task> --title <text> --summary <text>",
329
+ options: ["--title", "--summary"]
330
+ },
331
+ { name: "list", summary: "List Milestones for a Task.", usage: "yui task milestone list <task>" },
332
+ { name: "show", summary: "Show one Milestone.", usage: "yui task milestone show <task> <milestone>" }
333
+ ]
334
+ },
335
+ {
336
+ name: "event",
337
+ summary: "Inspect the durable Task event history.",
338
+ sections: [{ id: "manage", title: "Commands", entries: ["list", "show"] }],
339
+ children: [
340
+ { name: "list", summary: "List Task events.", usage: "yui task event list <task>" },
341
+ { name: "show", summary: "Show one Task event.", usage: "yui task event show <task> <event>" }
342
+ ]
343
+ },
344
+ { name: "enter", summary: "Enter a Task Role, defaulting to Leader.", usage: "yui task enter <task> [role]" }
345
+ ];
346
+ export const ROOT_COMMAND = buildNode({
347
+ name: "yui",
348
+ summary: "Coordinate native Agent CLI sessions through tmux.",
349
+ usage: "yui [--json] <command>",
350
+ sections: [
351
+ { id: "general", title: "General", entries: ["help", "version", "update", "setup", "doctor", "completion"] },
352
+ { id: "workflow", title: "Workflow", entries: ["operator", "repository", "task"] },
353
+ { id: "configuration", title: "Configuration", entries: ["agent", "role"] },
354
+ { id: "operations", title: "Operations", entries: ["controller", "jobs"] },
355
+ { id: "internal", title: "Internal", entries: ["internal"] }
356
+ ],
357
+ children: [
358
+ { name: "help", summary: "Show root or scoped command help.", usage: "yui help [command ...]", commandPathArguments: true },
359
+ { name: "version", summary: "Print the installed Yui version." },
360
+ { name: "update", summary: "Install the latest published Yui package globally." },
361
+ { name: "setup", summary: "Initialize or update Yui configuration." },
362
+ { name: "doctor", summary: "Check Yui dependencies and file state." },
363
+ {
364
+ name: "completion",
365
+ summary: "Interactively configure shell completion.",
366
+ executable: true,
367
+ acceptsArguments: false,
368
+ usage: ["yui completion", "yui completion <bash|zsh|fish>"],
369
+ sections: [
370
+ { id: "shells", title: "Shells", entries: ["bash", "zsh", "fish"] },
371
+ { id: "internal", title: "Internal", entries: ["candidates"] }
372
+ ],
373
+ children: [
374
+ { name: "bash", summary: "Interactively configure Bash completion." },
375
+ { name: "zsh", summary: "Interactively configure Zsh completion." },
376
+ { name: "fish", summary: "Interactively configure Fish completion." },
377
+ {
378
+ name: "candidates",
379
+ summary: "Resolve internal dynamic completion candidates.",
380
+ usage: "yui completion candidates <prefix> -- <words...>",
381
+ hidden: true
382
+ }
383
+ ]
384
+ },
385
+ {
386
+ name: "controller",
387
+ summary: "Inspect, stop, or restart the local Controller.",
388
+ sections: [{ id: "lifecycle", title: "Commands", entries: ["status", "stop", "restart"] }],
389
+ children: [
390
+ { name: "status", summary: "Show Controller status." },
391
+ { name: "stop", summary: "Stop the Controller." },
392
+ { name: "restart", summary: "Restart internal services without stopping tmux sessions." }
393
+ ]
394
+ },
395
+ {
396
+ name: "operator",
397
+ summary: "Use the persistent Operator Role.",
398
+ sections: [{ id: "workflow", title: "Commands", entries: ["enter", "submit"] }],
399
+ children: [
400
+ { name: "enter", summary: "Enter the Operator's native session." },
401
+ {
402
+ name: "submit",
403
+ summary: "Submit work through the Operator.",
404
+ usage: "yui operator submit <body> [--task <id>]",
405
+ options: ["--task"]
406
+ }
407
+ ]
408
+ },
409
+ {
410
+ name: "repository",
411
+ summary: "Manage Git repositories available to Tasks.",
412
+ sections: [{ id: "manage", title: "Commands", entries: ["add", "list"] }],
413
+ children: [
414
+ {
415
+ name: "add",
416
+ summary: "Register a Git repository.",
417
+ usage: "yui repository add <name> <path> [--base <ref>]",
418
+ options: ["--base"],
419
+ fileArguments: [1]
420
+ },
421
+ { name: "list", summary: "List registered repositories." }
422
+ ]
423
+ },
424
+ {
425
+ name: "agent",
426
+ summary: "Manage configured native Agent CLIs.",
427
+ sections: [
428
+ { id: "inspect", title: "Inspect", entries: ["list", "show"] },
429
+ { id: "manage", title: "Manage", entries: ["add", "update", "remove"] }
430
+ ],
431
+ children: agentChildren
432
+ },
433
+ {
434
+ name: "role",
435
+ summary: "Manage reusable global Roles and their native sessions.",
436
+ sections: [
437
+ { id: "inspect", title: "Inspect", entries: ["list", "show"] },
438
+ { id: "manage", title: "Manage", entries: ["add", "update", "remove", "bind"] },
439
+ { id: "sessions", title: "Sessions", entries: ["enter", "session"] }
440
+ ],
441
+ children: roleChildren
442
+ },
443
+ {
444
+ name: "task",
445
+ summary: "Manage Tasks, Roles, work items, and Runs.",
446
+ sections: [
447
+ { id: "lifecycle", title: "Lifecycle", entries: ["create", "update", "activate", "complete", "reopen", "list", "show", "context", "archive", "reconcile"] },
448
+ { id: "collaboration", title: "Collaboration", entries: ["message", "input", "role", "work", "run", "enter"] },
449
+ { id: "knowledge", title: "Task Knowledge", entries: ["brief", "decision", "milestone", "event"] }
450
+ ],
451
+ children: taskChildren
452
+ },
453
+ {
454
+ name: "jobs",
455
+ summary: "Inspect scheduler wake and recovery records.",
456
+ sections: [{ id: "manage", title: "Commands", entries: ["list", "retry"] }],
457
+ children: [
458
+ { name: "list", summary: "List scheduler wake and recovery records." },
459
+ { name: "retry", summary: "Retry a failed Leader recovery.", usage: "yui jobs retry <id>" }
460
+ ]
461
+ },
462
+ {
463
+ name: "internal",
464
+ summary: "Internal Yui callbacks.",
465
+ hidden: true,
466
+ sections: [{ id: "callbacks", title: "Callbacks", entries: ["session-notify"] }],
467
+ children: [{
468
+ name: "session-notify",
469
+ summary: "Record a structured native session notification.",
470
+ usage: "yui internal session-notify <payload>"
471
+ }]
472
+ }
473
+ ]
474
+ });
475
+ export function visibleChildren(node) {
476
+ const byName = new Map(node.children.map((child) => [child.name, child]));
477
+ return node.sections.flatMap((section) => section.entries.flatMap((entry) => {
478
+ const child = byName.get(entry);
479
+ return child === undefined || child.hidden ? [] : [child];
480
+ }));
481
+ }
482
+ export function visibleCommandSections(node) {
483
+ const children = new Map(node.children.map((child) => [child.name, child]));
484
+ const values = new Map(node.values.map((value) => [value.name, value]));
485
+ return node.sections.flatMap((section) => {
486
+ const entries = section.entries.flatMap((entry) => {
487
+ const child = children.get(entry);
488
+ if (child !== undefined)
489
+ return child.hidden ? [] : [child];
490
+ const value = values.get(entry);
491
+ return value === undefined ? [] : [value];
492
+ });
493
+ return entries.length === 0 ? [] : [{ id: section.id, title: section.title, entries }];
494
+ });
495
+ }
496
+ export function orderedImmediateTokens(node) {
497
+ return visibleCommandSections(node).flatMap((section) => section.entries.map((entry) => entry.name));
498
+ }
499
+ export function findChild(node, name) {
500
+ return node.children.find((child) => child.name === name);
501
+ }
502
+ export function findCommandNode(path) {
503
+ let node = ROOT_COMMAND;
504
+ for (const segment of path[0] === ROOT_COMMAND.name ? path.slice(1) : path) {
505
+ const child = findChild(node, segment);
506
+ if (child === undefined)
507
+ return undefined;
508
+ node = child;
509
+ }
510
+ return node;
511
+ }
512
+ export const findCommand = findCommandNode;
513
+ export function validateCommandCatalog(root) {
514
+ const reservedAliases = new Set(["-h", "--help", "-help", "-v", "--version"]);
515
+ const commandPathProviders = [];
516
+ const visit = (node) => {
517
+ if (node.summary.trim().length === 0)
518
+ throw new Error(`Command summary is required: ${node.path.join(" ")}`);
519
+ if (node.usage.length === 0)
520
+ throw new Error(`Command usage is required: ${node.path.join(" ")}`);
521
+ if (node.commandPathArguments) {
522
+ commandPathProviders.push(node);
523
+ const ownsOtherCompletionMetadata = node.kind !== "leaf"
524
+ || node.hidden
525
+ || node.children.length > 0
526
+ || node.values.length > 0
527
+ || node.sections.length > 0
528
+ || node.options.length > 0
529
+ || node.hiddenOptions.length > 0
530
+ || Object.keys(node.optionValues).length > 0
531
+ || Object.keys(node.argumentValues).length > 0
532
+ || node.fileOptions.length > 0
533
+ || node.fileArguments.length > 0
534
+ || node.executableOptions.length > 0;
535
+ if (ownsOtherCompletionMetadata) {
536
+ throw new Error(`Command-path provider must be a visible metadata-free leaf: ${node.path.join(" ")}`);
537
+ }
538
+ }
539
+ const immediate = new Set();
540
+ for (const child of node.children) {
541
+ if (immediate.has(child.name))
542
+ throw new Error(`Duplicate command path: ${child.path.join(" ")}`);
543
+ if (reservedAliases.has(child.name))
544
+ throw new Error(`Reserved alias token is not allowed: ${child.path.join(" ")}`);
545
+ immediate.add(child.name);
546
+ }
547
+ for (const value of node.values) {
548
+ if (value.name.trim().length === 0)
549
+ throw new Error(`Command value name is required: ${node.path.join(" ")}`);
550
+ if (value.summary.trim().length === 0)
551
+ throw new Error(`Command value summary is required: ${[...node.path, value.name].join(" ")}`);
552
+ if (immediate.has(value.name))
553
+ throw new Error(`Duplicate command token: ${[...node.path, value.name].join(" ")}`);
554
+ immediate.add(value.name);
555
+ }
556
+ const options = new Set();
557
+ for (const option of [...node.options, ...node.hiddenOptions]) {
558
+ if (reservedAliases.has(option))
559
+ throw new Error(`Reserved alias token is not allowed: ${[...node.path, option].join(" ")}`);
560
+ if (options.has(option) || immediate.has(option))
561
+ throw new Error(`Duplicate command token: ${[...node.path, option].join(" ")}`);
562
+ options.add(option);
563
+ }
564
+ const sectionIds = new Set();
565
+ const sectionEntries = new Set();
566
+ for (const section of node.sections) {
567
+ if (sectionIds.has(section.id))
568
+ throw new Error(`Duplicate command section: ${[...node.path, section.id].join(" ")}`);
569
+ if (section.id.trim().length === 0 || section.title.trim().length === 0) {
570
+ throw new Error(`Command section id and title are required: ${node.path.join(" ")}`);
571
+ }
572
+ sectionIds.add(section.id);
573
+ for (const entry of section.entries) {
574
+ if (!immediate.has(entry))
575
+ throw new Error(`Unknown section entry: ${[...node.path, entry].join(" ")}`);
576
+ if (sectionEntries.has(entry))
577
+ throw new Error(`Duplicate section entry: ${[...node.path, entry].join(" ")}`);
578
+ sectionEntries.add(entry);
579
+ }
580
+ }
581
+ for (const token of immediate) {
582
+ if (!sectionEntries.has(token))
583
+ throw new Error(`Missing from command sections: ${[...node.path, token].join(" ")}`);
584
+ }
585
+ for (const [option, values] of Object.entries(node.optionValues)) {
586
+ if (!options.has(option))
587
+ throw new Error(`Option values reference unknown option: ${[...node.path, option].join(" ")}`);
588
+ if (new Set(values).size !== values.length)
589
+ throw new Error(`Duplicate option value: ${[...node.path, option].join(" ")}`);
590
+ }
591
+ const argumentValuePositions = new Set();
592
+ for (const [positionText, values] of Object.entries(node.argumentValues)) {
593
+ const position = Number(positionText);
594
+ if (!Number.isInteger(position) || position < 0)
595
+ throw new Error(`Argument value positions must be non-negative integers: ${node.path.join(" ")}`);
596
+ argumentValuePositions.add(position);
597
+ if (new Set(values).size !== values.length)
598
+ throw new Error(`Duplicate argument value: ${node.path.join(" ")} argument ${position}`);
599
+ }
600
+ if (new Set(node.fileOptions).size !== node.fileOptions.length)
601
+ throw new Error(`Duplicate file completion option: ${node.path.join(" ")}`);
602
+ for (const option of node.fileOptions) {
603
+ if (!options.has(option))
604
+ throw new Error(`File completion references unknown option: ${[...node.path, option].join(" ")}`);
605
+ }
606
+ if (new Set(node.executableOptions).size !== node.executableOptions.length)
607
+ throw new Error(`Duplicate executable completion option: ${node.path.join(" ")}`);
608
+ for (const option of node.executableOptions) {
609
+ if (!options.has(option))
610
+ throw new Error(`Executable completion references unknown option: ${[...node.path, option].join(" ")}`);
611
+ }
612
+ for (const option of options) {
613
+ const owners = Number(Object.hasOwn(node.optionValues, option))
614
+ + Number(node.fileOptions.includes(option))
615
+ + Number(node.executableOptions.includes(option));
616
+ if (owners > 1)
617
+ throw new Error(`Multiple completion owners for option: ${[...node.path, option].join(" ")}`);
618
+ }
619
+ if (new Set(node.fileArguments).size !== node.fileArguments.length
620
+ || node.fileArguments.some((index) => !Number.isInteger(index) || index < 0)) {
621
+ throw new Error(`File argument positions must be unique non-negative integers: ${node.path.join(" ")}`);
622
+ }
623
+ for (const position of node.fileArguments) {
624
+ if (argumentValuePositions.has(position))
625
+ throw new Error(`Multiple completion owners for argument ${position}: ${node.path.join(" ")}`);
626
+ }
627
+ node.children.forEach(visit);
628
+ };
629
+ visit(root);
630
+ if (commandPathProviders.length > 1)
631
+ throw new Error(`Multiple command-path providers are not allowed: ${root.path.join(" ")}`);
632
+ const provider = commandPathProviders[0];
633
+ if (provider !== undefined && provider.path.length !== root.path.length + 1) {
634
+ throw new Error(`Command-path provider must be a root command: ${provider.path.join(" ")}`);
635
+ }
636
+ }
637
+ export function listPublicCommandPaths(root = ROOT_COMMAND) {
638
+ const paths = [];
639
+ const visit = (node) => {
640
+ if (!node.hidden && node !== root)
641
+ paths.push(node.path.slice(1).join(" "));
642
+ node.children.filter((child) => !child.hidden).forEach(visit);
643
+ };
644
+ visit(root);
645
+ return paths;
646
+ }
647
+ validateCommandCatalog(ROOT_COMMAND);