@adhdev/daemon-core 0.9.82-rc.209 → 0.9.82-rc.210

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 (64) hide show
  1. package/dist/cli-adapters/terminal-backends/ghostty-vt-backend.d.ts +2 -0
  2. package/dist/commands/router.d.ts +6 -0
  3. package/dist/git/git-commands.d.ts +2 -0
  4. package/dist/git/git-diff.d.ts +6 -0
  5. package/dist/index.d.ts +11 -5
  6. package/dist/index.js +5699 -3486
  7. package/dist/index.js.map +1 -1
  8. package/dist/index.mjs +5679 -3483
  9. package/dist/index.mjs.map +1 -1
  10. package/dist/mesh/coordinator-prompt.d.ts +6 -0
  11. package/dist/mesh/mesh-delivery-policy.d.ts +5 -0
  12. package/dist/mesh/mesh-events-coordinator.d.ts +151 -0
  13. package/dist/mesh/mesh-events-pending.d.ts +33 -0
  14. package/dist/mesh/mesh-events-stale.d.ts +40 -0
  15. package/dist/mesh/mesh-events-utils.d.ts +14 -0
  16. package/dist/mesh/mesh-events.d.ts +5 -198
  17. package/dist/mesh/mesh-ledger-reconciliation.d.ts +23 -3
  18. package/dist/mesh/mesh-ledger.d.ts +19 -0
  19. package/dist/mesh/mesh-missions.d.ts +58 -0
  20. package/dist/mesh/mesh-review-inbox.d.ts +90 -0
  21. package/dist/mesh/mesh-runtime-store.d.ts +175 -0
  22. package/dist/mesh/mesh-task-stats.d.ts +49 -0
  23. package/dist/mesh/mesh-work-queue.d.ts +82 -0
  24. package/dist/mesh/refine-config.d.ts +24 -2
  25. package/dist/mesh/worktree-bootstrap-config.d.ts +22 -0
  26. package/dist/providers/acp-provider-instance.d.ts +2 -0
  27. package/dist/providers/spec/driver.d.ts +8 -0
  28. package/dist/providers/spec/evaluator.d.ts +4 -5
  29. package/dist/providers/spec/loader.d.ts +1 -0
  30. package/dist/providers/spec/schema.gen.d.ts +1409 -6
  31. package/dist/providers/spec/types.d.ts +188 -175
  32. package/dist/repo-mesh-types.d.ts +1 -0
  33. package/package.json +1 -1
  34. package/src/boot/daemon-lifecycle.ts +3 -0
  35. package/src/cli-adapters/terminal-backends/ghostty-vt-backend.ts +20 -7
  36. package/src/commands/router.ts +594 -66
  37. package/src/git/git-commands.ts +5 -5
  38. package/src/git/git-diff.ts +53 -0
  39. package/src/index.ts +11 -5
  40. package/src/mesh/coordinator-prompt.ts +14 -1
  41. package/src/mesh/mesh-delivery-policy.ts +17 -0
  42. package/src/mesh/mesh-events-coordinator.ts +1404 -0
  43. package/src/mesh/mesh-events-pending.ts +371 -0
  44. package/src/mesh/mesh-events-stale.ts +283 -0
  45. package/src/mesh/mesh-events-utils.ts +161 -0
  46. package/src/mesh/mesh-events.ts +27 -2143
  47. package/src/mesh/mesh-ledger-reconciliation.ts +12 -5
  48. package/src/mesh/mesh-ledger.ts +134 -2
  49. package/src/mesh/mesh-missions.ts +151 -0
  50. package/src/mesh/mesh-review-inbox.ts +307 -0
  51. package/src/mesh/mesh-runtime-store.ts +539 -3
  52. package/src/mesh/mesh-task-stats.ts +154 -0
  53. package/src/mesh/mesh-work-queue.ts +233 -17
  54. package/src/mesh/refine-config.ts +42 -5
  55. package/src/mesh/worktree-bootstrap-config.ts +79 -0
  56. package/src/providers/acp-provider-instance.ts +15 -1
  57. package/src/providers/cli-provider-instance.ts +34 -13
  58. package/src/providers/spec/driver.ts +57 -29
  59. package/src/providers/spec/evaluator.ts +302 -112
  60. package/src/providers/spec/loader.ts +226 -37
  61. package/src/providers/spec/schema.gen.ts +450 -334
  62. package/src/providers/spec/schema.json +162 -75
  63. package/src/providers/spec/types.ts +234 -183
  64. package/src/repo-mesh-types.ts +1 -0
@@ -1,21 +1,23 @@
1
1
  /**
2
- * spec.json loader + strict validator. Rejects spec files that don't
3
- * conform to schema.json — unknown fields, missing required fields,
4
- * wrong types, regex strings that don't compile, state references
5
- * that don't exist, etc.
2
+ * spec.json loader + strict validator.
6
3
  *
7
- * Validation runs once at load. Hot reload re-runs validation.
4
+ * Supports both v1 (adhdev:cli/spec@1) and v3 (adhdev:cli/spec@3).
5
+ * v1 specs are auto-migrated to v3 before validation.
6
+ * Rejects spec files that don't conform to the schema.
8
7
  */
9
8
  'use strict';
10
9
 
11
10
  import * as fs from 'node:fs';
12
11
  import * as path from 'node:path';
13
12
  import Ajv from 'ajv';
14
- import type { CliSpec } from './types.js';
15
- import { SCHEMA as schema } from './schema.gen.js';
13
+ import type { CliSpec, SectionDef, Condition, ExtractTitle, ExtractButtons } from './types.js';
14
+ import { SCHEMA_V1, SCHEMA_V3 } from './schema.gen.js';
16
15
 
17
- const ajv = new Ajv({ allErrors: true, strict: false });
18
- const validate = ajv.compile<CliSpec>(schema);
16
+ const ajvV1 = new Ajv({ allErrors: true, strict: false });
17
+ const validateV1 = ajvV1.compile(SCHEMA_V1);
18
+
19
+ const ajvV3 = new Ajv({ allErrors: true, strict: false });
20
+ const validateV3 = ajvV3.compile<CliSpec>(SCHEMA_V3);
19
21
 
20
22
  export interface SpecLoadResult {
21
23
  ok: true;
@@ -38,8 +40,19 @@ export function loadSpec(sourcePath: string): SpecLoadResult | SpecLoadError {
38
40
  return { ok: false, errors: [`Failed to read spec: ${(err as Error).message}`], sourcePath };
39
41
  }
40
42
 
41
- if (!validate(raw)) {
42
- const errors = (validate.errors || []).map(e => `${e.instancePath || '(root)'} ${e.message ?? 'invalid'}`);
43
+ // Auto-migrate v1 → v3
44
+ const schemaStr = (raw as any)?.$schema;
45
+ if (schemaStr === 'adhdev:cli/spec@1') {
46
+ // Validate as v1 first
47
+ if (!validateV1(raw)) {
48
+ const errors = (validateV1.errors || []).map(e => `${e.instancePath || '(root)'} ${e.message ?? 'invalid'} (v1 parse)`);
49
+ return { ok: false, errors, sourcePath };
50
+ }
51
+ raw = migrateV1toV3(raw as any);
52
+ }
53
+
54
+ if (!validateV3(raw)) {
55
+ const errors = (validateV3.errors || []).map(e => `${e.instancePath || '(root)'} ${e.message ?? 'invalid'}`);
43
56
  return { ok: false, errors, sourcePath };
44
57
  }
45
58
 
@@ -47,19 +60,178 @@ export function loadSpec(sourcePath: string): SpecLoadResult | SpecLoadError {
47
60
  const extra = validateRefs(spec);
48
61
  if (extra.length > 0) return { ok: false, errors: extra, sourcePath };
49
62
 
63
+ // Populate legacy `debounce` alias from `timing` for backward compat
64
+ attachDebounceAlias(spec);
65
+
50
66
  return { ok: true, spec, sourcePath };
51
67
  }
52
68
 
53
69
  /**
54
- * Cross-field validation that JSON Schema can't express on its own:
55
- * - section references actually point to a defined section
56
- * - state references in control_bar/notifications/delegate exist
57
- * - default_state exists
58
- * - regex / pattern strings compile
70
+ * Populate spec.debounce as a backward-compat alias for spec.timing.
71
+ * The driver and tests read debounce.{busy_hold_ms, idle_hold_ms, ...}.
72
+ * After this call both fields point at equivalent data.
59
73
  */
74
+ function attachDebounceAlias(spec: CliSpec): void {
75
+ const t = spec.timing;
76
+ if (!t) return;
77
+ const cm = t.completion_marker;
78
+ (spec as any).debounce = {
79
+ ...(t.busy_hold_ms !== undefined ? { busy_hold_ms: t.busy_hold_ms } : {}),
80
+ ...(t.idle_hold_ms !== undefined ? { idle_hold_ms: t.idle_hold_ms } : {}),
81
+ ...(t.startup_grace_ms !== undefined ? { startup_grace_ms: t.startup_grace_ms } : {}),
82
+ ...(cm ? {
83
+ completion_idle_after: {
84
+ ...(cm.section ? { section: cm.section } : {}),
85
+ regex: cm.matches,
86
+ ...(cm.flags ? { flags: cm.flags } : {}),
87
+ hold_ms: cm.hold_ms,
88
+ ...(cm.force_after_ms !== undefined ? { force_after_ms: cm.force_after_ms } : {}),
89
+ },
90
+ } : {}),
91
+ };
92
+ }
93
+
94
+ // ─────────────────────────────────────────────────────────────────────────────
95
+ // Migration: v1 → v3
96
+ // ─────────────────────────────────────────────────────────────────────────────
97
+
98
+ export function migrateV1toV3(raw: any): any {
99
+ // sections: array → object
100
+ const sections: Record<string, SectionDef> = {};
101
+ for (const sec of raw.layout?.sections ?? []) {
102
+ const { id, anchor_regex, until_regex, until_regex_flags, ...rest } = sec;
103
+ const sectionDef: any = { ...rest };
104
+
105
+ // anchor_regex → anchor
106
+ if (anchor_regex) sectionDef.anchor = anchor_regex;
107
+
108
+ // until: { section: id } → until: id (section reference)
109
+ if (rest.until?.section) {
110
+ sectionDef.until = rest.until.section;
111
+ } else {
112
+ delete sectionDef.until;
113
+ }
114
+
115
+ // keep until_regex for anchor-based extension
116
+ if (until_regex) sectionDef.until_regex = until_regex;
117
+ if (until_regex_flags) sectionDef.until_regex_flags = until_regex_flags;
118
+
119
+ sections[id] = sectionDef;
120
+ }
121
+
122
+ // states: convert when/extract_title/modal_buttons
123
+ const states = (raw.states ?? []).map((s: any) => {
124
+ // Convert when: SectionRegex → when: AllCondition | AnyCondition
125
+ let when: any;
126
+ if (s.when?.cursor_above_lines && s.when?.changed) {
127
+ // v1 delta detection condition
128
+ when = {
129
+ any: [
130
+ { cursor_above: s.when.cursor_above_lines, changed: true as const },
131
+ ],
132
+ };
133
+ } else if (s.when?.regex) {
134
+ const regexCond: any = {
135
+ section: s.when.section,
136
+ matches: s.when.regex,
137
+ ...(s.when.flags ? { flags: s.when.flags } : {}),
138
+ ...(s.when.cursor_row_min !== undefined ? { cursor_row_min: s.when.cursor_row_min } : {}),
139
+ ...(s.when.cursor_row_max !== undefined ? { cursor_row_max: s.when.cursor_row_max } : {}),
140
+ ...(s.when.cursor_col_min !== undefined ? { cursor_col_min: s.when.cursor_col_min } : {}),
141
+ ...(s.when.cursor_col_max !== undefined ? { cursor_col_max: s.when.cursor_col_max } : {}),
142
+ };
143
+ if (!regexCond.section) delete regexCond.section;
144
+ when = { all: [regexCond] };
145
+ } else {
146
+ // No regex — empty all (always matches)
147
+ when = { all: [] };
148
+ }
149
+
150
+ const extract: any = {};
151
+ if (s.extract_title) {
152
+ if (s.extract_title.first_line) {
153
+ extract.title = { section: s.extract_title.section, first_line: true as const };
154
+ if (!extract.title.section) delete extract.title.section;
155
+ } else if (s.extract_title.regex) {
156
+ extract.title = {
157
+ section: s.extract_title.section,
158
+ regex: s.extract_title.regex,
159
+ ...(s.extract_title.flags ? { flags: s.extract_title.flags } : {}),
160
+ };
161
+ if (!extract.title.section) delete extract.title.section;
162
+ }
163
+ }
164
+ if (s.modal_buttons) {
165
+ const mb = s.modal_buttons;
166
+ // Use first pattern from patterns[] array, or single pattern
167
+ const pat = mb.patterns?.length ? mb.patterns[0].pattern : mb.pattern;
168
+ const flg = mb.patterns?.length ? mb.patterns[0].flags : mb.flags;
169
+ if (pat) {
170
+ extract.buttons = {
171
+ ...(mb.section ? { section: mb.section } : {}),
172
+ pattern: pat,
173
+ ...(flg ? { flags: flg } : {}),
174
+ key_for_index: mb.key_for_index,
175
+ ...(mb.min_count !== undefined ? { min_count: mb.min_count } : {}),
176
+ ...(mb.continuation_lines !== undefined ? { continuation_lines: mb.continuation_lines } : {}),
177
+ };
178
+ }
179
+ }
180
+
181
+ return {
182
+ id: s.id,
183
+ label: s.label,
184
+ when,
185
+ ...(Object.keys(extract).length > 0 ? { extract } : {}),
186
+ };
187
+ });
188
+
189
+ // timing (from debounce)
190
+ const d = raw.debounce ?? {};
191
+ const timing: any = {};
192
+ if (d.busy_hold_ms !== undefined) timing.busy_hold_ms = d.busy_hold_ms;
193
+ if (d.idle_hold_ms !== undefined) timing.idle_hold_ms = d.idle_hold_ms;
194
+ if (d.startup_grace_ms !== undefined) timing.startup_grace_ms = d.startup_grace_ms;
195
+ if (d.completion_idle_after) {
196
+ timing.completion_marker = {
197
+ ...(d.completion_idle_after.section ? { section: d.completion_idle_after.section } : {}),
198
+ matches: d.completion_idle_after.regex,
199
+ ...(d.completion_idle_after.flags ? { flags: d.completion_idle_after.flags } : {}),
200
+ hold_ms: d.completion_idle_after.hold_ms,
201
+ ...(d.completion_idle_after.force_after_ms !== undefined
202
+ ? { force_after_ms: d.completion_idle_after.force_after_ms } : {}),
203
+ };
204
+ }
205
+
206
+ return {
207
+ $schema: 'adhdev:cli/spec@3',
208
+ id: raw.id,
209
+ name: raw.name,
210
+ binary: raw.binary,
211
+ ...(raw.cli_version_range ? { cli_version_range: raw.cli_version_range } : {}),
212
+ ...(raw.spawn_args ? { spawn_args: raw.spawn_args } : {}),
213
+ ...(raw.env ? { env: raw.env } : {}),
214
+ send_message: raw.send_message,
215
+ sections,
216
+ states,
217
+ default_state: raw.default_state,
218
+ ...(Object.keys(timing).length > 0 ? { timing } : {}),
219
+ ...(raw.control_bar ? { control_bar: raw.control_bar } : {}),
220
+ ...(raw.notifications ? { notifications: raw.notifications } : {}),
221
+ ...(raw.delegate ? { delegate: raw.delegate } : {}),
222
+ ...(raw.native_history ? { native_history: raw.native_history } : {}),
223
+ ...(raw.requiresFinalAssistantBeforeIdle
224
+ ? { requiresFinalAssistantBeforeIdle: raw.requiresFinalAssistantBeforeIdle } : {}),
225
+ };
226
+ }
227
+
228
+ // ─────────────────────────────────────────────────────────────────────────────
229
+ // Cross-field validation
230
+ // ─────────────────────────────────────────────────────────────────────────────
231
+
60
232
  function validateRefs(spec: CliSpec): string[] {
61
233
  const errs: string[] = [];
62
- const sectionIds = new Set(spec.layout.sections.map(s => s.id));
234
+ const sectionIds = new Set(Object.keys(spec.sections));
63
235
  const stateIds = new Set(spec.states.map(s => s.id));
64
236
 
65
237
  if (!stateIds.has(spec.default_state)) {
@@ -67,22 +239,21 @@ function validateRefs(spec: CliSpec): string[] {
67
239
  }
68
240
 
69
241
  for (const s of spec.states) {
70
- checkSectionRef(s.when, `states[${s.id}].when`, sectionIds, errs);
71
- compileRegex(s.when.regex, s.when.flags, `states[${s.id}].when.regex`, errs);
72
- if (s.extract_title) {
73
- checkSectionRef(s.extract_title, `states[${s.id}].extract_title`, sectionIds, errs);
74
- compileRegex(s.extract_title.regex, s.extract_title.flags, `states[${s.id}].extract_title.regex`, errs);
75
- }
76
- if (s.modal_buttons) {
77
- if (s.modal_buttons.section && !sectionIds.has(s.modal_buttons.section)) {
78
- errs.push(`states[${s.id}].modal_buttons.section "${s.modal_buttons.section}" unknown`);
242
+ validateConditionRefs(s.when, `states[${s.id}].when`, sectionIds, errs);
243
+ const ext = s.extract;
244
+ if (ext?.title) {
245
+ if (ext.title.section && !sectionIds.has(ext.title.section)) {
246
+ errs.push(`states[${s.id}].extract.title.section "${ext.title.section}" unknown`);
79
247
  }
80
- if (s.modal_buttons.pattern) {
81
- compileRegex(s.modal_buttons.pattern, s.modal_buttons.flags ?? 'm', `states[${s.id}].modal_buttons.pattern`, errs);
248
+ if (ext.title.regex) {
249
+ compileRegexCheck(ext.title.regex, ext.title.flags, `states[${s.id}].extract.title.regex`, errs);
82
250
  }
83
- for (const [pi, p] of (s.modal_buttons.patterns ?? []).entries()) {
84
- compileRegex(p.pattern, p.flags ?? 'm', `states[${s.id}].modal_buttons.patterns[${pi}]`, errs);
251
+ }
252
+ if (ext?.buttons) {
253
+ if (ext.buttons.section && !sectionIds.has(ext.buttons.section)) {
254
+ errs.push(`states[${s.id}].extract.buttons.section "${ext.buttons.section}" unknown`);
85
255
  }
256
+ compileRegexCheck(ext.buttons.pattern, ext.buttons.flags ?? 'm', `states[${s.id}].extract.buttons.pattern`, errs);
86
257
  }
87
258
  }
88
259
 
@@ -91,12 +262,16 @@ function validateRefs(spec: CliSpec): string[] {
91
262
  if (!stateIds.has(stId)) errs.push(`control_bar[${c.id}].visible_when_state references unknown state "${stId}"`);
92
263
  }
93
264
  if (c.action.type === 'open_picker') {
94
- checkSectionRef(c.action.wait_for, `control_bar[${c.id}].action.wait_for`, sectionIds, errs);
95
- compileRegex(c.action.wait_for.regex, c.action.wait_for.flags, `control_bar[${c.id}].action.wait_for.regex`, errs);
265
+ if (c.action.wait_for.section && !sectionIds.has(c.action.wait_for.section ?? '')) {
266
+ errs.push(`control_bar[${c.id}].action.wait_for.section "${c.action.wait_for.section}" unknown`);
267
+ }
268
+ if (c.action.wait_for.regex) {
269
+ compileRegexCheck(c.action.wait_for.regex, c.action.wait_for.flags, `control_bar[${c.id}].action.wait_for.regex`, errs);
270
+ }
96
271
  if (c.action.extract_choices.section && !sectionIds.has(c.action.extract_choices.section)) {
97
272
  errs.push(`control_bar[${c.id}].action.extract_choices.section "${c.action.extract_choices.section}" unknown`);
98
273
  }
99
- compileRegex(c.action.extract_choices.pattern, c.action.extract_choices.flags ?? 'm', `control_bar[${c.id}].action.extract_choices.pattern`, errs);
274
+ compileRegexCheck(c.action.extract_choices.pattern, c.action.extract_choices.flags ?? 'm', `control_bar[${c.id}].action.extract_choices.pattern`, errs);
100
275
  }
101
276
  }
102
277
 
@@ -121,12 +296,26 @@ function validateRefs(spec: CliSpec): string[] {
121
296
  return errs;
122
297
  }
123
298
 
124
- function checkSectionRef(ref: { section?: string }, where: string, valid: Set<string>, errs: string[]): void {
125
- if (ref.section && !valid.has(ref.section)) errs.push(`${where}.section "${ref.section}" unknown`);
299
+ function validateConditionRefs(cond: any, where: string, sectionIds: Set<string>, errs: string[]): void {
300
+ if (!cond) return;
301
+ if ('all' in cond) {
302
+ for (const c of cond.all ?? []) validateConditionRefs(c, where, sectionIds, errs);
303
+ } else if ('any' in cond) {
304
+ for (const c of cond.any ?? []) validateConditionRefs(c, where, sectionIds, errs);
305
+ } else if ('matches' in cond) {
306
+ if (cond.section && !sectionIds.has(cond.section)) {
307
+ errs.push(`${where}.section "${cond.section}" unknown`);
308
+ }
309
+ compileRegexCheck(cond.matches, cond.flags, `${where}.matches`, errs);
310
+ }
311
+ // ChangedCondition has no section refs
126
312
  }
127
313
 
128
- function compileRegex(source: string, flags: string | undefined, where: string, errs: string[]): void {
129
- try { new RegExp(source, flags ?? ''); } catch (err) { errs.push(`${where} regex invalid: ${(err as Error).message}`); }
314
+ function compileRegexCheck(source: string | undefined, flags: string | undefined, where: string, errs: string[]): void {
315
+ if (!source) return;
316
+ try { new RegExp(source, flags ?? ''); } catch (err) {
317
+ errs.push(`${where} regex invalid: ${(err as Error).message}`);
318
+ }
130
319
  }
131
320
 
132
321
  /** Convenience: look up a provider's spec.json next to its provider dir. */