@dezycro-ai/agent-plugin 2.1.2 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +14 -98
  2. package/dist/cli/dezycro.js +50 -0
  3. package/dist/cli/install.js +7 -0
  4. package/dist/dezycro-config.d.ts +52 -0
  5. package/dist/dezycro-config.js +167 -0
  6. package/dist/gateway/compact-cli.js +8 -0
  7. package/dist/gateway/proxy.js +35 -0
  8. package/dist/hooks/lib/router.js +14 -830
  9. package/dist/schemas/anthropic.d.ts +119 -0
  10. package/dist/schemas/anthropic.js +43 -0
  11. package/dist/schemas/dezycro.d.ts +19 -0
  12. package/dist/schemas/dezycro.js +22 -0
  13. package/dist/schemas/gateway.d.ts +40 -0
  14. package/dist/schemas/gateway.js +30 -0
  15. package/dist/schemas/gating.d.ts +32 -0
  16. package/dist/schemas/gating.js +15 -0
  17. package/dist/schemas/index.d.ts +14 -0
  18. package/dist/schemas/index.js +29 -0
  19. package/package.json +21 -13
  20. package/LICENSE +0 -18
  21. package/bin/resolve-auth.js +0 -112
  22. package/dist/hooks/lib/authoring.d.ts +0 -118
  23. package/dist/hooks/lib/authoring.js +0 -277
  24. package/dist/hooks/lib/command-bus.d.ts +0 -66
  25. package/dist/hooks/lib/command-bus.js +0 -357
  26. package/dist/hooks/lib/config.d.ts +0 -28
  27. package/dist/hooks/lib/config.js +0 -175
  28. package/dist/hooks/lib/controller-match.d.ts +0 -16
  29. package/dist/hooks/lib/controller-match.js +0 -96
  30. package/dist/hooks/lib/coverage.d.ts +0 -17
  31. package/dist/hooks/lib/coverage.js +0 -66
  32. package/dist/hooks/lib/hashing.d.ts +0 -8
  33. package/dist/hooks/lib/hashing.js +0 -49
  34. package/dist/hooks/lib/logging.d.ts +0 -13
  35. package/dist/hooks/lib/logging.js +0 -72
  36. package/dist/hooks/lib/publish-spec.d.ts +0 -17
  37. package/dist/hooks/lib/publish-spec.js +0 -64
  38. package/dist/hooks/lib/quality-gate.d.ts +0 -67
  39. package/dist/hooks/lib/quality-gate.js +0 -187
  40. package/dist/hooks/lib/router.d.ts +0 -32
  41. package/dist/hooks/lib/state.d.ts +0 -34
  42. package/dist/hooks/lib/state.js +0 -245
  43. package/dist/hooks/lib/undo.d.ts +0 -11
  44. package/dist/hooks/lib/undo.js +0 -71
  45. package/dist/hooks/lib/verify.d.ts +0 -28
  46. package/dist/hooks/lib/verify.js +0 -94
  47. package/dist/hooks/lib/workbook.d.ts +0 -21
  48. package/dist/hooks/lib/workbook.js +0 -77
  49. package/dist/hooks/types.d.ts +0 -293
  50. package/dist/hooks/types.js +0 -14
  51. package/install.js +0 -84
  52. package/setup.js +0 -53
@@ -1,830 +1,14 @@
1
- /**
2
- * router.ts dispatches hook invocations to the right handler.
3
- *
4
- * The companion-runner shim parses argv and stdin payloads into a
5
- * RouterInvocation and calls dispatch(). Per-handler boundaries try/catch
6
- * via the runner entry point: any exception inside this module is logged
7
- * by the runner and converts to silent exit so a hook never breaks a
8
- * user session.
9
- */
10
- 'use strict';
11
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
12
- if (k2 === undefined) k2 = k;
13
- var desc = Object.getOwnPropertyDescriptor(m, k);
14
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
15
- desc = { enumerable: true, get: function() { return m[k]; } };
16
- }
17
- Object.defineProperty(o, k2, desc);
18
- }) : (function(o, m, k, k2) {
19
- if (k2 === undefined) k2 = k;
20
- o[k2] = m[k];
21
- }));
22
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
23
- Object.defineProperty(o, "default", { enumerable: true, value: v });
24
- }) : function(o, v) {
25
- o["default"] = v;
26
- });
27
- var __importStar = (this && this.__importStar) || (function () {
28
- var ownKeys = function(o) {
29
- ownKeys = Object.getOwnPropertyNames || function (o) {
30
- var ar = [];
31
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
32
- return ar;
33
- };
34
- return ownKeys(o);
35
- };
36
- return function (mod) {
37
- if (mod && mod.__esModule) return mod;
38
- var result = {};
39
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
40
- __setModuleDefault(result, mod);
41
- return result;
42
- };
43
- })();
44
- Object.defineProperty(exports, "__esModule", { value: true });
45
- exports.TRIVIAL_TURN_CHAR_DEFAULT_EXPORT = exports._transcriptCharsSinceLastStop = exports._readActiveFeatureId = exports._handleSessionStart = exports._handleWorkbookMutation = exports._handleUserPromptSubmit = exports._handleStop = exports._handleUpdateTaskGate = exports._handlePostEdit = exports._extractEditedPaths = exports.QUALITY_GATE_REASON_PREFIX = void 0;
46
- exports.dispatch = dispatch;
47
- const fs = __importStar(require("fs"));
48
- const path = __importStar(require("path"));
49
- const config = __importStar(require("./config"));
50
- const state = __importStar(require("./state"));
51
- const logging = __importStar(require("./logging"));
52
- const verify = __importStar(require("./verify"));
53
- const publishSpec = __importStar(require("./publish-spec"));
54
- const controllerMatch = __importStar(require("./controller-match"));
55
- const authoring = __importStar(require("./authoring"));
56
- const qualityGate = __importStar(require("./quality-gate"));
57
- const hashing = __importStar(require("./hashing"));
58
- const workbook = __importStar(require("./workbook"));
59
- const undo = __importStar(require("./undo"));
60
- const coverage = __importStar(require("./coverage"));
61
- const commandBus = __importStar(require("./command-bus"));
62
- const DONE_STATUSES = new Set(['DONE', 'PUSHED']);
63
- const WRAP_PHRASES = [
64
- '/dezycro:sync',
65
- 'log what we did',
66
- 'log this',
67
- 'save progress',
68
- 'save what we did',
69
- 'wrap up',
70
- "we're done",
71
- 'we are done',
72
- 'end of session',
73
- 'sweep the workbook',
74
- ];
75
- function detectWrapPhrase(userMessage) {
76
- if (typeof userMessage !== 'string')
77
- return null;
78
- const m = userMessage.toLowerCase().trim();
79
- if (!m)
80
- return null;
81
- for (const p of WRAP_PHRASES) {
82
- if (m.includes(p))
83
- return p;
84
- }
85
- return null;
86
- }
87
- exports.QUALITY_GATE_REASON_PREFIX = 'TRD quality gate (deterministic layer) blocked APPROVED transition: ';
88
- async function dispatch(invocation) {
89
- const { event, route, payload, flags } = invocation;
90
- if (event === 'state') {
91
- return handleStateSubcommand(flags || {});
92
- }
93
- if (event === 'authoring-state') {
94
- return handleAuthoringState(flags || {});
95
- }
96
- if (event === 'pre-tool-use' && route === 'update_task') {
97
- return handleUpdateTaskGate(payload || {});
98
- }
99
- if (event === 'pre-tool-use' && route === 'change_document_status') {
100
- return handleChangeDocumentStatus(payload || {});
101
- }
102
- if (event === 'post-tool-use' && route === 'edit') {
103
- return handlePostEdit(payload || {});
104
- }
105
- if (event === 'post-tool-use' && route === 'workbook_mutation') {
106
- return handleWorkbookMutation(payload || {});
107
- }
108
- if (event === 'session-start') {
109
- return handleSessionStart(payload || {});
110
- }
111
- if (event === 'user-prompt-submit') {
112
- return handleUserPromptSubmit(payload || {});
113
- }
114
- if (event === 'stop') {
115
- return handleStop(payload || {});
116
- }
117
- return 0;
118
- }
119
- /* ─── Pillar 1 — pre-DONE verify gate (TRD §5.1) ─────────────────────────── */
120
- function handleUpdateTaskGate(payload) {
121
- const repoRoot = config.findRepoRoot(process.cwd());
122
- if (!repoRoot) {
123
- process.stdout.write('{}');
124
- return 0;
125
- }
126
- const toolInput = (payload && payload.tool_input) || {};
127
- const status = String(toolInput.status || '').toUpperCase();
128
- if (!DONE_STATUSES.has(status)) {
129
- process.stdout.write('{}');
130
- return 0;
131
- }
132
- const pathRefsRaw = toolInput.pathRefs;
133
- const pathRefs = Array.isArray(pathRefsRaw) ? pathRefsRaw.filter(Boolean) : [];
134
- if (pathRefs.length === 0) {
135
- process.stdout.write('{}');
136
- return 0;
137
- }
138
- const cfg = safeLoadConfig(repoRoot);
139
- const av = (cfg && cfg.companion && cfg.companion.autoVerify) || {};
140
- if (av.preTaskDone === false) {
141
- process.stdout.write('{}');
142
- return 0;
143
- }
144
- const passed = verify.hasRecentPassingVerify({ repoRoot, paths: pathRefs });
145
- if (passed) {
146
- logging.info(repoRoot, 'pre_done_gate_pass', { status, pathRefs });
147
- // Pillar 4 pre-PUSH coverage nudge.
148
- if (status === 'PUSHED') {
149
- const nudge = buildPrePushCoverageNudge(repoRoot, cfg);
150
- if (nudge) {
151
- logging.info(repoRoot, 'pre_push_coverage_nudge', { line: nudge });
152
- const out = { systemMessage: nudge };
153
- process.stdout.write(JSON.stringify(out));
154
- return 0;
155
- }
156
- }
157
- process.stdout.write('{}');
158
- return 0;
159
- }
160
- state.incrementMetric(repoRoot, 'verifyAutoRuns');
161
- state.incrementMetric(repoRoot, 'verifyAutoFailures');
162
- logging.warn(repoRoot, 'pre_done_gate_block', { status, pathRefs });
163
- const reason = verify.buildBlockReason({ paths: pathRefs, status }).reason;
164
- const out = {
165
- decision: 'block',
166
- reason,
167
- hookSpecificOutput: {
168
- hookEventName: 'PreToolUse',
169
- permissionDecision: 'deny',
170
- permissionDecisionReason: reason,
171
- },
172
- };
173
- process.stdout.write(JSON.stringify(out));
174
- return 0;
175
- }
176
- function buildPrePushCoverageNudge(repoRoot, cfg) {
177
- const cv = (cfg && cfg.companion && cfg.companion.coverageNudges) || {};
178
- if (cv.enabled === false)
179
- return null;
180
- const snap = state.read(repoRoot).coverageSnapshot;
181
- const line = coverage.formatOneLiner(snap);
182
- if (!line)
183
- return null;
184
- return `[Companion] ${line} — verify passed, but consider checking these before push.`;
185
- }
186
- /* ─── Pillar 5 — TRD quality gate on change_document_status (TRD §5.5.3) ── */
187
- function handleChangeDocumentStatus(payload) {
188
- const toolInput = (payload && payload.tool_input) || {};
189
- const ti = toolInput;
190
- const status = ti.status || ti.newStatus || null;
191
- const docId = ti.docId || ti.documentId || ti.id || null;
192
- if (status !== 'APPROVED') {
193
- process.stdout.write('{}');
194
- return 0;
195
- }
196
- const repoRoot = config.findRepoRoot(process.cwd());
197
- if (!repoRoot) {
198
- process.stdout.write('{}');
199
- return 0;
200
- }
201
- const sidecar = authoring.readDraftSidecar(repoRoot);
202
- if (!sidecar || (docId && sidecar.docId && sidecar.docId !== docId)) {
203
- logging.warn(repoRoot, 'quality_gate_degraded_permit', {
204
- reason: sidecar ? 'docId_mismatch' : 'sidecar_missing',
205
- docId,
206
- sidecarDocId: sidecar ? sidecar.docId : null,
207
- });
208
- process.stdout.write('{}');
209
- return 0;
210
- }
211
- if (sidecar.docType !== 'TRD') {
212
- process.stdout.write('{}');
213
- return 0;
214
- }
215
- const result = qualityGate.deterministicTrdGate({
216
- trdContent: sidecar.content,
217
- prdContent: sidecar.prdContent || '',
218
- });
219
- if (result.passed) {
220
- logging.info(repoRoot, 'quality_gate_deterministic_pass', { docId: sidecar.docId });
221
- process.stdout.write('{}');
222
- return 0;
223
- }
224
- state.incrementMetric(repoRoot, 'qualityGateFailures');
225
- const summary = result.blockingIssues
226
- .map((b) => `[${b.section}] ${b.issue}`)
227
- .slice(0, 5)
228
- .join(' | ');
229
- const reason = `${exports.QUALITY_GATE_REASON_PREFIX}${summary || 'unspecified failure'}. Fix and try again.`;
230
- logging.warn(repoRoot, 'quality_gate_blocked', { docId: sidecar.docId, issues: result.blockingIssues });
231
- const out = {
232
- decision: 'block',
233
- reason,
234
- hookSpecificOutput: {
235
- hookEventName: 'PreToolUse',
236
- permissionDecision: 'deny',
237
- permissionDecisionReason: reason,
238
- },
239
- };
240
- process.stdout.write(JSON.stringify(out));
241
- return 0;
242
- }
243
- /* ─── Pillars 1+2 — post-edit pulse + controller-match (TRD §5.1+§5.2) ─── */
244
- async function handlePostEdit(payload) {
245
- const repoRoot = config.findRepoRoot(process.cwd());
246
- if (!repoRoot)
247
- return 0;
248
- // Autonomous-loop poll for the Agent Command Bus inbox (TRD §Pillar 1 PostToolUse).
249
- // Subject to the floor, so most edits no-op. When events arrive, surface them as
250
- // additionalContext and short-circuit — pulse/spec-publish logic skips this turn.
251
- {
252
- const cfg = safeLoadConfig(repoRoot);
253
- const cur = state.read(repoRoot);
254
- const result = await commandBus.pollIfDue(repoRoot, 'postToolUse', cfg?.companion?.commandBus, cur);
255
- if (result.statePatch)
256
- state.patch(repoRoot, result.statePatch);
257
- const block = commandBus.renderForAgent(result.events, result.reactionMode);
258
- if (block) {
259
- const out = {
260
- hookSpecificOutput: {
261
- hookEventName: 'PostToolUse',
262
- additionalContext: block,
263
- },
264
- };
265
- process.stdout.write(JSON.stringify(out));
266
- return 0;
267
- }
268
- }
269
- const toolInput = (payload && payload.tool_input) || {};
270
- const editedPaths = extractEditedPaths(toolInput);
271
- if (editedPaths.length === 0)
272
- return 0;
273
- const cfg = safeLoadConfig(repoRoot);
274
- const av = (cfg && cfg.companion && cfg.companion.autoVerify) || {};
275
- const globs = Array.isArray(av.controllerPaths) ? av.controllerPaths : [];
276
- const pulseEnabled = av.postEditPulse && av.postEditPulse.enabled !== false;
277
- const pulseThreshold = (av.postEditPulse && Number(av.postEditPulse.threshold)) || 5;
278
- const matchedControllers = controllerMatch.selectControllers(editedPaths, globs);
279
- const editedTs = new Date().toISOString();
280
- const current = state.read(repoRoot);
281
- const nextPulseCount = (current.pulseCount || 0) + 1;
282
- const shouldNudge = pulseEnabled &&
283
- nextPulseCount >= pulseThreshold &&
284
- matchedControllers.length > 0;
285
- if (matchedControllers.length === 0) {
286
- state.patch(repoRoot, {
287
- pulseCount: nextPulseCount,
288
- lastEditTs: editedTs,
289
- });
290
- }
291
- else {
292
- for (let i = 0; i < matchedControllers.length; i += 1) {
293
- const isLast = i === matchedControllers.length - 1;
294
- const editPatch = {
295
- pushControllerEdit: { path: matchedControllers[i], editedTs },
296
- };
297
- if (isLast) {
298
- editPatch.needsSpecPublish = true;
299
- editPatch.lastEditTs = editedTs;
300
- editPatch.pulseCount = shouldNudge ? 0 : nextPulseCount;
301
- }
302
- state.patch(repoRoot, editPatch);
303
- }
304
- }
305
- logging.info(repoRoot, 'post_edit', {
306
- edited: editedPaths.length,
307
- matchedControllers: matchedControllers.length,
308
- pulseCount: shouldNudge ? 0 : nextPulseCount,
309
- nudged: shouldNudge,
310
- });
311
- if (shouldNudge) {
312
- const nudge = '[Companion] ' + nextPulseCount + ' edits since last verify and controller paths were touched ' +
313
- '(' + matchedControllers.join(', ') + '). Consider running `/dezycro:verify` to catch regressions early.';
314
- process.stdout.write(JSON.stringify({
315
- hookSpecificOutput: {
316
- hookEventName: 'PostToolUse',
317
- additionalContext: nudge,
318
- },
319
- }));
320
- }
321
- return 0;
322
- }
323
- function extractEditedPaths(toolInput) {
324
- if (!toolInput || typeof toolInput !== 'object')
325
- return [];
326
- const fp = toolInput.file_path;
327
- if (typeof fp === 'string')
328
- return [fp];
329
- const p = toolInput.path;
330
- if (typeof p === 'string')
331
- return [p];
332
- const edits = toolInput.edits;
333
- if (Array.isArray(edits)) {
334
- const seen = new Set();
335
- for (const e of edits) {
336
- if (e && typeof e.file_path === 'string') {
337
- seen.add(e.file_path);
338
- }
339
- }
340
- return Array.from(seen);
341
- }
342
- return [];
343
- }
344
- /* ─── Skill-facing subcommands (state / authoring-state) ─────────────────── */
345
- function handleStateSubcommand(flags) {
346
- const repoRoot = config.findRepoRoot(process.cwd()) || process.cwd();
347
- if (flags.get) {
348
- const snap = state.read(repoRoot);
349
- process.stdout.write(JSON.stringify(snap, null, 2));
350
- return 0;
351
- }
352
- if (typeof flags.patch === 'string') {
353
- let parsed;
354
- try {
355
- parsed = JSON.parse(flags.patch);
356
- }
357
- catch (err) {
358
- const msg = err.message;
359
- process.stdout.write(JSON.stringify({ ok: false, error: 'invalid JSON for --patch: ' + msg }));
360
- return 1;
361
- }
362
- if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
363
- process.stdout.write(JSON.stringify({ ok: false, error: '--patch must be a JSON object' }));
364
- return 1;
365
- }
366
- const next = state.patch(repoRoot, parsed);
367
- if (next === null) {
368
- process.stdout.write(JSON.stringify({ ok: false, error: 'lock contention — state not patched' }));
369
- return 1;
370
- }
371
- process.stdout.write(JSON.stringify({ ok: true }));
372
- return 0;
373
- }
374
- process.stdout.write(JSON.stringify({
375
- ok: false,
376
- error: 'state requires one of --get or --patch=<json>',
377
- }));
378
- return 1;
379
- }
380
- function handleAuthoringState(flags) {
381
- const repoRoot = config.findRepoRoot(process.cwd()) || process.cwd();
382
- const action = flags.set || (flags.get ? 'get' : null);
383
- if (action === 'enter') {
384
- const docId = flags['doc-id'];
385
- const docType = flags['doc-type'];
386
- if (!docId || !docType) {
387
- process.stdout.write(JSON.stringify({
388
- ok: false,
389
- error: '--doc-id and --doc-type are required for --set=enter',
390
- }));
391
- return 1;
392
- }
393
- try {
394
- const transcript = authoring.enter(repoRoot, {
395
- docId,
396
- docType: docType,
397
- featureId: flags['feature-id'] || null,
398
- seed: flags.seed || null,
399
- });
400
- process.stdout.write(JSON.stringify({ ok: true, transcript }));
401
- return 0;
402
- }
403
- catch (err) {
404
- process.stdout.write(JSON.stringify({ ok: false, error: err.message }));
405
- return 1;
406
- }
407
- }
408
- if (action === 'exit') {
409
- try {
410
- authoring.exit(repoRoot);
411
- process.stdout.write(JSON.stringify({ ok: true }));
412
- return 0;
413
- }
414
- catch (err) {
415
- process.stdout.write(JSON.stringify({ ok: false, error: err.message }));
416
- return 1;
417
- }
418
- }
419
- if (action === 'get') {
420
- const snap = authoring.getState(repoRoot);
421
- process.stdout.write(JSON.stringify({ ok: true, ...snap }));
422
- return 0;
423
- }
424
- process.stdout.write(JSON.stringify({
425
- ok: false,
426
- error: 'authoring-state requires one of --set=enter, --set=exit, --get',
427
- }));
428
- return 1;
429
- }
430
- /* ─── Pillar 3 — Proactive Workbook Updates (TRD §5.3) ────────────────────── */
431
- const TRIVIAL_TURN_CHAR_DEFAULT = 600;
432
- async function handleSessionStart(_payload) {
433
- const repoRoot = config.findRepoRoot(process.cwd());
434
- if (!repoRoot)
435
- return 0;
436
- state.patch(repoRoot, {
437
- sessionAutoLogCount: 0,
438
- turnAutoLogCount: 0,
439
- currentTurnId: 0,
440
- lastAutoWorkbookMutation: null,
441
- coverageFetchPending: false,
442
- });
443
- logging.info(repoRoot, 'session_start', {});
444
- // Bootstrap-on-start poll for the Agent Command Bus inbox. Bypasses the floor.
445
- // Failures swallowed; fetched events surface on the next UserPromptSubmit.
446
- const cfg = safeLoadConfig(repoRoot);
447
- const current = state.read(repoRoot);
448
- const result = await commandBus.pollIfDue(repoRoot, 'sessionStart', cfg?.companion?.commandBus, current);
449
- if (result.statePatch)
450
- state.patch(repoRoot, result.statePatch);
451
- return 0;
452
- }
453
- async function handleUserPromptSubmit(payload) {
454
- const repoRoot = config.findRepoRoot(process.cwd());
455
- if (!repoRoot)
456
- return 0;
457
- const current = state.read(repoRoot);
458
- const nextTurnId = (current.currentTurnId || 0) + 1;
459
- state.patch(repoRoot, {
460
- currentTurnId: nextTurnId,
461
- turnAutoLogCount: 0,
462
- });
463
- // Pre-reasoning poll for the Agent Command Bus inbox. If new events arrived
464
- // since the last poll, surface them as additionalContext so the agent factors
465
- // them in before responding. Wins over the existing undo / coverage paths
466
- // for this turn — invalidations are blocking by nature.
467
- {
468
- const cfg = safeLoadConfig(repoRoot);
469
- const result = await commandBus.pollIfDue(repoRoot, 'userPromptSubmit', cfg?.companion?.commandBus, current);
470
- if (result.statePatch)
471
- state.patch(repoRoot, result.statePatch);
472
- const block = commandBus.renderForAgent(result.events, result.reactionMode);
473
- if (block) {
474
- const out = {
475
- hookSpecificOutput: {
476
- hookEventName: 'UserPromptSubmit',
477
- additionalContext: block,
478
- },
479
- };
480
- process.stdout.write(JSON.stringify(out));
481
- return 0;
482
- }
483
- }
484
- const userMessage = String(payload?.prompt ?? payload?.user_message ?? payload?.message ?? '').trim();
485
- const wrap = detectWrapPhrase(userMessage);
486
- if (wrap) {
487
- state.patch(repoRoot, { pendingWorkbookSweep: true, pendingWorkbookSweepReason: 'wrap_phrase:' + wrap });
488
- logging.info(repoRoot, 'wrap_phrase_detected', { phrase: wrap });
489
- }
490
- if (userMessage && undo.isUndoMessage(userMessage)) {
491
- const mut = current.lastAutoWorkbookMutation;
492
- if (mut && undo.isInUndoWindow(mut, nextTurnId)) {
493
- const directive = undo.buildReversalDirective(mut);
494
- if (directive) {
495
- state.incrementMetric(repoRoot, 'undoCount');
496
- state.patch(repoRoot, { lastAutoWorkbookMutation: null });
497
- logging.info(repoRoot, 'undo_directive', { tool: directive.tool, entityId: mut.entityId });
498
- const reason = '[Dezycro Companion — undo] ' + directive.humanLine + '\n\n' +
499
- 'Call ' + directive.tool + ' with arguments:\n' +
500
- JSON.stringify(directive.args, null, 2) + '\n\n' +
501
- 'Then confirm the reversal in one short sentence.';
502
- const out = {
503
- hookSpecificOutput: {
504
- hookEventName: 'UserPromptSubmit',
505
- additionalContext: reason,
506
- },
507
- };
508
- process.stdout.write(JSON.stringify(out));
509
- return 0;
510
- }
511
- }
512
- }
513
- // Pillar 2 — persistent publish-spec/verify reminder. The PostToolUse pulse
514
- // nudge only fires on the exact edit that touches a controller path (and only
515
- // once pulseCount crosses the threshold), so endpoint changes routinely go
516
- // un-surfaced. Re-surface on every UserPromptSubmit while needsSpecPublish is
517
- // set, so the agent is reminded each turn until it publishes the spec. Gated
518
- // by companion.autoVerify.autoPublishSpecOnControllerChange (default true).
519
- {
520
- const specNudge = buildPublishSpecNudge(repoRoot);
521
- if (specNudge) {
522
- logging.info(repoRoot, 'publish_spec_nudge', { paths: specNudge.count });
523
- const out = {
524
- hookSpecificOutput: {
525
- hookEventName: 'UserPromptSubmit',
526
- additionalContext: specNudge.text,
527
- },
528
- };
529
- process.stdout.write(JSON.stringify(out));
530
- return 0;
531
- }
532
- }
533
- maybeSurfaceCoverage(repoRoot, current);
534
- process.stdout.write('{}');
535
- return 0;
536
- }
537
- /**
538
- * Build the persistent publish-spec/verify reminder, or null when nothing is
539
- * pending or the feature is disabled. Surfaced on every UserPromptSubmit while
540
- * `needsSpecPublish` is set; clears automatically once `/dezycro:publish-spec`
541
- * resets the flags.
542
- */
543
- function buildPublishSpecNudge(repoRoot) {
544
- const cfg = safeLoadConfig(repoRoot);
545
- const av = (cfg && cfg.companion && cfg.companion.autoVerify) || {};
546
- if (av.autoPublishSpecOnControllerChange === false)
547
- return null;
548
- const check = publishSpec.needsPublishBeforeVerify(repoRoot);
549
- if (!check.needed || check.controllerPaths.length === 0)
550
- return null;
551
- const MAX_LISTED = 8;
552
- const paths = check.controllerPaths;
553
- const shown = paths.slice(0, MAX_LISTED).map((p) => ' - ' + p);
554
- if (paths.length > MAX_LISTED)
555
- shown.push(' - …and ' + (paths.length - MAX_LISTED) + ' more');
556
- const text = '[Dezycro Companion] ' + paths.length + ' controller/API file(s) changed since the last published spec:\n' +
557
- shown.join('\n') + '\n\n' +
558
- 'New or changed endpoints are NOT yet reflected in the published OpenAPI spec, so verification ' +
559
- 'cannot see them. Before continuing, run `/dezycro:publish-spec` to upload the updated spec (this ' +
560
- 'regenerates the JEP), then `/dezycro:verify` to check the feature against it. This reminder repeats ' +
561
- 'each turn until the spec is published and clears automatically after `/dezycro:publish-spec`.';
562
- return { text, count: paths.length };
563
- }
564
- function maybeSurfaceCoverage(repoRoot, current) {
565
- const cfg = safeLoadConfig(repoRoot);
566
- if (cfg?.companion?.coverageNudges?.enabled === false)
567
- return;
568
- const snap = current.coverageSnapshot;
569
- if (!snap)
570
- return;
571
- if (snap.surfacedTs)
572
- return;
573
- const line = coverage.formatOneLiner(snap);
574
- if (!line)
575
- return;
576
- state.patch(repoRoot, {
577
- coverageSnapshot: { ...snap, surfacedTs: new Date().toISOString() },
578
- });
579
- logging.info(repoRoot, 'coverage_one_liner', { line });
580
- const out = {
581
- hookSpecificOutput: {
582
- hookEventName: 'UserPromptSubmit',
583
- additionalContext: '[Companion] ' + line,
584
- },
585
- };
586
- process.stdout.write(JSON.stringify(out));
587
- }
588
- async function handleStop(payload) {
589
- const repoRoot = config.findRepoRoot(process.cwd());
590
- if (!repoRoot)
591
- return 0;
592
- const cfg = safeLoadConfig(repoRoot);
593
- // Between-turn poll for the Agent Command Bus inbox (TRD §Pillar 1 Stop). Subject to
594
- // the floor. When events arrive, surface as a top-level systemMessage (Stop has no
595
- // hookSpecificOutput) and short-circuit — workbook sweep skips this turn.
596
- {
597
- const cur0 = state.read(repoRoot);
598
- const result = await commandBus.pollIfDue(repoRoot, 'stop', cfg?.companion?.commandBus, cur0);
599
- if (result.statePatch)
600
- state.patch(repoRoot, result.statePatch);
601
- const block = commandBus.renderForAgent(result.events, result.reactionMode);
602
- if (block) {
603
- const out = { systemMessage: block };
604
- process.stdout.write(JSON.stringify(out));
605
- return 0;
606
- }
607
- }
608
- const ws = cfg?.companion?.autoWorkbookUpdates || {};
609
- if (cfg?.companion?.safeMode === true)
610
- return silentExit();
611
- if (ws.enabled === false)
612
- return silentExit();
613
- const cur = state.read(repoRoot);
614
- const verifyAdvanced = Boolean(cur.lastVerifyCompletionTs &&
615
- cur.lastVerifyCompletionTs !== cur.lastSeenVerifyCompletionTs);
616
- const hasTrigger = Boolean(cur.pendingWorkbookSweep || verifyAdvanced);
617
- if (!hasTrigger) {
618
- logging.info(repoRoot, 'stop_skip', { reason: 'no_trigger' });
619
- if (verifyAdvanced) {
620
- state.patch(repoRoot, { lastSeenVerifyCompletionTs: cur.lastVerifyCompletionTs });
621
- }
622
- return maybeStopCoverageDelta(repoRoot, cur);
623
- }
624
- if (cur.authoringMode) {
625
- logging.info(repoRoot, 'stop_skip', { reason: 'authoring_mode' });
626
- state.patch(repoRoot, {
627
- pendingWorkbookSweep: false,
628
- pendingWorkbookSweepReason: null,
629
- lastSeenVerifyCompletionTs: cur.lastVerifyCompletionTs || cur.lastSeenVerifyCompletionTs,
630
- });
631
- return maybeStopCoverageDelta(repoRoot, cur);
632
- }
633
- const maxTurn = Number(ws.maxAutoLogsPerTurn ?? 2);
634
- const maxSession = Number(ws.maxAutoLogsPerSession ?? 10);
635
- const perTurnSlotsLeft = Math.max(0, maxTurn - (cur.turnAutoLogCount || 0));
636
- const perSessionSlotsLeft = Math.max(0, maxSession - (cur.sessionAutoLogCount || 0));
637
- if (perTurnSlotsLeft === 0 || perSessionSlotsLeft === 0) {
638
- logging.info(repoRoot, 'stop_skip', {
639
- reason: 'caps_exhausted',
640
- perTurnSlotsLeft, perSessionSlotsLeft,
641
- });
642
- state.patch(repoRoot, {
643
- pendingWorkbookSweep: false,
644
- pendingWorkbookSweepReason: null,
645
- lastSeenVerifyCompletionTs: cur.lastVerifyCompletionTs || cur.lastSeenVerifyCompletionTs,
646
- });
647
- return maybeStopCoverageDelta(repoRoot, cur);
648
- }
649
- if (cur.suppressWorkbookUpdatesUntil && Date.now() < new Date(cur.suppressWorkbookUpdatesUntil).getTime()) {
650
- logging.info(repoRoot, 'stop_skip', { reason: 'suppressed' });
651
- state.patch(repoRoot, {
652
- pendingWorkbookSweep: false,
653
- pendingWorkbookSweepReason: null,
654
- lastSeenVerifyCompletionTs: cur.lastVerifyCompletionTs || cur.lastSeenVerifyCompletionTs,
655
- });
656
- return maybeStopCoverageDelta(repoRoot, cur);
657
- }
658
- state.patch(repoRoot, {
659
- pendingWorkbookSweep: false,
660
- pendingWorkbookSweepReason: null,
661
- lastSeenVerifyCompletionTs: cur.lastVerifyCompletionTs || cur.lastSeenVerifyCompletionTs,
662
- });
663
- const thresholds = config.resolveThresholds(cfg);
664
- const recentHashes = [
665
- ...(cur.recentDecisionHashes || []),
666
- ...(cur.recentIssueHashes || []),
667
- ...(cur.recentAssumptionHashes || []),
668
- ];
669
- const activeFeature = readActiveFeature(repoRoot);
670
- let directive = workbook.buildSweepDirective({
671
- thresholds,
672
- perTurnSlotsLeft,
673
- perSessionSlotsLeft,
674
- recentHashes,
675
- activeFeatureId: activeFeature.featureId,
676
- activeFeatureName: activeFeature.featureName,
677
- });
678
- logging.info(repoRoot, 'stop_sweep_directive', {
679
- perTurnSlotsLeft, perSessionSlotsLeft, recentHashCount: recentHashes.length,
680
- });
681
- const delta = computeCoverageDeltaForStop(repoRoot, cur);
682
- if (delta)
683
- directive += '\n\n' + delta;
684
- const out = { decision: 'block', reason: directive };
685
- process.stdout.write(JSON.stringify(out));
686
- return 0;
687
- }
688
- function maybeStopCoverageDelta(repoRoot, cur) {
689
- const delta = computeCoverageDeltaForStop(repoRoot, cur);
690
- if (!delta)
691
- return silentExit();
692
- logging.info(repoRoot, 'stop_coverage_delta', { delta });
693
- const out = { systemMessage: delta };
694
- process.stdout.write(JSON.stringify(out));
695
- return 0;
696
- }
697
- function computeCoverageDeltaForStop(repoRoot, cur) {
698
- const lastVerify = cur.lastVerifyCompletionTs;
699
- const snap = cur.coverageSnapshot;
700
- if (!lastVerify || !snap)
701
- return null;
702
- const surfaced = snap.surfacedTs ? new Date(snap.surfacedTs).getTime() : 0;
703
- const verifyTs = new Date(lastVerify).getTime();
704
- if (verifyTs <= surfaced)
705
- return null;
706
- const line = coverage.formatDelta(snap.previous || null, snap);
707
- if (!line)
708
- return null;
709
- state.patch(repoRoot, { coverageSnapshot: { ...snap, surfacedTs: new Date().toISOString() } });
710
- return '[Companion] ' + line;
711
- }
712
- function silentExit() { process.stdout.write('{}'); return 0; }
713
- function transcriptCharsSinceLastStop(payload) {
714
- try {
715
- const tp = payload?.transcript_path;
716
- if (!tp || typeof tp !== 'string' || !fs.existsSync(tp))
717
- return null;
718
- const raw = fs.readFileSync(tp, 'utf8');
719
- const lines = raw.split('\n').filter(Boolean);
720
- let chars = 0;
721
- let foundPrev = false;
722
- for (let i = lines.length - 1; i >= 0; i -= 1) {
723
- let evt;
724
- try {
725
- evt = JSON.parse(lines[i]);
726
- }
727
- catch {
728
- continue;
729
- }
730
- const role = evt?.message?.role ?? evt?.role;
731
- if (role === 'assistant') {
732
- const txt = JSON.stringify(evt.message?.content ?? evt.content ?? '');
733
- chars += txt.length;
734
- }
735
- else if (role === 'user') {
736
- if (foundPrev)
737
- break;
738
- foundPrev = true;
739
- const txt = JSON.stringify(evt.message?.content ?? evt.content ?? '');
740
- chars += txt.length;
741
- }
742
- if (chars > 1000000)
743
- break;
744
- }
745
- return chars;
746
- }
747
- catch {
748
- return null;
749
- }
750
- }
751
- function readActiveFeatureId(repoRoot) {
752
- return readActiveFeature(repoRoot).featureId;
753
- }
754
- function readActiveFeature(repoRoot) {
755
- try {
756
- const raw = fs.readFileSync(path.join(repoRoot, '.dezycro', 'active-feature.json'), 'utf8');
757
- const p = JSON.parse(raw);
758
- return { featureId: p?.featureId || null, featureName: p?.featureName || null };
759
- }
760
- catch {
761
- return { featureId: null, featureName: null };
762
- }
763
- }
764
- function safeLoadConfig(repoRoot) {
765
- try {
766
- return config.load(repoRoot);
767
- }
768
- catch {
769
- return JSON.parse(JSON.stringify(config.DEFAULT_CONFIG));
770
- }
771
- }
772
- function handleWorkbookMutation(payload) {
773
- const repoRoot = config.findRepoRoot(process.cwd());
774
- if (!repoRoot)
775
- return 0;
776
- const toolName = payload?.tool_name || '';
777
- const kind = workbook.entityKindForTool(toolName);
778
- if (!kind)
779
- return 0;
780
- const toolInput = (payload?.tool_input || {});
781
- const toolResponse = (payload?.tool_response || payload?.response || {});
782
- const summary = String(toolInput.summary || toolInput.title || toolInput.description || toolInput.statement || '');
783
- const linkedTaskId = toolInput.taskId || toolInput.linkedTaskId || null;
784
- const hash = hashing.hashCandidate(kind, summary, linkedTaskId);
785
- const respData = toolResponse.data;
786
- const entityId = toolResponse.id ||
787
- (respData ? respData.id : undefined) ||
788
- toolInput.taskId ||
789
- null;
790
- const cur = state.read(repoRoot);
791
- const turnAutoLogCount = (cur.turnAutoLogCount || 0) + 1;
792
- const sessionAutoLogCount = (cur.sessionAutoLogCount || 0) + 1;
793
- const patch = {
794
- turnAutoLogCount,
795
- sessionAutoLogCount,
796
- lastAutoWorkbookMutation: {
797
- tool: toolName.split('__').pop() || '',
798
- entityId,
799
- createdInTurnId: cur.currentTurnId || 0,
800
- createdTs: new Date().toISOString(),
801
- priorStatus: toolInput.priorStatus || null,
802
- },
803
- };
804
- if (kind === 'decision')
805
- patch.pushDecisionHash = hash;
806
- if (kind === 'issue')
807
- patch.pushIssueHash = hash;
808
- if (kind === 'assumption')
809
- patch.pushAssumptionHash = hash;
810
- state.patch(repoRoot, patch);
811
- const metricKey = kind === 'decision' ? 'autoDecisionLogs' :
812
- kind === 'issue' ? 'autoIssueLogs' :
813
- kind === 'assumption' ? 'autoAssumptionLogs' :
814
- 'autoTaskStatusLogs';
815
- state.incrementMetric(repoRoot, metricKey);
816
- logging.info(repoRoot, 'workbook_mutation', { kind, entityId, turnAutoLogCount, sessionAutoLogCount });
817
- process.stdout.write('{}');
818
- return 0;
819
- }
820
- // Exposed for tests:
821
- exports._extractEditedPaths = extractEditedPaths;
822
- exports._handlePostEdit = handlePostEdit;
823
- exports._handleUpdateTaskGate = handleUpdateTaskGate;
824
- exports._handleStop = handleStop;
825
- exports._handleUserPromptSubmit = handleUserPromptSubmit;
826
- exports._handleWorkbookMutation = handleWorkbookMutation;
827
- exports._handleSessionStart = handleSessionStart;
828
- exports._readActiveFeatureId = readActiveFeatureId;
829
- exports._transcriptCharsSinceLastStop = transcriptCharsSinceLastStop;
830
- exports.TRIVIAL_TURN_CHAR_DEFAULT_EXPORT = TRIVIAL_TURN_CHAR_DEFAULT;
1
+ "use strict";var re=Object.create;var M=Object.defineProperty;var se=Object.getOwnPropertyDescriptor;var ie=Object.getOwnPropertyNames;var ae=Object.getPrototypeOf,ce=Object.prototype.hasOwnProperty;var ue=(e,t)=>{for(var n in t)M(e,n,{get:t[n],enumerable:!0})},ot=(e,t,n,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of ie(t))!ce.call(e,r)&&r!==n&&M(e,r,{get:()=>t[r],enumerable:!(o=se(t,r))||o.enumerable});return e};var T=(e,t,n)=>(n=e!=null?re(ae(e)):{},ot(t||!e||!e.__esModule?M(n,"default",{value:e,enumerable:!0}):n,e)),le=e=>ot(M({},"__esModule",{value:!0}),e);var Cn={};ue(Cn,{QUALITY_GATE_REASON_PREFIX:()=>qt,TRIVIAL_TURN_CHAR_DEFAULT_EXPORT:()=>In,_extractEditedPaths:()=>mn,_handlePostEdit:()=>hn,_handleSessionStart:()=>bn,_handleStop:()=>Sn,_handleUpdateTaskGate:()=>yn,_handleUserPromptSubmit:()=>Tn,_handleWorkbookMutation:()=>kn,_readActiveFeatureId:()=>wn,_transcriptCharsSinceLastStop:()=>vn,dispatch:()=>rn});module.exports=le(Cn);var N=T(require("fs")),Wt=T(require("path"));var U=T(require("fs")),E=T(require("path")),rt=Object.freeze({conservative:{decision:.85,issue:.8,assumption:.9,taskStatus:.9},balanced:{decision:.75,issue:.7,assumption:.8,taskStatus:.85},aggressive:{decision:.6,issue:.55,assumption:.7,taskStatus:.75}}),R=Object.freeze({companion:{autoVerify:{preTaskDone:!0,prePush:!0,postEditPulse:{enabled:!0,threshold:5},autoPublishSpecOnControllerChange:!0,controllerPaths:["**/controllers/**/*.{kt,java,py,go,ts,tsx,js,rs,rb,cs}","**/api/**/*.{kt,java,py,go,ts,tsx,js,rs,rb,cs}"],verifyTimeoutSeconds:300,publishSpecTimeoutSeconds:300},autoWorkbookUpdates:{enabled:!0,preset:"conservative",maxAutoLogsPerTurn:2,maxAutoLogsPerSession:10,undoWindow:"next-turn",thresholds:{decision:null,issue:null,assumption:null,taskStatus:null},trivialTurnCharCutoff:600,hashRingBufferSize:32},coverageNudges:{enabled:!0,staleBaselineDays:14,fetchMode:"async"},authoring:{defaultDepth:"adaptive",existingWorkScan:!0,qualityGate:{enabled:!0},minViableContextGate:{enabled:!0}},safeMode:!1}});function W(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)}function st(e,t){if(!W(t))return t===void 0?e:t;let n=Array.isArray(e)?[...e]:{...e};for(let o of Object.keys(t)){let r=n[o],s=t[o];W(r)&&W(s)?n[o]=st(r,s):n[o]=s}return n}function it(e){let t=E.join(e,".dezycro","config.json"),n;try{n=U.readFileSync(t,"utf8")}catch(r){if(r.code==="ENOENT")return JSON.parse(JSON.stringify(R));throw r}let o;try{o=JSON.parse(n)}catch{return JSON.parse(JSON.stringify(R))}return st(JSON.parse(JSON.stringify(R)),o)}function at(e){let t=e?.companion?.autoWorkbookUpdates||{},n=t.preset||"conservative",o=rt[n]||rt.conservative,r=t.thresholds||{};return{decision:typeof r.decision=="number"?r.decision:o.decision,issue:typeof r.issue=="number"?r.issue:o.issue,assumption:typeof r.assumption=="number"?r.assumption:o.assumption,taskStatus:typeof r.taskStatus=="number"?r.taskStatus:o.taskStatus}}function A(e){let t=E.resolve(e||process.cwd());for(let n=0;n<64;n+=1){if(U.existsSync(E.join(t,".dezycro")))return t;let o=E.dirname(t);if(o===t)return null;t=o}return null}var h=T(require("fs")),O=T(require("path")),pe=500,fe=10,q=32,ct=64,ut=Object.freeze({autoDecisionLogs:0,autoIssueLogs:0,autoAssumptionLogs:0,autoTaskStatusLogs:0,undoCount:0,verifyAutoRuns:0,verifyAutoFailures:0,qualityGateFailures:0,contextGateFailures:0,lockContention:0,forceApprovals:0}),ge=Object.freeze({schemaVersion:1,currentSessionId:null,authoringMode:!1,authoringDocId:null,authoringDocType:null,authoringTranscriptPath:null,authoringStartedTs:null,pulseCount:0,lastEditTs:null,needsSpecPublish:!1,controllerEditsSinceLastPublish:[],coverageSnapshot:null,coverageSnapshotTs:null,coverageFetchPending:!1,lastVerifyCompletionTs:null,recentDecisionHashes:[],recentIssueHashes:[],recentAssumptionHashes:[],lastAutoWorkbookMutation:null,sessionAutoLogCount:0,turnAutoLogCount:0,currentTurnId:0,suppressWorkbookUpdatesUntil:null,pendingWorkbookSweep:!1,pendingWorkbookSweepReason:null,lastSeenVerifyCompletionTs:null,commandBus:{cursor:null,lastPollTs:null,lastAttemptTs:null,consecutiveFailures:0,recentEventIds:[]},metrics:ut});function lt(e){return O.join(e,".dezycro","companion-state.json")}function dt(e){return O.join(e,".dezycro",".companion-state.lock")}function me(e){return O.join(e,".dezycro",".companion-state.json.tmp")}function he(e){h.mkdirSync(O.join(e,".dezycro"),{recursive:!0})}function K(){return JSON.parse(JSON.stringify(ge))}function k(e){let t=lt(e);try{let n=h.readFileSync(t,"utf8"),o=JSON.parse(n);return{...K(),...o,metrics:{...ut,...o.metrics||{}}}}catch(n){let o=n;return o&&o.code==="ENOENT",K()}}function ye(e){let t=dt(e),n=Date.now();for(;Date.now()-n<pe;){try{let r=h.openSync(t,"wx");return h.writeSync(r,String(process.pid)),h.closeSync(r),!0}catch(r){if(r.code!=="EEXIST")throw r}let o=Date.now()+fe;for(;Date.now()<o;);}return!1}function Se(e){try{h.unlinkSync(dt(e))}catch{}}function Te(e,t){return t?{...e,...t}:e}function X(e,t,n){if(!t||e.includes(t))return e;let o=e.concat([t]);return o.length>n?o.slice(o.length-n):o}function d(e,t){if(he(e),!ye(e))return null;try{let n=k(e),o=t||{},r={...n};for(let a of Object.keys(o)){let c=o[a];if(a==="metrics")r.metrics=Te(n.metrics,c);else if(a==="pushDecisionHash")r.recentDecisionHashes=X(n.recentDecisionHashes,c,q);else if(a==="pushIssueHash")r.recentIssueHashes=X(n.recentIssueHashes,c,q);else if(a==="pushAssumptionHash")r.recentAssumptionHashes=X(n.recentAssumptionHashes,c,q);else if(a==="pushControllerEdit"){let u=c,p=n.controllerEditsSinceLastPublish.filter(y=>y.path!==u.path).concat([u]);r.controllerEditsSinceLastPublish=p.length>ct?p.slice(p.length-ct):p}else r[a]=c}let s=me(e),i=h.openSync(s,"w");return h.writeSync(i,JSON.stringify(r,null,2)),h.fsyncSync(i),h.closeSync(i),h.renameSync(s,lt(e)),r}finally{Se(e)}}function x(e,t,n=1){let o=k(e).metrics[t]||0;return d(e,{metrics:{[t]:o+n}})}var F=T(require("fs")),Z=T(require("path")),ke="companion.log";function pt(e,t){try{let n=Z.join(e,".dezycro");if(!F.existsSync(n))return;let o=JSON.stringify({ts:new Date().toISOString(),...t})+`
2
+ `;F.appendFileSync(Z.join(n,ke),o,{encoding:"utf8"})}catch{}}function f(e,t,n){pt(e,{level:"info",event:t,...n||{}})}function H(e,t,n){pt(e,{level:"warn",event:t,...n||{}})}var gt=T(require("fs")),mt=T(require("path"));function ht(e){let t=e||{},n=t.repoRoot,o=typeof t.maxAgeMinutes=="number"?t.maxAgeMinutes:60;if(!n)return!1;let r=mt.join(n,".dezycro","state.json"),s;try{let l=gt.readFileSync(r,"utf8");s=JSON.parse(l)}catch{return!1}if(!s||typeof s!="object")return!1;let i=s.lastVerifyTs,a=s.lastVerifyRunId;if(!i||!a)return!1;let c=Date.parse(i);if(!Number.isFinite(c))return!1;let u=Date.now()-c;return!(u<0||u>o*60*1e3)}function yt(e){let t=e||{},n=t.paths||[],o=t.status||"DONE",r=n.length>0?n.join(","):"<paths>";return{reason:"Cannot mark task "+o+" \u2014 no recent passing verifier run found for the linked paths. Run `/dezycro:verify --path "+r+"` first to attach passing verifier evidence, then retry."}}function St(e){if(!e)return{needed:!1,controllerPaths:[],lastEditTs:null};let t=k(e),n=Array.isArray(t.controllerEditsSinceLastPublish)?t.controllerEditsSinceLastPublish:[],o=n.map(i=>i&&i.path).filter(Boolean),r=n.length>0&&n[n.length-1].editedTs||null;return{needed:!!t.needsSpecPublish&&o.length>0,controllerPaths:o,lastEditTs:r}}function ve(e){return String(e||"").replace(/\\/g,"/")}function kt(e){let t=e.match(/\{([^{}]+)\}/);if(!t||t.index===void 0)return[e];let n=t[0],r=t[1].split(","),s=[];for(let i of r){let a=e.slice(0,t.index)+i+e.slice(t.index+n.length);s.push(...kt(a))}return s}function Ie(e){let t="^";for(let n=0;n<e.length;n+=1){let o=e[n];o==="*"?e[n+1]==="*"?(n+=1,e[n+1]==="/"?(n+=1,t+="(?:.*/)?"):t+=".*"):t+="[^/]*":o==="?"?t+="[^/]":".+^$()|[]\\".indexOf(o)!==-1?t+="\\"+o:t+=o}return t+="$",new RegExp(t)}var Tt=new Map;function Ce(e){let t=Tt.get(e);if(t)return t;let n=kt(e).map(Ie);return Tt.set(e,n),n}function Ae(e,t){if(!e||!Array.isArray(t)||t.length===0)return!1;let n=ve(e);for(let o of t){if(typeof o!="string"||o.length===0)continue;let r=Ce(o);for(let s of r)if(s.test(n))return!0}return!1}function bt(e,t){return Array.isArray(e)?e.filter(n=>Ae(n,t)):[]}var b=T(require("fs")),j=T(require("path"));var wt=Object.freeze({IDLE:"IDLE",DETECT_INTENT:"DETECT_INTENT",SCAN_EXISTING_WORK:"SCAN_EXISTING_WORK",CLARIFY:"CLARIFY",PROPOSE_PLACEMENT:"PROPOSE_PLACEMENT",CREATE_FEATURE:"CREATE_FEATURE",TRD_DISCOVERY:"TRD_DISCOVERY",MIN_CONTEXT_GATE:"MIN_CONTEXT_GATE",DRAFT:"DRAFT",ITERATE:"ITERATE",QUALITY_GATE:"QUALITY_GATE",FINALIZE:"FINALIZE"}),vt=Object.freeze({PRD:"PRD",TRD:"TRD"}),_n=Object.freeze(["skip","draft it","just write it","next"]),Pn=Object.freeze([wt.MIN_CONTEXT_GATE,wt.QUALITY_GATE]),Pe="authoring-transcript.json",Ee="authoring-current-draft.json";function It(e){return j.join(e,".dezycro",Pe)}function Ct(e){return j.join(e,".dezycro",Ee)}function xe(e){b.mkdirSync(j.join(e,".dezycro"),{recursive:!0})}function Re(e,t){let n=`${e}.tmp`,o=b.openSync(n,"w");try{b.writeSync(o,JSON.stringify(t,null,2)),b.fsyncSync(o)}finally{b.closeSync(o)}b.renameSync(n,e)}function At(e){try{let t=b.readFileSync(e,"utf8");return JSON.parse(t)}catch{return null}}function _t(e,t){if(!t||!t.docId||!t.docType)throw new Error("authoring.enter: docId and docType required");if(t.docType!==vt.PRD&&t.docType!==vt.TRD)throw new Error(`authoring.enter: invalid docType ${t.docType}`);xe(e);let n=It(e),o=new Date().toISOString(),r=At(n),s=r&&r.docId===t.docId?r:{docId:t.docId,docType:t.docType,featureId:t.featureId||null,seed:t.seed||null,startedTs:o,discoverySummary:null,qaTurns:[],draftHistory:[],qualityGateHistory:[]};return Re(n,s),d(e,{authoringMode:!0,authoringDocId:t.docId,authoringDocType:t.docType,authoringTranscriptPath:n,authoringStartedTs:o}),s}function Pt(e){d(e,{authoringMode:!1,authoringDocId:null,authoringDocType:null,authoringTranscriptPath:null,authoringStartedTs:null});for(let t of[It(e),Ct(e)])try{b.unlinkSync(t)}catch{}}function Et(e){let t=k(e);return{active:!!(t&&t.authoringMode),docId:t?t.authoringDocId:null,docType:t?t.authoringDocType:null,transcriptPath:t?t.authoringTranscriptPath:null,startedTs:t?t.authoringStartedTs:null}}function xt(e){return At(Ct(e))}var En=Object.freeze(["architecture","data model","data-model","datamodel","api contract","api-contract","apicontract","migration","auth","queue","verifier-behavior","verifier behavior"]),De=Object.freeze([{id:"architecture",pattern:/^##\s+architecture\b/im},{id:"data_model",pattern:/^##\s+data\s+model\b/im},{id:"api_contracts",pattern:/^##\s+api\s+contracts?\b/im},{id:"failure_modes",pattern:/^##\s+failure\s+modes?/im},{id:"migration",pattern:/^##\s+migration\b/im},{id:"open_questions",pattern:/^##\s+open\s+questions?\b/im}]),Ne=/\b(TBD|TODO|FIXME|fill in later|to be determined)\b/i,Rt=1e3,Ot=/^##\s+Pillar\s+\d+\s*:\s*(.+?)\s*$/gim,xn=Object.freeze(["data_model","api_contracts","state_or_persistence","failure_modes","migration_or_rollout"]);function Le(e){if(!e||typeof e!="string")return[];let t=[];Ot.lastIndex=0;let n;for(;(n=Ot.exec(e))!==null;){let o=n[1].trim();o&&t.push(o)}return t}function Dt(e){let{trdContent:t,prdContent:n}=e,o=[],r=(t||"").toString(),s=r.trim().length;s<Rt&&o.push({section:"document",issue:`TRD is too short (${s} chars; minimum ${Rt}).`,required_fix:"Expand the draft with concrete technical detail."});for(let c of De)c.pattern.test(r)||o.push({section:c.id,issue:"Required H2 section is missing.",required_fix:`Add a "## ${c.id.replace(/_/g," ")}" section grounded in the PRD + discovery.`});let i=r.match(Ne);i&&o.push({section:"placeholders",issue:`Placeholder token "${i[0]}" present \u2014 TRDs must not ship with unresolved placeholders.`,required_fix:"Resolve or move the open item into the ## Open Questions section."});let a=Le(n);if(a.length>0){let c=[],u=r.toLowerCase();for(let l of a)u.includes(l.toLowerCase())||c.push(l);c.length>0&&o.push({section:"pillar_coverage",issue:`TRD does not mention PRD pillar(s): ${c.join(", ")}.`,required_fix:"Add a per-pillar technical-design section, or reference each pillar by name in the relevant TRD section."})}return{passed:o.length===0,blockingIssues:o,deterministic:!0}}var Nt=T(require("crypto"));function Lt(e,t,n){let o=String(e||"").toLowerCase().trim(),r=String(t||"").toLowerCase().trim().replace(/\s+/g," "),s=String(n||"").toLowerCase().trim();return Nt.createHash("sha1").update(`${o}::${r}::${s}`).digest("hex").slice(0,16)}var B=Object.freeze({decision:"Logging decision: {{summary}} \u2014 reason: {{reason}}. Say 'undo that' if wrong.",issue:"Logging issue ({{severity}}): {{summary}}. Say 'undo that' if wrong.",assumption:"Logging assumption: {{summary}} \u2014 rationale: {{rationale}}. Say 'undo that' if wrong.",task_status:"Marking task {{task_id}} as {{proposed_status}} \u2014 rationale: {{rationale}}. Say 'undo that' if wrong."}),Fe="Auto-logging paused \u2014 {{count}} entries logged this session. Type /dezycro:sync to log manually.";function Mt(e){let t=e.thresholds||{},n=Math.max(0,Number(e.perTurnSlotsLeft)||0),o=Math.max(0,Number(e.perSessionSlotsLeft)||0);if(n<=0||o<=0)return Fe.replace("{{count}}",o<=0?"session-cap":"turn-cap");let r=Math.min(n,o),s=(e.recentHashes||[]).filter(Boolean),i=["[Dezycro Companion]",`Feature: ${He(e.activeFeatureName,e.activeFeatureId)}. Slots: ${r} (${n}/turn, ${o}/session).`,"","Scan since your last message for committed signals to log:",` - Decision (conf \u2265 ${t.decision??.85}) \u2014 committed technical/product choice`,` - Issue (conf \u2265 ${t.issue??.8}) \u2014 current blocker/bug`,` - Assumption (conf \u2265 ${t.assumption??.9}) \u2014 unverified belief affecting impl`,` - Task-status (conf \u2265 ${t.taskStatus??.9}) \u2014 task is now DONE/PUSHED`,"","Skip: hypotheticals, brainstorming, implementation-obvious details, ephemeral content, intent statements while authoring a PRD/TRD."];return s.length>0&&i.push(`Already-logged hashes (skip dupes): ${s.join(", ")}`),i.push("","For each surviving candidate (max "+r+"): print the announce line verbatim and call the matching MCP tool.",' decision: "'+B.decision+'" \u2192 mcp__dezycro(-dev)?__add_decision',' issue: "'+B.issue+'" \u2192 mcp__dezycro(-dev)?__add_issue',' assumption: "'+B.assumption+'" \u2192 mcp__dezycro(-dev)?__add_assumption',' task_status: "'+B.task_status+'" \u2192 mcp__dezycro(-dev)?__update_task',"","If nothing meets the bar, simply continue with whatever the user asked next \u2014 do NOT output any acknowledgment line. The hook will silence itself for trivial turns."),i.join(`
3
+ `)}function He(e,t){if(!t)return"(none \u2014 skip if no feature is active)";let n=String(t).slice(0,8);return e?`${e} (${n}\u2026)`:n+"\u2026"}function Ut(e){return typeof e!="string"?null:/add_decision$/.test(e)?"decision":/add_issue$/.test(e)?"issue":/add_assumption$/.test(e)?"assumption":/update_task$/.test(e)?"task-status":null}var Ft=Object.freeze(["undo that","no, wrong","revert that","undo the last log","wrong, undo"]);function Ht(e){if(typeof e!="string")return!1;let t=e.toLowerCase().trim();if(!t)return!1;for(let n=0;n<Ft.length;n+=1)if(t.includes(Ft[n]))return!0;return!1}function jt(e){if(!e||!e.tool||!e.entityId)return null;let t=e.entityId;switch(e.tool){case"add_decision":return{tool:"update_decision",args:{id:t,status:"INVALIDATED",reason:"undo by user"},humanLine:`Reverting the last auto-logged decision (${t.slice(0,8)}\u2026) \u2014 marking INVALIDATED.`};case"add_issue":return{tool:"update_issue",args:{id:t,status:"CANCELLED",reason:"undo by user"},humanLine:`Reverting the last auto-logged issue (${t.slice(0,8)}\u2026) \u2014 marking CANCELLED.`};case"add_assumption":return{tool:"update_issue",args:{id:t,status:"INVALIDATED",kind:"ASSUMPTION",reason:"undo by user"},humanLine:`Reverting the last auto-logged assumption (${t.slice(0,8)}\u2026) \u2014 marking INVALIDATED.`};case"update_task":return e.priorStatus?{tool:"update_task",args:{id:t,status:e.priorStatus},humanLine:`Reverting the last auto-task-status change \u2014 restoring ${t.slice(0,8)}\u2026 to ${e.priorStatus}.`}:null;default:return null}}function Bt(e,t){return!e||typeof e.createdInTurnId!="number"?!1:t===e.createdInTurnId+1}function Q(e,t){return`${e} ${t}${e===1?"":"s"}`}function tt(e){return!e||typeof e!="object"?null:{uncoveredPaths:Number(e.uncoveredPaths??0)||0,endpointsNotInSpec:Number(e.endpointsNotInSpec??0)||0,staleBaselines:Number(e.staleBaselines??0)||0}}function $(e){let t=tt(e);if(!t)return null;let n=[];return t.uncoveredPaths>0&&n.push(`${Q(t.uncoveredPaths,"path")} uncovered`),t.endpointsNotInSpec>0&&n.push(`${Q(t.endpointsNotInSpec,"endpoint")} not in spec`),t.staleBaselines>0&&n.push(`${Q(t.staleBaselines,"baseline")} stale`),n.length===0?null:`Coverage: ${n.join(", ")}`}function $t(e,t){let n=tt(e),o=tt(t);if(!o)return null;if(!n)return $(t);let s=[["paths uncovered",o.uncoveredPaths-n.uncoveredPaths],["endpoints not in spec",o.endpointsNotInSpec-n.endpointsNotInSpec],["baselines stale",o.staleBaselines-n.staleBaselines]].filter(([,a])=>a!==0);return s.length===0?null:`Coverage delta since last verify: ${s.map(([a,c])=>`${c>0?"+":""}${c} ${a}`).join(", ")}`}var V=T(require("fs")),Jt=T(require("os")),I=T(require("path")),J=class e{constructor(t){this.repoRoot=t;this.config=e.readJson(I.join(t,".dezycro","config.json"))}repoRoot;config;static at(t=process.cwd()){return new e(e.findRepoRoot(t)??I.resolve(t))}get workspaceId(){return e.env("DZ_WORKSPACE_ID")??e.str(this.config?.workspaceId)}get tenantId(){return e.env("DZ_TENANT_ID")??e.str(this.config?.tenantId)}get projectId(){return e.str(this.config?.projectId)}get apiUrl(){let t=e.env("DZ_API_URL");if(t)return e.trimSlash(t);let n=Array.isArray(this.config?.environments)?this.config?.environments:[],o=n.find(s=>e.isObj(s)&&s.default===!0)??n[0],r=e.isObj(o)?e.str(o.apiUrl):null;return r?e.trimSlash(r):null}get token(){let t=e.env("DZ_TOKEN");if(t)return t;let n=[I.join(this.repoRoot,".dezycro","companion-token.local.json"),I.join(Jt.homedir(),".dezycro","token.json")];for(let o of n){let r=e.str(e.readJson(o)?.token);if(r)return r}return null}activeFeature(){let t=e.readJson(I.join(this.repoRoot,".dezycro","active-feature.json")),n=e.str(t?.featureId);if(!n)return null;let o=e.str(t?.workspaceId)??this.workspaceId;return o?{workspaceId:o,featureId:n}:null}connection(){let t=this.apiUrl;if(!t)return{error:"no apiUrl (set DZ_API_URL or .dezycro/config.json environments[].apiUrl)"};let n=this.workspaceId;if(!n)return{error:"no workspaceId (set DZ_WORKSPACE_ID or .dezycro/config.json workspaceId)"};let o=this.token;return o?{apiUrl:t,workspaceId:n,token:o}:{error:"no token (run `dezycro setup` or set DZ_TOKEN)"}}static findRepoRoot(t){let n=I.resolve(t||process.cwd());for(let o=0;o<64;o+=1){if(V.existsSync(I.join(n,".dezycro")))return n;let r=I.dirname(n);if(r===n)return null;n=r}return null}static readJson(t){try{return JSON.parse(V.readFileSync(t,"utf8"))}catch{return null}}static isObj(t){return t!==null&&typeof t=="object"&&!Array.isArray(t)}static str(t){return typeof t=="string"&&t.length>0?t:null}static env(t){let n=process.env[t];return n&&n.length>0?n:null}static trimSlash(t){return t.replace(/\/$/,"")}};var Je=["sessionStart","userPromptSubmit","stop","postToolUse"],Ve=60,ze="HALT_AND_REPLAN",Vt=200,Ge=100,We=3,qe=1800;function zt(e,t,n,o){if(t<n)return e;let r=t-n+1,s=e*Math.pow(2,r);return Math.min(s,o)}async function D(e,t,n,o){let r=Ye(n),s=r.reactionMode,i={events:[],statePatch:null,reactionMode:s};if(!r.enabled||!r.hooks.has(t)&&t!=="sessionStart")return i;let a=o.commandBus,c=a.lastAttemptTs??a.lastPollTs;if(t!=="sessionStart"&&c){let S=Date.parse(c);if(!Number.isNaN(S)){let g=zt(r.minPollIntervalSeconds,a.consecutiveFailures??0,r.circuitBreakerThreshold,r.maxBackoffSeconds);if((Date.now()-S)/1e3<g)return i}}let u=new J(e),l=u.activeFeature();if(!l)return i;let p=u.apiUrl;if(!p)return i;let y=u.token;if(!y)return f(e,"command_bus_skip_no_token",{hookEvent:t}),i;let v=t==="sessionStart"?null:a.cursor,m=new Date().toISOString();try{let S=await Ze(p,l,y,v),g=new Set(a.recentEventIds??[]),w=S.events.filter(P=>!g.has(P.eventId)),C=Qe([...a.recentEventIds??[],...w.map(P=>P.eventId)]),L={commandBus:{cursor:S.nextCursor||a.cursor,lastPollTs:S.serverTs,lastAttemptTs:m,consecutiveFailures:0,recentEventIds:C}};return w.length>0&&f(e,"command_bus_events_received",{hookEvent:t,count:w.length,types:w.map(P=>P.eventType)}),{events:w,statePatch:L,reactionMode:s}}catch(S){let g=(a.consecutiveFailures??0)+1,w={commandBus:{...a,lastAttemptTs:m,consecutiveFailures:g}},C=zt(r.minPollIntervalSeconds,g,r.circuitBreakerThreshold,r.maxBackoffSeconds);return f(e,"command_bus_poll_failed",{hookEvent:t,error:S instanceof Error?S.message:String(S),consecutiveFailures:g,nextPollAfterSeconds:C,backoff:g>=r.circuitBreakerThreshold}),{events:[],statePatch:w,reactionMode:s}}}function z(e,t){if(e.length===0)return"";let n=[];n.push(`\u26A0\uFE0F Dezycro Command Bus: ${e.length} new event(s) on this feature.`),n.push("");for(let o of e)n.push(Ke(o));return e.some(o=>o.urgency==="BLOCKING")&&(n.push(""),n.push(Xe(t))),n.join(`
4
+ `)}function Ke(e){if(e.eventType==="INVALIDATION"){let t=et(e.payload,"artifactSummary")??"(no summary)",n=et(e.payload,"reason")??"(no reason)",o=et(e.payload,"previousStatus")??"?",r=`${e.sourceType.toLowerCase()} ${e.sourceId??"?"}`;return`- INVALIDATION (${e.urgency}): ${r} ("${Gt(t,120)}") \u2014 reason: "${Gt(n,200)}" \u2014 was ${o}.`}return`- ${e.eventType} (${e.urgency}) source=${e.sourceType}/${e.sourceId??"-"}.`}function Xe(e){switch(e){case"HALT_AND_REPLAN":return"Reaction mode: HALT_AND_REPLAN \u2014 pause your current line of work, acknowledge in chat, and re-plan around these invalidations before touching code again.";case"ACKNOWLEDGE_AND_CONTINUE":return"Reaction mode: ACKNOWLEDGE_AND_CONTINUE \u2014 surface this in your reply but continue current work; do not silently reuse invalidated artifacts.";case"SILENT_LOG":return"Reaction mode: SILENT_LOG \u2014 recorded for the session transcript; no chat interruption.";default:return""}}function Ye(e){let t=e?.enabled!==!1,n=new Set(e?.pollHooks??Je);n.add("sessionStart");let o=Math.max(0,e?.minPollIntervalSeconds??Ve),r=e?.reactionMode??ze,s=Math.max(1,e?.circuitBreakerThreshold??We),i=Math.max(o,e?.maxBackoffSeconds??qe);return{enabled:t,hooks:n,minPollIntervalSeconds:o,reactionMode:r,circuitBreakerThreshold:s,maxBackoffSeconds:i}}async function Ze(e,t,n,o){let r=new URL(`${e}/api/v1/workspaces/${t.workspaceId}/features/${t.featureId}/inbox`);o&&r.searchParams.set("since",o),r.searchParams.set("limit",String(Ge));let s=new AbortController,i=setTimeout(()=>s.abort(),5e3);try{let a=await fetch(r.toString(),{method:"GET",headers:{Authorization:`Bearer ${n}`,Accept:"application/json"},signal:s.signal});if(!a.ok)throw new Error(`inbox poll returned ${a.status}`);let c=await a.json();if(!c||typeof c.serverTs!="string"||typeof c.nextCursor!="string")throw new Error("inbox poll response missing serverTs/nextCursor");return{serverTs:c.serverTs,nextCursor:c.nextCursor,events:Array.isArray(c.events)?c.events:[]}}finally{clearTimeout(i)}}function Qe(e){return e.length<=Vt?e:e.slice(e.length-Vt)}function et(e,t){let n=e?.[t];return typeof n=="string"?n:null}function Gt(e,t){return e.length<=t?e:e.slice(0,t-1)+"\u2026"}var en=new Set(["DONE","PUSHED"]),nn=["/dezycro:sync","log what we did","log this","save progress","save what we did","wrap up","we're done","we are done","end of session","sweep the workbook"];function on(e){if(typeof e!="string")return null;let t=e.toLowerCase().trim();if(!t)return null;for(let n of nn)if(t.includes(n))return n;return null}var qt="TRD quality gate (deterministic layer) blocked APPROVED transition: ";async function rn(e){let{event:t,route:n,payload:o,flags:r}=e;return t==="state"?cn(r||{}):t==="authoring-state"?un(r||{}):t==="pre-tool-use"&&n==="update_task"?Kt(o||{}):t==="pre-tool-use"&&n==="change_document_status"?an(o||{}):t==="post-tool-use"&&n==="edit"?Xt(o||{}):t==="post-tool-use"&&n==="workbook_mutation"?oe(o||{}):t==="session-start"?Zt(o||{}):t==="user-prompt-submit"?Qt(o||{}):t==="stop"?te(o||{}):0}function Kt(e){let t=A(process.cwd());if(!t)return process.stdout.write("{}"),0;let n=e&&e.tool_input||{},o=String(n.status||"").toUpperCase();if(!en.has(o))return process.stdout.write("{}"),0;let r=n.pathRefs,s=Array.isArray(r)?r.filter(Boolean):[];if(s.length===0)return process.stdout.write("{}"),0;let i=_(t);if((i&&i.companion&&i.companion.autoVerify||{}).preTaskDone===!1)return process.stdout.write("{}"),0;if(ht({repoRoot:t,paths:s})){if(f(t,"pre_done_gate_pass",{status:o,pathRefs:s}),o==="PUSHED"){let p=sn(t,i);if(p){f(t,"pre_push_coverage_nudge",{line:p});let y={systemMessage:p};return process.stdout.write(JSON.stringify(y)),0}}return process.stdout.write("{}"),0}x(t,"verifyAutoRuns"),x(t,"verifyAutoFailures"),H(t,"pre_done_gate_block",{status:o,pathRefs:s});let u=yt({paths:s,status:o}).reason,l={decision:"block",reason:u,hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:u}};return process.stdout.write(JSON.stringify(l)),0}function sn(e,t){if((t&&t.companion&&t.companion.coverageNudges||{}).enabled===!1)return null;let o=k(e).coverageSnapshot,r=$(o);return r?`[Companion] ${r} \u2014 verify passed, but consider checking these before push.`:null}function an(e){let n=e&&e.tool_input||{},o=n.status||n.newStatus||null,r=n.docId||n.documentId||n.id||null;if(o!=="APPROVED")return process.stdout.write("{}"),0;let s=A(process.cwd());if(!s)return process.stdout.write("{}"),0;let i=xt(s);if(!i||r&&i.docId&&i.docId!==r)return H(s,"quality_gate_degraded_permit",{reason:i?"docId_mismatch":"sidecar_missing",docId:r,sidecarDocId:i?i.docId:null}),process.stdout.write("{}"),0;if(i.docType!=="TRD")return process.stdout.write("{}"),0;let a=Dt({trdContent:i.content,prdContent:i.prdContent||""});if(a.passed)return f(s,"quality_gate_deterministic_pass",{docId:i.docId}),process.stdout.write("{}"),0;x(s,"qualityGateFailures");let c=a.blockingIssues.map(p=>`[${p.section}] ${p.issue}`).slice(0,5).join(" | "),u=`${qt}${c||"unspecified failure"}. Fix and try again.`;H(s,"quality_gate_blocked",{docId:i.docId,issues:a.blockingIssues});let l={decision:"block",reason:u,hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:u}};return process.stdout.write(JSON.stringify(l)),0}async function Xt(e){let t=A(process.cwd());if(!t)return 0;{let m=_(t),S=k(t),g=await D(t,"postToolUse",m?.companion?.commandBus,S);g.statePatch&&d(t,g.statePatch);let w=z(g.events,g.reactionMode);if(w){let C={hookSpecificOutput:{hookEventName:"PostToolUse",additionalContext:w}};return process.stdout.write(JSON.stringify(C)),0}}let n=e&&e.tool_input||{},o=Yt(n);if(o.length===0)return 0;let r=_(t),s=r&&r.companion&&r.companion.autoVerify||{},i=Array.isArray(s.controllerPaths)?s.controllerPaths:[],a=s.postEditPulse&&s.postEditPulse.enabled!==!1,c=s.postEditPulse&&Number(s.postEditPulse.threshold)||5,u=bt(o,i),l=new Date().toISOString(),y=(k(t).pulseCount||0)+1,v=a&&y>=c&&u.length>0;if(u.length===0)d(t,{pulseCount:y,lastEditTs:l});else for(let m=0;m<u.length;m+=1){let S=m===u.length-1,g={pushControllerEdit:{path:u[m],editedTs:l}};S&&(g.needsSpecPublish=!0,g.lastEditTs=l,g.pulseCount=v?0:y),d(t,g)}if(f(t,"post_edit",{edited:o.length,matchedControllers:u.length,pulseCount:v?0:y,nudged:v}),v){let m="[Companion] "+y+" edits since last verify and controller paths were touched ("+u.join(", ")+"). Consider running `/dezycro:verify` to catch regressions early.";process.stdout.write(JSON.stringify({hookSpecificOutput:{hookEventName:"PostToolUse",additionalContext:m}}))}return 0}function Yt(e){if(!e||typeof e!="object")return[];let t=e.file_path;if(typeof t=="string")return[t];let n=e.path;if(typeof n=="string")return[n];let o=e.edits;if(Array.isArray(o)){let r=new Set;for(let s of o)s&&typeof s.file_path=="string"&&r.add(s.file_path);return Array.from(r)}return[]}function cn(e){let t=A(process.cwd())||process.cwd();if(e.get){let n=k(t);return process.stdout.write(JSON.stringify(n,null,2)),0}if(typeof e.patch=="string"){let n;try{n=JSON.parse(e.patch)}catch(r){let s=r.message;return process.stdout.write(JSON.stringify({ok:!1,error:"invalid JSON for --patch: "+s})),1}return n===null||typeof n!="object"||Array.isArray(n)?(process.stdout.write(JSON.stringify({ok:!1,error:"--patch must be a JSON object"})),1):d(t,n)===null?(process.stdout.write(JSON.stringify({ok:!1,error:"lock contention \u2014 state not patched"})),1):(process.stdout.write(JSON.stringify({ok:!0})),0)}return process.stdout.write(JSON.stringify({ok:!1,error:"state requires one of --get or --patch=<json>"})),1}function un(e){let t=A(process.cwd())||process.cwd(),n=e.set||(e.get?"get":null);if(n==="enter"){let o=e["doc-id"],r=e["doc-type"];if(!o||!r)return process.stdout.write(JSON.stringify({ok:!1,error:"--doc-id and --doc-type are required for --set=enter"})),1;try{let s=_t(t,{docId:o,docType:r,featureId:e["feature-id"]||null,seed:e.seed||null});return process.stdout.write(JSON.stringify({ok:!0,transcript:s})),0}catch(s){return process.stdout.write(JSON.stringify({ok:!1,error:s.message})),1}}if(n==="exit")try{return Pt(t),process.stdout.write(JSON.stringify({ok:!0})),0}catch(o){return process.stdout.write(JSON.stringify({ok:!1,error:o.message})),1}if(n==="get"){let o=Et(t);return process.stdout.write(JSON.stringify({ok:!0,...o})),0}return process.stdout.write(JSON.stringify({ok:!1,error:"authoring-state requires one of --set=enter, --set=exit, --get"})),1}var ln=600;async function Zt(e){let t=A(process.cwd());if(!t)return 0;d(t,{sessionAutoLogCount:0,turnAutoLogCount:0,currentTurnId:0,lastAutoWorkbookMutation:null,coverageFetchPending:!1}),f(t,"session_start",{});let n=_(t),o=k(t),r=await D(t,"sessionStart",n?.companion?.commandBus,o);return r.statePatch&&d(t,r.statePatch),0}async function Qt(e){let t=A(process.cwd());if(!t)return 0;let n=k(t),o=(n.currentTurnId||0)+1;d(t,{currentTurnId:o,turnAutoLogCount:0});{let i=_(t),a=await D(t,"userPromptSubmit",i?.companion?.commandBus,n);a.statePatch&&d(t,a.statePatch);let c=z(a.events,a.reactionMode);if(c){let u={hookSpecificOutput:{hookEventName:"UserPromptSubmit",additionalContext:c}};return process.stdout.write(JSON.stringify(u)),0}}let r=String(e?.prompt??e?.user_message??e?.message??"").trim(),s=on(r);if(s&&(d(t,{pendingWorkbookSweep:!0,pendingWorkbookSweepReason:"wrap_phrase:"+s}),f(t,"wrap_phrase_detected",{phrase:s})),r&&Ht(r)){let i=n.lastAutoWorkbookMutation;if(i&&Bt(i,o)){let a=jt(i);if(a){x(t,"undoCount"),d(t,{lastAutoWorkbookMutation:null}),f(t,"undo_directive",{tool:a.tool,entityId:i.entityId});let u={hookSpecificOutput:{hookEventName:"UserPromptSubmit",additionalContext:"[Dezycro Companion \u2014 undo] "+a.humanLine+`
5
+
6
+ Call `+a.tool+` with arguments:
7
+ `+JSON.stringify(a.args,null,2)+`
8
+
9
+ Then confirm the reversal in one short sentence.`}};return process.stdout.write(JSON.stringify(u)),0}}}{let i=dn(t);if(i){f(t,"publish_spec_nudge",{paths:i.count});let a={hookSpecificOutput:{hookEventName:"UserPromptSubmit",additionalContext:i.text}};return process.stdout.write(JSON.stringify(a)),0}}return pn(t,n),process.stdout.write("{}"),0}function dn(e){let t=_(e);if((t&&t.companion&&t.companion.autoVerify||{}).autoPublishSpecOnControllerChange===!1)return null;let o=St(e);if(!o.needed||o.controllerPaths.length===0)return null;let r=8,s=o.controllerPaths,i=s.slice(0,r).map(c=>" - "+c);return s.length>r&&i.push(" - \u2026and "+(s.length-r)+" more"),{text:"[Dezycro Companion] "+s.length+` controller/API file(s) changed since the last published spec:
10
+ `+i.join(`
11
+ `)+"\n\nNew or changed endpoints are NOT yet reflected in the published OpenAPI spec, so verification cannot see them. Before continuing, run `/dezycro:publish-spec` to upload the updated spec (this regenerates the JEP), then `/dezycro:verify` to check the feature against it. This reminder repeats each turn until the spec is published and clears automatically after `/dezycro:publish-spec`.",count:s.length}}function pn(e,t){if(_(e)?.companion?.coverageNudges?.enabled===!1)return;let o=t.coverageSnapshot;if(!o||o.surfacedTs)return;let r=$(o);if(!r)return;d(e,{coverageSnapshot:{...o,surfacedTs:new Date().toISOString()}}),f(e,"coverage_one_liner",{line:r});let s={hookSpecificOutput:{hookEventName:"UserPromptSubmit",additionalContext:"[Companion] "+r}};process.stdout.write(JSON.stringify(s))}async function te(e){let t=A(process.cwd());if(!t)return 0;let n=_(t);{let w=k(t),C=await D(t,"stop",n?.companion?.commandBus,w);C.statePatch&&d(t,C.statePatch);let L=z(C.events,C.reactionMode);if(L){let P={systemMessage:L};return process.stdout.write(JSON.stringify(P)),0}}let o=n?.companion?.autoWorkbookUpdates||{};if(n?.companion?.safeMode===!0||o.enabled===!1)return nt();let r=k(t),s=!!(r.lastVerifyCompletionTs&&r.lastVerifyCompletionTs!==r.lastSeenVerifyCompletionTs);if(!!!(r.pendingWorkbookSweep||s))return f(t,"stop_skip",{reason:"no_trigger"}),s&&d(t,{lastSeenVerifyCompletionTs:r.lastVerifyCompletionTs}),G(t,r);if(r.authoringMode)return f(t,"stop_skip",{reason:"authoring_mode"}),d(t,{pendingWorkbookSweep:!1,pendingWorkbookSweepReason:null,lastSeenVerifyCompletionTs:r.lastVerifyCompletionTs||r.lastSeenVerifyCompletionTs}),G(t,r);let a=Number(o.maxAutoLogsPerTurn??2),c=Number(o.maxAutoLogsPerSession??10),u=Math.max(0,a-(r.turnAutoLogCount||0)),l=Math.max(0,c-(r.sessionAutoLogCount||0));if(u===0||l===0)return f(t,"stop_skip",{reason:"caps_exhausted",perTurnSlotsLeft:u,perSessionSlotsLeft:l}),d(t,{pendingWorkbookSweep:!1,pendingWorkbookSweepReason:null,lastSeenVerifyCompletionTs:r.lastVerifyCompletionTs||r.lastSeenVerifyCompletionTs}),G(t,r);if(r.suppressWorkbookUpdatesUntil&&Date.now()<new Date(r.suppressWorkbookUpdatesUntil).getTime())return f(t,"stop_skip",{reason:"suppressed"}),d(t,{pendingWorkbookSweep:!1,pendingWorkbookSweepReason:null,lastSeenVerifyCompletionTs:r.lastVerifyCompletionTs||r.lastSeenVerifyCompletionTs}),G(t,r);d(t,{pendingWorkbookSweep:!1,pendingWorkbookSweepReason:null,lastSeenVerifyCompletionTs:r.lastVerifyCompletionTs||r.lastSeenVerifyCompletionTs});let p=at(n),y=[...r.recentDecisionHashes||[],...r.recentIssueHashes||[],...r.recentAssumptionHashes||[]],v=ne(t),m=Mt({thresholds:p,perTurnSlotsLeft:u,perSessionSlotsLeft:l,recentHashes:y,activeFeatureId:v.featureId,activeFeatureName:v.featureName});f(t,"stop_sweep_directive",{perTurnSlotsLeft:u,perSessionSlotsLeft:l,recentHashCount:y.length});let S=ee(t,r);S&&(m+=`
12
+
13
+ `+S);let g={decision:"block",reason:m};return process.stdout.write(JSON.stringify(g)),0}function G(e,t){let n=ee(e,t);if(!n)return nt();f(e,"stop_coverage_delta",{delta:n});let o={systemMessage:n};return process.stdout.write(JSON.stringify(o)),0}function ee(e,t){let n=t.lastVerifyCompletionTs,o=t.coverageSnapshot;if(!n||!o)return null;let r=o.surfacedTs?new Date(o.surfacedTs).getTime():0;if(new Date(n).getTime()<=r)return null;let i=$t(o.previous||null,o);return i?(d(e,{coverageSnapshot:{...o,surfacedTs:new Date().toISOString()}}),"[Companion] "+i):null}function nt(){return process.stdout.write("{}"),0}function fn(e){try{let t=e?.transcript_path;if(!t||typeof t!="string"||!N.existsSync(t))return null;let o=N.readFileSync(t,"utf8").split(`
14
+ `).filter(Boolean),r=0,s=!1;for(let i=o.length-1;i>=0;i-=1){let a;try{a=JSON.parse(o[i])}catch{continue}let c=a?.message?.role??a?.role;if(c==="assistant"){let u=JSON.stringify(a.message?.content??a.content??"");r+=u.length}else if(c==="user"){if(s)break;s=!0;let u=JSON.stringify(a.message?.content??a.content??"");r+=u.length}if(r>1e6)break}return r}catch{return null}}function gn(e){return ne(e).featureId}function ne(e){try{let t=N.readFileSync(Wt.join(e,".dezycro","active-feature.json"),"utf8"),n=JSON.parse(t);return{featureId:n?.featureId||null,featureName:n?.featureName||null}}catch{return{featureId:null,featureName:null}}}function _(e){try{return it(e)}catch{return JSON.parse(JSON.stringify(R))}}function oe(e){let t=A(process.cwd());if(!t)return 0;let n=e?.tool_name||"",o=Ut(n);if(!o)return 0;let r=e?.tool_input||{},s=e?.tool_response||e?.response||{},i=String(r.summary||r.title||r.description||r.statement||""),a=r.taskId||r.linkedTaskId||null,c=Lt(o,i,a),u=s.data,l=s.id||(u?u.id:void 0)||r.taskId||null,p=k(t),y=(p.turnAutoLogCount||0)+1,v=(p.sessionAutoLogCount||0)+1,m={turnAutoLogCount:y,sessionAutoLogCount:v,lastAutoWorkbookMutation:{tool:n.split("__").pop()||"",entityId:l,createdInTurnId:p.currentTurnId||0,createdTs:new Date().toISOString(),priorStatus:r.priorStatus||null}};return o==="decision"&&(m.pushDecisionHash=c),o==="issue"&&(m.pushIssueHash=c),o==="assumption"&&(m.pushAssumptionHash=c),d(t,m),x(t,o==="decision"?"autoDecisionLogs":o==="issue"?"autoIssueLogs":o==="assumption"?"autoAssumptionLogs":"autoTaskStatusLogs"),f(t,"workbook_mutation",{kind:o,entityId:l,turnAutoLogCount:y,sessionAutoLogCount:v}),process.stdout.write("{}"),0}var mn=Yt,hn=Xt,yn=Kt,Sn=te,Tn=Qt,kn=oe,bn=Zt,wn=gn,vn=fn,In=ln;0&&(module.exports={QUALITY_GATE_REASON_PREFIX,TRIVIAL_TURN_CHAR_DEFAULT_EXPORT,_extractEditedPaths,_handlePostEdit,_handleSessionStart,_handleStop,_handleUpdateTaskGate,_handleUserPromptSubmit,_handleWorkbookMutation,_readActiveFeatureId,_transcriptCharsSinceLastStop,dispatch});