@dezycro-ai/agent-plugin 2.1.2 → 2.3.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 (53) hide show
  1. package/README.md +14 -98
  2. package/dist/cli/dezycro.js +54 -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/skills/verify/SKILL.md +17 -15
  21. package/LICENSE +0 -18
  22. package/bin/resolve-auth.js +0 -112
  23. package/dist/hooks/lib/authoring.d.ts +0 -118
  24. package/dist/hooks/lib/authoring.js +0 -277
  25. package/dist/hooks/lib/command-bus.d.ts +0 -66
  26. package/dist/hooks/lib/command-bus.js +0 -357
  27. package/dist/hooks/lib/config.d.ts +0 -28
  28. package/dist/hooks/lib/config.js +0 -175
  29. package/dist/hooks/lib/controller-match.d.ts +0 -16
  30. package/dist/hooks/lib/controller-match.js +0 -96
  31. package/dist/hooks/lib/coverage.d.ts +0 -17
  32. package/dist/hooks/lib/coverage.js +0 -66
  33. package/dist/hooks/lib/hashing.d.ts +0 -8
  34. package/dist/hooks/lib/hashing.js +0 -49
  35. package/dist/hooks/lib/logging.d.ts +0 -13
  36. package/dist/hooks/lib/logging.js +0 -72
  37. package/dist/hooks/lib/publish-spec.d.ts +0 -17
  38. package/dist/hooks/lib/publish-spec.js +0 -64
  39. package/dist/hooks/lib/quality-gate.d.ts +0 -67
  40. package/dist/hooks/lib/quality-gate.js +0 -187
  41. package/dist/hooks/lib/router.d.ts +0 -32
  42. package/dist/hooks/lib/state.d.ts +0 -34
  43. package/dist/hooks/lib/state.js +0 -245
  44. package/dist/hooks/lib/undo.d.ts +0 -11
  45. package/dist/hooks/lib/undo.js +0 -71
  46. package/dist/hooks/lib/verify.d.ts +0 -28
  47. package/dist/hooks/lib/verify.js +0 -94
  48. package/dist/hooks/lib/workbook.d.ts +0 -21
  49. package/dist/hooks/lib/workbook.js +0 -77
  50. package/dist/hooks/types.d.ts +0 -293
  51. package/dist/hooks/types.js +0 -14
  52. package/install.js +0 -84
  53. package/setup.js +0 -53
@@ -1,357 +0,0 @@
1
- /**
2
- * command-bus.ts — pulls the Agent Command Bus inbox on hook boundaries and renders
3
- * unseen events for the agent transcript.
4
- *
5
- * Polling model (per the Agent Command Bus TRD):
6
- * * `session-start` always polls (no floor).
7
- * * `user-prompt-submit`, `stop`, `post-tool-use` poll only if `minPollIntervalSeconds`
8
- * has elapsed since the last successful poll.
9
- * * On 2xx: advance cursor + lastPollTs, dedup by `eventId` (recent ring, cap 200),
10
- * return new events for surfacing.
11
- * * On non-2xx / network failure: cursor stays put, consecutiveFailures++, and
12
- * `lastAttemptTs` advances so the floor throttles repeated failures. Once
13
- * failures cross `circuitBreakerThreshold` the floor grows exponentially
14
- * (capped at `maxBackoffSeconds`), so an unreachable endpoint is not polled on
15
- * every hook fire. A single success resets the counter and the interval.
16
- *
17
- * Auth: a bearer token from `.dezycro/companion-token.local.json` (`{"token": "dzy_…"}`)
18
- * is sent on every request. Separate from the MCP server token (the user maintains both).
19
- *
20
- * URL: `<environments[default].apiUrl>/api/v1/workspaces/<wsId>/features/<fId>/inbox`.
21
- * Active feature comes from `.dezycro/active-feature.json` written by `/dezycro:work`.
22
- */
23
- 'use strict';
24
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
25
- if (k2 === undefined) k2 = k;
26
- var desc = Object.getOwnPropertyDescriptor(m, k);
27
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
28
- desc = { enumerable: true, get: function() { return m[k]; } };
29
- }
30
- Object.defineProperty(o, k2, desc);
31
- }) : (function(o, m, k, k2) {
32
- if (k2 === undefined) k2 = k;
33
- o[k2] = m[k];
34
- }));
35
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
36
- Object.defineProperty(o, "default", { enumerable: true, value: v });
37
- }) : function(o, v) {
38
- o["default"] = v;
39
- });
40
- var __importStar = (this && this.__importStar) || (function () {
41
- var ownKeys = function(o) {
42
- ownKeys = Object.getOwnPropertyNames || function (o) {
43
- var ar = [];
44
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
45
- return ar;
46
- };
47
- return ownKeys(o);
48
- };
49
- return function (mod) {
50
- if (mod && mod.__esModule) return mod;
51
- var result = {};
52
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
53
- __setModuleDefault(result, mod);
54
- return result;
55
- };
56
- })();
57
- Object.defineProperty(exports, "__esModule", { value: true });
58
- exports._internals = void 0;
59
- exports.backoffSeconds = backoffSeconds;
60
- exports.pollIfDue = pollIfDue;
61
- exports.renderForAgent = renderForAgent;
62
- const fs = __importStar(require("fs"));
63
- const os = __importStar(require("os"));
64
- const path = __importStar(require("path"));
65
- const logging = __importStar(require("./logging"));
66
- const DEFAULT_HOOKS = [
67
- 'sessionStart',
68
- 'userPromptSubmit',
69
- 'stop',
70
- 'postToolUse',
71
- ];
72
- const DEFAULT_MIN_POLL_INTERVAL_SECONDS = 60;
73
- const DEFAULT_REACTION_MODE = 'HALT_AND_REPLAN';
74
- const RECENT_EVENT_IDS_CAP = 200;
75
- const POLL_LIMIT = 100;
76
- // After this many consecutive failures the poll interval starts doubling per
77
- // extra failure (capped at maxBackoffSeconds) so an unreachable inbox endpoint
78
- // — e.g. a default `local` env that isn't running — stops being polled on every
79
- // hook fire. Self-heals: a single success resets consecutiveFailures to 0.
80
- const DEFAULT_CIRCUIT_BREAKER_THRESHOLD = 3;
81
- const DEFAULT_MAX_BACKOFF_SECONDS = 1800;
82
- /**
83
- * Effective poll interval given the number of consecutive failures so far.
84
- * Returns the base interval until `threshold` failures, then doubles per extra
85
- * failure, capped at `maxSeconds`. Never throws.
86
- */
87
- function backoffSeconds(baseSeconds, consecutiveFailures, threshold, maxSeconds) {
88
- if (consecutiveFailures < threshold)
89
- return baseSeconds;
90
- const extra = consecutiveFailures - threshold + 1;
91
- const scaled = baseSeconds * Math.pow(2, extra);
92
- return Math.min(scaled, maxSeconds);
93
- }
94
- /**
95
- * Entry point called from each hook handler. Returns the events that should be surfaced
96
- * to the agent on this hook fire, plus a state patch the caller must apply.
97
- *
98
- * Never throws — failures are logged and result in `{events: [], statePatch: null}`.
99
- */
100
- async function pollIfDue(repoRoot, hookEvent, config, state) {
101
- const resolved = resolveConfig(config);
102
- const reactionMode = resolved.reactionMode;
103
- const empty = { events: [], statePatch: null, reactionMode };
104
- if (!resolved.enabled)
105
- return empty;
106
- if (!resolved.hooks.has(hookEvent) && hookEvent !== 'sessionStart')
107
- return empty;
108
- // SessionStart always polls regardless of the configured hook set — bootstrap-on-start
109
- // is non-negotiable for cursor recovery after compaction.
110
- const cbState = state.commandBus;
111
- // Throttle from the last ATTEMPT (success or failure), not just the last
112
- // success — otherwise a never-succeeding endpoint (lastPollTs stays null) is
113
- // polled on every hook fire. The floor grows via exponential backoff once
114
- // consecutive failures cross the circuit-breaker threshold.
115
- const lastAttempt = cbState.lastAttemptTs ?? cbState.lastPollTs;
116
- if (hookEvent !== 'sessionStart' && lastAttempt) {
117
- const lastMs = Date.parse(lastAttempt);
118
- if (!Number.isNaN(lastMs)) {
119
- const floorSeconds = backoffSeconds(resolved.minPollIntervalSeconds, cbState.consecutiveFailures ?? 0, resolved.circuitBreakerThreshold, resolved.maxBackoffSeconds);
120
- const ageSeconds = (Date.now() - lastMs) / 1000;
121
- if (ageSeconds < floorSeconds)
122
- return empty;
123
- }
124
- }
125
- const feature = readActiveFeature(repoRoot);
126
- if (!feature)
127
- return empty;
128
- const apiUrl = readApiUrl(repoRoot);
129
- if (!apiUrl)
130
- return empty;
131
- const token = readToken(repoRoot);
132
- if (!token) {
133
- logging.info(repoRoot, 'command_bus_skip_no_token', { hookEvent });
134
- return empty;
135
- }
136
- const since = hookEvent === 'sessionStart' ? null : cbState.cursor;
137
- const attemptTs = new Date().toISOString();
138
- try {
139
- const fetched = await fetchInbox(apiUrl, feature, token, since);
140
- const seen = new Set(cbState.recentEventIds ?? []);
141
- const fresh = fetched.events.filter((e) => !seen.has(e.eventId));
142
- const newRing = trimRecent([...(cbState.recentEventIds ?? []), ...fresh.map((e) => e.eventId)]);
143
- const patch = {
144
- commandBus: {
145
- cursor: fetched.nextCursor || cbState.cursor,
146
- lastPollTs: fetched.serverTs,
147
- lastAttemptTs: attemptTs,
148
- consecutiveFailures: 0,
149
- recentEventIds: newRing,
150
- },
151
- };
152
- if (fresh.length > 0) {
153
- logging.info(repoRoot, 'command_bus_events_received', {
154
- hookEvent,
155
- count: fresh.length,
156
- types: fresh.map((e) => e.eventType),
157
- });
158
- }
159
- return { events: fresh, statePatch: patch, reactionMode };
160
- }
161
- catch (e) {
162
- const consecutiveFailures = (cbState.consecutiveFailures ?? 0) + 1;
163
- const patch = {
164
- commandBus: {
165
- ...cbState,
166
- lastAttemptTs: attemptTs,
167
- consecutiveFailures,
168
- },
169
- };
170
- const nextFloor = backoffSeconds(resolved.minPollIntervalSeconds, consecutiveFailures, resolved.circuitBreakerThreshold, resolved.maxBackoffSeconds);
171
- logging.info(repoRoot, 'command_bus_poll_failed', {
172
- hookEvent,
173
- error: e instanceof Error ? e.message : String(e),
174
- consecutiveFailures,
175
- nextPollAfterSeconds: nextFloor,
176
- backoff: consecutiveFailures >= resolved.circuitBreakerThreshold,
177
- });
178
- return { events: [], statePatch: patch, reactionMode };
179
- }
180
- }
181
- /**
182
- * Render zero or more events as a chat-ready Markdown block. Returns the empty string
183
- * when there is nothing to surface — callers can guard with the return value's length.
184
- */
185
- function renderForAgent(events, reactionMode) {
186
- if (events.length === 0)
187
- return '';
188
- const lines = [];
189
- lines.push(`⚠️ Dezycro Command Bus: ${events.length} new event(s) on this feature.`);
190
- lines.push('');
191
- for (const e of events) {
192
- lines.push(renderEvent(e));
193
- }
194
- if (events.some((e) => e.urgency === 'BLOCKING')) {
195
- lines.push('');
196
- lines.push(reactionGuidance(reactionMode));
197
- }
198
- return lines.join('\n');
199
- }
200
- function renderEvent(e) {
201
- if (e.eventType === 'INVALIDATION') {
202
- const summary = stringField(e.payload, 'artifactSummary') ?? '(no summary)';
203
- const reason = stringField(e.payload, 'reason') ?? '(no reason)';
204
- const prev = stringField(e.payload, 'previousStatus') ?? '?';
205
- const sourceLabel = `${e.sourceType.toLowerCase()} ${e.sourceId ?? '?'}`;
206
- return `- INVALIDATION (${e.urgency}): ${sourceLabel} ("${truncate(summary, 120)}") — reason: "${truncate(reason, 200)}" — was ${prev}.`;
207
- }
208
- return `- ${e.eventType} (${e.urgency}) source=${e.sourceType}/${e.sourceId ?? '-'}.`;
209
- }
210
- function reactionGuidance(mode) {
211
- switch (mode) {
212
- case 'HALT_AND_REPLAN':
213
- return 'Reaction mode: HALT_AND_REPLAN — pause your current line of work, acknowledge in chat, and re-plan around these invalidations before touching code again.';
214
- case 'ACKNOWLEDGE_AND_CONTINUE':
215
- return 'Reaction mode: ACKNOWLEDGE_AND_CONTINUE — surface this in your reply but continue current work; do not silently reuse invalidated artifacts.';
216
- case 'SILENT_LOG':
217
- return 'Reaction mode: SILENT_LOG — recorded for the session transcript; no chat interruption.';
218
- default:
219
- return '';
220
- }
221
- }
222
- /* ─── Helpers ─────────────────────────────────────────────────────────── */
223
- function resolveConfig(c) {
224
- const enabled = c?.enabled !== false; // default true
225
- const hooks = new Set(c?.pollHooks ?? DEFAULT_HOOKS);
226
- hooks.add('sessionStart'); // bootstrap is non-removable
227
- const minPollIntervalSeconds = Math.max(0, c?.minPollIntervalSeconds ?? DEFAULT_MIN_POLL_INTERVAL_SECONDS);
228
- const reactionMode = c?.reactionMode ?? DEFAULT_REACTION_MODE;
229
- const circuitBreakerThreshold = Math.max(1, c?.circuitBreakerThreshold ?? DEFAULT_CIRCUIT_BREAKER_THRESHOLD);
230
- const maxBackoffSeconds = Math.max(minPollIntervalSeconds, c?.maxBackoffSeconds ?? DEFAULT_MAX_BACKOFF_SECONDS);
231
- return { enabled, hooks, minPollIntervalSeconds, reactionMode, circuitBreakerThreshold, maxBackoffSeconds };
232
- }
233
- function readActiveFeature(repoRoot) {
234
- try {
235
- const p = path.join(repoRoot, '.dezycro', 'active-feature.json');
236
- if (!fs.existsSync(p))
237
- return null;
238
- const raw = JSON.parse(fs.readFileSync(p, 'utf8'));
239
- const featureId = typeof raw.featureId === 'string' ? raw.featureId : null;
240
- const workspaceId = typeof raw.workspaceId === 'string' ? raw.workspaceId : null;
241
- if (!featureId)
242
- return null;
243
- if (workspaceId)
244
- return { workspaceId, featureId };
245
- // Older active-feature.json shapes only carry featureId; fall back to config.
246
- const wsFromConfig = readWorkspaceId(repoRoot);
247
- if (!wsFromConfig)
248
- return null;
249
- return { workspaceId: wsFromConfig, featureId };
250
- }
251
- catch {
252
- return null;
253
- }
254
- }
255
- function readApiUrl(repoRoot) {
256
- const cfg = readDezycroConfig(repoRoot);
257
- if (!cfg)
258
- return null;
259
- const environments = Array.isArray(cfg.environments) ? cfg.environments : [];
260
- const defaultEnv = environments.find((e) => e && e.default === true) ?? environments[0];
261
- if (!defaultEnv)
262
- return null;
263
- const apiUrl = defaultEnv.apiUrl;
264
- return typeof apiUrl === 'string' && apiUrl.length > 0 ? apiUrl.replace(/\/$/, '') : null;
265
- }
266
- function readWorkspaceId(repoRoot) {
267
- const cfg = readDezycroConfig(repoRoot);
268
- if (!cfg)
269
- return null;
270
- const wsId = cfg.workspaceId;
271
- return typeof wsId === 'string' ? wsId : null;
272
- }
273
- function readDezycroConfig(repoRoot) {
274
- try {
275
- const p = path.join(repoRoot, '.dezycro', 'config.json');
276
- if (!fs.existsSync(p))
277
- return null;
278
- return JSON.parse(fs.readFileSync(p, 'utf8'));
279
- }
280
- catch {
281
- return null;
282
- }
283
- }
284
- function readToken(repoRoot) {
285
- // Project-local override first, then the user-global token written by
286
- // `dezycro-setup` (`~/.dezycro/token.json`). Typical install needs no
287
- // per-repo file at all.
288
- const candidates = [
289
- path.join(repoRoot, '.dezycro', 'companion-token.local.json'),
290
- path.join(os.homedir(), '.dezycro', 'token.json'),
291
- ];
292
- for (const p of candidates) {
293
- try {
294
- if (!fs.existsSync(p))
295
- continue;
296
- const raw = JSON.parse(fs.readFileSync(p, 'utf8'));
297
- const token = raw.token;
298
- if (typeof token === 'string' && token.length > 0)
299
- return token;
300
- }
301
- catch {
302
- // Try the next candidate.
303
- }
304
- }
305
- return null;
306
- }
307
- async function fetchInbox(apiUrl, feature, token, since) {
308
- const url = new URL(`${apiUrl}/api/v1/workspaces/${feature.workspaceId}/features/${feature.featureId}/inbox`);
309
- if (since)
310
- url.searchParams.set('since', since);
311
- url.searchParams.set('limit', String(POLL_LIMIT));
312
- const controller = new AbortController();
313
- const timeoutId = setTimeout(() => controller.abort(), 5000);
314
- try {
315
- const res = await fetch(url.toString(), {
316
- method: 'GET',
317
- headers: {
318
- Authorization: `Bearer ${token}`,
319
- Accept: 'application/json',
320
- },
321
- signal: controller.signal,
322
- });
323
- if (!res.ok) {
324
- throw new Error(`inbox poll returned ${res.status}`);
325
- }
326
- const body = (await res.json());
327
- if (!body || typeof body.serverTs !== 'string' || typeof body.nextCursor !== 'string') {
328
- throw new Error('inbox poll response missing serverTs/nextCursor');
329
- }
330
- return {
331
- serverTs: body.serverTs,
332
- nextCursor: body.nextCursor,
333
- events: Array.isArray(body.events) ? body.events : [],
334
- };
335
- }
336
- finally {
337
- clearTimeout(timeoutId);
338
- }
339
- }
340
- function trimRecent(ids) {
341
- return ids.length <= RECENT_EVENT_IDS_CAP ? ids : ids.slice(ids.length - RECENT_EVENT_IDS_CAP);
342
- }
343
- function stringField(payload, key) {
344
- const v = payload?.[key];
345
- return typeof v === 'string' ? v : null;
346
- }
347
- function truncate(s, max) {
348
- return s.length <= max ? s : s.slice(0, max - 1) + '…';
349
- }
350
- /* Exported for tests. */
351
- exports._internals = {
352
- resolveConfig,
353
- renderEvent,
354
- reactionGuidance,
355
- trimRecent,
356
- backoffSeconds,
357
- };
@@ -1,28 +0,0 @@
1
- /**
2
- * config.ts — reads `.dezycro/config.json` and resolves Companion settings.
3
- *
4
- * Owns the named-preset constant table (TRD §7.2) for the workbook sweep
5
- * confidence-threshold logic.
6
- */
7
- import type { CompanionConfig, PresetName, Thresholds } from '../types';
8
- export declare const PRESETS: Readonly<Record<PresetName, Thresholds>>;
9
- export declare const DEFAULT_CONFIG: Readonly<CompanionConfig>;
10
- export declare function deepMerge<T>(base: T, override: unknown): T;
11
- /**
12
- * Load `.dezycro/config.json` from the given repo root, deep-merged with
13
- * DEFAULT_CONFIG. Returns DEFAULT_CONFIG if the file is missing or unparseable.
14
- */
15
- export declare function load(repoRoot: string): CompanionConfig;
16
- /**
17
- * Resolve per-type confidence thresholds for the workbook sweep, honoring
18
- * `companion.autoWorkbookUpdates.preset` and any numeric overrides under
19
- * `companion.autoWorkbookUpdates.thresholds.*`.
20
- */
21
- export declare function resolveThresholds(config: CompanionConfig): Thresholds;
22
- /**
23
- * Find the nearest ancestor of `startDir` that contains a `.dezycro/`
24
- * directory, returning that ancestor as the repo root. Returns null if not
25
- * found before reaching the filesystem root.
26
- */
27
- export declare function findRepoRoot(startDir: string | null | undefined): string | null;
28
- export declare const _deepMerge: typeof deepMerge;
@@ -1,175 +0,0 @@
1
- /**
2
- * config.ts — reads `.dezycro/config.json` and resolves Companion settings.
3
- *
4
- * Owns the named-preset constant table (TRD §7.2) for the workbook sweep
5
- * confidence-threshold logic.
6
- */
7
- 'use strict';
8
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
9
- if (k2 === undefined) k2 = k;
10
- var desc = Object.getOwnPropertyDescriptor(m, k);
11
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
12
- desc = { enumerable: true, get: function() { return m[k]; } };
13
- }
14
- Object.defineProperty(o, k2, desc);
15
- }) : (function(o, m, k, k2) {
16
- if (k2 === undefined) k2 = k;
17
- o[k2] = m[k];
18
- }));
19
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
20
- Object.defineProperty(o, "default", { enumerable: true, value: v });
21
- }) : function(o, v) {
22
- o["default"] = v;
23
- });
24
- var __importStar = (this && this.__importStar) || (function () {
25
- var ownKeys = function(o) {
26
- ownKeys = Object.getOwnPropertyNames || function (o) {
27
- var ar = [];
28
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
- return ar;
30
- };
31
- return ownKeys(o);
32
- };
33
- return function (mod) {
34
- if (mod && mod.__esModule) return mod;
35
- var result = {};
36
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
- __setModuleDefault(result, mod);
38
- return result;
39
- };
40
- })();
41
- Object.defineProperty(exports, "__esModule", { value: true });
42
- exports._deepMerge = exports.DEFAULT_CONFIG = exports.PRESETS = void 0;
43
- exports.deepMerge = deepMerge;
44
- exports.load = load;
45
- exports.resolveThresholds = resolveThresholds;
46
- exports.findRepoRoot = findRepoRoot;
47
- const fs = __importStar(require("fs"));
48
- const path = __importStar(require("path"));
49
- exports.PRESETS = Object.freeze({
50
- conservative: { decision: 0.85, issue: 0.80, assumption: 0.90, taskStatus: 0.90 },
51
- balanced: { decision: 0.75, issue: 0.70, assumption: 0.80, taskStatus: 0.85 },
52
- aggressive: { decision: 0.60, issue: 0.55, assumption: 0.70, taskStatus: 0.75 },
53
- });
54
- exports.DEFAULT_CONFIG = Object.freeze({
55
- companion: {
56
- autoVerify: {
57
- preTaskDone: true,
58
- prePush: true,
59
- postEditPulse: { enabled: true, threshold: 5 },
60
- autoPublishSpecOnControllerChange: true,
61
- controllerPaths: [
62
- '**/controllers/**/*.{kt,java,py,go,ts,tsx,js,rs,rb,cs}',
63
- '**/api/**/*.{kt,java,py,go,ts,tsx,js,rs,rb,cs}',
64
- ],
65
- verifyTimeoutSeconds: 300,
66
- publishSpecTimeoutSeconds: 300,
67
- },
68
- autoWorkbookUpdates: {
69
- enabled: true,
70
- preset: 'conservative',
71
- maxAutoLogsPerTurn: 2,
72
- maxAutoLogsPerSession: 10,
73
- undoWindow: 'next-turn',
74
- thresholds: {
75
- decision: null,
76
- issue: null,
77
- assumption: null,
78
- taskStatus: null,
79
- },
80
- trivialTurnCharCutoff: 600,
81
- hashRingBufferSize: 32,
82
- },
83
- coverageNudges: {
84
- enabled: true,
85
- staleBaselineDays: 14,
86
- fetchMode: 'async',
87
- },
88
- authoring: {
89
- defaultDepth: 'adaptive',
90
- existingWorkScan: true,
91
- qualityGate: { enabled: true },
92
- minViableContextGate: { enabled: true },
93
- },
94
- safeMode: false,
95
- },
96
- });
97
- function isPlainObject(v) {
98
- return v !== null && typeof v === 'object' && !Array.isArray(v);
99
- }
100
- function deepMerge(base, override) {
101
- if (!isPlainObject(override))
102
- return override === undefined ? base : override;
103
- const baseObj = (Array.isArray(base) ? [...base] : { ...base });
104
- for (const key of Object.keys(override)) {
105
- const b = baseObj[key];
106
- const o = override[key];
107
- if (isPlainObject(b) && isPlainObject(o)) {
108
- baseObj[key] = deepMerge(b, o);
109
- }
110
- else {
111
- baseObj[key] = o;
112
- }
113
- }
114
- return baseObj;
115
- }
116
- /**
117
- * Load `.dezycro/config.json` from the given repo root, deep-merged with
118
- * DEFAULT_CONFIG. Returns DEFAULT_CONFIG if the file is missing or unparseable.
119
- */
120
- function load(repoRoot) {
121
- const file = path.join(repoRoot, '.dezycro', 'config.json');
122
- let raw;
123
- try {
124
- raw = fs.readFileSync(file, 'utf8');
125
- }
126
- catch (err) {
127
- const e = err;
128
- if (e.code === 'ENOENT')
129
- return JSON.parse(JSON.stringify(exports.DEFAULT_CONFIG));
130
- throw err;
131
- }
132
- let parsed;
133
- try {
134
- parsed = JSON.parse(raw);
135
- }
136
- catch {
137
- return JSON.parse(JSON.stringify(exports.DEFAULT_CONFIG));
138
- }
139
- return deepMerge(JSON.parse(JSON.stringify(exports.DEFAULT_CONFIG)), parsed);
140
- }
141
- /**
142
- * Resolve per-type confidence thresholds for the workbook sweep, honoring
143
- * `companion.autoWorkbookUpdates.preset` and any numeric overrides under
144
- * `companion.autoWorkbookUpdates.thresholds.*`.
145
- */
146
- function resolveThresholds(config) {
147
- const ws = config?.companion?.autoWorkbookUpdates || {};
148
- const presetName = (ws.preset || 'conservative');
149
- const base = exports.PRESETS[presetName] || exports.PRESETS.conservative;
150
- const overrides = ws.thresholds || {};
151
- return {
152
- decision: typeof overrides.decision === 'number' ? overrides.decision : base.decision,
153
- issue: typeof overrides.issue === 'number' ? overrides.issue : base.issue,
154
- assumption: typeof overrides.assumption === 'number' ? overrides.assumption : base.assumption,
155
- taskStatus: typeof overrides.taskStatus === 'number' ? overrides.taskStatus : base.taskStatus,
156
- };
157
- }
158
- /**
159
- * Find the nearest ancestor of `startDir` that contains a `.dezycro/`
160
- * directory, returning that ancestor as the repo root. Returns null if not
161
- * found before reaching the filesystem root.
162
- */
163
- function findRepoRoot(startDir) {
164
- let dir = path.resolve(startDir || process.cwd());
165
- for (let i = 0; i < 64; i += 1) {
166
- if (fs.existsSync(path.join(dir, '.dezycro')))
167
- return dir;
168
- const parent = path.dirname(dir);
169
- if (parent === dir)
170
- return null;
171
- dir = parent;
172
- }
173
- return null;
174
- }
175
- exports._deepMerge = deepMerge;
@@ -1,16 +0,0 @@
1
- /**
2
- * controller-match.ts — match file paths against `controllerPaths` globs from
3
- * `.dezycro/config.json` (TRD §5.2).
4
- *
5
- * Minimal glob subset (stdlib only):
6
- * - `**` zero or more path segments
7
- * - `*` any chars within a single segment (no slashes)
8
- * - `?` any single char within a single segment
9
- * - `{a,b}` alternation (`*.{kt,java}`)
10
- *
11
- * Limitations: no character classes, no escaping, forward-slash only.
12
- */
13
- export declare function expandAlternation(glob: string): string[];
14
- export declare function globToRegex(glob: string): RegExp;
15
- export declare function matchesControllerPath(filePath: string, globs: string[]): boolean;
16
- export declare function selectControllers(filePaths: string[], globs: string[]): string[];
@@ -1,96 +0,0 @@
1
- /**
2
- * controller-match.ts — match file paths against `controllerPaths` globs from
3
- * `.dezycro/config.json` (TRD §5.2).
4
- *
5
- * Minimal glob subset (stdlib only):
6
- * - `**` zero or more path segments
7
- * - `*` any chars within a single segment (no slashes)
8
- * - `?` any single char within a single segment
9
- * - `{a,b}` alternation (`*.{kt,java}`)
10
- *
11
- * Limitations: no character classes, no escaping, forward-slash only.
12
- */
13
- 'use strict';
14
- Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.expandAlternation = expandAlternation;
16
- exports.globToRegex = globToRegex;
17
- exports.matchesControllerPath = matchesControllerPath;
18
- exports.selectControllers = selectControllers;
19
- function normalizePath(p) {
20
- return String(p || '').replace(/\\/g, '/');
21
- }
22
- function expandAlternation(glob) {
23
- const m = glob.match(/\{([^{}]+)\}/);
24
- if (!m || m.index === undefined)
25
- return [glob];
26
- const whole = m[0];
27
- const inner = m[1];
28
- const parts = inner.split(',');
29
- const out = [];
30
- for (const part of parts) {
31
- const expanded = glob.slice(0, m.index) + part + glob.slice(m.index + whole.length);
32
- out.push(...expandAlternation(expanded));
33
- }
34
- return out;
35
- }
36
- function globToRegex(glob) {
37
- let re = '^';
38
- for (let i = 0; i < glob.length; i += 1) {
39
- const c = glob[i];
40
- if (c === '*') {
41
- if (glob[i + 1] === '*') {
42
- i += 1;
43
- if (glob[i + 1] === '/') {
44
- i += 1;
45
- re += '(?:.*/)?';
46
- }
47
- else {
48
- re += '.*';
49
- }
50
- }
51
- else {
52
- re += '[^/]*';
53
- }
54
- }
55
- else if (c === '?') {
56
- re += '[^/]';
57
- }
58
- else if ('.+^$()|[]\\'.indexOf(c) !== -1) {
59
- re += '\\' + c;
60
- }
61
- else {
62
- re += c;
63
- }
64
- }
65
- re += '$';
66
- return new RegExp(re);
67
- }
68
- const _regexCache = new Map();
69
- function compileGlob(glob) {
70
- const hit = _regexCache.get(glob);
71
- if (hit)
72
- return hit;
73
- const patterns = expandAlternation(glob).map(globToRegex);
74
- _regexCache.set(glob, patterns);
75
- return patterns;
76
- }
77
- function matchesControllerPath(filePath, globs) {
78
- if (!filePath || !Array.isArray(globs) || globs.length === 0)
79
- return false;
80
- const norm = normalizePath(filePath);
81
- for (const glob of globs) {
82
- if (typeof glob !== 'string' || glob.length === 0)
83
- continue;
84
- const regexes = compileGlob(glob);
85
- for (const re of regexes) {
86
- if (re.test(norm))
87
- return true;
88
- }
89
- }
90
- return false;
91
- }
92
- function selectControllers(filePaths, globs) {
93
- if (!Array.isArray(filePaths))
94
- return [];
95
- return filePaths.filter((p) => matchesControllerPath(p, globs));
96
- }
@@ -1,17 +0,0 @@
1
- /**
2
- * coverage.ts — deterministic coverage-snapshot formatter (TRD §5.4.2).
3
- *
4
- * One-liner format:
5
- * Coverage: N path[s] uncovered, M endpoint[s] not in spec, K baseline[s] stale
6
- *
7
- * Zero-count segments are omitted. If all counts are zero, returns null.
8
- */
9
- import type { CoverageSnapshot } from '../types';
10
- export declare function formatOneLiner(snapshot: CoverageSnapshot | null | undefined): string | null;
11
- export declare function formatDelta(prev: CoverageSnapshot | null | undefined, next: CoverageSnapshot | null | undefined): string | null;
12
- /**
13
- * Detached coverage-fetch entry point. Deferred to V2.1 — for V2.0 the
14
- * /dezycro:verify skill calls get_coverage_report directly and updates
15
- * state via `companion-runner state --patch=…`.
16
- */
17
- export declare function fetch(_featureId: string): Promise<null>;