sentinelayer-cli 0.6.2 → 0.8.1

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 (280) hide show
  1. package/README.md +1009 -996
  2. package/bin/create-sentinelayer.js +5 -5
  3. package/bin/sentinelayer-cli.js +4 -4
  4. package/bin/sl.js +5 -5
  5. package/package.json +64 -63
  6. package/src/agents/ai-governance/index.js +12 -0
  7. package/src/agents/ai-governance/tools/base.js +171 -0
  8. package/src/agents/ai-governance/tools/eval-regression.js +47 -0
  9. package/src/agents/ai-governance/tools/hitl-audit.js +81 -0
  10. package/src/agents/ai-governance/tools/index.js +52 -0
  11. package/src/agents/ai-governance/tools/prompt-drift.js +42 -0
  12. package/src/agents/ai-governance/tools/provenance-check.js +69 -0
  13. package/src/agents/backend/index.js +12 -0
  14. package/src/agents/backend/tools/base.js +189 -0
  15. package/src/agents/backend/tools/circuit-breaker-check.js +123 -0
  16. package/src/agents/backend/tools/idempotency-audit.js +105 -0
  17. package/src/agents/backend/tools/index.js +87 -0
  18. package/src/agents/backend/tools/retry-audit.js +132 -0
  19. package/src/agents/backend/tools/timeout-audit.js +144 -0
  20. package/src/agents/code-quality/index.js +12 -0
  21. package/src/agents/code-quality/tools/base.js +159 -0
  22. package/src/agents/code-quality/tools/complexity-measure.js +197 -0
  23. package/src/agents/code-quality/tools/coupling-analysis.js +81 -0
  24. package/src/agents/code-quality/tools/cycle-detect.js +49 -0
  25. package/src/agents/code-quality/tools/dep-graph.js +196 -0
  26. package/src/agents/code-quality/tools/index.js +89 -0
  27. package/src/agents/data-layer/index.js +12 -0
  28. package/src/agents/data-layer/tools/base.js +181 -0
  29. package/src/agents/data-layer/tools/index-audit.js +165 -0
  30. package/src/agents/data-layer/tools/index.js +83 -0
  31. package/src/agents/data-layer/tools/migration-scan.js +135 -0
  32. package/src/agents/data-layer/tools/query-explain.js +120 -0
  33. package/src/agents/data-layer/tools/tenancy-scan.js +166 -0
  34. package/src/agents/documentation/index.js +12 -0
  35. package/src/agents/documentation/tools/api-diff.js +91 -0
  36. package/src/agents/documentation/tools/base.js +151 -0
  37. package/src/agents/documentation/tools/dead-link-check.js +58 -0
  38. package/src/agents/documentation/tools/docstring-coverage.js +78 -0
  39. package/src/agents/documentation/tools/index.js +52 -0
  40. package/src/agents/documentation/tools/readme-freshness.js +61 -0
  41. package/src/agents/envelope/fix-cycle.js +45 -0
  42. package/src/agents/envelope/index.js +31 -0
  43. package/src/agents/envelope/loop.js +150 -0
  44. package/src/agents/envelope/pulse.js +18 -0
  45. package/src/agents/envelope/stream.js +40 -0
  46. package/src/agents/infrastructure/index.js +12 -0
  47. package/src/agents/infrastructure/tools/base.js +171 -0
  48. package/src/agents/infrastructure/tools/checkov-run.js +32 -0
  49. package/src/agents/infrastructure/tools/drift-detect.js +59 -0
  50. package/src/agents/infrastructure/tools/iam-least-priv-check.js +78 -0
  51. package/src/agents/infrastructure/tools/index.js +52 -0
  52. package/src/agents/infrastructure/tools/tflint-run.js +31 -0
  53. package/src/agents/jules/config/definition.js +160 -160
  54. package/src/agents/jules/config/system-prompt.js +182 -182
  55. package/src/agents/jules/error-intake.js +51 -51
  56. package/src/agents/jules/fix-cycle.js +17 -17
  57. package/src/agents/jules/loop.js +460 -450
  58. package/src/agents/jules/pulse.js +10 -10
  59. package/src/agents/jules/stream.js +187 -186
  60. package/src/agents/jules/swarm/file-scanner.js +74 -74
  61. package/src/agents/jules/swarm/index.js +11 -11
  62. package/src/agents/jules/swarm/orchestrator.js +362 -362
  63. package/src/agents/jules/swarm/pattern-hunter.js +123 -123
  64. package/src/agents/jules/swarm/sub-agent.js +315 -309
  65. package/src/agents/jules/tools/aidenid-email.js +189 -189
  66. package/src/agents/jules/tools/auth-audit.js +1708 -1691
  67. package/src/agents/jules/tools/dispatch.js +340 -335
  68. package/src/agents/jules/tools/file-edit.js +2 -2
  69. package/src/agents/jules/tools/file-read.js +2 -2
  70. package/src/agents/jules/tools/frontend-analyze.js +570 -570
  71. package/src/agents/jules/tools/glob.js +2 -2
  72. package/src/agents/jules/tools/grep.js +2 -2
  73. package/src/agents/jules/tools/index.js +29 -29
  74. package/src/agents/jules/tools/path-guards.js +2 -2
  75. package/src/agents/jules/tools/runtime-audit.js +507 -507
  76. package/src/agents/jules/tools/shell.js +2 -2
  77. package/src/agents/jules/tools/url-policy.js +100 -100
  78. package/src/agents/mode.js +113 -0
  79. package/src/agents/observability/index.js +12 -0
  80. package/src/agents/observability/tools/alert-audit.js +39 -0
  81. package/src/agents/observability/tools/base.js +181 -0
  82. package/src/agents/observability/tools/dashboard-gap.js +42 -0
  83. package/src/agents/observability/tools/index.js +54 -0
  84. package/src/agents/observability/tools/log-schema-check.js +74 -0
  85. package/src/agents/observability/tools/span-coverage.js +74 -0
  86. package/src/agents/persona-visuals.js +102 -61
  87. package/src/agents/release/index.js +12 -0
  88. package/src/agents/release/tools/base.js +181 -0
  89. package/src/agents/release/tools/changelog-diff.js +86 -0
  90. package/src/agents/release/tools/feature-flag-audit.js +126 -0
  91. package/src/agents/release/tools/index.js +61 -0
  92. package/src/agents/release/tools/rollback-verify.js +129 -0
  93. package/src/agents/release/tools/semver-check.js +109 -0
  94. package/src/agents/reliability/index.js +12 -0
  95. package/src/agents/reliability/tools/backpressure-check.js +129 -0
  96. package/src/agents/reliability/tools/base.js +181 -0
  97. package/src/agents/reliability/tools/chaos-probe.js +109 -0
  98. package/src/agents/reliability/tools/graceful-degradation-check.js +114 -0
  99. package/src/agents/reliability/tools/health-check-audit.js +111 -0
  100. package/src/agents/reliability/tools/index.js +87 -0
  101. package/src/agents/run-persona.js +109 -0
  102. package/src/agents/security/index.js +12 -0
  103. package/src/agents/security/tools/authz-audit.js +134 -0
  104. package/src/agents/security/tools/base.js +190 -0
  105. package/src/agents/security/tools/crypto-review.js +175 -0
  106. package/src/agents/security/tools/index.js +97 -0
  107. package/src/agents/security/tools/sast-scan.js +175 -0
  108. package/src/agents/security/tools/secrets-scan.js +216 -0
  109. package/src/agents/shared-tools/dispatch-core.js +320 -315
  110. package/src/agents/shared-tools/file-edit.js +180 -180
  111. package/src/agents/shared-tools/file-read.js +100 -100
  112. package/src/agents/shared-tools/glob.js +168 -168
  113. package/src/agents/shared-tools/grep.js +228 -228
  114. package/src/agents/shared-tools/index.js +46 -46
  115. package/src/agents/shared-tools/path-guards.js +161 -161
  116. package/src/agents/shared-tools/shell.js +383 -383
  117. package/src/agents/supply-chain/index.js +12 -0
  118. package/src/agents/supply-chain/tools/attestation-check.js +42 -0
  119. package/src/agents/supply-chain/tools/base.js +151 -0
  120. package/src/agents/supply-chain/tools/index.js +52 -0
  121. package/src/agents/supply-chain/tools/lockfile-integrity.js +73 -0
  122. package/src/agents/supply-chain/tools/package-verify.js +56 -0
  123. package/src/agents/supply-chain/tools/sbom-diff.js +34 -0
  124. package/src/agents/testing/index.js +12 -0
  125. package/src/agents/testing/tools/base.js +202 -0
  126. package/src/agents/testing/tools/coverage-gap.js +144 -0
  127. package/src/agents/testing/tools/flake-detect.js +125 -0
  128. package/src/agents/testing/tools/index.js +85 -0
  129. package/src/agents/testing/tools/mutation-test.js +143 -0
  130. package/src/agents/testing/tools/snapshot-diff.js +103 -0
  131. package/src/ai/aidenid.js +1021 -1009
  132. package/src/ai/client.js +553 -553
  133. package/src/ai/domain-target-store.js +268 -268
  134. package/src/ai/identity-store.js +270 -270
  135. package/src/ai/proxy.js +137 -137
  136. package/src/ai/site-store.js +145 -145
  137. package/src/audit/agents/architecture.js +180 -180
  138. package/src/audit/agents/compliance.js +179 -179
  139. package/src/audit/agents/documentation.js +165 -165
  140. package/src/audit/agents/performance.js +145 -145
  141. package/src/audit/agents/security.js +215 -215
  142. package/src/audit/agents/testing.js +172 -172
  143. package/src/audit/orchestrator.js +557 -557
  144. package/src/audit/package.js +204 -204
  145. package/src/audit/registry.js +284 -284
  146. package/src/audit/replay.js +103 -103
  147. package/src/auth/gate.js +428 -371
  148. package/src/auth/http.js +681 -611
  149. package/src/auth/service.js +1106 -1106
  150. package/src/auth/session-store.js +813 -813
  151. package/src/cli.js +257 -252
  152. package/src/commands/ai/identity-lifecycle.js +1338 -1338
  153. package/src/commands/ai/provision-governance.js +1272 -1272
  154. package/src/commands/ai/shared.js +147 -147
  155. package/src/commands/ai.js +11 -11
  156. package/src/commands/apply.js +12 -12
  157. package/src/commands/audit.js +1171 -1166
  158. package/src/commands/auth.js +419 -419
  159. package/src/commands/chat.js +184 -191
  160. package/src/commands/config.js +184 -184
  161. package/src/commands/cost.js +311 -311
  162. package/src/commands/daemon/core.js +850 -850
  163. package/src/commands/daemon/extended.js +1048 -1048
  164. package/src/commands/daemon/shared.js +213 -213
  165. package/src/commands/daemon.js +11 -11
  166. package/src/commands/guide.js +174 -174
  167. package/src/commands/ingest.js +58 -58
  168. package/src/commands/init.js +55 -55
  169. package/src/commands/legacy-args.js +20 -10
  170. package/src/commands/mcp.js +461 -461
  171. package/src/commands/omargate.js +63 -29
  172. package/src/commands/persona.js +65 -20
  173. package/src/commands/plugin.js +260 -260
  174. package/src/commands/policy.js +132 -132
  175. package/src/commands/prompt.js +238 -238
  176. package/src/commands/review.js +704 -704
  177. package/src/commands/scan.js +865 -872
  178. package/src/commands/session.js +1238 -0
  179. package/src/commands/spec.js +771 -716
  180. package/src/commands/swarm.js +651 -651
  181. package/src/commands/telemetry.js +202 -202
  182. package/src/commands/watch.js +511 -511
  183. package/src/config/agent-dictionary.js +182 -182
  184. package/src/config/io.js +56 -56
  185. package/src/config/paths.js +18 -18
  186. package/src/config/schema.js +55 -55
  187. package/src/config/service.js +184 -184
  188. package/src/coord/events-log.js +141 -0
  189. package/src/coord/handshake.js +719 -0
  190. package/src/coord/index.js +35 -0
  191. package/src/coord/paths.js +84 -0
  192. package/src/coord/priority.js +62 -0
  193. package/src/coord/tarjan.js +157 -0
  194. package/src/cost/budget.js +235 -235
  195. package/src/cost/history.js +188 -188
  196. package/src/cost/tokenizer.js +160 -0
  197. package/src/cost/tracker.js +232 -171
  198. package/src/daemon/artifact-lineage.js +896 -534
  199. package/src/daemon/assignment-ledger.js +1083 -770
  200. package/src/daemon/ast-drift.js +496 -0
  201. package/src/daemon/ast-parser-layer.js +258 -258
  202. package/src/daemon/budget-governor.js +633 -633
  203. package/src/daemon/callgraph-overlay.js +646 -646
  204. package/src/daemon/error-worker.js +1209 -626
  205. package/src/daemon/fix-cycle.js +384 -377
  206. package/src/daemon/hybrid-mapper.js +929 -929
  207. package/src/daemon/ingest-refresh.js +79 -11
  208. package/src/daemon/jira-lifecycle.js +767 -632
  209. package/src/daemon/operator-control.js +657 -657
  210. package/src/daemon/pulse.js +327 -327
  211. package/src/daemon/reliability-lane.js +471 -471
  212. package/src/daemon/scope-engine.js +1068 -0
  213. package/src/daemon/watchdog.js +971 -971
  214. package/src/events/schema.js +190 -0
  215. package/src/guide/generator.js +316 -316
  216. package/src/ingest/engine.js +933 -918
  217. package/src/ingest/ownership.js +380 -0
  218. package/src/interactive/index.js +97 -97
  219. package/src/legacy-cli.js +3228 -2994
  220. package/src/mcp/registry.js +695 -695
  221. package/src/memory/blackboard.js +301 -301
  222. package/src/memory/retrieval.js +581 -581
  223. package/src/orchestrator/kai-chen.js +126 -0
  224. package/src/plugin/manifest.js +553 -553
  225. package/src/policy/packs.js +144 -144
  226. package/src/prompt/generator.js +136 -118
  227. package/src/review/ai-review.js +672 -679
  228. package/src/review/compliance-pack.js +389 -0
  229. package/src/review/investor-dd-config.js +54 -0
  230. package/src/review/investor-dd-file-loop.js +303 -0
  231. package/src/review/investor-dd-file-router.js +406 -0
  232. package/src/review/investor-dd-html-report.js +233 -0
  233. package/src/review/investor-dd-notification.js +120 -0
  234. package/src/review/investor-dd-orchestrator.js +405 -0
  235. package/src/review/investor-dd-persona-runner.js +275 -0
  236. package/src/review/live-validator.js +253 -0
  237. package/src/review/local-review.js +1351 -1305
  238. package/src/review/omargate-interactive.js +68 -68
  239. package/src/review/omargate-orchestrator.js +492 -300
  240. package/src/review/persona-prompts.js +484 -296
  241. package/src/review/reconciliation-rules.js +329 -0
  242. package/src/review/replay.js +235 -235
  243. package/src/review/report.js +664 -664
  244. package/src/review/reproducibility-chain.js +136 -0
  245. package/src/review/scan-modes.js +147 -42
  246. package/src/review/spec-binding.js +487 -487
  247. package/src/scaffold/generator.js +67 -67
  248. package/src/scaffold/templates.js +150 -150
  249. package/src/scan/generator.js +418 -418
  250. package/src/scan/gh-secrets.js +107 -107
  251. package/src/session/agent-registry.js +359 -0
  252. package/src/session/analytics.js +479 -0
  253. package/src/session/daemon.js +1396 -0
  254. package/src/session/file-locks.js +666 -0
  255. package/src/session/paths.js +37 -0
  256. package/src/session/recap.js +567 -0
  257. package/src/session/redact.js +82 -0
  258. package/src/session/runtime-bridge.js +762 -0
  259. package/src/session/scoring.js +406 -0
  260. package/src/session/setup-guides.js +304 -0
  261. package/src/session/store.js +704 -0
  262. package/src/session/stream.js +333 -0
  263. package/src/session/sync.js +753 -0
  264. package/src/session/tasks.js +1054 -0
  265. package/src/session/templates.js +188 -0
  266. package/src/spec/generator.js +619 -519
  267. package/src/spec/regenerate.js +237 -237
  268. package/src/spec/templates.js +91 -91
  269. package/src/swarm/dashboard.js +247 -247
  270. package/src/swarm/factory.js +363 -363
  271. package/src/swarm/pentest.js +934 -934
  272. package/src/swarm/registry.js +419 -419
  273. package/src/swarm/report.js +158 -158
  274. package/src/swarm/runtime.js +569 -576
  275. package/src/swarm/scenario-dsl.js +272 -272
  276. package/src/telemetry/ledger.js +302 -302
  277. package/src/telemetry/session-tracker.js +234 -234
  278. package/src/telemetry/sync.js +203 -203
  279. package/src/ui/command-hints.js +13 -13
  280. package/src/ui/markdown.js +220 -220
@@ -1,850 +1,850 @@
1
- import path from "node:path";
2
-
3
- import pc from "picocolors";
4
-
5
- import { WORK_ITEM_STATUSES, listErrorQueue } from "../../daemon/error-worker.js";
6
- import {
7
- ASSIGNMENT_STATUSES,
8
- claimAssignment,
9
- heartbeatAssignment,
10
- listAssignments,
11
- reassignAssignment,
12
- releaseAssignment,
13
- } from "../../daemon/assignment-ledger.js";
14
- import {
15
- JIRA_STATUSES,
16
- commentJiraIssue,
17
- listJiraIssues,
18
- openJiraIssue,
19
- startJiraLifecycle,
20
- transitionJiraIssue,
21
- } from "../../daemon/jira-lifecycle.js";
22
- import {
23
- DAEMON_BUDGET_LIFECYCLE_STATES,
24
- applyDaemonBudgetCheck,
25
- listBudgetStates,
26
- } from "../../daemon/budget-governor.js";
27
- import {
28
- OPERATOR_STOP_MODES,
29
- applyOperatorStopControl,
30
- buildOperatorControlSnapshot,
31
- normalizeOperatorStopMode,
32
- } from "../../daemon/operator-control.js";
33
- import {
34
- parseBoolean,
35
- parseCsv,
36
- parseMetadata,
37
- parsePositiveInteger,
38
- printAssignmentSummary,
39
- printBudgetSummary,
40
- printControlPlaneSummary,
41
- printJiraSummary,
42
- printQueueSummary,
43
- shouldEmitJson,
44
- } from "./shared.js";
45
-
46
- export function registerDaemonCoreCommands(daemon) {
47
- daemon
48
- .option("--path <path>", "Workspace path for artifact/config resolution", ".")
49
- .option("--output-dir <path>", "Optional output dir override for daemon artifacts")
50
- .option("--json", "Emit machine-readable output")
51
- .action(async (options, command) => {
52
- const targetPath = path.resolve(process.cwd(), String(options.path || "."));
53
- const limit = 20;
54
- const listed = await listErrorQueue({
55
- targetPath,
56
- outputDir: options.outputDir,
57
- limit,
58
- });
59
- const payload = {
60
- command: "daemon",
61
- targetPath,
62
- queuePath: listed.queuePath,
63
- statePath: listed.statePath,
64
- streamPath: listed.streamPath,
65
- totalCount: listed.totalCount,
66
- visibleCount: listed.items.length,
67
- items: listed.items,
68
- workerState: listed.state,
69
- };
70
- if (shouldEmitJson(options, command)) {
71
- console.log(JSON.stringify(payload, null, 2));
72
- return;
73
- }
74
- printQueueSummary(payload);
75
- });
76
-
77
- const assign = daemon
78
- .command("assign")
79
- .description("Global assignment ledger for daemon queue ownership and lease lifecycle");
80
-
81
- assign
82
- .option("--path <path>", "Workspace path for artifact/config resolution", ".")
83
- .option("--output-dir <path>", "Optional output dir override for daemon artifacts")
84
- .option("--json", "Emit machine-readable output")
85
- .action(async (options, command) => {
86
- const targetPath = path.resolve(process.cwd(), String(options.path || "."));
87
- const listed = await listAssignments({
88
- targetPath,
89
- outputDir: options.outputDir,
90
- limit: 20,
91
- });
92
- const payload = {
93
- command: "daemon assign",
94
- targetPath,
95
- ledgerPath: listed.ledgerPath,
96
- queuePath: listed.queuePath,
97
- eventsPath: listed.eventsPath,
98
- totalCount: listed.totalCount,
99
- visibleCount: listed.assignments.length,
100
- assignments: listed.assignments,
101
- };
102
- if (shouldEmitJson(options, command)) {
103
- console.log(JSON.stringify(payload, null, 2));
104
- return;
105
- }
106
- printAssignmentSummary(payload);
107
- });
108
-
109
- assign
110
- .command("list")
111
- .description("List assignment ledger records")
112
- .option("--path <path>", "Workspace path for artifact/config resolution", ".")
113
- .option("--output-dir <path>", "Optional output dir override for daemon artifacts")
114
- .option(
115
- "--status <csv>",
116
- `Optional assignment status filter (${ASSIGNMENT_STATUSES.join(", ")})`
117
- )
118
- .option("--agent <identity>", "Filter by assigned agent identity")
119
- .option("--include-expired <bool>", "Include expired active leases (true/false)", "true")
120
- .option("--limit <n>", "Maximum assignments to return", "50")
121
- .option("--json", "Emit machine-readable output")
122
- .action(async (options, command) => {
123
- const targetPath = path.resolve(process.cwd(), String(options.path || "."));
124
- const listed = await listAssignments({
125
- targetPath,
126
- outputDir: options.outputDir,
127
- statuses: parseCsv(options.status),
128
- agentIdentity: options.agent,
129
- includeExpired: parseBoolean(options.includeExpired, true),
130
- limit: parsePositiveInteger(options.limit, "limit", 50),
131
- });
132
- const payload = {
133
- command: "daemon assign list",
134
- targetPath,
135
- statuses: parseCsv(options.status),
136
- agentIdentity: options.agent || null,
137
- includeExpired: parseBoolean(options.includeExpired, true),
138
- ledgerPath: listed.ledgerPath,
139
- queuePath: listed.queuePath,
140
- eventsPath: listed.eventsPath,
141
- totalCount: listed.totalCount,
142
- visibleCount: listed.assignments.length,
143
- assignments: listed.assignments,
144
- };
145
- if (shouldEmitJson(options, command)) {
146
- console.log(JSON.stringify(payload, null, 2));
147
- return;
148
- }
149
- printAssignmentSummary(payload);
150
- });
151
-
152
- assign
153
- .command("claim")
154
- .description("Claim a queue work item for an agent identity with lease metadata")
155
- .argument("<workItemId>", "Queue work item id")
156
- .option("--path <path>", "Workspace path for artifact/config resolution", ".")
157
- .option("--output-dir <path>", "Optional output dir override for daemon artifacts")
158
- .requiredOption("--agent <identity>", "Assigned agent identity (email or handle)")
159
- .option("--lease-ttl-seconds <n>", "Lease duration in seconds", "1800")
160
- .option("--stage <stage>", "Current workflow stage", "triage")
161
- .option("--run-id <id>", "Optional runtime run id")
162
- .option("--jira-issue-key <key>", "Optional Jira issue key")
163
- .option("--budget-snapshot-json <json>", "Optional budget snapshot JSON object")
164
- .option("--json", "Emit machine-readable output")
165
- .action(async (workItemId, options, command) => {
166
- const targetPath = path.resolve(process.cwd(), String(options.path || "."));
167
- const claimed = await claimAssignment({
168
- targetPath,
169
- outputDir: options.outputDir,
170
- workItemId,
171
- agentIdentity: options.agent,
172
- leaseTtlSeconds: parsePositiveInteger(options.leaseTtlSeconds, "lease-ttl-seconds", 1800),
173
- stage: options.stage,
174
- runId: options.runId,
175
- jiraIssueKey: options.jiraIssueKey,
176
- budgetSnapshot: parseMetadata(options.budgetSnapshotJson),
177
- });
178
- const payload = {
179
- command: "daemon assign claim",
180
- targetPath,
181
- ledgerPath: claimed.ledgerPath,
182
- queuePath: claimed.queuePath,
183
- eventsPath: claimed.eventsPath,
184
- assignment: claimed.assignment,
185
- };
186
- if (shouldEmitJson(options, command)) {
187
- console.log(JSON.stringify(payload, null, 2));
188
- return;
189
- }
190
- console.log(pc.bold("Assignment claimed"));
191
- console.log(
192
- `${claimed.assignment.workItemId} -> ${claimed.assignment.assignedAgentIdentity} (expires ${claimed.assignment.leaseExpiresAt})`
193
- );
194
- });
195
-
196
- assign
197
- .command("heartbeat")
198
- .description("Refresh lease heartbeat for an assigned work item")
199
- .argument("<workItemId>", "Queue work item id")
200
- .option("--path <path>", "Workspace path for artifact/config resolution", ".")
201
- .option("--output-dir <path>", "Optional output dir override for daemon artifacts")
202
- .requiredOption("--agent <identity>", "Assigned agent identity (email or handle)")
203
- .option("--lease-ttl-seconds <n>", "Lease duration in seconds", "1800")
204
- .option("--stage <stage>", "Current workflow stage")
205
- .option("--run-id <id>", "Optional runtime run id")
206
- .option("--jira-issue-key <key>", "Optional Jira issue key")
207
- .option("--budget-snapshot-json <json>", "Optional budget snapshot JSON object")
208
- .option("--json", "Emit machine-readable output")
209
- .action(async (workItemId, options, command) => {
210
- const targetPath = path.resolve(process.cwd(), String(options.path || "."));
211
- const heartbeat = await heartbeatAssignment({
212
- targetPath,
213
- outputDir: options.outputDir,
214
- workItemId,
215
- agentIdentity: options.agent,
216
- leaseTtlSeconds: parsePositiveInteger(options.leaseTtlSeconds, "lease-ttl-seconds", 1800),
217
- stage: options.stage,
218
- runId: options.runId,
219
- jiraIssueKey: options.jiraIssueKey,
220
- budgetSnapshot: parseMetadata(options.budgetSnapshotJson),
221
- });
222
- const payload = {
223
- command: "daemon assign heartbeat",
224
- targetPath,
225
- ledgerPath: heartbeat.ledgerPath,
226
- queuePath: heartbeat.queuePath,
227
- eventsPath: heartbeat.eventsPath,
228
- assignment: heartbeat.assignment,
229
- };
230
- if (shouldEmitJson(options, command)) {
231
- console.log(JSON.stringify(payload, null, 2));
232
- return;
233
- }
234
- console.log(pc.bold("Assignment heartbeat updated"));
235
- console.log(
236
- `${heartbeat.assignment.workItemId} -> ${heartbeat.assignment.assignedAgentIdentity} (expires ${heartbeat.assignment.leaseExpiresAt})`
237
- );
238
- });
239
-
240
- assign
241
- .command("release")
242
- .description("Release an assignment and transition work item status")
243
- .argument("<workItemId>", "Queue work item id")
244
- .option("--path <path>", "Workspace path for artifact/config resolution", ".")
245
- .option("--output-dir <path>", "Optional output dir override for daemon artifacts")
246
- .option("--agent <identity>", "Optional assigned agent identity check")
247
- .option("--status <status>", "Release status (QUEUED|DONE|BLOCKED|SQUASHED)", "QUEUED")
248
- .option("--stage <stage>", "Current workflow stage")
249
- .option("--run-id <id>", "Optional runtime run id")
250
- .option("--jira-issue-key <key>", "Optional Jira issue key")
251
- .option("--reason <reason>", "Optional release reason")
252
- .option("--budget-snapshot-json <json>", "Optional budget snapshot JSON object")
253
- .option("--json", "Emit machine-readable output")
254
- .action(async (workItemId, options, command) => {
255
- const targetPath = path.resolve(process.cwd(), String(options.path || "."));
256
- const released = await releaseAssignment({
257
- targetPath,
258
- outputDir: options.outputDir,
259
- workItemId,
260
- agentIdentity: options.agent,
261
- status: options.status,
262
- stage: options.stage,
263
- runId: options.runId,
264
- jiraIssueKey: options.jiraIssueKey,
265
- reason: options.reason,
266
- budgetSnapshot: parseMetadata(options.budgetSnapshotJson),
267
- });
268
- const payload = {
269
- command: "daemon assign release",
270
- targetPath,
271
- ledgerPath: released.ledgerPath,
272
- queuePath: released.queuePath,
273
- eventsPath: released.eventsPath,
274
- assignment: released.assignment,
275
- };
276
- if (shouldEmitJson(options, command)) {
277
- console.log(JSON.stringify(payload, null, 2));
278
- return;
279
- }
280
- console.log(pc.bold("Assignment released"));
281
- console.log(
282
- `${released.assignment.workItemId} status=${released.assignment.status} agent=${released.assignment.assignedAgentIdentity || "n/a"}`
283
- );
284
- });
285
-
286
- assign
287
- .command("reassign")
288
- .description("Reassign a work item lease to a different agent identity")
289
- .argument("<workItemId>", "Queue work item id")
290
- .option("--path <path>", "Workspace path for artifact/config resolution", ".")
291
- .option("--output-dir <path>", "Optional output dir override for daemon artifacts")
292
- .option("--from-agent <identity>", "Optional expected current agent identity")
293
- .requiredOption("--to-agent <identity>", "New assigned agent identity")
294
- .option("--lease-ttl-seconds <n>", "Lease duration in seconds", "1800")
295
- .option("--stage <stage>", "Current workflow stage", "triage")
296
- .option("--run-id <id>", "Optional runtime run id")
297
- .option("--jira-issue-key <key>", "Optional Jira issue key")
298
- .option("--budget-snapshot-json <json>", "Optional budget snapshot JSON object")
299
- .option("--json", "Emit machine-readable output")
300
- .action(async (workItemId, options, command) => {
301
- const targetPath = path.resolve(process.cwd(), String(options.path || "."));
302
- const reassigned = await reassignAssignment({
303
- targetPath,
304
- outputDir: options.outputDir,
305
- workItemId,
306
- fromAgentIdentity: options.fromAgent,
307
- toAgentIdentity: options.toAgent,
308
- leaseTtlSeconds: parsePositiveInteger(options.leaseTtlSeconds, "lease-ttl-seconds", 1800),
309
- stage: options.stage,
310
- runId: options.runId,
311
- jiraIssueKey: options.jiraIssueKey,
312
- budgetSnapshot: parseMetadata(options.budgetSnapshotJson),
313
- });
314
- const payload = {
315
- command: "daemon assign reassign",
316
- targetPath,
317
- ledgerPath: reassigned.ledgerPath,
318
- queuePath: reassigned.queuePath,
319
- eventsPath: reassigned.eventsPath,
320
- assignment: reassigned.assignment,
321
- };
322
- if (shouldEmitJson(options, command)) {
323
- console.log(JSON.stringify(payload, null, 2));
324
- return;
325
- }
326
- console.log(pc.bold("Assignment reassigned"));
327
- console.log(
328
- `${reassigned.assignment.workItemId} -> ${reassigned.assignment.assignedAgentIdentity} (expires ${reassigned.assignment.leaseExpiresAt})`
329
- );
330
- });
331
-
332
- const jira = daemon
333
- .command("jira")
334
- .description("Jira lifecycle artifacts for daemon work-item transitions and plan comments");
335
-
336
- jira
337
- .option("--path <path>", "Workspace path for artifact/config resolution", ".")
338
- .option("--output-dir <path>", "Optional output dir override for daemon artifacts")
339
- .option("--json", "Emit machine-readable output")
340
- .action(async (options, command) => {
341
- const targetPath = path.resolve(process.cwd(), String(options.path || "."));
342
- const listed = await listJiraIssues({
343
- targetPath,
344
- outputDir: options.outputDir,
345
- limit: 20,
346
- });
347
- const payload = {
348
- command: "daemon jira",
349
- targetPath,
350
- lifecyclePath: listed.lifecyclePath,
351
- eventsPath: listed.eventsPath,
352
- totalCount: listed.totalCount,
353
- visibleCount: listed.issues.length,
354
- issues: listed.issues,
355
- };
356
- if (shouldEmitJson(options, command)) {
357
- console.log(JSON.stringify(payload, null, 2));
358
- return;
359
- }
360
- printJiraSummary(payload);
361
- });
362
-
363
- jira
364
- .command("list")
365
- .description("List daemon Jira lifecycle issues")
366
- .option("--path <path>", "Workspace path for artifact/config resolution", ".")
367
- .option("--output-dir <path>", "Optional output dir override for daemon artifacts")
368
- .option("--work-item-id <id>", "Filter by work item id")
369
- .option("--issue-key <key>", "Filter by issue key")
370
- .option("--status <csv>", `Optional status filter (${JIRA_STATUSES.join(", ")})`)
371
- .option("--limit <n>", "Maximum issues to return", "50")
372
- .option("--json", "Emit machine-readable output")
373
- .action(async (options, command) => {
374
- const targetPath = path.resolve(process.cwd(), String(options.path || "."));
375
- const listed = await listJiraIssues({
376
- targetPath,
377
- outputDir: options.outputDir,
378
- workItemId: options.workItemId,
379
- issueKey: options.issueKey,
380
- statuses: parseCsv(options.status),
381
- limit: parsePositiveInteger(options.limit, "limit", 50),
382
- });
383
- const payload = {
384
- command: "daemon jira list",
385
- targetPath,
386
- workItemId: options.workItemId || null,
387
- issueKey: options.issueKey || null,
388
- statuses: parseCsv(options.status),
389
- lifecyclePath: listed.lifecyclePath,
390
- eventsPath: listed.eventsPath,
391
- totalCount: listed.totalCount,
392
- visibleCount: listed.issues.length,
393
- issues: listed.issues,
394
- };
395
- if (shouldEmitJson(options, command)) {
396
- console.log(JSON.stringify(payload, null, 2));
397
- return;
398
- }
399
- printJiraSummary(payload);
400
- });
401
-
402
- jira
403
- .command("open")
404
- .description("Create (or reuse) Jira lifecycle issue for a work item")
405
- .argument("<workItemId>", "Queue work item id")
406
- .option("--path <path>", "Workspace path for artifact/config resolution", ".")
407
- .option("--output-dir <path>", "Optional output dir override for daemon artifacts")
408
- .option("--summary <summary>", "Issue summary override")
409
- .option("--description <description>", "Issue description override")
410
- .option("--labels <csv>", "Additional labels")
411
- .option("--assignee <identity>", "Assignee identity")
412
- .option("--issue-key <key>", "Explicit issue key override")
413
- .option("--issue-key-prefix <prefix>", "Generated issue key prefix", "SLD")
414
- .option("--actor <identity>", "Lifecycle actor identity", "omar-daemon")
415
- .option("--json", "Emit machine-readable output")
416
- .action(async (workItemId, options, command) => {
417
- const targetPath = path.resolve(process.cwd(), String(options.path || "."));
418
- const opened = await openJiraIssue({
419
- targetPath,
420
- outputDir: options.outputDir,
421
- workItemId,
422
- summary: options.summary,
423
- description: options.description,
424
- labels: parseCsv(options.labels),
425
- assignee: options.assignee,
426
- issueKey: options.issueKey,
427
- issueKeyPrefix: options.issueKeyPrefix,
428
- actor: options.actor,
429
- });
430
- const payload = {
431
- command: "daemon jira open",
432
- targetPath,
433
- created: opened.created,
434
- lifecyclePath: opened.lifecyclePath,
435
- eventsPath: opened.eventsPath,
436
- issue: opened.issue,
437
- };
438
- if (shouldEmitJson(options, command)) {
439
- console.log(JSON.stringify(payload, null, 2));
440
- return;
441
- }
442
- console.log(pc.bold(opened.created ? "Jira issue created" : "Jira issue reused"));
443
- console.log(`${opened.issue.issueKey} -> work_item=${opened.issue.workItemId}`);
444
- });
445
-
446
- jira
447
- .command("start")
448
- .description("Create/reuse issue, post agent plan comment, and transition to IN_PROGRESS")
449
- .argument("<workItemId>", "Queue work item id")
450
- .requiredOption("--plan <message>", "Agent execution plan text")
451
- .option("--path <path>", "Workspace path for artifact/config resolution", ".")
452
- .option("--output-dir <path>", "Optional output dir override for daemon artifacts")
453
- .option("--actor <identity>", "Lifecycle actor identity", "omar-daemon")
454
- .option("--assignee <identity>", "Assignee identity")
455
- .option("--summary <summary>", "Issue summary override")
456
- .option("--description <description>", "Issue description override")
457
- .option("--labels <csv>", "Additional labels")
458
- .option("--issue-key <key>", "Explicit issue key override")
459
- .option("--issue-key-prefix <prefix>", "Generated issue key prefix", "SLD")
460
- .option("--json", "Emit machine-readable output")
461
- .action(async (workItemId, options, command) => {
462
- const targetPath = path.resolve(process.cwd(), String(options.path || "."));
463
- const started = await startJiraLifecycle({
464
- targetPath,
465
- outputDir: options.outputDir,
466
- workItemId,
467
- actor: options.actor,
468
- assignee: options.assignee,
469
- summary: options.summary,
470
- description: options.description,
471
- labels: parseCsv(options.labels),
472
- planMessage: options.plan,
473
- issueKey: options.issueKey,
474
- issueKeyPrefix: options.issueKeyPrefix,
475
- });
476
- const payload = {
477
- command: "daemon jira start",
478
- targetPath,
479
- created: started.created,
480
- lifecyclePath: started.lifecyclePath,
481
- eventsPath: started.eventsPath,
482
- issue: started.issue,
483
- transition: started.transition,
484
- comment: started.comment,
485
- };
486
- if (shouldEmitJson(options, command)) {
487
- console.log(JSON.stringify(payload, null, 2));
488
- return;
489
- }
490
- console.log(pc.bold("Jira lifecycle started"));
491
- console.log(`${started.issue.issueKey} status=${started.issue.status}`);
492
- });
493
-
494
- jira
495
- .command("comment")
496
- .description("Append a Jira lifecycle comment")
497
- .option("--path <path>", "Workspace path for artifact/config resolution", ".")
498
- .option("--output-dir <path>", "Optional output dir override for daemon artifacts")
499
- .option("--work-item-id <id>", "Work item id")
500
- .option("--issue-key <key>", "Issue key")
501
- .option("--actor <identity>", "Lifecycle actor identity", "omar-daemon")
502
- .option("--type <type>", "Comment type label", "checkpoint")
503
- .requiredOption("--message <message>", "Comment message")
504
- .option("--json", "Emit machine-readable output")
505
- .action(async (options, command) => {
506
- const targetPath = path.resolve(process.cwd(), String(options.path || "."));
507
- const commented = await commentJiraIssue({
508
- targetPath,
509
- outputDir: options.outputDir,
510
- workItemId: options.workItemId,
511
- issueKey: options.issueKey,
512
- actor: options.actor,
513
- type: options.type,
514
- message: options.message,
515
- });
516
- const payload = {
517
- command: "daemon jira comment",
518
- targetPath,
519
- lifecyclePath: commented.lifecyclePath,
520
- eventsPath: commented.eventsPath,
521
- issue: commented.issue,
522
- comment: commented.comment,
523
- };
524
- if (shouldEmitJson(options, command)) {
525
- console.log(JSON.stringify(payload, null, 2));
526
- return;
527
- }
528
- console.log(pc.bold("Jira lifecycle comment appended"));
529
- console.log(`${commented.issue.issueKey} type=${commented.comment.type}`);
530
- });
531
-
532
- jira
533
- .command("transition")
534
- .description("Transition Jira lifecycle issue status")
535
- .option("--path <path>", "Workspace path for artifact/config resolution", ".")
536
- .option("--output-dir <path>", "Optional output dir override for daemon artifacts")
537
- .option("--work-item-id <id>", "Work item id")
538
- .option("--issue-key <key>", "Issue key")
539
- .requiredOption("--to <status>", `Target status (${JIRA_STATUSES.join(", ")})`)
540
- .option("--actor <identity>", "Lifecycle actor identity", "omar-daemon")
541
- .option("--reason <text>", "Optional transition reason")
542
- .option("--json", "Emit machine-readable output")
543
- .action(async (options, command) => {
544
- const targetPath = path.resolve(process.cwd(), String(options.path || "."));
545
- const transitioned = await transitionJiraIssue({
546
- targetPath,
547
- outputDir: options.outputDir,
548
- workItemId: options.workItemId,
549
- issueKey: options.issueKey,
550
- toStatus: options.to,
551
- actor: options.actor,
552
- reason: options.reason,
553
- });
554
- const payload = {
555
- command: "daemon jira transition",
556
- targetPath,
557
- lifecyclePath: transitioned.lifecyclePath,
558
- eventsPath: transitioned.eventsPath,
559
- issue: transitioned.issue,
560
- transition: transitioned.transition,
561
- };
562
- if (shouldEmitJson(options, command)) {
563
- console.log(JSON.stringify(payload, null, 2));
564
- return;
565
- }
566
- console.log(pc.bold("Jira lifecycle transitioned"));
567
- console.log(
568
- `${transitioned.issue.issueKey} ${transitioned.transition.from} -> ${transitioned.transition.to}`
569
- );
570
- });
571
-
572
- const budget = daemon
573
- .command("budget")
574
- .description("Runtime budget governance checks with deterministic quarantine and kill actions");
575
-
576
- budget
577
- .option("--path <path>", "Workspace path for artifact/config resolution", ".")
578
- .option("--output-dir <path>", "Optional output dir override for daemon artifacts")
579
- .option("--json", "Emit machine-readable output")
580
- .action(async (options, command) => {
581
- const targetPath = path.resolve(process.cwd(), String(options.path || "."));
582
- const listed = await listBudgetStates({
583
- targetPath,
584
- outputDir: options.outputDir,
585
- limit: 20,
586
- });
587
- const payload = {
588
- command: "daemon budget",
589
- targetPath,
590
- budgetStatePath: listed.budgetStatePath,
591
- budgetEventsPath: listed.budgetEventsPath,
592
- totalCount: listed.totalCount,
593
- visibleCount: listed.records.length,
594
- records: listed.records,
595
- };
596
- if (shouldEmitJson(options, command)) {
597
- console.log(JSON.stringify(payload, null, 2));
598
- return;
599
- }
600
- printBudgetSummary(payload);
601
- });
602
-
603
- budget
604
- .command("status")
605
- .description("List budget governance state records")
606
- .option("--path <path>", "Workspace path for artifact/config resolution", ".")
607
- .option("--output-dir <path>", "Optional output dir override for daemon artifacts")
608
- .option("--work-item-id <id>", "Filter by work item id")
609
- .option(
610
- "--lifecycle-state <csv>",
611
- `Optional lifecycle filter (${DAEMON_BUDGET_LIFECYCLE_STATES.join(", ")})`
612
- )
613
- .option("--limit <n>", "Maximum records to return", "50")
614
- .option("--json", "Emit machine-readable output")
615
- .action(async (options, command) => {
616
- const targetPath = path.resolve(process.cwd(), String(options.path || "."));
617
- const listed = await listBudgetStates({
618
- targetPath,
619
- outputDir: options.outputDir,
620
- workItemId: options.workItemId,
621
- lifecycleStates: parseCsv(options.lifecycleState),
622
- limit: parsePositiveInteger(options.limit, "limit", 50),
623
- });
624
- const payload = {
625
- command: "daemon budget status",
626
- targetPath,
627
- workItemId: options.workItemId || null,
628
- lifecycleStates: parseCsv(options.lifecycleState),
629
- budgetStatePath: listed.budgetStatePath,
630
- budgetEventsPath: listed.budgetEventsPath,
631
- totalCount: listed.totalCount,
632
- visibleCount: listed.records.length,
633
- records: listed.records,
634
- };
635
- if (shouldEmitJson(options, command)) {
636
- console.log(JSON.stringify(payload, null, 2));
637
- return;
638
- }
639
- printBudgetSummary(payload);
640
- });
641
-
642
- budget
643
- .command("check")
644
- .description("Apply one budget-governance evaluation tick for a work item")
645
- .argument("<workItemId>", "Queue work item id")
646
- .option("--path <path>", "Workspace path for artifact/config resolution", ".")
647
- .option("--output-dir <path>", "Optional output dir override for daemon artifacts")
648
- .option(
649
- "--usage-json <json>",
650
- "Usage snapshot JSON (tokensUsed,costUsd,runtimeMs,toolCalls,pathOutOfScopeHits,networkDomainViolations)",
651
- "{}"
652
- )
653
- .option(
654
- "--budget-json <json>",
655
- "Budget envelope JSON (maxTokens,maxCostUsd,maxRuntimeMs,maxToolCalls,maxPathViolations,maxNetworkViolations,warningThresholdPercent,quarantineGraceSeconds)",
656
- "{}"
657
- )
658
- .option("--now-iso <timestamp>", "Optional deterministic timestamp override")
659
- .option("--json", "Emit machine-readable output")
660
- .action(async (workItemId, options, command) => {
661
- const targetPath = path.resolve(process.cwd(), String(options.path || "."));
662
- const checked = await applyDaemonBudgetCheck({
663
- targetPath,
664
- outputDir: options.outputDir,
665
- workItemId,
666
- usage: parseMetadata(options.usageJson),
667
- budget: parseMetadata(options.budgetJson),
668
- nowIso: options.nowIso,
669
- });
670
- const payload = {
671
- command: "daemon budget check",
672
- targetPath,
673
- workItemId,
674
- runId: checked.runId,
675
- runPath: checked.runPath,
676
- budgetStatePath: checked.budgetStatePath,
677
- budgetEventsPath: checked.budgetEventsPath,
678
- lifecycleState: checked.lifecycleState,
679
- action: checked.action,
680
- warnings: checked.warnings,
681
- stopReasons: checked.stopReasons,
682
- budget: checked.budget,
683
- usage: checked.usage,
684
- quarantineStartedAt: checked.quarantineStartedAt,
685
- quarantineUntil: checked.quarantineUntil,
686
- record: checked.record,
687
- };
688
- if (shouldEmitJson(options, command)) {
689
- console.log(JSON.stringify(payload, null, 2));
690
- return;
691
- }
692
- console.log(pc.bold("Budget governance check applied"));
693
- console.log(
694
- `${workItemId} lifecycle=${checked.lifecycleState} action=${checked.action} quarantine_until=${checked.quarantineUntil || "n/a"}`
695
- );
696
- });
697
-
698
- const control = daemon
699
- .command("control")
700
- .description(
701
- "Operator control plane snapshot and stop controls (agent roster, budget health, session timers)"
702
- );
703
-
704
- control
705
- .option("--path <path>", "Workspace path for artifact/config resolution", ".")
706
- .option("--output-dir <path>", "Optional output dir override for daemon artifacts")
707
- .option(
708
- "--status <csv>",
709
- `Optional queue status filter (${WORK_ITEM_STATUSES.join(", ")})`
710
- )
711
- .option("--agent <identity>", "Optional assigned-agent filter")
712
- .option("--limit <n>", "Maximum work items to return", "50")
713
- .option("--json", "Emit machine-readable output")
714
- .action(async (options, command) => {
715
- const targetPath = path.resolve(process.cwd(), String(options.path || "."));
716
- const snapshot = await buildOperatorControlSnapshot({
717
- targetPath,
718
- outputDir: options.outputDir,
719
- statuses: parseCsv(options.status),
720
- agentIdentity: options.agent,
721
- limit: parsePositiveInteger(options.limit, "limit", 50),
722
- });
723
- const payload = {
724
- command: "daemon control",
725
- targetPath,
726
- runId: snapshot.runId,
727
- runPath: snapshot.runPath,
728
- operatorStatePath: snapshot.operatorStatePath,
729
- operatorEventsPath: snapshot.operatorEventsPath,
730
- totalQueueItems: snapshot.totalQueueItems,
731
- visibleWorkItems: snapshot.visibleWorkItems,
732
- statusCounts: snapshot.statusCounts,
733
- healthCounts: snapshot.healthCounts,
734
- workItems: snapshot.workItems,
735
- agentRoster: snapshot.agentRoster,
736
- };
737
- if (shouldEmitJson(options, command)) {
738
- console.log(JSON.stringify(payload, null, 2));
739
- return;
740
- }
741
- printControlPlaneSummary(payload);
742
- });
743
-
744
- control
745
- .command("snapshot")
746
- .description("Create one deterministic operator control-plane snapshot artifact")
747
- .option("--path <path>", "Workspace path for artifact/config resolution", ".")
748
- .option("--output-dir <path>", "Optional output dir override for daemon artifacts")
749
- .option(
750
- "--status <csv>",
751
- `Optional queue status filter (${WORK_ITEM_STATUSES.join(", ")})`
752
- )
753
- .option("--agent <identity>", "Optional assigned-agent filter")
754
- .option("--limit <n>", "Maximum work items to return", "50")
755
- .option("--now-iso <timestamp>", "Optional deterministic timestamp override")
756
- .option("--json", "Emit machine-readable output")
757
- .action(async (options, command) => {
758
- const targetPath = path.resolve(process.cwd(), String(options.path || "."));
759
- const snapshot = await buildOperatorControlSnapshot({
760
- targetPath,
761
- outputDir: options.outputDir,
762
- statuses: parseCsv(options.status),
763
- agentIdentity: options.agent,
764
- limit: parsePositiveInteger(options.limit, "limit", 50),
765
- nowIso: options.nowIso,
766
- });
767
- const payload = {
768
- command: "daemon control snapshot",
769
- targetPath,
770
- runId: snapshot.runId,
771
- runPath: snapshot.runPath,
772
- operatorStatePath: snapshot.operatorStatePath,
773
- operatorEventsPath: snapshot.operatorEventsPath,
774
- totalQueueItems: snapshot.totalQueueItems,
775
- visibleWorkItems: snapshot.visibleWorkItems,
776
- statusCounts: snapshot.statusCounts,
777
- healthCounts: snapshot.healthCounts,
778
- workItems: snapshot.workItems,
779
- agentRoster: snapshot.agentRoster,
780
- };
781
- if (shouldEmitJson(options, command)) {
782
- console.log(JSON.stringify(payload, null, 2));
783
- return;
784
- }
785
- printControlPlaneSummary(payload);
786
- });
787
-
788
- control
789
- .command("stop")
790
- .description("Apply confirmed operator stop control (quarantine or squash) to a work item")
791
- .argument("<workItemId>", "Queue work item id")
792
- .option("--path <path>", "Workspace path for artifact/config resolution", ".")
793
- .option("--output-dir <path>", "Optional output dir override for daemon artifacts")
794
- .option(
795
- "--mode <mode>",
796
- `Stop mode (${OPERATOR_STOP_MODES.join(", ")})`,
797
- "QUARANTINE"
798
- )
799
- .option("--reason <text>", "Operator reason for stop action")
800
- .option("--actor <identity>", "Operator identity", "omar-operator")
801
- .option("--confirm", "Confirm stop action execution")
802
- .option("--now-iso <timestamp>", "Optional deterministic timestamp override")
803
- .option("--json", "Emit machine-readable output")
804
- .action(async (workItemId, options, command) => {
805
- const targetPath = path.resolve(process.cwd(), String(options.path || "."));
806
- const stopped = await applyOperatorStopControl({
807
- targetPath,
808
- outputDir: options.outputDir,
809
- workItemId,
810
- mode: normalizeOperatorStopMode(options.mode),
811
- reason: options.reason,
812
- actor: options.actor,
813
- confirm: Boolean(options.confirm),
814
- nowIso: options.nowIso,
815
- });
816
- const payload = {
817
- command: "daemon control stop",
818
- targetPath,
819
- workItemId: stopped.workItemId,
820
- mode: stopped.mode,
821
- targetStatus: stopped.targetStatus,
822
- actor: stopped.actor,
823
- reason: stopped.reason,
824
- operatorEventsPath: stopped.operatorEventsPath,
825
- queuePath: stopped.queuePath,
826
- queueItem: stopped.queueItem,
827
- assignment: stopped.assignment,
828
- jiraIssueKey: stopped.jiraIssueKey,
829
- jiraCommented: stopped.jiraCommented,
830
- jiraCommentWarning: stopped.jiraCommentWarning,
831
- };
832
- if (shouldEmitJson(options, command)) {
833
- console.log(JSON.stringify(payload, null, 2));
834
- return;
835
- }
836
- console.log(pc.bold("Operator stop applied"));
837
- console.log(
838
- `${stopped.workItemId} mode=${stopped.mode} status=${stopped.targetStatus} actor=${stopped.actor}`
839
- );
840
- if (stopped.jiraIssueKey) {
841
- console.log(
842
- `jira=${stopped.jiraIssueKey} commented=${stopped.jiraCommented ? "true" : "false"}`
843
- );
844
- }
845
- if (stopped.jiraCommentWarning) {
846
- console.log(pc.yellow(`jira_warning=${stopped.jiraCommentWarning}`));
847
- }
848
- });
849
-
850
- }
1
+ import path from "node:path";
2
+
3
+ import pc from "picocolors";
4
+
5
+ import { WORK_ITEM_STATUSES, listErrorQueue } from "../../daemon/error-worker.js";
6
+ import {
7
+ ASSIGNMENT_STATUSES,
8
+ claimAssignment,
9
+ heartbeatAssignment,
10
+ listAssignments,
11
+ reassignAssignment,
12
+ releaseAssignment,
13
+ } from "../../daemon/assignment-ledger.js";
14
+ import {
15
+ JIRA_STATUSES,
16
+ commentJiraIssue,
17
+ listJiraIssues,
18
+ openJiraIssue,
19
+ startJiraLifecycle,
20
+ transitionJiraIssue,
21
+ } from "../../daemon/jira-lifecycle.js";
22
+ import {
23
+ DAEMON_BUDGET_LIFECYCLE_STATES,
24
+ applyDaemonBudgetCheck,
25
+ listBudgetStates,
26
+ } from "../../daemon/budget-governor.js";
27
+ import {
28
+ OPERATOR_STOP_MODES,
29
+ applyOperatorStopControl,
30
+ buildOperatorControlSnapshot,
31
+ normalizeOperatorStopMode,
32
+ } from "../../daemon/operator-control.js";
33
+ import {
34
+ parseBoolean,
35
+ parseCsv,
36
+ parseMetadata,
37
+ parsePositiveInteger,
38
+ printAssignmentSummary,
39
+ printBudgetSummary,
40
+ printControlPlaneSummary,
41
+ printJiraSummary,
42
+ printQueueSummary,
43
+ shouldEmitJson,
44
+ } from "./shared.js";
45
+
46
+ export function registerDaemonCoreCommands(daemon) {
47
+ daemon
48
+ .option("--path <path>", "Workspace path for artifact/config resolution", ".")
49
+ .option("--output-dir <path>", "Optional output dir override for daemon artifacts")
50
+ .option("--json", "Emit machine-readable output")
51
+ .action(async (options, command) => {
52
+ const targetPath = path.resolve(process.cwd(), String(options.path || "."));
53
+ const limit = 20;
54
+ const listed = await listErrorQueue({
55
+ targetPath,
56
+ outputDir: options.outputDir,
57
+ limit,
58
+ });
59
+ const payload = {
60
+ command: "daemon",
61
+ targetPath,
62
+ queuePath: listed.queuePath,
63
+ statePath: listed.statePath,
64
+ streamPath: listed.streamPath,
65
+ totalCount: listed.totalCount,
66
+ visibleCount: listed.items.length,
67
+ items: listed.items,
68
+ workerState: listed.state,
69
+ };
70
+ if (shouldEmitJson(options, command)) {
71
+ console.log(JSON.stringify(payload, null, 2));
72
+ return;
73
+ }
74
+ printQueueSummary(payload);
75
+ });
76
+
77
+ const assign = daemon
78
+ .command("assign")
79
+ .description("Global assignment ledger for daemon queue ownership and lease lifecycle");
80
+
81
+ assign
82
+ .option("--path <path>", "Workspace path for artifact/config resolution", ".")
83
+ .option("--output-dir <path>", "Optional output dir override for daemon artifacts")
84
+ .option("--json", "Emit machine-readable output")
85
+ .action(async (options, command) => {
86
+ const targetPath = path.resolve(process.cwd(), String(options.path || "."));
87
+ const listed = await listAssignments({
88
+ targetPath,
89
+ outputDir: options.outputDir,
90
+ limit: 20,
91
+ });
92
+ const payload = {
93
+ command: "daemon assign",
94
+ targetPath,
95
+ ledgerPath: listed.ledgerPath,
96
+ queuePath: listed.queuePath,
97
+ eventsPath: listed.eventsPath,
98
+ totalCount: listed.totalCount,
99
+ visibleCount: listed.assignments.length,
100
+ assignments: listed.assignments,
101
+ };
102
+ if (shouldEmitJson(options, command)) {
103
+ console.log(JSON.stringify(payload, null, 2));
104
+ return;
105
+ }
106
+ printAssignmentSummary(payload);
107
+ });
108
+
109
+ assign
110
+ .command("list")
111
+ .description("List assignment ledger records")
112
+ .option("--path <path>", "Workspace path for artifact/config resolution", ".")
113
+ .option("--output-dir <path>", "Optional output dir override for daemon artifacts")
114
+ .option(
115
+ "--status <csv>",
116
+ `Optional assignment status filter (${ASSIGNMENT_STATUSES.join(", ")})`
117
+ )
118
+ .option("--agent <identity>", "Filter by assigned agent identity")
119
+ .option("--include-expired <bool>", "Include expired active leases (true/false)", "true")
120
+ .option("--limit <n>", "Maximum assignments to return", "50")
121
+ .option("--json", "Emit machine-readable output")
122
+ .action(async (options, command) => {
123
+ const targetPath = path.resolve(process.cwd(), String(options.path || "."));
124
+ const listed = await listAssignments({
125
+ targetPath,
126
+ outputDir: options.outputDir,
127
+ statuses: parseCsv(options.status),
128
+ agentIdentity: options.agent,
129
+ includeExpired: parseBoolean(options.includeExpired, true),
130
+ limit: parsePositiveInteger(options.limit, "limit", 50),
131
+ });
132
+ const payload = {
133
+ command: "daemon assign list",
134
+ targetPath,
135
+ statuses: parseCsv(options.status),
136
+ agentIdentity: options.agent || null,
137
+ includeExpired: parseBoolean(options.includeExpired, true),
138
+ ledgerPath: listed.ledgerPath,
139
+ queuePath: listed.queuePath,
140
+ eventsPath: listed.eventsPath,
141
+ totalCount: listed.totalCount,
142
+ visibleCount: listed.assignments.length,
143
+ assignments: listed.assignments,
144
+ };
145
+ if (shouldEmitJson(options, command)) {
146
+ console.log(JSON.stringify(payload, null, 2));
147
+ return;
148
+ }
149
+ printAssignmentSummary(payload);
150
+ });
151
+
152
+ assign
153
+ .command("claim")
154
+ .description("Claim a queue work item for an agent identity with lease metadata")
155
+ .argument("<workItemId>", "Queue work item id")
156
+ .option("--path <path>", "Workspace path for artifact/config resolution", ".")
157
+ .option("--output-dir <path>", "Optional output dir override for daemon artifacts")
158
+ .requiredOption("--agent <identity>", "Assigned agent identity (email or handle)")
159
+ .option("--lease-ttl-seconds <n>", "Lease duration in seconds", "1800")
160
+ .option("--stage <stage>", "Current workflow stage", "triage")
161
+ .option("--run-id <id>", "Optional runtime run id")
162
+ .option("--jira-issue-key <key>", "Optional Jira issue key")
163
+ .option("--budget-snapshot-json <json>", "Optional budget snapshot JSON object")
164
+ .option("--json", "Emit machine-readable output")
165
+ .action(async (workItemId, options, command) => {
166
+ const targetPath = path.resolve(process.cwd(), String(options.path || "."));
167
+ const claimed = await claimAssignment({
168
+ targetPath,
169
+ outputDir: options.outputDir,
170
+ workItemId,
171
+ agentIdentity: options.agent,
172
+ leaseTtlSeconds: parsePositiveInteger(options.leaseTtlSeconds, "lease-ttl-seconds", 1800),
173
+ stage: options.stage,
174
+ runId: options.runId,
175
+ jiraIssueKey: options.jiraIssueKey,
176
+ budgetSnapshot: parseMetadata(options.budgetSnapshotJson),
177
+ });
178
+ const payload = {
179
+ command: "daemon assign claim",
180
+ targetPath,
181
+ ledgerPath: claimed.ledgerPath,
182
+ queuePath: claimed.queuePath,
183
+ eventsPath: claimed.eventsPath,
184
+ assignment: claimed.assignment,
185
+ };
186
+ if (shouldEmitJson(options, command)) {
187
+ console.log(JSON.stringify(payload, null, 2));
188
+ return;
189
+ }
190
+ console.log(pc.bold("Assignment claimed"));
191
+ console.log(
192
+ `${claimed.assignment.workItemId} -> ${claimed.assignment.assignedAgentIdentity} (expires ${claimed.assignment.leaseExpiresAt})`
193
+ );
194
+ });
195
+
196
+ assign
197
+ .command("heartbeat")
198
+ .description("Refresh lease heartbeat for an assigned work item")
199
+ .argument("<workItemId>", "Queue work item id")
200
+ .option("--path <path>", "Workspace path for artifact/config resolution", ".")
201
+ .option("--output-dir <path>", "Optional output dir override for daemon artifacts")
202
+ .requiredOption("--agent <identity>", "Assigned agent identity (email or handle)")
203
+ .option("--lease-ttl-seconds <n>", "Lease duration in seconds", "1800")
204
+ .option("--stage <stage>", "Current workflow stage")
205
+ .option("--run-id <id>", "Optional runtime run id")
206
+ .option("--jira-issue-key <key>", "Optional Jira issue key")
207
+ .option("--budget-snapshot-json <json>", "Optional budget snapshot JSON object")
208
+ .option("--json", "Emit machine-readable output")
209
+ .action(async (workItemId, options, command) => {
210
+ const targetPath = path.resolve(process.cwd(), String(options.path || "."));
211
+ const heartbeat = await heartbeatAssignment({
212
+ targetPath,
213
+ outputDir: options.outputDir,
214
+ workItemId,
215
+ agentIdentity: options.agent,
216
+ leaseTtlSeconds: parsePositiveInteger(options.leaseTtlSeconds, "lease-ttl-seconds", 1800),
217
+ stage: options.stage,
218
+ runId: options.runId,
219
+ jiraIssueKey: options.jiraIssueKey,
220
+ budgetSnapshot: parseMetadata(options.budgetSnapshotJson),
221
+ });
222
+ const payload = {
223
+ command: "daemon assign heartbeat",
224
+ targetPath,
225
+ ledgerPath: heartbeat.ledgerPath,
226
+ queuePath: heartbeat.queuePath,
227
+ eventsPath: heartbeat.eventsPath,
228
+ assignment: heartbeat.assignment,
229
+ };
230
+ if (shouldEmitJson(options, command)) {
231
+ console.log(JSON.stringify(payload, null, 2));
232
+ return;
233
+ }
234
+ console.log(pc.bold("Assignment heartbeat updated"));
235
+ console.log(
236
+ `${heartbeat.assignment.workItemId} -> ${heartbeat.assignment.assignedAgentIdentity} (expires ${heartbeat.assignment.leaseExpiresAt})`
237
+ );
238
+ });
239
+
240
+ assign
241
+ .command("release")
242
+ .description("Release an assignment and transition work item status")
243
+ .argument("<workItemId>", "Queue work item id")
244
+ .option("--path <path>", "Workspace path for artifact/config resolution", ".")
245
+ .option("--output-dir <path>", "Optional output dir override for daemon artifacts")
246
+ .option("--agent <identity>", "Optional assigned agent identity check")
247
+ .option("--status <status>", "Release status (QUEUED|DONE|BLOCKED|SQUASHED)", "QUEUED")
248
+ .option("--stage <stage>", "Current workflow stage")
249
+ .option("--run-id <id>", "Optional runtime run id")
250
+ .option("--jira-issue-key <key>", "Optional Jira issue key")
251
+ .option("--reason <reason>", "Optional release reason")
252
+ .option("--budget-snapshot-json <json>", "Optional budget snapshot JSON object")
253
+ .option("--json", "Emit machine-readable output")
254
+ .action(async (workItemId, options, command) => {
255
+ const targetPath = path.resolve(process.cwd(), String(options.path || "."));
256
+ const released = await releaseAssignment({
257
+ targetPath,
258
+ outputDir: options.outputDir,
259
+ workItemId,
260
+ agentIdentity: options.agent,
261
+ status: options.status,
262
+ stage: options.stage,
263
+ runId: options.runId,
264
+ jiraIssueKey: options.jiraIssueKey,
265
+ reason: options.reason,
266
+ budgetSnapshot: parseMetadata(options.budgetSnapshotJson),
267
+ });
268
+ const payload = {
269
+ command: "daemon assign release",
270
+ targetPath,
271
+ ledgerPath: released.ledgerPath,
272
+ queuePath: released.queuePath,
273
+ eventsPath: released.eventsPath,
274
+ assignment: released.assignment,
275
+ };
276
+ if (shouldEmitJson(options, command)) {
277
+ console.log(JSON.stringify(payload, null, 2));
278
+ return;
279
+ }
280
+ console.log(pc.bold("Assignment released"));
281
+ console.log(
282
+ `${released.assignment.workItemId} status=${released.assignment.status} agent=${released.assignment.assignedAgentIdentity || "n/a"}`
283
+ );
284
+ });
285
+
286
+ assign
287
+ .command("reassign")
288
+ .description("Reassign a work item lease to a different agent identity")
289
+ .argument("<workItemId>", "Queue work item id")
290
+ .option("--path <path>", "Workspace path for artifact/config resolution", ".")
291
+ .option("--output-dir <path>", "Optional output dir override for daemon artifacts")
292
+ .option("--from-agent <identity>", "Optional expected current agent identity")
293
+ .requiredOption("--to-agent <identity>", "New assigned agent identity")
294
+ .option("--lease-ttl-seconds <n>", "Lease duration in seconds", "1800")
295
+ .option("--stage <stage>", "Current workflow stage", "triage")
296
+ .option("--run-id <id>", "Optional runtime run id")
297
+ .option("--jira-issue-key <key>", "Optional Jira issue key")
298
+ .option("--budget-snapshot-json <json>", "Optional budget snapshot JSON object")
299
+ .option("--json", "Emit machine-readable output")
300
+ .action(async (workItemId, options, command) => {
301
+ const targetPath = path.resolve(process.cwd(), String(options.path || "."));
302
+ const reassigned = await reassignAssignment({
303
+ targetPath,
304
+ outputDir: options.outputDir,
305
+ workItemId,
306
+ fromAgentIdentity: options.fromAgent,
307
+ toAgentIdentity: options.toAgent,
308
+ leaseTtlSeconds: parsePositiveInteger(options.leaseTtlSeconds, "lease-ttl-seconds", 1800),
309
+ stage: options.stage,
310
+ runId: options.runId,
311
+ jiraIssueKey: options.jiraIssueKey,
312
+ budgetSnapshot: parseMetadata(options.budgetSnapshotJson),
313
+ });
314
+ const payload = {
315
+ command: "daemon assign reassign",
316
+ targetPath,
317
+ ledgerPath: reassigned.ledgerPath,
318
+ queuePath: reassigned.queuePath,
319
+ eventsPath: reassigned.eventsPath,
320
+ assignment: reassigned.assignment,
321
+ };
322
+ if (shouldEmitJson(options, command)) {
323
+ console.log(JSON.stringify(payload, null, 2));
324
+ return;
325
+ }
326
+ console.log(pc.bold("Assignment reassigned"));
327
+ console.log(
328
+ `${reassigned.assignment.workItemId} -> ${reassigned.assignment.assignedAgentIdentity} (expires ${reassigned.assignment.leaseExpiresAt})`
329
+ );
330
+ });
331
+
332
+ const jira = daemon
333
+ .command("jira")
334
+ .description("Jira lifecycle artifacts for daemon work-item transitions and plan comments");
335
+
336
+ jira
337
+ .option("--path <path>", "Workspace path for artifact/config resolution", ".")
338
+ .option("--output-dir <path>", "Optional output dir override for daemon artifacts")
339
+ .option("--json", "Emit machine-readable output")
340
+ .action(async (options, command) => {
341
+ const targetPath = path.resolve(process.cwd(), String(options.path || "."));
342
+ const listed = await listJiraIssues({
343
+ targetPath,
344
+ outputDir: options.outputDir,
345
+ limit: 20,
346
+ });
347
+ const payload = {
348
+ command: "daemon jira",
349
+ targetPath,
350
+ lifecyclePath: listed.lifecyclePath,
351
+ eventsPath: listed.eventsPath,
352
+ totalCount: listed.totalCount,
353
+ visibleCount: listed.issues.length,
354
+ issues: listed.issues,
355
+ };
356
+ if (shouldEmitJson(options, command)) {
357
+ console.log(JSON.stringify(payload, null, 2));
358
+ return;
359
+ }
360
+ printJiraSummary(payload);
361
+ });
362
+
363
+ jira
364
+ .command("list")
365
+ .description("List daemon Jira lifecycle issues")
366
+ .option("--path <path>", "Workspace path for artifact/config resolution", ".")
367
+ .option("--output-dir <path>", "Optional output dir override for daemon artifacts")
368
+ .option("--work-item-id <id>", "Filter by work item id")
369
+ .option("--issue-key <key>", "Filter by issue key")
370
+ .option("--status <csv>", `Optional status filter (${JIRA_STATUSES.join(", ")})`)
371
+ .option("--limit <n>", "Maximum issues to return", "50")
372
+ .option("--json", "Emit machine-readable output")
373
+ .action(async (options, command) => {
374
+ const targetPath = path.resolve(process.cwd(), String(options.path || "."));
375
+ const listed = await listJiraIssues({
376
+ targetPath,
377
+ outputDir: options.outputDir,
378
+ workItemId: options.workItemId,
379
+ issueKey: options.issueKey,
380
+ statuses: parseCsv(options.status),
381
+ limit: parsePositiveInteger(options.limit, "limit", 50),
382
+ });
383
+ const payload = {
384
+ command: "daemon jira list",
385
+ targetPath,
386
+ workItemId: options.workItemId || null,
387
+ issueKey: options.issueKey || null,
388
+ statuses: parseCsv(options.status),
389
+ lifecyclePath: listed.lifecyclePath,
390
+ eventsPath: listed.eventsPath,
391
+ totalCount: listed.totalCount,
392
+ visibleCount: listed.issues.length,
393
+ issues: listed.issues,
394
+ };
395
+ if (shouldEmitJson(options, command)) {
396
+ console.log(JSON.stringify(payload, null, 2));
397
+ return;
398
+ }
399
+ printJiraSummary(payload);
400
+ });
401
+
402
+ jira
403
+ .command("open")
404
+ .description("Create (or reuse) Jira lifecycle issue for a work item")
405
+ .argument("<workItemId>", "Queue work item id")
406
+ .option("--path <path>", "Workspace path for artifact/config resolution", ".")
407
+ .option("--output-dir <path>", "Optional output dir override for daemon artifacts")
408
+ .option("--summary <summary>", "Issue summary override")
409
+ .option("--description <description>", "Issue description override")
410
+ .option("--labels <csv>", "Additional labels")
411
+ .option("--assignee <identity>", "Assignee identity")
412
+ .option("--issue-key <key>", "Explicit issue key override")
413
+ .option("--issue-key-prefix <prefix>", "Generated issue key prefix", "SLD")
414
+ .option("--actor <identity>", "Lifecycle actor identity", "omar-daemon")
415
+ .option("--json", "Emit machine-readable output")
416
+ .action(async (workItemId, options, command) => {
417
+ const targetPath = path.resolve(process.cwd(), String(options.path || "."));
418
+ const opened = await openJiraIssue({
419
+ targetPath,
420
+ outputDir: options.outputDir,
421
+ workItemId,
422
+ summary: options.summary,
423
+ description: options.description,
424
+ labels: parseCsv(options.labels),
425
+ assignee: options.assignee,
426
+ issueKey: options.issueKey,
427
+ issueKeyPrefix: options.issueKeyPrefix,
428
+ actor: options.actor,
429
+ });
430
+ const payload = {
431
+ command: "daemon jira open",
432
+ targetPath,
433
+ created: opened.created,
434
+ lifecyclePath: opened.lifecyclePath,
435
+ eventsPath: opened.eventsPath,
436
+ issue: opened.issue,
437
+ };
438
+ if (shouldEmitJson(options, command)) {
439
+ console.log(JSON.stringify(payload, null, 2));
440
+ return;
441
+ }
442
+ console.log(pc.bold(opened.created ? "Jira issue created" : "Jira issue reused"));
443
+ console.log(`${opened.issue.issueKey} -> work_item=${opened.issue.workItemId}`);
444
+ });
445
+
446
+ jira
447
+ .command("start")
448
+ .description("Create/reuse issue, post agent plan comment, and transition to IN_PROGRESS")
449
+ .argument("<workItemId>", "Queue work item id")
450
+ .requiredOption("--plan <message>", "Agent execution plan text")
451
+ .option("--path <path>", "Workspace path for artifact/config resolution", ".")
452
+ .option("--output-dir <path>", "Optional output dir override for daemon artifacts")
453
+ .option("--actor <identity>", "Lifecycle actor identity", "omar-daemon")
454
+ .option("--assignee <identity>", "Assignee identity")
455
+ .option("--summary <summary>", "Issue summary override")
456
+ .option("--description <description>", "Issue description override")
457
+ .option("--labels <csv>", "Additional labels")
458
+ .option("--issue-key <key>", "Explicit issue key override")
459
+ .option("--issue-key-prefix <prefix>", "Generated issue key prefix", "SLD")
460
+ .option("--json", "Emit machine-readable output")
461
+ .action(async (workItemId, options, command) => {
462
+ const targetPath = path.resolve(process.cwd(), String(options.path || "."));
463
+ const started = await startJiraLifecycle({
464
+ targetPath,
465
+ outputDir: options.outputDir,
466
+ workItemId,
467
+ actor: options.actor,
468
+ assignee: options.assignee,
469
+ summary: options.summary,
470
+ description: options.description,
471
+ labels: parseCsv(options.labels),
472
+ planMessage: options.plan,
473
+ issueKey: options.issueKey,
474
+ issueKeyPrefix: options.issueKeyPrefix,
475
+ });
476
+ const payload = {
477
+ command: "daemon jira start",
478
+ targetPath,
479
+ created: started.created,
480
+ lifecyclePath: started.lifecyclePath,
481
+ eventsPath: started.eventsPath,
482
+ issue: started.issue,
483
+ transition: started.transition,
484
+ comment: started.comment,
485
+ };
486
+ if (shouldEmitJson(options, command)) {
487
+ console.log(JSON.stringify(payload, null, 2));
488
+ return;
489
+ }
490
+ console.log(pc.bold("Jira lifecycle started"));
491
+ console.log(`${started.issue.issueKey} status=${started.issue.status}`);
492
+ });
493
+
494
+ jira
495
+ .command("comment")
496
+ .description("Append a Jira lifecycle comment")
497
+ .option("--path <path>", "Workspace path for artifact/config resolution", ".")
498
+ .option("--output-dir <path>", "Optional output dir override for daemon artifacts")
499
+ .option("--work-item-id <id>", "Work item id")
500
+ .option("--issue-key <key>", "Issue key")
501
+ .option("--actor <identity>", "Lifecycle actor identity", "omar-daemon")
502
+ .option("--type <type>", "Comment type label", "checkpoint")
503
+ .requiredOption("--message <message>", "Comment message")
504
+ .option("--json", "Emit machine-readable output")
505
+ .action(async (options, command) => {
506
+ const targetPath = path.resolve(process.cwd(), String(options.path || "."));
507
+ const commented = await commentJiraIssue({
508
+ targetPath,
509
+ outputDir: options.outputDir,
510
+ workItemId: options.workItemId,
511
+ issueKey: options.issueKey,
512
+ actor: options.actor,
513
+ type: options.type,
514
+ message: options.message,
515
+ });
516
+ const payload = {
517
+ command: "daemon jira comment",
518
+ targetPath,
519
+ lifecyclePath: commented.lifecyclePath,
520
+ eventsPath: commented.eventsPath,
521
+ issue: commented.issue,
522
+ comment: commented.comment,
523
+ };
524
+ if (shouldEmitJson(options, command)) {
525
+ console.log(JSON.stringify(payload, null, 2));
526
+ return;
527
+ }
528
+ console.log(pc.bold("Jira lifecycle comment appended"));
529
+ console.log(`${commented.issue.issueKey} type=${commented.comment.type}`);
530
+ });
531
+
532
+ jira
533
+ .command("transition")
534
+ .description("Transition Jira lifecycle issue status")
535
+ .option("--path <path>", "Workspace path for artifact/config resolution", ".")
536
+ .option("--output-dir <path>", "Optional output dir override for daemon artifacts")
537
+ .option("--work-item-id <id>", "Work item id")
538
+ .option("--issue-key <key>", "Issue key")
539
+ .requiredOption("--to <status>", `Target status (${JIRA_STATUSES.join(", ")})`)
540
+ .option("--actor <identity>", "Lifecycle actor identity", "omar-daemon")
541
+ .option("--reason <text>", "Optional transition reason")
542
+ .option("--json", "Emit machine-readable output")
543
+ .action(async (options, command) => {
544
+ const targetPath = path.resolve(process.cwd(), String(options.path || "."));
545
+ const transitioned = await transitionJiraIssue({
546
+ targetPath,
547
+ outputDir: options.outputDir,
548
+ workItemId: options.workItemId,
549
+ issueKey: options.issueKey,
550
+ toStatus: options.to,
551
+ actor: options.actor,
552
+ reason: options.reason,
553
+ });
554
+ const payload = {
555
+ command: "daemon jira transition",
556
+ targetPath,
557
+ lifecyclePath: transitioned.lifecyclePath,
558
+ eventsPath: transitioned.eventsPath,
559
+ issue: transitioned.issue,
560
+ transition: transitioned.transition,
561
+ };
562
+ if (shouldEmitJson(options, command)) {
563
+ console.log(JSON.stringify(payload, null, 2));
564
+ return;
565
+ }
566
+ console.log(pc.bold("Jira lifecycle transitioned"));
567
+ console.log(
568
+ `${transitioned.issue.issueKey} ${transitioned.transition.from} -> ${transitioned.transition.to}`
569
+ );
570
+ });
571
+
572
+ const budget = daemon
573
+ .command("budget")
574
+ .description("Runtime budget governance checks with deterministic quarantine and kill actions");
575
+
576
+ budget
577
+ .option("--path <path>", "Workspace path for artifact/config resolution", ".")
578
+ .option("--output-dir <path>", "Optional output dir override for daemon artifacts")
579
+ .option("--json", "Emit machine-readable output")
580
+ .action(async (options, command) => {
581
+ const targetPath = path.resolve(process.cwd(), String(options.path || "."));
582
+ const listed = await listBudgetStates({
583
+ targetPath,
584
+ outputDir: options.outputDir,
585
+ limit: 20,
586
+ });
587
+ const payload = {
588
+ command: "daemon budget",
589
+ targetPath,
590
+ budgetStatePath: listed.budgetStatePath,
591
+ budgetEventsPath: listed.budgetEventsPath,
592
+ totalCount: listed.totalCount,
593
+ visibleCount: listed.records.length,
594
+ records: listed.records,
595
+ };
596
+ if (shouldEmitJson(options, command)) {
597
+ console.log(JSON.stringify(payload, null, 2));
598
+ return;
599
+ }
600
+ printBudgetSummary(payload);
601
+ });
602
+
603
+ budget
604
+ .command("status")
605
+ .description("List budget governance state records")
606
+ .option("--path <path>", "Workspace path for artifact/config resolution", ".")
607
+ .option("--output-dir <path>", "Optional output dir override for daemon artifacts")
608
+ .option("--work-item-id <id>", "Filter by work item id")
609
+ .option(
610
+ "--lifecycle-state <csv>",
611
+ `Optional lifecycle filter (${DAEMON_BUDGET_LIFECYCLE_STATES.join(", ")})`
612
+ )
613
+ .option("--limit <n>", "Maximum records to return", "50")
614
+ .option("--json", "Emit machine-readable output")
615
+ .action(async (options, command) => {
616
+ const targetPath = path.resolve(process.cwd(), String(options.path || "."));
617
+ const listed = await listBudgetStates({
618
+ targetPath,
619
+ outputDir: options.outputDir,
620
+ workItemId: options.workItemId,
621
+ lifecycleStates: parseCsv(options.lifecycleState),
622
+ limit: parsePositiveInteger(options.limit, "limit", 50),
623
+ });
624
+ const payload = {
625
+ command: "daemon budget status",
626
+ targetPath,
627
+ workItemId: options.workItemId || null,
628
+ lifecycleStates: parseCsv(options.lifecycleState),
629
+ budgetStatePath: listed.budgetStatePath,
630
+ budgetEventsPath: listed.budgetEventsPath,
631
+ totalCount: listed.totalCount,
632
+ visibleCount: listed.records.length,
633
+ records: listed.records,
634
+ };
635
+ if (shouldEmitJson(options, command)) {
636
+ console.log(JSON.stringify(payload, null, 2));
637
+ return;
638
+ }
639
+ printBudgetSummary(payload);
640
+ });
641
+
642
+ budget
643
+ .command("check")
644
+ .description("Apply one budget-governance evaluation tick for a work item")
645
+ .argument("<workItemId>", "Queue work item id")
646
+ .option("--path <path>", "Workspace path for artifact/config resolution", ".")
647
+ .option("--output-dir <path>", "Optional output dir override for daemon artifacts")
648
+ .option(
649
+ "--usage-json <json>",
650
+ "Usage snapshot JSON (tokensUsed,costUsd,runtimeMs,toolCalls,pathOutOfScopeHits,networkDomainViolations)",
651
+ "{}"
652
+ )
653
+ .option(
654
+ "--budget-json <json>",
655
+ "Budget envelope JSON (maxTokens,maxCostUsd,maxRuntimeMs,maxToolCalls,maxPathViolations,maxNetworkViolations,warningThresholdPercent,quarantineGraceSeconds)",
656
+ "{}"
657
+ )
658
+ .option("--now-iso <timestamp>", "Optional deterministic timestamp override")
659
+ .option("--json", "Emit machine-readable output")
660
+ .action(async (workItemId, options, command) => {
661
+ const targetPath = path.resolve(process.cwd(), String(options.path || "."));
662
+ const checked = await applyDaemonBudgetCheck({
663
+ targetPath,
664
+ outputDir: options.outputDir,
665
+ workItemId,
666
+ usage: parseMetadata(options.usageJson),
667
+ budget: parseMetadata(options.budgetJson),
668
+ nowIso: options.nowIso,
669
+ });
670
+ const payload = {
671
+ command: "daemon budget check",
672
+ targetPath,
673
+ workItemId,
674
+ runId: checked.runId,
675
+ runPath: checked.runPath,
676
+ budgetStatePath: checked.budgetStatePath,
677
+ budgetEventsPath: checked.budgetEventsPath,
678
+ lifecycleState: checked.lifecycleState,
679
+ action: checked.action,
680
+ warnings: checked.warnings,
681
+ stopReasons: checked.stopReasons,
682
+ budget: checked.budget,
683
+ usage: checked.usage,
684
+ quarantineStartedAt: checked.quarantineStartedAt,
685
+ quarantineUntil: checked.quarantineUntil,
686
+ record: checked.record,
687
+ };
688
+ if (shouldEmitJson(options, command)) {
689
+ console.log(JSON.stringify(payload, null, 2));
690
+ return;
691
+ }
692
+ console.log(pc.bold("Budget governance check applied"));
693
+ console.log(
694
+ `${workItemId} lifecycle=${checked.lifecycleState} action=${checked.action} quarantine_until=${checked.quarantineUntil || "n/a"}`
695
+ );
696
+ });
697
+
698
+ const control = daemon
699
+ .command("control")
700
+ .description(
701
+ "Operator control plane snapshot and stop controls (agent roster, budget health, session timers)"
702
+ );
703
+
704
+ control
705
+ .option("--path <path>", "Workspace path for artifact/config resolution", ".")
706
+ .option("--output-dir <path>", "Optional output dir override for daemon artifacts")
707
+ .option(
708
+ "--status <csv>",
709
+ `Optional queue status filter (${WORK_ITEM_STATUSES.join(", ")})`
710
+ )
711
+ .option("--agent <identity>", "Optional assigned-agent filter")
712
+ .option("--limit <n>", "Maximum work items to return", "50")
713
+ .option("--json", "Emit machine-readable output")
714
+ .action(async (options, command) => {
715
+ const targetPath = path.resolve(process.cwd(), String(options.path || "."));
716
+ const snapshot = await buildOperatorControlSnapshot({
717
+ targetPath,
718
+ outputDir: options.outputDir,
719
+ statuses: parseCsv(options.status),
720
+ agentIdentity: options.agent,
721
+ limit: parsePositiveInteger(options.limit, "limit", 50),
722
+ });
723
+ const payload = {
724
+ command: "daemon control",
725
+ targetPath,
726
+ runId: snapshot.runId,
727
+ runPath: snapshot.runPath,
728
+ operatorStatePath: snapshot.operatorStatePath,
729
+ operatorEventsPath: snapshot.operatorEventsPath,
730
+ totalQueueItems: snapshot.totalQueueItems,
731
+ visibleWorkItems: snapshot.visibleWorkItems,
732
+ statusCounts: snapshot.statusCounts,
733
+ healthCounts: snapshot.healthCounts,
734
+ workItems: snapshot.workItems,
735
+ agentRoster: snapshot.agentRoster,
736
+ };
737
+ if (shouldEmitJson(options, command)) {
738
+ console.log(JSON.stringify(payload, null, 2));
739
+ return;
740
+ }
741
+ printControlPlaneSummary(payload);
742
+ });
743
+
744
+ control
745
+ .command("snapshot")
746
+ .description("Create one deterministic operator control-plane snapshot artifact")
747
+ .option("--path <path>", "Workspace path for artifact/config resolution", ".")
748
+ .option("--output-dir <path>", "Optional output dir override for daemon artifacts")
749
+ .option(
750
+ "--status <csv>",
751
+ `Optional queue status filter (${WORK_ITEM_STATUSES.join(", ")})`
752
+ )
753
+ .option("--agent <identity>", "Optional assigned-agent filter")
754
+ .option("--limit <n>", "Maximum work items to return", "50")
755
+ .option("--now-iso <timestamp>", "Optional deterministic timestamp override")
756
+ .option("--json", "Emit machine-readable output")
757
+ .action(async (options, command) => {
758
+ const targetPath = path.resolve(process.cwd(), String(options.path || "."));
759
+ const snapshot = await buildOperatorControlSnapshot({
760
+ targetPath,
761
+ outputDir: options.outputDir,
762
+ statuses: parseCsv(options.status),
763
+ agentIdentity: options.agent,
764
+ limit: parsePositiveInteger(options.limit, "limit", 50),
765
+ nowIso: options.nowIso,
766
+ });
767
+ const payload = {
768
+ command: "daemon control snapshot",
769
+ targetPath,
770
+ runId: snapshot.runId,
771
+ runPath: snapshot.runPath,
772
+ operatorStatePath: snapshot.operatorStatePath,
773
+ operatorEventsPath: snapshot.operatorEventsPath,
774
+ totalQueueItems: snapshot.totalQueueItems,
775
+ visibleWorkItems: snapshot.visibleWorkItems,
776
+ statusCounts: snapshot.statusCounts,
777
+ healthCounts: snapshot.healthCounts,
778
+ workItems: snapshot.workItems,
779
+ agentRoster: snapshot.agentRoster,
780
+ };
781
+ if (shouldEmitJson(options, command)) {
782
+ console.log(JSON.stringify(payload, null, 2));
783
+ return;
784
+ }
785
+ printControlPlaneSummary(payload);
786
+ });
787
+
788
+ control
789
+ .command("stop")
790
+ .description("Apply confirmed operator stop control (quarantine or squash) to a work item")
791
+ .argument("<workItemId>", "Queue work item id")
792
+ .option("--path <path>", "Workspace path for artifact/config resolution", ".")
793
+ .option("--output-dir <path>", "Optional output dir override for daemon artifacts")
794
+ .option(
795
+ "--mode <mode>",
796
+ `Stop mode (${OPERATOR_STOP_MODES.join(", ")})`,
797
+ "QUARANTINE"
798
+ )
799
+ .option("--reason <text>", "Operator reason for stop action")
800
+ .option("--actor <identity>", "Operator identity", "omar-operator")
801
+ .option("--confirm", "Confirm stop action execution")
802
+ .option("--now-iso <timestamp>", "Optional deterministic timestamp override")
803
+ .option("--json", "Emit machine-readable output")
804
+ .action(async (workItemId, options, command) => {
805
+ const targetPath = path.resolve(process.cwd(), String(options.path || "."));
806
+ const stopped = await applyOperatorStopControl({
807
+ targetPath,
808
+ outputDir: options.outputDir,
809
+ workItemId,
810
+ mode: normalizeOperatorStopMode(options.mode),
811
+ reason: options.reason,
812
+ actor: options.actor,
813
+ confirm: Boolean(options.confirm),
814
+ nowIso: options.nowIso,
815
+ });
816
+ const payload = {
817
+ command: "daemon control stop",
818
+ targetPath,
819
+ workItemId: stopped.workItemId,
820
+ mode: stopped.mode,
821
+ targetStatus: stopped.targetStatus,
822
+ actor: stopped.actor,
823
+ reason: stopped.reason,
824
+ operatorEventsPath: stopped.operatorEventsPath,
825
+ queuePath: stopped.queuePath,
826
+ queueItem: stopped.queueItem,
827
+ assignment: stopped.assignment,
828
+ jiraIssueKey: stopped.jiraIssueKey,
829
+ jiraCommented: stopped.jiraCommented,
830
+ jiraCommentWarning: stopped.jiraCommentWarning,
831
+ };
832
+ if (shouldEmitJson(options, command)) {
833
+ console.log(JSON.stringify(payload, null, 2));
834
+ return;
835
+ }
836
+ console.log(pc.bold("Operator stop applied"));
837
+ console.log(
838
+ `${stopped.workItemId} mode=${stopped.mode} status=${stopped.targetStatus} actor=${stopped.actor}`
839
+ );
840
+ if (stopped.jiraIssueKey) {
841
+ console.log(
842
+ `jira=${stopped.jiraIssueKey} commented=${stopped.jiraCommented ? "true" : "false"}`
843
+ );
844
+ }
845
+ if (stopped.jiraCommentWarning) {
846
+ console.log(pc.yellow(`jira_warning=${stopped.jiraCommentWarning}`));
847
+ }
848
+ });
849
+
850
+ }