@devory/core 0.1.1 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -30,21 +30,156 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var src_exports = {};
32
32
  __export(src_exports, {
33
+ ESCALATION_REASONS: () => ESCALATION_REASONS,
34
+ EXECUTION_POLICY_FILENAME: () => EXECUTION_POLICY_FILENAME,
35
+ EXECUTION_POLICY_VERSION: () => EXECUTION_POLICY_VERSION,
36
+ EXECUTION_POLICY_WORKSPACE_PATH: () => EXECUTION_POLICY_WORKSPACE_PATH,
37
+ HUMAN_INTERRUPTION_POLICY_FILENAME: () => HUMAN_INTERRUPTION_POLICY_FILENAME,
38
+ HUMAN_INTERRUPTION_POLICY_MAP: () => HUMAN_INTERRUPTION_POLICY_MAP,
39
+ HUMAN_INTERRUPTION_POLICY_VERSION: () => HUMAN_INTERRUPTION_POLICY_VERSION,
40
+ HUMAN_INTERRUPTION_POLICY_WORKSPACE_PATH: () => HUMAN_INTERRUPTION_POLICY_WORKSPACE_PATH,
41
+ HUMAN_QUESTION_ARTIFACT_DIR: () => HUMAN_QUESTION_ARTIFACT_DIR,
42
+ HUMAN_QUESTION_ARTIFACT_TYPE: () => HUMAN_QUESTION_ARTIFACT_TYPE,
43
+ HUMAN_QUESTION_DIGEST_ARTIFACT_DIR: () => HUMAN_QUESTION_DIGEST_ARTIFACT_DIR,
44
+ HUMAN_QUESTION_DIGEST_VERSION: () => HUMAN_QUESTION_DIGEST_VERSION,
45
+ HUMAN_QUESTION_EVENT_ARTIFACT_DIR: () => HUMAN_QUESTION_EVENT_ARTIFACT_DIR,
46
+ HUMAN_QUESTION_EVENT_VERSION: () => HUMAN_QUESTION_EVENT_VERSION,
47
+ HUMAN_QUESTION_VERSION: () => HUMAN_QUESTION_VERSION,
48
+ PLANNING_DRAFT_COMMIT_STATES: () => PLANNING_DRAFT_COMMIT_STATES,
49
+ PLANNING_DRAFT_CONTRACT_VERSION: () => PLANNING_DRAFT_CONTRACT_VERSION,
50
+ PLANNING_DRAFT_KINDS: () => PLANNING_DRAFT_KINDS,
51
+ PLANNING_DRAFT_PERSISTENCE_MODES: () => PLANNING_DRAFT_PERSISTENCE_MODES,
52
+ PLANNING_DRAFT_VALIDATION_STATUSES: () => PLANNING_DRAFT_VALIDATION_STATUSES,
53
+ PROGRESS_EVENT_CATEGORIES: () => PROGRESS_EVENT_CATEGORIES,
54
+ REQUIRED_FIELDS: () => REQUIRED_FIELDS,
55
+ RESUMABLE_RUN_STATUSES: () => RESUMABLE_RUN_STATUSES,
56
+ REVIEW_CONTROL_ACTIONS: () => REVIEW_CONTROL_ACTIONS,
57
+ REVIEW_CONTROL_CONTRACT_VERSION: () => REVIEW_CONTROL_CONTRACT_VERSION,
58
+ REVIEW_CONTROL_MECHANISMS: () => REVIEW_CONTROL_MECHANISMS,
59
+ REVIEW_QUEUE_ITEM_KINDS: () => REVIEW_QUEUE_ITEM_KINDS,
60
+ REVIEW_QUEUE_RUN_STATUSES: () => REVIEW_QUEUE_RUN_STATUSES,
61
+ REVIEW_QUEUE_TASK_STAGES: () => REVIEW_QUEUE_TASK_STAGES,
62
+ REVIEW_QUEUE_TRIAGE_STAGES: () => REVIEW_QUEUE_TRIAGE_STAGES,
63
+ ROUTING_DECISION_VERSION: () => ROUTING_DECISION_VERSION,
64
+ RUN_LEDGER_VERSION: () => RUN_LEDGER_VERSION,
65
+ SLACK_NOTIFICATION_CONFIG_VERSION: () => SLACK_NOTIFICATION_CONFIG_VERSION,
33
66
  STANDARDS_FILENAME: () => STANDARDS_FILENAME,
67
+ TASK_DRAFT_BODY_SECTION_ORDER: () => TASK_DRAFT_BODY_SECTION_ORDER,
68
+ TASK_DRAFT_COMMIT_STAGES: () => TASK_DRAFT_COMMIT_STAGES,
69
+ TASK_DRAFT_OPTIONAL_FRONTMATTER_FIELDS: () => TASK_DRAFT_OPTIONAL_FRONTMATTER_FIELDS,
70
+ TASK_DRAFT_RENDER_CONTRACT_VERSION: () => TASK_DRAFT_RENDER_CONTRACT_VERSION,
71
+ TASK_DRAFT_REQUIRED_FRONTMATTER_FIELDS: () => TASK_DRAFT_REQUIRED_FRONTMATTER_FIELDS,
72
+ TASK_MARKDOWN_FRONTMATTER_ORDER: () => TASK_MARKDOWN_FRONTMATTER_ORDER,
73
+ TASK_MARKDOWN_RENDERER_VERSION: () => TASK_MARKDOWN_RENDERER_VERSION,
74
+ TASK_MARKDOWN_SECTION_ORDER: () => TASK_MARKDOWN_SECTION_ORDER,
75
+ TASK_REVIEW_ACTIONS: () => TASK_REVIEW_ACTIONS,
76
+ TASK_REVIEW_ACTION_STAGE_MAP: () => TASK_REVIEW_ACTION_STAGE_MAP,
77
+ UNATTENDED_CHECKPOINT_TRIGGERS: () => UNATTENDED_CHECKPOINT_TRIGGERS,
78
+ UNATTENDED_CHECKPOINT_VERSION: () => UNATTENDED_CHECKPOINT_VERSION,
79
+ UNATTENDED_EXECUTION_CONTRACT_VERSION: () => UNATTENDED_EXECUTION_CONTRACT_VERSION,
80
+ UNATTENDED_RUN_STATUSES: () => UNATTENDED_RUN_STATUSES,
81
+ UNATTENDED_STALL_POLICY_FILENAME: () => UNATTENDED_STALL_POLICY_FILENAME,
82
+ UNATTENDED_STALL_POLICY_VERSION: () => UNATTENDED_STALL_POLICY_VERSION,
83
+ UNATTENDED_STALL_POLICY_WORKSPACE_PATH: () => UNATTENDED_STALL_POLICY_WORKSPACE_PATH,
84
+ VALID_HUMAN_NOTIFICATION_MODES: () => VALID_HUMAN_NOTIFICATION_MODES,
85
+ VALID_HUMAN_POLICY_THRESHOLD_KEYS: () => VALID_HUMAN_POLICY_THRESHOLD_KEYS,
86
+ VALID_POLICY_ESCALATION_BEHAVIORS: () => VALID_POLICY_ESCALATION_BEHAVIORS,
87
+ VALID_ROUTING_CONTEXT_INTENSITIES: () => VALID_ROUTING_CONTEXT_INTENSITIES,
88
+ VALID_ROUTING_EXECUTION_PROFILES: () => VALID_ROUTING_EXECUTION_PROFILES,
89
+ VALID_ROUTING_PRIORITY_LEVELS: () => VALID_ROUTING_PRIORITY_LEVELS,
90
+ VALID_SLACK_DELIVERY_MODES: () => VALID_SLACK_DELIVERY_MODES,
91
+ VALID_SLACK_DIGEST_GROUP_BY: () => VALID_SLACK_DIGEST_GROUP_BY,
92
+ VALID_SLACK_TRANSPORT_KINDS: () => VALID_SLACK_TRANSPORT_KINDS,
93
+ WORKER_HEALTH_STATUSES: () => WORKER_HEALTH_STATUSES,
94
+ applyExecutionPolicyOverrides: () => applyExecutionPolicyOverrides,
95
+ applyHumanInterruptionPolicyOverrides: () => applyHumanInterruptionPolicyOverrides,
96
+ applyTaskDraftValidation: () => applyTaskDraftValidation,
97
+ applyTaskRoutingOutcomeEvaluation: () => applyTaskRoutingOutcomeEvaluation,
98
+ applyUnattendedStallPolicyOverrides: () => applyUnattendedStallPolicyOverrides,
99
+ attachRoutingDecisionLinkage: () => attachRoutingDecisionLinkage,
100
+ buildEpicPlanningDraft: () => buildEpicPlanningDraft,
101
+ buildEpicPlanningDraftFixture: () => buildEpicPlanningDraftFixture,
102
+ buildExecutionPolicyInjection: () => buildExecutionPolicyInjection,
103
+ buildHumanQuestionArtifactRelativePath: () => buildHumanQuestionArtifactRelativePath,
104
+ buildHumanQuestionDigest: () => buildHumanQuestionDigest,
105
+ buildHumanQuestionDigestRelativePath: () => buildHumanQuestionDigestRelativePath,
106
+ buildHumanQuestionFixture: () => buildHumanQuestionFixture,
107
+ buildHumanQuestionLifecycleEvent: () => buildHumanQuestionLifecycleEvent,
108
+ buildHumanQuestionLifecycleEventRelativePath: () => buildHumanQuestionLifecycleEventRelativePath,
109
+ buildMinimalTaskDraftFixture: () => buildMinimalTaskDraftFixture,
110
+ buildPlanningDraftArtifactPath: () => buildPlanningDraftArtifactPath,
111
+ buildPlanningDraftStorageRelativePath: () => buildPlanningDraftStorageRelativePath,
112
+ buildRichTaskDraftFixture: () => buildRichTaskDraftFixture,
113
+ buildRoutingDecisionId: () => buildRoutingDecisionId,
114
+ buildRoutingOutcomeEvaluation: () => buildRoutingOutcomeEvaluation,
115
+ buildRunAttentionQueueItem: () => buildRunAttentionQueueItem,
116
+ buildTaskDraftRenderFixture: () => buildTaskDraftRenderFixture,
117
+ buildTaskDraftTargetPath: () => buildTaskDraftTargetPath,
118
+ buildTaskPlanningDraftFixture: () => buildTaskPlanningDraftFixture,
119
+ buildTaskReviewQueueItem: () => buildTaskReviewQueueItem,
120
+ buildTaskTriageQueueItem: () => buildTaskTriageQueueItem,
121
+ clearLicenseCache: () => clearLicenseCache,
122
+ clearLicenseToken: () => clearLicenseToken,
34
123
  detectTier: () => detectTier,
124
+ extractHumanQuestionArtifactMetadata: () => extractHumanQuestionArtifactMetadata,
35
125
  factoryPaths: () => factoryPaths,
36
126
  findFactoryContextDir: () => findFactoryContextDir,
127
+ getLicenseCacheFilePath: () => getLicenseCacheFilePath,
128
+ getLicenseFilePath: () => getLicenseFilePath,
129
+ getLicenseStatus: () => getLicenseStatus,
130
+ getSupportedReviewActions: () => getSupportedReviewActions,
131
+ getTaskHumanInterruptionPolicyOverrides: () => getTaskHumanInterruptionPolicyOverrides,
37
132
  isFeatureEnabled: () => isFeatureEnabled,
38
133
  loadBaseline: () => loadBaseline,
134
+ loadDefaultExecutionPolicy: () => loadDefaultExecutionPolicy,
135
+ loadDefaultHumanInterruptionPolicy: () => loadDefaultHumanInterruptionPolicy,
136
+ loadDefaultUnattendedStallPolicy: () => loadDefaultUnattendedStallPolicy,
39
137
  loadStandards: () => loadStandards,
138
+ loadWorkspaceExecutionPolicy: () => loadWorkspaceExecutionPolicy,
139
+ loadWorkspaceHumanInterruptionPolicy: () => loadWorkspaceHumanInterruptionPolicy,
140
+ loadWorkspaceUnattendedStallPolicy: () => loadWorkspaceUnattendedStallPolicy,
40
141
  mergeStandards: () => mergeStandards,
142
+ normalizeExecutionPolicyOverrides: () => normalizeExecutionPolicyOverrides,
143
+ normalizeHumanInterruptionPolicyOverrides: () => normalizeHumanInterruptionPolicyOverrides,
144
+ normalizePlanningDraft: () => normalizePlanningDraft,
145
+ normalizeReviewQueueItem: () => normalizeReviewQueueItem,
146
+ normalizeRoutingInput: () => normalizeRoutingInput,
147
+ normalizeRunRecord: () => normalizeRunRecord,
148
+ normalizeSlackNotificationConfig: () => normalizeSlackNotificationConfig,
149
+ normalizeTaskDraft: () => normalizeTaskDraft,
150
+ normalizeTaskRecord: () => normalizeTaskRecord,
151
+ normalizeUnattendedCheckpointArtifact: () => normalizeUnattendedCheckpointArtifact,
152
+ normalizeUnattendedExecutionSnapshot: () => normalizeUnattendedExecutionSnapshot,
153
+ normalizeUnattendedStallPolicyOverrides: () => normalizeUnattendedStallPolicyOverrides,
41
154
  parseFrontmatter: () => parseFrontmatter,
155
+ parseHumanQuestionArtifact: () => parseHumanQuestionArtifact,
156
+ parseHumanQuestionLifecycleEvent: () => parseHumanQuestionLifecycleEvent,
157
+ renderHumanQuestionDigestMarkdown: () => renderHumanQuestionDigestMarkdown,
158
+ renderTaskDraftMarkdown: () => renderTaskDraftMarkdown2,
159
+ renderTaskDraftTarget: () => renderTaskDraftTarget2,
160
+ renderTaskMarkdown: () => renderTaskDraftMarkdown,
161
+ renderTaskMarkdownTarget: () => renderTaskDraftTarget,
162
+ renderTaskPlanningDraftTarget: () => renderTaskPlanningDraftTarget,
42
163
  resolveBaselinePath: () => resolveBaselinePath,
164
+ resolveExecutionPolicy: () => resolveExecutionPolicy,
43
165
  resolveFactoryEnvironment: () => resolveFactoryEnvironment,
44
166
  resolveFactoryMode: () => resolveFactoryMode,
45
167
  resolveFactoryRoot: () => resolveFactoryRoot,
168
+ resolveHumanInterruptionPolicy: () => resolveHumanInterruptionPolicy,
169
+ resolveUnattendedStallPolicy: () => resolveUnattendedStallPolicy,
170
+ serializeHumanQuestionArtifact: () => serializeHumanQuestionArtifact,
171
+ serializeHumanQuestionDigest: () => serializeHumanQuestionDigest,
172
+ serializeHumanQuestionLifecycleEvent: () => serializeHumanQuestionLifecycleEvent,
173
+ serializePlanningDraft: () => serializePlanningDraft,
46
174
  serializeStandardsAsDoctrine: () => serializeStandardsAsDoctrine,
47
- tierGateMessage: () => tierGateMessage
175
+ tierGateMessage: () => tierGateMessage,
176
+ toPlanningDraftValidationRecord: () => toPlanningDraftValidationRecord,
177
+ updateEpicPlanningDraft: () => updateEpicPlanningDraft,
178
+ validateTask: () => validateTask,
179
+ validateTaskBody: () => validateTaskBody,
180
+ validateTaskDraft: () => validateTaskDraft,
181
+ validateTaskMarkdown: () => validateTaskMarkdown,
182
+ writeLicenseToken: () => writeLicenseToken
48
183
  });
49
184
  module.exports = __toCommonJS(src_exports);
50
185
 
@@ -83,71 +218,417 @@ function parseFrontmatter(content) {
83
218
  return { meta, body };
84
219
  }
85
220
 
86
- // src/factory-environment.ts
221
+ // src/license.ts
87
222
  var fs = __toESM(require("fs"));
88
223
  var path = __toESM(require("path"));
89
- var FACTORY_MARKER = "FACTORY_CONTEXT.md";
90
- function trimEnv(value) {
91
- if (typeof value !== "string")
224
+ var crypto = __toESM(require("crypto"));
225
+ var https = __toESM(require("https"));
226
+ var ENV_VAR = "DEVORY_LICENSE_KEY";
227
+ var LICENSE_FILE = path.join(".devory", "license");
228
+ var LICENSE_CACHE_FILE = path.join(".devory", "license-cache.json");
229
+ var KEY_CACHE_FILE = path.join(".devory", "key-cache.json");
230
+ var LOCAL_JWK_FILE = path.join(".devory", "license.jwk");
231
+ var KEYS_BASE_URL = "https://keys.devory.ai";
232
+ var LICENSE_CACHE_TTL = 86400;
233
+ var KEY_CACHE_TTL = 604800;
234
+ var KEY_FETCH_TIMEOUT_MS = 5e3;
235
+ function tokenHash(token) {
236
+ return crypto.createHash("sha256").update(token).digest("hex");
237
+ }
238
+ function b64urlToBuffer(s) {
239
+ return Buffer.from(s.replace(/-/g, "+").replace(/_/g, "/"), "base64");
240
+ }
241
+ function b64urlDecode(s) {
242
+ return b64urlToBuffer(s).toString("utf-8");
243
+ }
244
+ function parseJwt(token) {
245
+ const parts = token.split(".");
246
+ if (parts.length !== 3) {
247
+ throw new Error("malformed JWT: expected header.payload.signature");
248
+ }
249
+ const [headerB64, payloadB64, sigB64] = parts;
250
+ let header;
251
+ let payload;
252
+ try {
253
+ header = JSON.parse(b64urlDecode(headerB64));
254
+ payload = JSON.parse(b64urlDecode(payloadB64));
255
+ } catch {
256
+ throw new Error("malformed JWT: could not JSON-parse header or payload");
257
+ }
258
+ if (!header.kid)
259
+ throw new Error("malformed JWT: missing kid in header");
260
+ if (header.alg !== "RS256")
261
+ throw new Error(`unsupported algorithm: ${header.alg ?? "(none)"}`);
262
+ if (!payload.sub)
263
+ throw new Error("malformed JWT: missing sub claim");
264
+ if (!payload.tier)
265
+ throw new Error("malformed JWT: missing tier claim");
266
+ if (typeof payload.exp !== "number")
267
+ throw new Error("malformed JWT: missing or invalid exp claim");
268
+ if (typeof payload.iat !== "number")
269
+ throw new Error("malformed JWT: missing or invalid iat claim");
270
+ return {
271
+ header,
272
+ payload,
273
+ signingInput: `${headerB64}.${payloadB64}`,
274
+ signatureBytes: b64urlToBuffer(sigB64)
275
+ };
276
+ }
277
+ function verifyRs256(signingInput, signatureBytes, jwk) {
278
+ try {
279
+ const key = crypto.createPublicKey({ key: jwk, format: "jwk" });
280
+ return crypto.verify("RSA-SHA256", Buffer.from(signingInput, "utf-8"), key, signatureBytes);
281
+ } catch {
282
+ return false;
283
+ }
284
+ }
285
+ function fetchKeyFromNetwork(kid) {
286
+ return new Promise((resolve2, reject) => {
287
+ const url = `${KEYS_BASE_URL}/${encodeURIComponent(kid)}`;
288
+ const req = https.get(url, { timeout: KEY_FETCH_TIMEOUT_MS }, (res) => {
289
+ if (res.statusCode !== 200) {
290
+ reject(new Error(`key fetch returned HTTP ${res.statusCode}`));
291
+ return;
292
+ }
293
+ const chunks = [];
294
+ res.on("data", (chunk) => chunks.push(chunk));
295
+ res.on("end", () => {
296
+ try {
297
+ resolve2(JSON.parse(Buffer.concat(chunks).toString("utf-8")));
298
+ } catch {
299
+ reject(new Error("key fetch returned invalid JSON"));
300
+ }
301
+ });
302
+ });
303
+ req.on("timeout", () => {
304
+ req.destroy();
305
+ reject(new Error(`key fetch timed out after ${KEY_FETCH_TIMEOUT_MS} ms`));
306
+ });
307
+ req.on("error", reject);
308
+ });
309
+ }
310
+ function readKeyFromCache(kid, factoryRoot) {
311
+ const cachePath = path.join(factoryRoot, KEY_CACHE_FILE);
312
+ if (!fs.existsSync(cachePath))
92
313
  return null;
93
- const trimmed = value.trim();
94
- return trimmed === "" ? null : trimmed;
314
+ try {
315
+ const cache = JSON.parse(fs.readFileSync(cachePath, "utf-8"));
316
+ const entry = cache[kid];
317
+ if (!entry)
318
+ return null;
319
+ if (Math.floor(Date.now() / 1e3) >= entry.cached_at + KEY_CACHE_TTL)
320
+ return null;
321
+ return entry.jwk;
322
+ } catch {
323
+ return null;
324
+ }
95
325
  }
96
- function findFactoryContextDir(startDir) {
97
- let current = path.resolve(startDir);
98
- while (true) {
99
- if (fs.existsSync(path.join(current, FACTORY_MARKER))) {
100
- return current;
326
+ function writeKeyToCache(kid, jwk, factoryRoot) {
327
+ try {
328
+ const cachePath = path.join(factoryRoot, KEY_CACHE_FILE);
329
+ let cache = {};
330
+ if (fs.existsSync(cachePath)) {
331
+ try {
332
+ cache = JSON.parse(fs.readFileSync(cachePath, "utf-8"));
333
+ } catch {
334
+ }
101
335
  }
102
- const parent = path.dirname(current);
103
- if (parent === current) {
336
+ cache[kid] = { jwk, cached_at: Math.floor(Date.now() / 1e3) };
337
+ fs.mkdirSync(path.dirname(cachePath), { recursive: true });
338
+ fs.writeFileSync(cachePath, JSON.stringify(cache, null, 2));
339
+ } catch {
340
+ }
341
+ }
342
+ async function resolvePublicKey(kid, factoryRoot) {
343
+ if (factoryRoot) {
344
+ const localPath = path.join(factoryRoot, LOCAL_JWK_FILE);
345
+ if (fs.existsSync(localPath)) {
346
+ try {
347
+ const jwk2 = JSON.parse(fs.readFileSync(localPath, "utf-8"));
348
+ if (!jwk2.kid || jwk2.kid === kid)
349
+ return jwk2;
350
+ } catch {
351
+ }
352
+ }
353
+ const cached = readKeyFromCache(kid, factoryRoot);
354
+ if (cached)
355
+ return cached;
356
+ }
357
+ const jwk = await fetchKeyFromNetwork(kid);
358
+ if (factoryRoot)
359
+ writeKeyToCache(kid, jwk, factoryRoot);
360
+ return jwk;
361
+ }
362
+ function readLicenseCache(factoryRoot, token) {
363
+ const cachePath = path.join(factoryRoot, LICENSE_CACHE_FILE);
364
+ if (!fs.existsSync(cachePath))
365
+ return null;
366
+ try {
367
+ const cache = JSON.parse(fs.readFileSync(cachePath, "utf-8"));
368
+ const now = Math.floor(Date.now() / 1e3);
369
+ if (!cache.token_hash || cache.token_hash !== tokenHash(token))
104
370
  return null;
371
+ if (now < cache.cache_until && now < cache.exp)
372
+ return cache;
373
+ return null;
374
+ } catch {
375
+ return null;
376
+ }
377
+ }
378
+ function tierFromClaim(tierClaim) {
379
+ if (tierClaim === "pro" || tierClaim === "pro_annual" || tierClaim === "lifetime")
380
+ return "pro";
381
+ if (tierClaim === "teams" || tierClaim === "teams_annual")
382
+ return "teams";
383
+ return "core";
384
+ }
385
+ function writeLicenseCache(factoryRoot, payload, kid) {
386
+ try {
387
+ const now = Math.floor(Date.now() / 1e3);
388
+ const { token } = resolveToken(factoryRoot);
389
+ if (!token)
390
+ return;
391
+ const cache = {
392
+ kid,
393
+ sub: payload.sub,
394
+ tier: tierFromClaim(payload.tier),
395
+ exp: payload.exp,
396
+ token_hash: tokenHash(token),
397
+ verified_at: now,
398
+ cache_until: now + LICENSE_CACHE_TTL
399
+ };
400
+ if (payload.org)
401
+ cache.org_id = payload.org;
402
+ if (payload.seats != null)
403
+ cache.seat_count = payload.seats;
404
+ const cachePath = path.join(factoryRoot, LICENSE_CACHE_FILE);
405
+ fs.mkdirSync(path.dirname(cachePath), { recursive: true });
406
+ fs.writeFileSync(cachePath, JSON.stringify(cache, null, 2));
407
+ } catch {
408
+ }
409
+ }
410
+ function resolveToken(factoryRoot) {
411
+ const envVal = process.env[ENV_VAR];
412
+ if (envVal !== void 0) {
413
+ return { token: envVal.trim() || null, source: "env" };
414
+ }
415
+ if (factoryRoot) {
416
+ const filePath = path.join(factoryRoot, LICENSE_FILE);
417
+ if (fs.existsSync(filePath)) {
418
+ const token = fs.readFileSync(filePath, "utf-8").trim();
419
+ return { token: token || null, source: "file", licenseFilePath: filePath };
105
420
  }
106
- current = parent;
107
421
  }
422
+ return { token: null, source: void 0 };
108
423
  }
109
- function resolveFactoryRoot(startDir = process.cwd()) {
110
- const explicit = trimEnv(process.env.DEVORY_FACTORY_ROOT);
111
- if (explicit) {
112
- return { root: explicit, source: "env:DEVORY_FACTORY_ROOT" };
424
+ function getLicenseFilePath(factoryRoot) {
425
+ return path.join(factoryRoot, LICENSE_FILE);
426
+ }
427
+ function getLicenseCacheFilePath(factoryRoot) {
428
+ return path.join(factoryRoot, LICENSE_CACHE_FILE);
429
+ }
430
+ function writeLicenseToken(factoryRoot, token) {
431
+ const trimmed = token.trim();
432
+ if (!trimmed) {
433
+ throw new Error("License key cannot be empty");
113
434
  }
114
- const legacy = trimEnv(process.env.FACTORY_ROOT);
115
- if (legacy) {
116
- return { root: legacy, source: "env:FACTORY_ROOT" };
435
+ const licensePath = getLicenseFilePath(factoryRoot);
436
+ fs.mkdirSync(path.dirname(licensePath), { recursive: true });
437
+ fs.writeFileSync(licensePath, `${trimmed}
438
+ `, { mode: 384 });
439
+ clearLicenseCache(factoryRoot);
440
+ return { path: licensePath };
441
+ }
442
+ function clearLicenseToken(factoryRoot) {
443
+ const licensePath = getLicenseFilePath(factoryRoot);
444
+ const removed = fs.existsSync(licensePath);
445
+ if (removed) {
446
+ fs.unlinkSync(licensePath);
117
447
  }
118
- const walked = findFactoryContextDir(startDir);
119
- if (walked) {
120
- return { root: walked, source: "git-walk" };
448
+ clearLicenseCache(factoryRoot);
449
+ return { path: licensePath, removed };
450
+ }
451
+ function clearLicenseCache(factoryRoot) {
452
+ const cachePath = getLicenseCacheFilePath(factoryRoot);
453
+ if (fs.existsSync(cachePath)) {
454
+ fs.unlinkSync(cachePath);
121
455
  }
122
- return { root: path.resolve(startDir), source: "cwd" };
123
456
  }
124
- function factoryPaths(root) {
457
+ async function getLicenseStatus(factoryRoot) {
458
+ const { token, source, licenseFilePath } = resolveToken(factoryRoot);
459
+ const cacheFilePath = factoryRoot ? getLicenseCacheFilePath(factoryRoot) : void 0;
460
+ const sourceLabel = source === "env" ? ENV_VAR : source === "file" ? ".devory/license" : void 0;
461
+ if (!token) {
462
+ return {
463
+ tier: "core",
464
+ reason: "No license key found \u2014 running on Core tier",
465
+ hasKey: false,
466
+ source,
467
+ sourceLabel,
468
+ envVarName: ENV_VAR,
469
+ licenseFilePath,
470
+ cacheFilePath,
471
+ cacheUsed: false,
472
+ fallbackToCore: false
473
+ };
474
+ }
475
+ if (factoryRoot) {
476
+ const cached = readLicenseCache(factoryRoot, token);
477
+ if (cached) {
478
+ return {
479
+ tier: cached.tier,
480
+ hasKey: true,
481
+ key: token,
482
+ source,
483
+ sourceLabel,
484
+ envVarName: ENV_VAR,
485
+ licenseFilePath,
486
+ cacheFilePath,
487
+ cacheUsed: true,
488
+ fallbackToCore: false,
489
+ userId: cached.sub,
490
+ kid: cached.kid,
491
+ expiresAt: new Date(cached.exp * 1e3).toISOString(),
492
+ orgId: cached.org_id,
493
+ seatCount: cached.seat_count,
494
+ reason: `License verified from cache (user: ${cached.sub}, expires: ${new Date(cached.exp * 1e3).toISOString().slice(0, 10)})`
495
+ };
496
+ }
497
+ }
498
+ let parsed;
499
+ try {
500
+ parsed = parseJwt(token);
501
+ } catch (err) {
502
+ return {
503
+ tier: "core",
504
+ hasKey: true,
505
+ key: token,
506
+ source,
507
+ sourceLabel,
508
+ envVarName: ENV_VAR,
509
+ licenseFilePath,
510
+ cacheFilePath,
511
+ cacheUsed: false,
512
+ fallbackToCore: true,
513
+ invalid: true,
514
+ reason: `License token is malformed: ${err instanceof Error ? err.message : String(err)} \u2014 falling back to Core tier`
515
+ };
516
+ }
517
+ const now = Math.floor(Date.now() / 1e3);
518
+ if (now >= parsed.payload.exp) {
519
+ return {
520
+ tier: "core",
521
+ hasKey: true,
522
+ key: token,
523
+ source,
524
+ sourceLabel,
525
+ envVarName: ENV_VAR,
526
+ licenseFilePath,
527
+ cacheFilePath,
528
+ cacheUsed: false,
529
+ fallbackToCore: true,
530
+ invalid: true,
531
+ expiresAt: new Date(parsed.payload.exp * 1e3).toISOString(),
532
+ kid: parsed.header.kid,
533
+ reason: "License token has expired \u2014 please renew your license"
534
+ };
535
+ }
536
+ let jwk;
537
+ try {
538
+ jwk = await resolvePublicKey(parsed.header.kid, factoryRoot);
539
+ } catch (err) {
540
+ return {
541
+ tier: "core",
542
+ hasKey: true,
543
+ key: token,
544
+ source,
545
+ sourceLabel,
546
+ envVarName: ENV_VAR,
547
+ licenseFilePath,
548
+ cacheFilePath,
549
+ cacheUsed: false,
550
+ fallbackToCore: true,
551
+ invalid: true,
552
+ expiresAt: new Date(parsed.payload.exp * 1e3).toISOString(),
553
+ kid: parsed.header.kid,
554
+ reason: `Could not resolve public key for kid "${parsed.header.kid}": ${err instanceof Error ? err.message : String(err)}`
555
+ };
556
+ }
557
+ if (!verifyRs256(parsed.signingInput, parsed.signatureBytes, jwk)) {
558
+ return {
559
+ tier: "core",
560
+ hasKey: true,
561
+ key: token,
562
+ source,
563
+ sourceLabel,
564
+ envVarName: ENV_VAR,
565
+ licenseFilePath,
566
+ cacheFilePath,
567
+ cacheUsed: false,
568
+ fallbackToCore: true,
569
+ invalid: true,
570
+ expiresAt: new Date(parsed.payload.exp * 1e3).toISOString(),
571
+ kid: parsed.header.kid,
572
+ reason: "License token signature is invalid \u2014 token may have been tampered with"
573
+ };
574
+ }
575
+ if (factoryRoot) {
576
+ writeLicenseCache(factoryRoot, parsed.payload, parsed.header.kid);
577
+ }
578
+ const tier = tierFromClaim(parsed.payload.tier);
579
+ const tierLabel = tier === "teams" ? "Teams" : "Pro";
580
+ const result = {
581
+ tier,
582
+ hasKey: true,
583
+ key: token,
584
+ source,
585
+ sourceLabel,
586
+ envVarName: ENV_VAR,
587
+ licenseFilePath,
588
+ cacheFilePath,
589
+ cacheUsed: false,
590
+ fallbackToCore: false,
591
+ userId: parsed.payload.sub,
592
+ expiresAt: new Date(parsed.payload.exp * 1e3).toISOString(),
593
+ kid: parsed.header.kid,
594
+ reason: `Valid ${tierLabel} license (user: ${parsed.payload.sub}, expires: ${new Date(parsed.payload.exp * 1e3).toISOString().slice(0, 10)})`
595
+ };
596
+ if (parsed.payload.org)
597
+ result.orgId = parsed.payload.org;
598
+ if (parsed.payload.seats != null)
599
+ result.seatCount = parsed.payload.seats;
600
+ return result;
601
+ }
602
+ async function detectTier(factoryRoot) {
603
+ const status = await getLicenseStatus(factoryRoot);
125
604
  return {
126
- tasksDir: path.join(root, "tasks"),
127
- runsDir: path.join(root, "runs"),
128
- artifactsDir: path.join(root, "artifacts"),
129
- contextFile: path.join(root, FACTORY_MARKER)
605
+ tier: status.tier,
606
+ key: status.key,
607
+ source: status.source,
608
+ invalid: status.invalid,
609
+ reason: status.reason,
610
+ userId: status.userId,
611
+ orgId: status.orgId,
612
+ seatCount: status.seatCount
130
613
  };
131
614
  }
132
- function resolveFactoryMode(env = process.env) {
133
- const explicitMode = trimEnv(env.DEVORY_FACTORY_MODE) ?? trimEnv(env.FACTORY_MODE);
134
- if (explicitMode === "hosted")
135
- return "hosted";
136
- if (explicitMode === "local")
137
- return "local";
138
- if (trimEnv(env.DEVORY_REMOTE_FACTORY_URL) || trimEnv(env.FACTORY_REMOTE_URL)) {
139
- return "hosted";
615
+ function isFeatureEnabled(feature, info) {
616
+ switch (feature) {
617
+ case "custom_rules":
618
+ case "baseline_overrides":
619
+ case "shared_doctrine":
620
+ case "pr_gates":
621
+ return info.tier === "pro" || info.tier === "teams";
140
622
  }
141
- return "local";
142
623
  }
143
- function resolveFactoryEnvironment(startDir = process.cwd(), env = process.env) {
144
- const { root, source } = resolveFactoryRoot(startDir);
145
- return {
146
- root,
147
- source,
148
- mode: resolveFactoryMode(env),
149
- paths: factoryPaths(root)
624
+ function tierGateMessage(feature) {
625
+ const featureLabel = {
626
+ custom_rules: "custom_rules in devory.standards.yml",
627
+ baseline_overrides: "baseline overrides",
628
+ shared_doctrine: "shared doctrine",
629
+ pr_gates: "PR gates"
150
630
  };
631
+ return `[devory] ${featureLabel[feature]} requires a Pro or Teams license \u2014 set DEVORY_LICENSE_KEY or create .devory/license to upgrade. This setting will be ignored on Core tier.`;
151
632
  }
152
633
 
153
634
  // src/standards.ts
@@ -2817,8 +3298,7 @@ var safeDump = renamed("safeDump", "dump");
2817
3298
 
2818
3299
  // src/standards.ts
2819
3300
  var import_meta = {};
2820
- var __filename = (0, import_url.fileURLToPath)(import_meta.url);
2821
- var __dirname = path2.dirname(__filename);
3301
+ var MODULE_DIR = typeof __dirname === "string" ? __dirname : path2.dirname((0, import_url.fileURLToPath)(import_meta.url));
2822
3302
  var STANDARDS_FILENAME = "devory.standards.yml";
2823
3303
  function loadStandards(factoryRoot) {
2824
3304
  const filePath = path2.join(factoryRoot, STANDARDS_FILENAME);
@@ -2846,7 +3326,7 @@ function loadStandards(factoryRoot) {
2846
3326
  };
2847
3327
  }
2848
3328
  var DEVORY_DEFAULTS_PREFIX = "@devory/defaults/";
2849
- var DEFAULTS_DIR = path2.join(__dirname, "defaults");
3329
+ var DEFAULTS_DIR = path2.join(MODULE_DIR, "defaults");
2850
3330
  var KNOWN_BASELINES = {
2851
3331
  generic: "generic.yml",
2852
3332
  "typescript-nextjs": "typescript-nextjs.yml",
@@ -2953,81 +3433,2971 @@ function serializeStandardsAsDoctrine(standards) {
2953
3433
  return lines.join("\n").trimEnd();
2954
3434
  }
2955
3435
 
2956
- // src/license.ts
3436
+ // src/factory-environment.ts
2957
3437
  var fs3 = __toESM(require("fs"));
2958
3438
  var path3 = __toESM(require("path"));
2959
- var ENV_VAR = "DEVORY_LICENSE_KEY";
2960
- var LICENSE_FILE = path3.join(".devory", "license");
2961
- var MIN_KEY_LENGTH = 16;
2962
- function detectTier(factoryRoot) {
2963
- const envKey = process.env[ENV_VAR];
2964
- if (envKey !== void 0) {
2965
- return validateKey(envKey.trim(), "env");
2966
- }
2967
- if (factoryRoot) {
2968
- const filePath = path3.join(factoryRoot, LICENSE_FILE);
2969
- if (fs3.existsSync(filePath)) {
2970
- const fileKey = fs3.readFileSync(filePath, "utf-8").trim();
2971
- return validateKey(fileKey, "file");
3439
+ var FACTORY_MARKER = "FACTORY_CONTEXT.md";
3440
+ function trimEnv(value) {
3441
+ if (typeof value !== "string")
3442
+ return null;
3443
+ const trimmed = value.trim();
3444
+ return trimmed === "" ? null : trimmed;
3445
+ }
3446
+ function findFactoryContextDir(startDir) {
3447
+ let current = path3.resolve(startDir);
3448
+ while (true) {
3449
+ if (fs3.existsSync(path3.join(current, FACTORY_MARKER))) {
3450
+ return current;
3451
+ }
3452
+ const parent = path3.dirname(current);
3453
+ if (parent === current) {
3454
+ return null;
2972
3455
  }
3456
+ current = parent;
3457
+ }
3458
+ }
3459
+ function resolveFactoryRoot(startDir = process.cwd()) {
3460
+ const explicit = trimEnv(process.env.DEVORY_FACTORY_ROOT);
3461
+ if (explicit) {
3462
+ return { root: explicit, source: "env:DEVORY_FACTORY_ROOT" };
3463
+ }
3464
+ const legacy = trimEnv(process.env.FACTORY_ROOT);
3465
+ if (legacy) {
3466
+ return { root: legacy, source: "env:FACTORY_ROOT" };
3467
+ }
3468
+ const walked = findFactoryContextDir(startDir);
3469
+ if (walked) {
3470
+ return { root: walked, source: "git-walk" };
2973
3471
  }
3472
+ return { root: path3.resolve(startDir), source: "cwd" };
3473
+ }
3474
+ function factoryPaths(root) {
2974
3475
  return {
2975
- tier: "core",
2976
- reason: "No license key found \u2014 running on Core tier"
3476
+ tasksDir: path3.join(root, "tasks"),
3477
+ runsDir: path3.join(root, "runs"),
3478
+ artifactsDir: path3.join(root, "artifacts"),
3479
+ contextFile: path3.join(root, FACTORY_MARKER)
2977
3480
  };
2978
3481
  }
2979
- function validateKey(key, source) {
2980
- if (!key || key.length < MIN_KEY_LENGTH) {
2981
- return {
2982
- tier: "core",
2983
- key,
2984
- source,
2985
- invalid: true,
2986
- reason: `License key from ${source === "env" ? "DEVORY_LICENSE_KEY" : ".devory/license"} is invalid (must be \u2265 ${MIN_KEY_LENGTH} characters) \u2014 falling back to Core tier`
2987
- };
3482
+ function resolveFactoryMode(env = process.env) {
3483
+ const explicitMode = trimEnv(env.DEVORY_FACTORY_MODE) ?? trimEnv(env.FACTORY_MODE);
3484
+ if (explicitMode === "hosted")
3485
+ return "hosted";
3486
+ if (explicitMode === "local")
3487
+ return "local";
3488
+ if (trimEnv(env.DEVORY_REMOTE_FACTORY_URL) || trimEnv(env.FACTORY_REMOTE_URL)) {
3489
+ return "hosted";
2988
3490
  }
3491
+ return "local";
3492
+ }
3493
+ function resolveFactoryEnvironment(startDir = process.cwd(), env = process.env) {
3494
+ const { root, source } = resolveFactoryRoot(startDir);
2989
3495
  return {
2990
- tier: "pro",
2991
- key,
3496
+ root,
2992
3497
  source,
2993
- reason: `License key found via ${source === "env" ? "DEVORY_LICENSE_KEY" : ".devory/license"} \u2014 Pro tier active`
3498
+ mode: resolveFactoryMode(env),
3499
+ paths: factoryPaths(root)
2994
3500
  };
2995
3501
  }
2996
- function isFeatureEnabled(feature, info) {
2997
- switch (feature) {
2998
- case "custom_rules":
2999
- case "baseline_overrides":
3000
- case "shared_doctrine":
3001
- case "pr_gates":
3002
- return info.tier === "pro";
3003
- }
3502
+
3503
+ // src/unattended-execution.ts
3504
+ var UNATTENDED_EXECUTION_CONTRACT_VERSION = "unattended-execution-v1";
3505
+ var UNATTENDED_RUN_STATUSES = [
3506
+ "starting",
3507
+ "active",
3508
+ "waiting_on_tool",
3509
+ "waiting_on_model",
3510
+ "checkpointing",
3511
+ "stalled",
3512
+ "blocked_on_human",
3513
+ "failed",
3514
+ "completed",
3515
+ "cancelled"
3516
+ ];
3517
+ var WORKER_HEALTH_STATUSES = [
3518
+ "healthy",
3519
+ "lagging",
3520
+ "stalled",
3521
+ "recovering",
3522
+ "offline"
3523
+ ];
3524
+ var PROGRESS_EVENT_CATEGORIES = [
3525
+ "session_started",
3526
+ "tool_activity",
3527
+ "file_mutation",
3528
+ "test_activity",
3529
+ "checkpoint_write",
3530
+ "compaction",
3531
+ "retry",
3532
+ "failover",
3533
+ "escalation",
3534
+ "status"
3535
+ ];
3536
+ var ESCALATION_REASONS = [
3537
+ "policy_blocked",
3538
+ "checkpoint_unavailable",
3539
+ "retry_exhausted",
3540
+ "stall_detected",
3541
+ "human_required",
3542
+ "fatal_error"
3543
+ ];
3544
+ function asObject(value) {
3545
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
3004
3546
  }
3005
- function tierGateMessage(feature) {
3006
- const featureLabel = {
3007
- custom_rules: "custom_rules in devory.standards.yml",
3008
- baseline_overrides: "baseline overrides",
3009
- shared_doctrine: "shared doctrine",
3010
- pr_gates: "PR gates"
3547
+ function asString(value, fallback = "") {
3548
+ return typeof value === "string" ? value : fallback;
3549
+ }
3550
+ function asNullableString(value) {
3551
+ return typeof value === "string" ? value : null;
3552
+ }
3553
+ function asNullableNumber(value) {
3554
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
3555
+ }
3556
+ function asNumber(value, fallback = 0) {
3557
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
3558
+ }
3559
+ function asBoolean(value, fallback = false) {
3560
+ return typeof value === "boolean" ? value : fallback;
3561
+ }
3562
+ function normalizeProgressCategory(value) {
3563
+ return typeof value === "string" && PROGRESS_EVENT_CATEGORIES.includes(value) ? value : null;
3564
+ }
3565
+ function normalizeUnattendedStatus(value) {
3566
+ return typeof value === "string" && UNATTENDED_RUN_STATUSES.includes(value) ? value : "starting";
3567
+ }
3568
+ function normalizeWorkerHealth(value) {
3569
+ return typeof value === "string" && WORKER_HEALTH_STATUSES.includes(value) ? value : "healthy";
3570
+ }
3571
+ function normalizeEscalationReason(value) {
3572
+ return typeof value === "string" && ESCALATION_REASONS.includes(value) ? value : null;
3573
+ }
3574
+ function normalizeHeartbeat(value) {
3575
+ const record = asObject(value);
3576
+ return {
3577
+ captured_at: asNullableString(record?.captured_at),
3578
+ age_ms: asNullableNumber(record?.age_ms),
3579
+ progress_sequence: asNullableNumber(record?.progress_sequence),
3580
+ active_task_id: asNullableString(record?.active_task_id),
3581
+ lane_id: asNullableString(record?.lane_id),
3582
+ tool_name: asNullableString(record?.tool_name),
3583
+ adapter_session_id: asNullableString(record?.adapter_session_id)
3584
+ };
3585
+ }
3586
+ function normalizeProgressPointer(value) {
3587
+ const record = asObject(value);
3588
+ return {
3589
+ latest_event_id: asNullableString(record?.latest_event_id),
3590
+ latest_event_at: asNullableString(record?.latest_event_at),
3591
+ sequence: asNullableNumber(record?.sequence),
3592
+ category: normalizeProgressCategory(record?.category),
3593
+ summary: asNullableString(record?.summary)
3594
+ };
3595
+ }
3596
+ function normalizeCheckpoint(value) {
3597
+ const record = asObject(value);
3598
+ return {
3599
+ artifact_path: asNullableString(record?.artifact_path),
3600
+ checkpoint_id: asNullableString(record?.checkpoint_id),
3601
+ captured_at: asNullableString(record?.captured_at),
3602
+ source_run_id: asNullableString(record?.source_run_id),
3603
+ resumed_from_run_id: asNullableString(record?.resumed_from_run_id)
3604
+ };
3605
+ }
3606
+ function normalizeRecovery(value) {
3607
+ const record = asObject(value);
3608
+ const state = record?.state === "not_attempted" || record?.state === "succeeded" || record?.state === "failed" ? record.state : "not_attempted";
3609
+ return {
3610
+ state,
3611
+ attempts: asNumber(record?.attempts, 0),
3612
+ last_attempt_at: asNullableString(record?.last_attempt_at),
3613
+ resumed_run_id: asNullableString(record?.resumed_run_id),
3614
+ failover_run_id: asNullableString(record?.failover_run_id),
3615
+ reason: asNullableString(record?.reason)
3616
+ };
3617
+ }
3618
+ function normalizeEscalation(value) {
3619
+ const record = asObject(value);
3620
+ return {
3621
+ required: asBoolean(record?.required),
3622
+ reason: normalizeEscalationReason(record?.reason),
3623
+ summary: asNullableString(record?.summary),
3624
+ triggered_at: asNullableString(record?.triggered_at)
3625
+ };
3626
+ }
3627
+ function normalizeUnattendedExecutionSnapshot(value) {
3628
+ const record = asObject(value);
3629
+ if (!record)
3630
+ return null;
3631
+ const runId = asString(record.run_id);
3632
+ if (!runId)
3633
+ return null;
3634
+ const durableSource = record.durable_source === "artifact" ? "artifact" : "run_record";
3635
+ return {
3636
+ version: UNATTENDED_EXECUTION_CONTRACT_VERSION,
3637
+ run_id: runId,
3638
+ status: normalizeUnattendedStatus(record.status),
3639
+ worker_health: normalizeWorkerHealth(record.worker_health),
3640
+ durable_source: durableSource,
3641
+ transient_adapter_state: asNullableString(record.transient_adapter_state),
3642
+ heartbeat: normalizeHeartbeat(record.heartbeat),
3643
+ progress: normalizeProgressPointer(record.progress),
3644
+ checkpoint: normalizeCheckpoint(record.checkpoint),
3645
+ recovery: normalizeRecovery(record.recovery),
3646
+ escalation: normalizeEscalation(record.escalation)
3647
+ };
3648
+ }
3649
+
3650
+ // src/run-ledger.ts
3651
+ var RUN_LEDGER_VERSION = "routing-evidence-v1";
3652
+ var RESUMABLE_RUN_STATUSES = [
3653
+ "failed",
3654
+ "paused_for_review"
3655
+ ];
3656
+ function asObject2(value) {
3657
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
3658
+ }
3659
+ function asString2(value, fallback = "") {
3660
+ return typeof value === "string" ? value : fallback;
3661
+ }
3662
+ function asNullableString2(value) {
3663
+ return typeof value === "string" ? value : null;
3664
+ }
3665
+ function asStringArray(value) {
3666
+ return Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
3667
+ }
3668
+ function asBoolean2(value, fallback = false) {
3669
+ return typeof value === "boolean" ? value : fallback;
3670
+ }
3671
+ function asNullableNumber2(value) {
3672
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
3673
+ }
3674
+ function asNumber2(value, fallback = 0) {
3675
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
3676
+ }
3677
+ function normalizeFailureRecord(value) {
3678
+ const record = asObject2(value);
3679
+ if (!record)
3680
+ return null;
3681
+ const taskId = asString2(record.task_id);
3682
+ const reason = asString2(record.reason);
3683
+ const timestamp2 = asString2(record.timestamp);
3684
+ if (!taskId || !reason || !timestamp2)
3685
+ return null;
3686
+ return {
3687
+ task_id: taskId,
3688
+ reason,
3689
+ timestamp: timestamp2
3690
+ };
3691
+ }
3692
+ function normalizeCostEventRecord(value) {
3693
+ const record = asObject2(value);
3694
+ if (!record)
3695
+ return null;
3696
+ const status = record.status === "warn" || record.status === "block" ? record.status : null;
3697
+ const taskId = asString2(record.task_id);
3698
+ const timestamp2 = asString2(record.timestamp);
3699
+ if (!status || !taskId || !timestamp2)
3700
+ return null;
3701
+ return {
3702
+ task_id: taskId,
3703
+ model_id: asNullableString2(record.model_id),
3704
+ status,
3705
+ reasons: asStringArray(record.reasons),
3706
+ spend_units: asNumber2(record.spend_units, 0),
3707
+ timestamp: timestamp2
3708
+ };
3709
+ }
3710
+ function normalizeProgressEventRecord(value) {
3711
+ const record = asObject2(value);
3712
+ if (!record)
3713
+ return null;
3714
+ const eventId = asString2(record.event_id);
3715
+ const sequence = asNumber2(record.sequence, Number.NaN);
3716
+ const createdAt = asString2(record.created_at);
3717
+ const summary = asString2(record.summary);
3718
+ const category = typeof record.category === "string" && PROGRESS_EVENT_CATEGORIES.includes(record.category) ? record.category : null;
3719
+ const status = typeof record.status === "string" && UNATTENDED_RUN_STATUSES.includes(record.status) ? record.status : null;
3720
+ if (!eventId || !Number.isFinite(sequence) || !createdAt || !summary || !category) {
3721
+ return null;
3722
+ }
3723
+ return {
3724
+ event_id: eventId,
3725
+ sequence,
3726
+ category,
3727
+ status,
3728
+ task_id: asNullableString2(record.task_id),
3729
+ created_at: createdAt,
3730
+ summary,
3731
+ details: asStringArray(record.details)
3732
+ };
3733
+ }
3734
+ function normalizeTaskBlockState(value) {
3735
+ const record = asObject2(value);
3736
+ if (!record)
3737
+ return null;
3738
+ const kind = record.kind === "human-question" || record.kind === "execution-failure" || record.kind === "dependency-wait" ? record.kind : null;
3739
+ const interruptionLevel = record.interruption_level === "level_1" || record.interruption_level === "level_2" || record.interruption_level === "level_3" ? record.interruption_level : null;
3740
+ const fallbackBehavior = record.fallback_behavior === "continue-other-work" || record.fallback_behavior === "pause-affected-lane" || record.fallback_behavior === "halt-run" || record.fallback_behavior === "assume-default" || record.fallback_behavior === "skip-task" ? record.fallback_behavior : null;
3741
+ return {
3742
+ kind,
3743
+ question_id: asNullableString2(record.question_id),
3744
+ dependency_task_id: asNullableString2(record.dependency_task_id),
3745
+ reason: asNullableString2(record.reason),
3746
+ since: asNullableString2(record.since),
3747
+ interruption_level: interruptionLevel,
3748
+ fallback_behavior: fallbackBehavior
3749
+ };
3750
+ }
3751
+ function normalizeRunInterruptionState(value) {
3752
+ const record = asObject2(value);
3753
+ if (!record)
3754
+ return null;
3755
+ const interruptionLevel = record.interruption_level === "level_1" || record.interruption_level === "level_2" || record.interruption_level === "level_3" ? record.interruption_level : null;
3756
+ const fallbackBehavior = record.fallback_behavior === "continue-other-work" || record.fallback_behavior === "pause-affected-lane" || record.fallback_behavior === "halt-run" || record.fallback_behavior === "assume-default" || record.fallback_behavior === "skip-task" ? record.fallback_behavior : null;
3757
+ const runDisposition = record.run_disposition === "continue" || record.run_disposition === "pause" || record.run_disposition === "halt" ? record.run_disposition : null;
3758
+ const laneState = record.lane_state === "running" || record.lane_state === "paused" ? record.lane_state : null;
3759
+ return {
3760
+ active: asBoolean2(record.active),
3761
+ question_id: asNullableString2(record.question_id),
3762
+ blocking_task_id: asNullableString2(record.blocking_task_id),
3763
+ lane_id: asNullableString2(record.lane_id),
3764
+ interruption_level: interruptionLevel,
3765
+ fallback_behavior: fallbackBehavior,
3766
+ run_disposition: runDisposition,
3767
+ lane_state: laneState,
3768
+ updated_at: asNullableString2(record.updated_at)
3769
+ };
3770
+ }
3771
+ function buildRoutingEvidence(record) {
3772
+ const existing = asObject2(record.routing_evidence);
3773
+ const decision = asObject2(existing?.routing_decision);
3774
+ const selection = asObject2(existing?.selection);
3775
+ const fallback = asObject2(existing?.fallback);
3776
+ const retries = asObject2(existing?.retries);
3777
+ const timing = asObject2(existing?.timing);
3778
+ const usage = asObject2(existing?.usage);
3779
+ const outcome = asObject2(existing?.outcome);
3780
+ const inputSnapshot = asObject2(existing?.input_snapshot);
3781
+ const normalizedInput = inputSnapshot?.normalized_input && typeof inputSnapshot.normalized_input === "object" ? inputSnapshot.normalized_input : null;
3782
+ const routingDecision = decision && typeof decision === "object" ? decision : null;
3783
+ const selectedEngine = asNullableString2(selection?.selected_engine) ?? asString2(record.engine);
3784
+ const selectedModel = asNullableString2(selection?.selected_model) ?? asNullableString2(record.model_id);
3785
+ const fallbackTaken = asBoolean2(fallback?.taken, asBoolean2(record.fallback_taken));
3786
+ const spendUnits = asNullableNumber2(usage?.spend_units) ?? asNullableNumber2(record.spend_units);
3787
+ const costTier = asNullableString2(usage?.cost_tier) ?? asNullableString2(record.cost_tier);
3788
+ const outcomeLabel = asNullableString2(outcome?.outcome_label) ?? asString2(record.outcome);
3789
+ return {
3790
+ routing_decision: routingDecision,
3791
+ requested_role: asNullableString2(existing?.requested_role),
3792
+ input_snapshot: {
3793
+ routing_decision_id: asNullableString2(inputSnapshot?.routing_decision_id) ?? asNullableString2(asObject2(routingDecision?.linkage)?.decision_id),
3794
+ related_routing_decision_ids: asStringArray(inputSnapshot?.related_routing_decision_ids),
3795
+ requested_role: asNullableString2(inputSnapshot?.requested_role),
3796
+ requested_engine: asNullableString2(inputSnapshot?.requested_engine),
3797
+ requested_pipeline: asNullableString2(inputSnapshot?.requested_pipeline),
3798
+ task_branch: asNullableString2(inputSnapshot?.task_branch),
3799
+ normalized_summary: asNullableString2(inputSnapshot?.normalized_summary),
3800
+ normalized_input: normalizedInput
3801
+ },
3802
+ selection: {
3803
+ selected_engine: selectedEngine || asNullableString2(routingDecision?.engine) || null,
3804
+ selected_provider: asNullableString2(selection?.selected_provider) ?? asNullableString2(routingDecision?.provider),
3805
+ selected_model: selectedModel ?? asNullableString2(routingDecision?.model_id),
3806
+ rationale: asStringArray(selection?.rationale).length > 0 ? asStringArray(selection?.rationale) : asStringArray(routingDecision?.rationale)
3807
+ },
3808
+ fallback: {
3809
+ taken: fallbackTaken,
3810
+ reason: asNullableString2(fallback?.reason) ?? (asStringArray(asObject2(routingDecision?.fallback_path)?.reasons)[0] ?? null),
3811
+ attempted_path: asStringArray(fallback?.attempted_path).length > 0 ? asStringArray(fallback?.attempted_path) : asStringArray(asObject2(routingDecision?.fallback_path)?.candidate_model_ids)
3812
+ },
3813
+ retries: {
3814
+ attempts: asNumber2(retries?.attempts, 0),
3815
+ resumed_from_run_id: asNullableString2(retries?.resumed_from_run_id),
3816
+ history: asStringArray(retries?.history)
3817
+ },
3818
+ timing: {
3819
+ queued_at: asNullableString2(timing?.queued_at),
3820
+ routing_started_at: asNullableString2(timing?.routing_started_at),
3821
+ routing_completed_at: asNullableString2(timing?.routing_completed_at),
3822
+ execution_started_at: asNullableString2(timing?.execution_started_at) ?? asString2(record.start_time),
3823
+ execution_completed_at: asNullableString2(timing?.execution_completed_at) ?? asString2(record.end_time)
3824
+ },
3825
+ usage: {
3826
+ prompt_tokens: asNullableNumber2(usage?.prompt_tokens),
3827
+ completion_tokens: asNullableNumber2(usage?.completion_tokens),
3828
+ total_tokens: asNullableNumber2(usage?.total_tokens),
3829
+ spend_units: spendUnits,
3830
+ estimated_cost_usd: asNullableNumber2(usage?.estimated_cost_usd),
3831
+ cost_tier: costTier
3832
+ },
3833
+ outcome: {
3834
+ final_stage: asNullableString2(outcome?.final_stage),
3835
+ verification_state: asNullableString2(outcome?.verification_state),
3836
+ outcome_label: outcomeLabel || null,
3837
+ operator_summary: asNullableString2(outcome?.operator_summary),
3838
+ evaluation: outcome?.evaluation && typeof outcome.evaluation === "object" ? outcome.evaluation : null
3839
+ }
3840
+ };
3841
+ }
3842
+ function normalizeTaskRecord(value) {
3843
+ const record = asObject2(value);
3844
+ if (!record)
3845
+ return null;
3846
+ const taskId = asString2(record.task_id);
3847
+ const outcome = asString2(record.outcome);
3848
+ const engine = asString2(record.engine);
3849
+ const startTime = asString2(record.start_time);
3850
+ const endTime = asString2(record.end_time);
3851
+ if (!taskId || !outcome || !engine || !startTime || !endTime) {
3852
+ return null;
3853
+ }
3854
+ return {
3855
+ task_id: taskId,
3856
+ outcome,
3857
+ engine,
3858
+ fallback_taken: asBoolean2(record.fallback_taken),
3859
+ start_time: startTime,
3860
+ end_time: endTime,
3861
+ notes: asStringArray(record.notes),
3862
+ model_id: asNullableString2(record.model_id),
3863
+ cost_tier: asNullableString2(record.cost_tier),
3864
+ spend_units: asNullableNumber2(record.spend_units),
3865
+ cost_guardrail_status: record.cost_guardrail_status === "allow" || record.cost_guardrail_status === "warn" || record.cost_guardrail_status === "block" ? record.cost_guardrail_status : null,
3866
+ cost_guardrail_notes: asStringArray(record.cost_guardrail_notes),
3867
+ routing_evidence: buildRoutingEvidence(record),
3868
+ block_state: normalizeTaskBlockState(record.block_state)
3869
+ };
3870
+ }
3871
+ function buildRunLedgerSummary(taskQueue, tasksExecuted, spendUnitsConsumed) {
3872
+ const fallbackCount = tasksExecuted.filter((task) => task.routing_evidence.fallback.taken).length;
3873
+ const retryCount = tasksExecuted.reduce(
3874
+ (sum, task) => sum + task.routing_evidence.retries.attempts,
3875
+ 0
3876
+ );
3877
+ const providers = new Set(
3878
+ tasksExecuted.map((task) => task.routing_evidence.selection.selected_provider).filter((value) => typeof value === "string" && value !== "")
3879
+ );
3880
+ const models = new Set(
3881
+ tasksExecuted.map((task) => task.routing_evidence.selection.selected_model).filter((value) => typeof value === "string" && value !== "")
3882
+ );
3883
+ const promptTokens = tasksExecuted.reduce((sum, task) => {
3884
+ const value = task.routing_evidence.usage.prompt_tokens;
3885
+ return value === null ? sum : (sum ?? 0) + value;
3886
+ }, null);
3887
+ const completionTokens = tasksExecuted.reduce((sum, task) => {
3888
+ const value = task.routing_evidence.usage.completion_tokens;
3889
+ return value === null ? sum : (sum ?? 0) + value;
3890
+ }, null);
3891
+ const totalTokens = tasksExecuted.reduce((sum, task) => {
3892
+ const value = task.routing_evidence.usage.total_tokens;
3893
+ return value === null ? sum : (sum ?? 0) + value;
3894
+ }, null);
3895
+ return {
3896
+ total_tasks: taskQueue.length,
3897
+ tasks_executed_count: tasksExecuted.length,
3898
+ tasks_remaining_count: Math.max(taskQueue.length - tasksExecuted.length, 0),
3899
+ success_count: tasksExecuted.filter((task) => task.outcome === "success").length,
3900
+ failure_count: tasksExecuted.filter((task) => task.outcome === "failure").length,
3901
+ review_count: tasksExecuted.filter((task) => task.outcome === "skipped_for_review").length,
3902
+ fallback_count: fallbackCount,
3903
+ retry_count: retryCount,
3904
+ engines_used: [...new Set(tasksExecuted.map((task) => task.engine))],
3905
+ providers_used: [...providers],
3906
+ models_used: [...models],
3907
+ spend_units_consumed: spendUnitsConsumed,
3908
+ prompt_tokens: promptTokens,
3909
+ completion_tokens: completionTokens,
3910
+ total_tokens: totalTokens
3911
+ };
3912
+ }
3913
+ function normalizeRunRecord(value, options = {}) {
3914
+ const record = asObject2(value);
3915
+ if (!record)
3916
+ return null;
3917
+ const runId = asString2(record.run_id);
3918
+ const status = asString2(record.status);
3919
+ const startTime = asString2(record.start_time);
3920
+ if (!runId || !status || !startTime) {
3921
+ return null;
3922
+ }
3923
+ const taskQueue = asStringArray(record.task_queue);
3924
+ const tasksExecuted = Array.isArray(record.tasks_executed) ? record.tasks_executed.map((task) => normalizeTaskRecord(task)).filter((task) => task !== null) : [];
3925
+ const spendUnitsConsumed = asNumber2(record.spend_units_consumed, 0);
3926
+ const existingLedger = asObject2(record.routing_ledger);
3927
+ const existingSummary = asObject2(existingLedger?.run_summary);
3928
+ const compatibilityMode = options.compatibilityMode ?? (asString2(existingLedger?.version) === RUN_LEDGER_VERSION ? "native" : "legacy-normalized");
3929
+ return {
3930
+ run_id: runId,
3931
+ status,
3932
+ task_queue: taskQueue,
3933
+ tasks_executed: tasksExecuted,
3934
+ failure: normalizeFailureRecord(record.failure),
3935
+ spend_units_consumed: spendUnitsConsumed,
3936
+ cost_events: Array.isArray(record.cost_events) ? record.cost_events.map((event) => normalizeCostEventRecord(event)).filter((event) => event !== null) : [],
3937
+ start_time: startTime,
3938
+ end_time: asNullableString2(record.end_time),
3939
+ unattended_execution: normalizeUnattendedExecutionSnapshot(record.unattended_execution),
3940
+ progress_events: Array.isArray(record.progress_events) ? record.progress_events.map((event) => normalizeProgressEventRecord(event)).filter((event) => event !== null) : [],
3941
+ interruption_state: normalizeRunInterruptionState(record.interruption_state),
3942
+ routing_ledger: {
3943
+ version: RUN_LEDGER_VERSION,
3944
+ compatibility_mode: compatibilityMode,
3945
+ run_summary: {
3946
+ ...buildRunLedgerSummary(taskQueue, tasksExecuted, spendUnitsConsumed),
3947
+ total_tasks: asNumber2(existingSummary?.total_tasks, taskQueue.length),
3948
+ tasks_executed_count: asNumber2(existingSummary?.tasks_executed_count, tasksExecuted.length),
3949
+ tasks_remaining_count: asNumber2(
3950
+ existingSummary?.tasks_remaining_count,
3951
+ Math.max(taskQueue.length - tasksExecuted.length, 0)
3952
+ ),
3953
+ success_count: asNumber2(
3954
+ existingSummary?.success_count,
3955
+ tasksExecuted.filter((task) => task.outcome === "success").length
3956
+ ),
3957
+ failure_count: asNumber2(
3958
+ existingSummary?.failure_count,
3959
+ tasksExecuted.filter((task) => task.outcome === "failure").length
3960
+ ),
3961
+ review_count: asNumber2(
3962
+ existingSummary?.review_count,
3963
+ tasksExecuted.filter((task) => task.outcome === "skipped_for_review").length
3964
+ ),
3965
+ fallback_count: asNumber2(
3966
+ existingSummary?.fallback_count,
3967
+ tasksExecuted.filter((task) => task.routing_evidence.fallback.taken).length
3968
+ ),
3969
+ retry_count: asNumber2(
3970
+ existingSummary?.retry_count,
3971
+ tasksExecuted.reduce((sum, task) => sum + task.routing_evidence.retries.attempts, 0)
3972
+ ),
3973
+ engines_used: asStringArray(existingSummary?.engines_used).length > 0 ? asStringArray(existingSummary?.engines_used) : [...new Set(tasksExecuted.map((task) => task.engine))],
3974
+ providers_used: asStringArray(existingSummary?.providers_used),
3975
+ models_used: asStringArray(existingSummary?.models_used),
3976
+ spend_units_consumed: asNullableNumber2(existingSummary?.spend_units_consumed) ?? spendUnitsConsumed,
3977
+ prompt_tokens: asNullableNumber2(existingSummary?.prompt_tokens),
3978
+ completion_tokens: asNullableNumber2(existingSummary?.completion_tokens),
3979
+ total_tokens: asNullableNumber2(existingSummary?.total_tokens)
3980
+ },
3981
+ outcome_placeholders: {
3982
+ requested_by: asNullableString2(asObject2(existingLedger?.outcome_placeholders)?.requested_by),
3983
+ operator_summary: asNullableString2(
3984
+ asObject2(existingLedger?.outcome_placeholders)?.operator_summary
3985
+ ),
3986
+ post_run_review: asNullableString2(
3987
+ asObject2(existingLedger?.outcome_placeholders)?.post_run_review
3988
+ )
3989
+ }
3990
+ }
3991
+ };
3992
+ }
3993
+ function applyTaskRoutingOutcomeEvaluation(run, taskId, evaluation, outcomePatch = {}) {
3994
+ const targetIndex = [...run.tasks_executed].map((task, index) => ({ task, index })).reverse().find(({ task }) => task.task_id === taskId)?.index;
3995
+ if (targetIndex === void 0) {
3996
+ return run;
3997
+ }
3998
+ const tasksExecuted = run.tasks_executed.map(
3999
+ (task, index) => index !== targetIndex ? task : {
4000
+ ...task,
4001
+ routing_evidence: {
4002
+ ...task.routing_evidence,
4003
+ outcome: {
4004
+ ...task.routing_evidence.outcome,
4005
+ ...outcomePatch,
4006
+ evaluation
4007
+ }
4008
+ }
4009
+ }
4010
+ );
4011
+ return normalizeRunRecord(
4012
+ {
4013
+ ...run,
4014
+ tasks_executed: tasksExecuted
4015
+ },
4016
+ { compatibilityMode: "native" }
4017
+ );
4018
+ }
4019
+
4020
+ // src/execution-policy.ts
4021
+ var fs4 = __toESM(require("fs"));
4022
+ var path4 = __toESM(require("path"));
4023
+ var import_url2 = require("url");
4024
+ var import_meta2 = {};
4025
+ var MODULE_DIR2 = typeof __dirname === "string" ? __dirname : path4.dirname((0, import_url2.fileURLToPath)(import_meta2.url));
4026
+ var EXECUTION_POLICY_VERSION = "execution-policy-v1";
4027
+ var EXECUTION_POLICY_FILENAME = "execution-policy.json";
4028
+ var EXECUTION_POLICY_WORKSPACE_PATH = path4.join(
4029
+ "config",
4030
+ EXECUTION_POLICY_FILENAME
4031
+ );
4032
+ var VALID_POLICY_ESCALATION_BEHAVIORS = [
4033
+ "allow",
4034
+ "require_approval",
4035
+ "halt_and_escalate",
4036
+ "fallback_to_defaults"
4037
+ ];
4038
+ var DEFAULTS_PATH = path4.join(MODULE_DIR2, "defaults", EXECUTION_POLICY_FILENAME);
4039
+ var VALID_MANAGERS = ["npm", "pnpm", "yarn", "bun"];
4040
+ function isRecord(value) {
4041
+ return value !== null && typeof value === "object" && !Array.isArray(value);
4042
+ }
4043
+ function asStringArray2(value) {
4044
+ if (!Array.isArray(value))
4045
+ return void 0;
4046
+ const normalized = value.filter((entry) => typeof entry === "string").map((entry) => entry.trim()).filter(Boolean);
4047
+ return [...new Set(normalized)];
4048
+ }
4049
+ function asBoolean3(value) {
4050
+ return typeof value === "boolean" ? value : void 0;
4051
+ }
4052
+ function normalizeEscalationBehavior(value) {
4053
+ return typeof value === "string" && VALID_POLICY_ESCALATION_BEHAVIORS.includes(value) ? value : void 0;
4054
+ }
4055
+ function normalizeAllowedManagers(value) {
4056
+ const values = asStringArray2(value);
4057
+ if (!values)
4058
+ return void 0;
4059
+ const normalized = values.filter(
4060
+ (entry) => VALID_MANAGERS.includes(entry)
4061
+ );
4062
+ return normalized.length > 0 ? normalized : [];
4063
+ }
4064
+ function normalizeExecutionPolicyOverrides(value) {
4065
+ if (!isRecord(value)) {
4066
+ throw new Error("devory: execution policy config must be a JSON object");
4067
+ }
4068
+ const overrides = {};
4069
+ if (isRecord(value.commands)) {
4070
+ overrides.commands = {};
4071
+ const allow = asStringArray2(value.commands.allow);
4072
+ const requireApproval = asStringArray2(value.commands.require_approval);
4073
+ const forbid = asStringArray2(value.commands.forbid);
4074
+ if (allow)
4075
+ overrides.commands.allow = allow;
4076
+ if (requireApproval)
4077
+ overrides.commands.require_approval = requireApproval;
4078
+ if (forbid)
4079
+ overrides.commands.forbid = forbid;
4080
+ }
4081
+ if (isRecord(value.filesystem)) {
4082
+ overrides.filesystem = {};
4083
+ const writableRoots = asStringArray2(value.filesystem.writable_roots);
4084
+ const readOnlyRoots = asStringArray2(value.filesystem.read_only_roots);
4085
+ const outsideApproval = asBoolean3(
4086
+ value.filesystem.require_approval_outside_writable_roots
4087
+ );
4088
+ if (writableRoots)
4089
+ overrides.filesystem.writable_roots = writableRoots;
4090
+ if (readOnlyRoots)
4091
+ overrides.filesystem.read_only_roots = readOnlyRoots;
4092
+ if (outsideApproval !== void 0) {
4093
+ overrides.filesystem.require_approval_outside_writable_roots = outsideApproval;
4094
+ }
4095
+ }
4096
+ if (isRecord(value.network)) {
4097
+ overrides.network = {};
4098
+ const allow = asBoolean3(value.network.allow);
4099
+ const allowedHosts = asStringArray2(value.network.allowed_hosts);
4100
+ const approvalHosts = asStringArray2(value.network.require_approval_for_hosts);
4101
+ if (allow !== void 0)
4102
+ overrides.network.allow = allow;
4103
+ if (allowedHosts)
4104
+ overrides.network.allowed_hosts = allowedHosts;
4105
+ if (approvalHosts)
4106
+ overrides.network.require_approval_for_hosts = approvalHosts;
4107
+ }
4108
+ if (isRecord(value.package_installs)) {
4109
+ overrides.package_installs = {};
4110
+ const allow = asBoolean3(value.package_installs.allow);
4111
+ const managers = normalizeAllowedManagers(value.package_installs.allowed_managers);
4112
+ const requireApproval = asBoolean3(value.package_installs.require_approval);
4113
+ if (allow !== void 0)
4114
+ overrides.package_installs.allow = allow;
4115
+ if (managers)
4116
+ overrides.package_installs.allowed_managers = managers;
4117
+ if (requireApproval !== void 0) {
4118
+ overrides.package_installs.require_approval = requireApproval;
4119
+ }
4120
+ }
4121
+ if (isRecord(value.test_execution)) {
4122
+ overrides.test_execution = {};
4123
+ const allow = asBoolean3(value.test_execution.allow);
4124
+ const allowedCommands = asStringArray2(value.test_execution.allowed_commands);
4125
+ const approvalCommands = asStringArray2(
4126
+ value.test_execution.require_approval_commands
4127
+ );
4128
+ if (allow !== void 0)
4129
+ overrides.test_execution.allow = allow;
4130
+ if (allowedCommands)
4131
+ overrides.test_execution.allowed_commands = allowedCommands;
4132
+ if (approvalCommands) {
4133
+ overrides.test_execution.require_approval_commands = approvalCommands;
4134
+ }
4135
+ }
4136
+ const approvalRequiredActions = asStringArray2(value.approval_required_actions);
4137
+ if (approvalRequiredActions) {
4138
+ overrides.approval_required_actions = approvalRequiredActions;
4139
+ }
4140
+ const forbiddenActions = asStringArray2(value.forbidden_actions);
4141
+ if (forbiddenActions) {
4142
+ overrides.forbidden_actions = forbiddenActions;
4143
+ }
4144
+ if (isRecord(value.escalation)) {
4145
+ overrides.escalation = {};
4146
+ const unmatched = normalizeEscalationBehavior(value.escalation.unmatched_command);
4147
+ const outOfPolicy = normalizeEscalationBehavior(
4148
+ value.escalation.out_of_policy_action
4149
+ );
4150
+ const invalidPolicy = normalizeEscalationBehavior(value.escalation.invalid_policy);
4151
+ if (unmatched)
4152
+ overrides.escalation.unmatched_command = unmatched;
4153
+ if (outOfPolicy)
4154
+ overrides.escalation.out_of_policy_action = outOfPolicy;
4155
+ if (invalidPolicy)
4156
+ overrides.escalation.invalid_policy = invalidPolicy;
4157
+ }
4158
+ return overrides;
4159
+ }
4160
+ function applyExecutionPolicyOverrides(base, overrides) {
4161
+ return {
4162
+ ...base,
4163
+ commands: {
4164
+ ...base.commands,
4165
+ ...overrides.commands
4166
+ },
4167
+ filesystem: {
4168
+ ...base.filesystem,
4169
+ ...overrides.filesystem
4170
+ },
4171
+ network: {
4172
+ ...base.network,
4173
+ ...overrides.network
4174
+ },
4175
+ package_installs: {
4176
+ ...base.package_installs,
4177
+ ...overrides.package_installs
4178
+ },
4179
+ test_execution: {
4180
+ ...base.test_execution,
4181
+ ...overrides.test_execution
4182
+ },
4183
+ approval_required_actions: overrides.approval_required_actions ?? base.approval_required_actions,
4184
+ forbidden_actions: overrides.forbidden_actions ?? base.forbidden_actions,
4185
+ escalation: {
4186
+ ...base.escalation,
4187
+ ...overrides.escalation
4188
+ }
4189
+ };
4190
+ }
4191
+ function loadDefaultExecutionPolicy() {
4192
+ const raw = fs4.readFileSync(DEFAULTS_PATH, "utf-8");
4193
+ const parsed = JSON.parse(raw);
4194
+ const overrides = normalizeExecutionPolicyOverrides(parsed);
4195
+ const base = {
4196
+ version: EXECUTION_POLICY_VERSION,
4197
+ commands: {
4198
+ allow: [],
4199
+ require_approval: [],
4200
+ forbid: []
4201
+ },
4202
+ filesystem: {
4203
+ writable_roots: ["."],
4204
+ read_only_roots: [".git"],
4205
+ require_approval_outside_writable_roots: true
4206
+ },
4207
+ network: {
4208
+ allow: false,
4209
+ allowed_hosts: [],
4210
+ require_approval_for_hosts: ["*"]
4211
+ },
4212
+ package_installs: {
4213
+ allow: false,
4214
+ allowed_managers: [],
4215
+ require_approval: true
4216
+ },
4217
+ test_execution: {
4218
+ allow: true,
4219
+ allowed_commands: [],
4220
+ require_approval_commands: []
4221
+ },
4222
+ approval_required_actions: [
4223
+ "write_outside_workspace",
4224
+ "network_access",
4225
+ "package_install"
4226
+ ],
4227
+ forbidden_actions: ["destructive_delete", "privilege_escalation"],
4228
+ escalation: {
4229
+ unmatched_command: "require_approval",
4230
+ out_of_policy_action: "halt_and_escalate",
4231
+ invalid_policy: "fallback_to_defaults"
4232
+ }
4233
+ };
4234
+ return applyExecutionPolicyOverrides(base, overrides);
4235
+ }
4236
+ function loadWorkspaceExecutionPolicy(factoryRoot) {
4237
+ const configPath = path4.join(factoryRoot, EXECUTION_POLICY_WORKSPACE_PATH);
4238
+ if (!fs4.existsSync(configPath))
4239
+ return null;
4240
+ const raw = fs4.readFileSync(configPath, "utf-8");
4241
+ return normalizeExecutionPolicyOverrides(JSON.parse(raw));
4242
+ }
4243
+ function resolveExecutionPolicy(factoryRoot, runOverride) {
4244
+ let policy = loadDefaultExecutionPolicy();
4245
+ const appliedLayers = [
4246
+ "shipped-defaults"
4247
+ ];
4248
+ const workspaceConfigPath = path4.join(factoryRoot, EXECUTION_POLICY_WORKSPACE_PATH);
4249
+ const workspaceOverrides = loadWorkspaceExecutionPolicy(factoryRoot);
4250
+ if (workspaceOverrides) {
4251
+ policy = applyExecutionPolicyOverrides(policy, workspaceOverrides);
4252
+ appliedLayers.push("workspace-config");
4253
+ }
4254
+ if (runOverride !== void 0 && runOverride !== null) {
4255
+ const normalizedOverride = normalizeExecutionPolicyOverrides(runOverride);
4256
+ policy = applyExecutionPolicyOverrides(policy, normalizedOverride);
4257
+ appliedLayers.push("run-override");
4258
+ }
4259
+ return {
4260
+ policy,
4261
+ applied_layers: appliedLayers,
4262
+ workspace_config_path: fs4.existsSync(workspaceConfigPath) ? workspaceConfigPath : null
4263
+ };
4264
+ }
4265
+ function buildExecutionPolicyInjection(resolution) {
4266
+ return {
4267
+ policy: resolution.policy,
4268
+ injection_source: "agent-context",
4269
+ applied_layers: resolution.applied_layers,
4270
+ workspace_config_path: resolution.workspace_config_path
4271
+ };
4272
+ }
4273
+
4274
+ // src/task-markdown-renderer.ts
4275
+ var TASK_MARKDOWN_RENDERER_VERSION = "task-markdown-renderer-v1";
4276
+ var TASK_MARKDOWN_FRONTMATTER_ORDER = [
4277
+ "id",
4278
+ "title",
4279
+ "project",
4280
+ "repo",
4281
+ "branch",
4282
+ "type",
4283
+ "priority",
4284
+ "status",
4285
+ "agent",
4286
+ "lane",
4287
+ "repo_area",
4288
+ "bundle_id",
4289
+ "bundle_title",
4290
+ "bundle_phase",
4291
+ "depends_on",
4292
+ "files_likely_affected",
4293
+ "verification"
4294
+ ];
4295
+ var TASK_MARKDOWN_SECTION_ORDER = [
4296
+ "## Goal",
4297
+ "## Context",
4298
+ "## Acceptance Criteria",
4299
+ "## Expected Artifacts",
4300
+ "## Failure Conditions",
4301
+ "## Reviewer Checklist"
4302
+ ];
4303
+ function slugify(value) {
4304
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
4305
+ }
4306
+ function buildTaskDraftTargetPath(taskId, title, stage) {
4307
+ return `tasks/${stage}/${taskId}-${slugify(title)}.md`;
4308
+ }
4309
+ function pushOptionalFrontmatter(lines, key, value) {
4310
+ if (value)
4311
+ lines.push(`${key}: ${value}`);
4312
+ }
4313
+ function pushArrayField(lines, key, values) {
4314
+ if (values.length === 0) {
4315
+ lines.push(`${key}: []`);
4316
+ return;
4317
+ }
4318
+ lines.push(`${key}:`);
4319
+ for (const value of values)
4320
+ lines.push(` - ${value}`);
4321
+ }
4322
+ function pushSection(lines, heading, entries) {
4323
+ lines.push(heading, "");
4324
+ if (entries.length === 0) {
4325
+ lines.push("- (none)");
4326
+ } else {
4327
+ for (const entry of entries)
4328
+ lines.push(`- ${entry}`);
4329
+ }
4330
+ lines.push("");
4331
+ }
4332
+ function pushReviewerChecklist(lines, entries) {
4333
+ lines.push("## Reviewer Checklist", "");
4334
+ for (const entry of entries)
4335
+ lines.push(`- ${entry}`);
4336
+ lines.push("");
4337
+ }
4338
+ function renderTaskDraftTarget(draft) {
4339
+ const targetStage = draft.commit.target_stage ?? draft.status;
4340
+ const committedTaskId = draft.commit.committed_task_id ?? draft.draft_id;
4341
+ const targetPath = draft.commit.target_path ?? buildTaskDraftTargetPath(committedTaskId, draft.title, targetStage);
4342
+ const lines = [
4343
+ "---",
4344
+ `id: ${committedTaskId}`,
4345
+ `title: ${draft.title}`,
4346
+ `project: ${draft.project}`,
4347
+ `repo: ${draft.repo}`,
4348
+ `branch: ${draft.branch}`,
4349
+ `type: ${draft.type}`,
4350
+ `priority: ${draft.priority}`,
4351
+ `status: ${targetStage}`,
4352
+ `agent: ${draft.agent}`
4353
+ ];
4354
+ pushOptionalFrontmatter(lines, "lane", draft.lane);
4355
+ pushOptionalFrontmatter(lines, "repo_area", draft.repo_area);
4356
+ pushOptionalFrontmatter(lines, "bundle_id", draft.bundle_id);
4357
+ pushOptionalFrontmatter(lines, "bundle_title", draft.bundle_title);
4358
+ pushOptionalFrontmatter(lines, "bundle_phase", draft.bundle_phase);
4359
+ pushArrayField(lines, "depends_on", draft.depends_on);
4360
+ pushArrayField(lines, "files_likely_affected", draft.files_likely_affected);
4361
+ pushArrayField(lines, "verification", draft.verification);
4362
+ lines.push("---", "", "## Goal", "", draft.goal, "");
4363
+ pushSection(lines, "## Context", draft.context);
4364
+ pushSection(lines, "## Acceptance Criteria", draft.acceptance_criteria);
4365
+ pushSection(lines, "## Expected Artifacts", draft.expected_artifacts);
4366
+ pushSection(lines, "## Failure Conditions", draft.failure_conditions);
4367
+ pushReviewerChecklist(lines, draft.reviewer_checklist);
4368
+ return {
4369
+ target_stage: targetStage,
4370
+ target_path: targetPath,
4371
+ markdown: lines.join("\n").trimEnd() + "\n"
4372
+ };
4373
+ }
4374
+ function renderTaskDraftMarkdown(draft) {
4375
+ return renderTaskDraftTarget(draft).markdown;
4376
+ }
4377
+
4378
+ // src/planning-draft.ts
4379
+ var PLANNING_DRAFT_CONTRACT_VERSION = "planning-draft-v1";
4380
+ var PLANNING_DRAFT_KINDS = ["epic", "task"];
4381
+ var PLANNING_DRAFT_PERSISTENCE_MODES = [
4382
+ "ephemeral",
4383
+ "artifact"
4384
+ ];
4385
+ var PLANNING_DRAFT_COMMIT_STATES = [
4386
+ "draft",
4387
+ "ready_to_commit",
4388
+ "committed"
4389
+ ];
4390
+ var PLANNING_DRAFT_VALIDATION_STATUSES = [
4391
+ "unchecked",
4392
+ "valid",
4393
+ "invalid"
4394
+ ];
4395
+ var TASK_DRAFT_COMMIT_STAGES = [
4396
+ "backlog",
4397
+ "ready",
4398
+ "doing",
4399
+ "review",
4400
+ "blocked",
4401
+ "done"
4402
+ ];
4403
+ function isRecord2(value) {
4404
+ return value !== null && typeof value === "object" && !Array.isArray(value);
4405
+ }
4406
+ function asString3(value) {
4407
+ return typeof value === "string" && value.trim() !== "" ? value.trim() : null;
4408
+ }
4409
+ function asOptionalString(value) {
4410
+ return typeof value === "string" && value.trim() !== "" ? value.trim() : void 0;
4411
+ }
4412
+ function asStringArray3(value) {
4413
+ return Array.isArray(value) ? value.filter((entry) => typeof entry === "string" && entry.trim() !== "") : [];
4414
+ }
4415
+ function normalizePersistenceMode(value) {
4416
+ return value === "ephemeral" ? "ephemeral" : "artifact";
4417
+ }
4418
+ function normalizeCommitState(value) {
4419
+ return value === "ready_to_commit" || value === "committed" ? value : "draft";
4420
+ }
4421
+ function normalizeCommitStage(value) {
4422
+ return typeof value === "string" && TASK_DRAFT_COMMIT_STAGES.includes(value) ? value : null;
4423
+ }
4424
+ function normalizeValidationStatus(value) {
4425
+ return value === "valid" || value === "invalid" ? value : "unchecked";
4426
+ }
4427
+ function normalizeStorage(value) {
4428
+ const record = isRecord2(value) ? value : {};
4429
+ return {
4430
+ authority: "planning-draft",
4431
+ persistence_mode: normalizePersistenceMode(record.persistence_mode),
4432
+ artifact_path: asString3(record.artifact_path)
4433
+ };
4434
+ }
4435
+ function normalizeCommit(value) {
4436
+ const record = isRecord2(value) ? value : {};
4437
+ return {
4438
+ state: normalizeCommitState(record.state),
4439
+ target_stage: normalizeCommitStage(record.target_stage),
4440
+ target_path: asString3(record.target_path),
4441
+ committed_task_id: asString3(record.committed_task_id)
4442
+ };
4443
+ }
4444
+ function normalizeValidation(value) {
4445
+ const record = isRecord2(value) ? value : {};
4446
+ return {
4447
+ status: normalizeValidationStatus(record.status),
4448
+ errors: asStringArray3(record.errors),
4449
+ warnings: asStringArray3(record.warnings)
4450
+ };
4451
+ }
4452
+ function normalizeBase(value) {
4453
+ const record = isRecord2(value) ? value : null;
4454
+ if (!record)
4455
+ return null;
4456
+ const draftId = asString3(record.draft_id);
4457
+ const createdAt = asString3(record.created_at);
4458
+ const updatedAt = asString3(record.updated_at);
4459
+ const title = asString3(record.title);
4460
+ const kind = record.kind === "epic" || record.kind === "task" ? record.kind : null;
4461
+ if (!draftId || !createdAt || !updatedAt || !title || !kind)
4462
+ return null;
4463
+ return {
4464
+ version: PLANNING_DRAFT_CONTRACT_VERSION,
4465
+ draft_id: draftId,
4466
+ kind,
4467
+ created_at: createdAt,
4468
+ updated_at: updatedAt,
4469
+ title,
4470
+ notes: asStringArray3(record.notes),
4471
+ storage: normalizeStorage(record.storage),
4472
+ commit: normalizeCommit(record.commit),
4473
+ validation: normalizeValidation(record.validation)
4474
+ };
4475
+ }
4476
+ function buildPlanningDraftStorageRelativePath(kind, draftId) {
4477
+ return `planning-drafts/${kind}/${draftId}.json`;
4478
+ }
4479
+ function buildPlanningDraftArtifactPath(kind, draftId) {
4480
+ return `artifacts/${buildPlanningDraftStorageRelativePath(kind, draftId)}`;
4481
+ }
4482
+ function buildEpicPlanningDraftFixture(overrides = {}) {
4483
+ const draftId = overrides.draft_id ?? "epic-draft-auth-reliability";
4484
+ const artifactPath = overrides.storage?.artifact_path ?? buildPlanningDraftArtifactPath("epic", draftId);
4485
+ return {
4486
+ version: PLANNING_DRAFT_CONTRACT_VERSION,
4487
+ draft_id: draftId,
4488
+ kind: "epic",
4489
+ created_at: overrides.created_at ?? "2026-03-29T00:00:00.000Z",
4490
+ updated_at: overrides.updated_at ?? "2026-03-29T00:00:00.000Z",
4491
+ title: overrides.title ?? "Improve auth and review workflow reliability",
4492
+ objective: overrides.objective ?? "Reduce operator friction by making planning, review, and recovery flows more predictable.",
4493
+ scope: overrides.scope ?? [
4494
+ "Define the planning draft contract",
4495
+ "Persist epic intent before task generation"
4496
+ ],
4497
+ out_of_scope: overrides.out_of_scope ?? [
4498
+ "Replacing the existing markdown task storage contract"
4499
+ ],
4500
+ constraints: overrides.constraints ?? [
4501
+ "Committed tasks must remain valid against the current validator",
4502
+ "Draft records must remain separate from lifecycle folders"
4503
+ ],
4504
+ risks: overrides.risks ?? [
4505
+ "UI and generator layers could drift without one shared schema"
4506
+ ],
4507
+ success_criteria: overrides.success_criteria ?? [
4508
+ "Epic intent can be saved and reopened without markdown editing",
4509
+ "Generated task drafts can map back to the current markdown protocol"
4510
+ ],
4511
+ notes: overrides.notes ?? ["Used as a stable contract fixture for planning flows."],
4512
+ storage: overrides.storage ?? {
4513
+ authority: "planning-draft",
4514
+ persistence_mode: "artifact",
4515
+ artifact_path: artifactPath
4516
+ },
4517
+ commit: overrides.commit ?? {
4518
+ state: "draft",
4519
+ target_stage: null,
4520
+ target_path: null,
4521
+ committed_task_id: null
4522
+ },
4523
+ validation: overrides.validation ?? {
4524
+ status: "unchecked",
4525
+ errors: [],
4526
+ warnings: []
4527
+ }
4528
+ };
4529
+ }
4530
+ function buildEpicPlanningDraft(input) {
4531
+ const createdAt = input.created_at ?? (/* @__PURE__ */ new Date()).toISOString();
4532
+ const updatedAt = input.updated_at ?? createdAt;
4533
+ const artifactPath = input.artifact_path === null ? null : input.artifact_path ?? buildPlanningDraftArtifactPath("epic", input.draft_id);
4534
+ return {
4535
+ version: PLANNING_DRAFT_CONTRACT_VERSION,
4536
+ draft_id: input.draft_id,
4537
+ kind: "epic",
4538
+ created_at: createdAt,
4539
+ updated_at: updatedAt,
4540
+ title: input.title.trim(),
4541
+ objective: input.objective.trim(),
4542
+ scope: input.scope ?? [],
4543
+ out_of_scope: input.out_of_scope ?? [],
4544
+ constraints: input.constraints ?? [],
4545
+ risks: input.risks ?? [],
4546
+ success_criteria: input.success_criteria ?? [],
4547
+ notes: input.notes ?? [],
4548
+ storage: {
4549
+ authority: "planning-draft",
4550
+ persistence_mode: input.persistence_mode ?? "artifact",
4551
+ artifact_path: artifactPath
4552
+ },
4553
+ commit: {
4554
+ state: "draft",
4555
+ target_stage: null,
4556
+ target_path: null,
4557
+ committed_task_id: null
4558
+ },
4559
+ validation: {
4560
+ status: "unchecked",
4561
+ errors: [],
4562
+ warnings: []
4563
+ }
4564
+ };
4565
+ }
4566
+ function buildTaskPlanningDraftFixture(overrides = {}) {
4567
+ const taskId = overrides.commit?.committed_task_id ?? overrides.draft_id ?? "factory-181";
4568
+ const title = overrides.title ?? "Define the structured planning draft model for epics and tasks";
4569
+ const status = overrides.status ?? "backlog";
4570
+ const externalSource = overrides.external_source;
4571
+ const externalKey = overrides.external_key;
4572
+ const externalUrl = overrides.external_url;
4573
+ const artifactPath = overrides.storage?.artifact_path ?? buildPlanningDraftArtifactPath("task", taskId);
4574
+ const targetPath = overrides.commit?.target_path ?? buildTaskDraftTargetPath(taskId, title, status);
4575
+ return {
4576
+ version: PLANNING_DRAFT_CONTRACT_VERSION,
4577
+ draft_id: overrides.draft_id ?? taskId,
4578
+ kind: "task",
4579
+ created_at: overrides.created_at ?? "2026-03-29T00:00:00.000Z",
4580
+ updated_at: overrides.updated_at ?? "2026-03-29T00:00:00.000Z",
4581
+ title,
4582
+ project: overrides.project ?? "ai-dev-factory",
4583
+ repo: overrides.repo ?? ".",
4584
+ branch: overrides.branch ?? `task/${taskId}-planning-draft-contract`,
4585
+ type: overrides.type ?? "feature",
4586
+ priority: overrides.priority ?? "high",
4587
+ status,
4588
+ agent: overrides.agent ?? "backend-builder",
4589
+ ...externalSource ? { external_source: externalSource } : {},
4590
+ ...externalKey ? { external_key: externalKey } : {},
4591
+ ...externalUrl ? { external_url: externalUrl } : {},
4592
+ depends_on: overrides.depends_on ?? [],
4593
+ files_likely_affected: overrides.files_likely_affected ?? [
4594
+ "docs/adr/",
4595
+ "packages/core/src/"
4596
+ ],
4597
+ verification: overrides.verification ?? [
4598
+ "npm run validate:task -- tasks/backlog/factory-181.md",
4599
+ "npm run test"
4600
+ ],
4601
+ lane: overrides.lane,
4602
+ repo_area: overrides.repo_area,
4603
+ bundle_id: overrides.bundle_id,
4604
+ bundle_title: overrides.bundle_title,
4605
+ bundle_phase: overrides.bundle_phase,
4606
+ goal: overrides.goal ?? "Define a shared draft contract for epic and task authoring before markdown commit time.",
4607
+ context: overrides.context ?? [
4608
+ "Markdown task storage already exists and remains authoritative after commit.",
4609
+ "Planning flows need a durable structure above raw markdown."
4610
+ ],
4611
+ acceptance_criteria: overrides.acceptance_criteria ?? [
4612
+ "A shared planning draft contract exists for epics and tasks.",
4613
+ "Task drafts render to markdown compatible with the current validator."
4614
+ ],
4615
+ expected_artifacts: overrides.expected_artifacts ?? [
4616
+ "Planning draft ADR",
4617
+ "Shared planning draft types",
4618
+ "Planning draft contract tests"
4619
+ ],
4620
+ failure_conditions: overrides.failure_conditions ?? [
4621
+ "Draft storage semantics are left ambiguous",
4622
+ "Rendered task markdown diverges from the current protocol"
4623
+ ],
4624
+ reviewer_checklist: overrides.reviewer_checklist ?? [
4625
+ "[ ] Contract covers epic and task drafts",
4626
+ "[ ] Draft storage remains distinct from lifecycle folders",
4627
+ "[ ] Rendered task target stays validator-compatible"
4628
+ ],
4629
+ notes: overrides.notes ?? ["Canonical fixture for planning draft tests."],
4630
+ storage: overrides.storage ?? {
4631
+ authority: "planning-draft",
4632
+ persistence_mode: "artifact",
4633
+ artifact_path: artifactPath
4634
+ },
4635
+ commit: overrides.commit ?? {
4636
+ state: "ready_to_commit",
4637
+ target_stage: status,
4638
+ target_path: targetPath,
4639
+ committed_task_id: taskId
4640
+ },
4641
+ validation: overrides.validation ?? {
4642
+ status: "valid",
4643
+ errors: [],
4644
+ warnings: []
4645
+ }
4646
+ };
4647
+ }
4648
+ function updateEpicPlanningDraft(draft, patch) {
4649
+ return {
4650
+ ...draft,
4651
+ title: patch.title?.trim() || draft.title,
4652
+ objective: patch.objective?.trim() || draft.objective,
4653
+ scope: patch.scope ?? draft.scope,
4654
+ out_of_scope: patch.out_of_scope ?? draft.out_of_scope,
4655
+ constraints: patch.constraints ?? draft.constraints,
4656
+ risks: patch.risks ?? draft.risks,
4657
+ success_criteria: patch.success_criteria ?? draft.success_criteria,
4658
+ notes: patch.notes ?? draft.notes,
4659
+ updated_at: patch.updated_at ?? (/* @__PURE__ */ new Date()).toISOString(),
4660
+ validation: patch.validation ? {
4661
+ status: patch.validation.status ?? draft.validation.status,
4662
+ errors: patch.validation.errors ?? draft.validation.errors,
4663
+ warnings: patch.validation.warnings ?? draft.validation.warnings
4664
+ } : draft.validation
4665
+ };
4666
+ }
4667
+ function normalizePlanningDraft(value) {
4668
+ const base = normalizeBase(value);
4669
+ const record = isRecord2(value) ? value : null;
4670
+ if (!base || !record)
4671
+ return null;
4672
+ if (base.kind === "epic") {
4673
+ const objective = asString3(record.objective);
4674
+ if (!objective)
4675
+ return null;
4676
+ return {
4677
+ ...base,
4678
+ kind: "epic",
4679
+ objective,
4680
+ scope: asStringArray3(record.scope),
4681
+ out_of_scope: asStringArray3(record.out_of_scope),
4682
+ constraints: asStringArray3(record.constraints),
4683
+ risks: asStringArray3(record.risks),
4684
+ success_criteria: asStringArray3(record.success_criteria)
4685
+ };
4686
+ }
4687
+ const project = asString3(record.project);
4688
+ const repo = asString3(record.repo);
4689
+ const branch = asString3(record.branch);
4690
+ const type2 = asString3(record.type);
4691
+ const priority = asString3(record.priority);
4692
+ const agent = asString3(record.agent);
4693
+ const goal = asString3(record.goal);
4694
+ if (!project || !repo || !branch || !type2 || !priority || !agent || !goal) {
4695
+ return null;
4696
+ }
4697
+ const externalSource = record.external_source === "github-issue" || record.external_source === "jira" ? record.external_source : void 0;
4698
+ const externalKey = asOptionalString(record.external_key);
4699
+ const externalUrl = asOptionalString(record.external_url);
4700
+ return {
4701
+ ...base,
4702
+ kind: "task",
4703
+ project,
4704
+ repo,
4705
+ branch,
4706
+ type: type2,
4707
+ priority,
4708
+ status: normalizeCommitStage(record.status) ?? "backlog",
4709
+ agent,
4710
+ ...externalSource ? { external_source: externalSource } : {},
4711
+ ...externalKey ? { external_key: externalKey } : {},
4712
+ ...externalUrl ? { external_url: externalUrl } : {},
4713
+ depends_on: asStringArray3(record.depends_on),
4714
+ files_likely_affected: asStringArray3(record.files_likely_affected),
4715
+ verification: asStringArray3(record.verification),
4716
+ lane: asOptionalString(record.lane),
4717
+ repo_area: asOptionalString(record.repo_area),
4718
+ bundle_id: asOptionalString(record.bundle_id),
4719
+ bundle_title: asOptionalString(record.bundle_title),
4720
+ bundle_phase: asOptionalString(record.bundle_phase),
4721
+ goal,
4722
+ context: asStringArray3(record.context),
4723
+ acceptance_criteria: asStringArray3(record.acceptance_criteria),
4724
+ expected_artifacts: asStringArray3(record.expected_artifacts),
4725
+ failure_conditions: asStringArray3(record.failure_conditions),
4726
+ reviewer_checklist: asStringArray3(record.reviewer_checklist)
4727
+ };
4728
+ }
4729
+ function renderTaskPlanningDraftTarget(draft) {
4730
+ return renderTaskDraftTarget(draft);
4731
+ }
4732
+ function serializePlanningDraft(draft) {
4733
+ const normalized = normalizePlanningDraft(draft);
4734
+ if (!normalized) {
4735
+ throw new Error(`Cannot serialize invalid planning draft: ${draft.draft_id}`);
4736
+ }
4737
+ return JSON.stringify(normalized, null, 2) + "\n";
4738
+ }
4739
+
4740
+ // src/task-draft.ts
4741
+ var TASK_DRAFT_RENDER_CONTRACT_VERSION = "task-draft-render-v1";
4742
+ var TASK_DRAFT_REQUIRED_FRONTMATTER_FIELDS = [
4743
+ "id",
4744
+ "title",
4745
+ "project",
4746
+ "repo",
4747
+ "branch",
4748
+ "type",
4749
+ "priority",
4750
+ "status",
4751
+ "agent"
4752
+ ];
4753
+ var TASK_DRAFT_OPTIONAL_FRONTMATTER_FIELDS = [
4754
+ "lane",
4755
+ "repo_area",
4756
+ "bundle_id",
4757
+ "bundle_title",
4758
+ "bundle_phase",
4759
+ "depends_on",
4760
+ "files_likely_affected",
4761
+ "verification"
4762
+ ];
4763
+ var TASK_DRAFT_BODY_SECTION_ORDER = [
4764
+ "## Goal",
4765
+ "## Context",
4766
+ "## Acceptance Criteria",
4767
+ "## Expected Artifacts",
4768
+ "## Failure Conditions",
4769
+ "## Reviewer Checklist"
4770
+ ];
4771
+ function normalizeTaskDraft(value) {
4772
+ const draft = normalizePlanningDraft(value);
4773
+ return draft?.kind === "task" ? draft : null;
4774
+ }
4775
+ function buildMinimalTaskDraftFixture(overrides = {}) {
4776
+ return buildTaskPlanningDraftFixture({
4777
+ draft_id: overrides.draft_id ?? "factory-184-minimal",
4778
+ title: overrides.title ?? "Define minimal task draft contract",
4779
+ project: overrides.project ?? "ai-dev-factory",
4780
+ repo: overrides.repo ?? ".",
4781
+ branch: overrides.branch ?? "task/factory-184-minimal-task-draft-contract",
4782
+ type: overrides.type ?? "feature",
4783
+ priority: overrides.priority ?? "high",
4784
+ status: overrides.status ?? "backlog",
4785
+ agent: overrides.agent ?? "backend-builder",
4786
+ external_source: overrides.external_source,
4787
+ external_key: overrides.external_key,
4788
+ external_url: overrides.external_url,
4789
+ goal: overrides.goal ?? "Define the minimum structured task draft that renders to valid Devory markdown.",
4790
+ context: overrides.context ?? [
4791
+ "The render contract must remain compatible with the current validators."
4792
+ ],
4793
+ acceptance_criteria: overrides.acceptance_criteria ?? [
4794
+ "A minimal task draft renders to valid markdown."
4795
+ ],
4796
+ expected_artifacts: overrides.expected_artifacts ?? [
4797
+ "Shared task draft contract"
4798
+ ],
4799
+ failure_conditions: overrides.failure_conditions ?? [
4800
+ "Rendered markdown omits required sections"
4801
+ ],
4802
+ reviewer_checklist: overrides.reviewer_checklist ?? [
4803
+ "[ ] Minimal draft is validator-compatible"
4804
+ ],
4805
+ depends_on: overrides.depends_on ?? [],
4806
+ files_likely_affected: overrides.files_likely_affected ?? [],
4807
+ verification: overrides.verification ?? ["npm run test"],
4808
+ lane: overrides.lane,
4809
+ repo_area: overrides.repo_area,
4810
+ bundle_id: overrides.bundle_id,
4811
+ bundle_title: overrides.bundle_title,
4812
+ bundle_phase: overrides.bundle_phase
4813
+ });
4814
+ }
4815
+ function buildRichTaskDraftFixture(overrides = {}) {
4816
+ return buildTaskPlanningDraftFixture({
4817
+ draft_id: overrides.draft_id ?? "factory-184-rich",
4818
+ title: overrides.title ?? "Define rich task draft contract with optional metadata",
4819
+ project: overrides.project ?? "ai-dev-factory",
4820
+ repo: overrides.repo ?? ".",
4821
+ branch: overrides.branch ?? "task/factory-184-rich-task-draft-contract",
4822
+ type: overrides.type ?? "feature",
4823
+ priority: overrides.priority ?? "high",
4824
+ status: overrides.status ?? "backlog",
4825
+ agent: overrides.agent ?? "backend-builder",
4826
+ external_source: overrides.external_source,
4827
+ external_key: overrides.external_key,
4828
+ external_url: overrides.external_url,
4829
+ lane: overrides.lane ?? "planning",
4830
+ repo_area: overrides.repo_area ?? "authoring",
4831
+ bundle_id: overrides.bundle_id ?? "epic-planning-authoring",
4832
+ bundle_title: overrides.bundle_title ?? "Planning & Task Authoring",
4833
+ bundle_phase: overrides.bundle_phase ?? "contract",
4834
+ depends_on: overrides.depends_on ?? ["factory-181"],
4835
+ files_likely_affected: overrides.files_likely_affected ?? [
4836
+ "packages/core/src/",
4837
+ "templates/"
4838
+ ],
4839
+ verification: overrides.verification ?? [
4840
+ "npm run validate:task -- tasks/backlog/factory-184.md",
4841
+ "npm run test"
4842
+ ],
4843
+ goal: overrides.goal ?? "Define the rich structured task draft contract including optional metadata and render ordering.",
4844
+ context: overrides.context ?? [
4845
+ "Task drafts must support workflow metadata such as dependencies and bundle linkage.",
4846
+ "Rendered markdown must remain compatible with existing readers and validators."
4847
+ ],
4848
+ acceptance_criteria: overrides.acceptance_criteria ?? [
4849
+ "Optional metadata renders deterministically.",
4850
+ "Required sections preserve the existing heading order."
4851
+ ],
4852
+ expected_artifacts: overrides.expected_artifacts ?? [
4853
+ "Task draft contract module",
4854
+ "Task render contract fixture",
4855
+ "Task draft tests"
4856
+ ],
4857
+ failure_conditions: overrides.failure_conditions ?? [
4858
+ "Optional metadata ordering drifts across renderers",
4859
+ "Rendered markdown breaks current task readers"
4860
+ ],
4861
+ reviewer_checklist: overrides.reviewer_checklist ?? [
4862
+ "[ ] Rich draft covers optional metadata",
4863
+ "[ ] Markdown ordering is explicit and deterministic"
4864
+ ]
4865
+ });
4866
+ }
4867
+ function renderTaskDraftMarkdown2(draft) {
4868
+ return renderTaskDraftMarkdown(draft);
4869
+ }
4870
+ function renderTaskDraftTarget2(draft) {
4871
+ return renderTaskDraftTarget(draft);
4872
+ }
4873
+ function buildTaskDraftRenderFixture(input = {}) {
4874
+ const draft = input.richness === "rich" ? buildRichTaskDraftFixture(input.draft) : buildMinimalTaskDraftFixture(input.draft);
4875
+ return renderTaskDraftTarget2({
4876
+ ...draft,
4877
+ status: input.target_stage ?? draft.status,
4878
+ commit: {
4879
+ ...draft.commit,
4880
+ target_stage: input.target_stage ?? draft.commit.target_stage ?? draft.status
4881
+ }
4882
+ });
4883
+ }
4884
+
4885
+ // src/task-validation.ts
4886
+ var REQUIRED_FIELDS = [
4887
+ "id",
4888
+ "title",
4889
+ "project",
4890
+ "status",
4891
+ "agent"
4892
+ ];
4893
+ function validateTaskCapabilityMetadata(meta) {
4894
+ const errors = [];
4895
+ if (meta.required_tier !== void 0 && typeof meta.required_tier === "string" && meta.required_tier.trim() === "") {
4896
+ errors.push('Task capability metadata "required_tier" cannot be empty');
4897
+ }
4898
+ if (!Array.isArray(meta.required_features)) {
4899
+ return errors;
4900
+ }
4901
+ for (const [index, feature] of meta.required_features.entries()) {
4902
+ if (typeof feature !== "string" || feature.trim() === "") {
4903
+ errors.push(
4904
+ `Task capability metadata "required_features" entry ${index + 1} must be a non-empty string`
4905
+ );
4906
+ }
4907
+ }
4908
+ return errors;
4909
+ }
4910
+ function validateTask(meta, expectedStatus) {
4911
+ const errors = [];
4912
+ for (const field of REQUIRED_FIELDS) {
4913
+ const value = meta[field];
4914
+ if (value === void 0 || value === null || String(value).trim() === "") {
4915
+ errors.push(`Missing required field: "${field}"`);
4916
+ }
4917
+ }
4918
+ if (meta.status && meta.status !== expectedStatus) {
4919
+ errors.push(`Expected status "${expectedStatus}", got "${meta.status}"`);
4920
+ }
4921
+ errors.push(...validateTaskCapabilityMetadata(meta));
4922
+ return { valid: errors.length === 0, errors, warnings: [] };
4923
+ }
4924
+ var REQUIRED_BODY_SECTIONS = [
4925
+ "## Goal",
4926
+ "## Context",
4927
+ "## Acceptance Criteria",
4928
+ "## Expected Artifacts",
4929
+ "## Failure Conditions"
4930
+ ];
4931
+ function extractSectionContent(body, heading) {
4932
+ const lines = body.split("\n");
4933
+ const start = lines.findIndex((line) => line.trim() === heading);
4934
+ if (start === -1)
4935
+ return [];
4936
+ const content = [];
4937
+ for (let i = start + 1; i < lines.length; i += 1) {
4938
+ const line = lines[i] ?? "";
4939
+ if (line.startsWith("## ") || line.startsWith("# "))
4940
+ break;
4941
+ if (line.trim())
4942
+ content.push(line.trim());
4943
+ }
4944
+ return content;
4945
+ }
4946
+ function validateTaskBody(body) {
4947
+ const errors = [];
4948
+ const warnings = [];
4949
+ for (const section of REQUIRED_BODY_SECTIONS) {
4950
+ if (!body.includes(section)) {
4951
+ errors.push(`Missing required section: "${section}"`);
4952
+ }
4953
+ }
4954
+ const criteriaContent = extractSectionContent(body, "## Acceptance Criteria");
4955
+ const criteriaItems = criteriaContent.filter((line) => line.startsWith("- "));
4956
+ if (criteriaItems.length === 0 && body.includes("## Acceptance Criteria")) {
4957
+ errors.push(`"## Acceptance Criteria" has no items \u2014 add at least one "- " line`);
4958
+ }
4959
+ if (!body.includes("## Reviewer Checklist")) {
4960
+ warnings.push(`"## Reviewer Checklist" section is missing \u2014 consider adding one`);
4961
+ } else {
4962
+ const checklistContent = extractSectionContent(body, "## Reviewer Checklist");
4963
+ const checklistItems = checklistContent.filter((line) => line.startsWith("- "));
4964
+ if (checklistItems.length === 0) {
4965
+ warnings.push(`"## Reviewer Checklist" has no items`);
4966
+ }
4967
+ }
4968
+ return { errors, warnings };
4969
+ }
4970
+ function validateTaskMarkdown(markdown, expectedStatus) {
4971
+ const { meta, body } = parseFrontmatter(markdown);
4972
+ const statusToCheck = expectedStatus ?? meta.status ?? "";
4973
+ const frontmatterResult = validateTask(meta, statusToCheck);
4974
+ const bodyResult = validateTaskBody(body);
4975
+ const errors = [...frontmatterResult.errors, ...bodyResult.errors];
4976
+ const warnings = [...frontmatterResult.warnings, ...bodyResult.warnings];
4977
+ return {
4978
+ valid: errors.length === 0,
4979
+ errors,
4980
+ warnings
4981
+ };
4982
+ }
4983
+ function validateTaskDraft(draft, expectedStatus) {
4984
+ const rendered = renderTaskDraftTarget(draft);
4985
+ const targetStage = expectedStatus ?? rendered.target_stage;
4986
+ const validation = validateTaskMarkdown(rendered.markdown, targetStage);
4987
+ return {
4988
+ ...validation,
4989
+ draft_id: draft.draft_id,
4990
+ target_stage: targetStage,
4991
+ target_path: rendered.target_path
4992
+ };
4993
+ }
4994
+ function toPlanningDraftValidationRecord(result) {
4995
+ return {
4996
+ status: result.errors.length === 0 ? "valid" : "invalid",
4997
+ errors: [...result.errors],
4998
+ warnings: [...result.warnings]
4999
+ };
5000
+ }
5001
+ function applyTaskDraftValidation(draft, expectedStatus) {
5002
+ const result = validateTaskDraft(draft, expectedStatus);
5003
+ return {
5004
+ ...draft,
5005
+ validation: toPlanningDraftValidationRecord(result)
5006
+ };
5007
+ }
5008
+
5009
+ // src/unattended-checkpoint.ts
5010
+ var UNATTENDED_CHECKPOINT_VERSION = "unattended-checkpoint-v1";
5011
+ var UNATTENDED_CHECKPOINT_TRIGGERS = [
5012
+ "recovery_sensitive_transition",
5013
+ "significant_progress",
5014
+ "retry_boundary",
5015
+ "compaction_boundary"
5016
+ ];
5017
+ function isRecord3(value) {
5018
+ return value !== null && typeof value === "object" && !Array.isArray(value);
5019
+ }
5020
+ function asString4(value) {
5021
+ return typeof value === "string" && value.trim() !== "" ? value : null;
5022
+ }
5023
+ function asNumber3(value) {
5024
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
5025
+ }
5026
+ function asStringArray4(value) {
5027
+ return Array.isArray(value) ? value.filter((entry) => typeof entry === "string" && entry.trim() !== "") : [];
5028
+ }
5029
+ function normalizeTrigger(value) {
5030
+ return typeof value === "string" && UNATTENDED_CHECKPOINT_TRIGGERS.includes(value) ? value : "significant_progress";
5031
+ }
5032
+ function normalizePolicySnapshot(value) {
5033
+ const record = isRecord3(value) ? value : {};
5034
+ return {
5035
+ policy: isRecord3(record.policy) ? record.policy : null,
5036
+ injection_source: record.injection_source === "agent-context" ? "agent-context" : "checkpoint-writer",
5037
+ applied_layers: asStringArray4(record.applied_layers),
5038
+ workspace_config_path: asString4(record.workspace_config_path)
5039
+ };
5040
+ }
5041
+ function normalizeArtifactReferences(value) {
5042
+ const record = isRecord3(value) ? value : {};
5043
+ return {
5044
+ heartbeat_snapshot: asString4(record.heartbeat_snapshot),
5045
+ task_snapshot: asString4(record.task_snapshot),
5046
+ execution_plan: asString4(record.execution_plan),
5047
+ staging_manifest: asString4(record.staging_manifest),
5048
+ agent_context: asString4(record.agent_context),
5049
+ routing_manifest: asString4(record.routing_manifest),
5050
+ execution_result: asString4(record.execution_result),
5051
+ retry_context: asString4(record.retry_context),
5052
+ review_package: asString4(record.review_package),
5053
+ changed_files: asString4(record.changed_files)
5054
+ };
5055
+ }
5056
+ function normalizeUnattendedCheckpointArtifact(value) {
5057
+ const record = isRecord3(value) ? value : null;
5058
+ if (!record)
5059
+ return null;
5060
+ const checkpointId = asString4(record.checkpoint_id);
5061
+ const runId = asString4(record.run_id);
5062
+ const createdAt = asString4(record.created_at);
5063
+ const currentPhase = asString4(record.current_phase);
5064
+ if (!checkpointId || !runId || !createdAt || !currentPhase)
5065
+ return null;
5066
+ return {
5067
+ version: UNATTENDED_CHECKPOINT_VERSION,
5068
+ checkpoint_id: checkpointId,
5069
+ run_id: runId,
5070
+ task_id: asString4(record.task_id),
5071
+ created_at: createdAt,
5072
+ trigger: normalizeTrigger(record.trigger),
5073
+ current_phase: currentPhase,
5074
+ current_adapter: asString4(record.current_adapter),
5075
+ current_attempt: asNumber3(record.current_attempt),
5076
+ recent_progress_summary: asString4(record.recent_progress_summary),
5077
+ pending_actions: asStringArray4(record.pending_actions),
5078
+ policy_snapshot: normalizePolicySnapshot(record.policy_snapshot),
5079
+ artifact_references: normalizeArtifactReferences(record.artifact_references)
5080
+ };
5081
+ }
5082
+
5083
+ // src/review-control.ts
5084
+ var REVIEW_CONTROL_CONTRACT_VERSION = "review-control-surface-v1";
5085
+ var TASK_REVIEW_ACTIONS = ["approve", "send-back", "block"];
5086
+ var REVIEW_CONTROL_ACTIONS = [
5087
+ ...TASK_REVIEW_ACTIONS,
5088
+ "resume-run"
5089
+ ];
5090
+ var REVIEW_QUEUE_ITEM_KINDS = [
5091
+ "task-review",
5092
+ "run-attention",
5093
+ "task-triage"
5094
+ ];
5095
+ var REVIEW_QUEUE_TASK_STAGES = ["review"];
5096
+ var REVIEW_QUEUE_TRIAGE_STAGES = ["blocked"];
5097
+ var REVIEW_QUEUE_RUN_STATUSES = RESUMABLE_RUN_STATUSES;
5098
+ var TASK_REVIEW_ACTION_STAGE_MAP = {
5099
+ approve: "done",
5100
+ "send-back": "doing",
5101
+ block: "blocked"
5102
+ };
5103
+ var REVIEW_CONTROL_MECHANISMS = {
5104
+ approve: {
5105
+ action: "approve",
5106
+ supported_item_kinds: ["task-review"],
5107
+ workflow_mechanism: "task-review-action",
5108
+ api_route: "/api/task/review-action",
5109
+ cli_equivalent: "devory task move <task> --to done",
5110
+ resulting_state: "task stage review -> done",
5111
+ audit_artifacts: ["artifacts/runs/*-review.md", "runs/<run-id>.json"]
5112
+ },
5113
+ "send-back": {
5114
+ action: "send-back",
5115
+ supported_item_kinds: ["task-review"],
5116
+ workflow_mechanism: "task-review-action",
5117
+ api_route: "/api/task/review-action",
5118
+ cli_equivalent: "devory task move <task> --to doing",
5119
+ resulting_state: "task stage review -> doing",
5120
+ audit_artifacts: ["artifacts/runs/*-review.md", "runs/<run-id>.json"]
5121
+ },
5122
+ block: {
5123
+ action: "block",
5124
+ supported_item_kinds: ["task-review"],
5125
+ workflow_mechanism: "task-review-action",
5126
+ api_route: "/api/task/review-action",
5127
+ cli_equivalent: "devory task move <task> --to blocked",
5128
+ resulting_state: "task stage review -> blocked",
5129
+ audit_artifacts: ["artifacts/runs/*-review.md", "runs/<run-id>.json"]
5130
+ },
5131
+ "resume-run": {
5132
+ action: "resume-run",
5133
+ supported_item_kinds: ["run-attention"],
5134
+ workflow_mechanism: "run-resume",
5135
+ api_route: "/api/run/resume",
5136
+ cli_equivalent: "devory run --resume <run-id>",
5137
+ resulting_state: "run status failed|paused_for_review -> running",
5138
+ audit_artifacts: [
5139
+ "runs/<run-id>.json",
5140
+ "artifacts/execution/<task>/checkpoints/*.json",
5141
+ "artifacts/runs/*-resume.md when mediated by a human question"
5142
+ ]
5143
+ }
5144
+ };
5145
+ var REVIEW_SUPPORTED_ACTIONS_BY_KIND = {
5146
+ "task-review": TASK_REVIEW_ACTIONS,
5147
+ "run-attention": ["resume-run"],
5148
+ "task-triage": []
5149
+ };
5150
+ function asRecord(value) {
5151
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
5152
+ }
5153
+ function asString5(value) {
5154
+ return typeof value === "string" && value.trim() !== "" ? value : null;
5155
+ }
5156
+ function asActionList(value) {
5157
+ return Array.isArray(value) ? value.filter(
5158
+ (entry) => typeof entry === "string" && REVIEW_CONTROL_ACTIONS.includes(entry)
5159
+ ) : [];
5160
+ }
5161
+ function normalizeTaskSource(value, expectedStage) {
5162
+ const record = asRecord(value);
5163
+ if (!record)
5164
+ return null;
5165
+ if (record.authority !== "tasks-folder")
5166
+ return null;
5167
+ if (record.stage !== expectedStage)
5168
+ return null;
5169
+ return { authority: "tasks-folder", stage: expectedStage };
5170
+ }
5171
+ function normalizeRunSource(value) {
5172
+ const record = asRecord(value);
5173
+ if (!record)
5174
+ return null;
5175
+ if (record.authority !== "run-ledger")
5176
+ return null;
5177
+ if (typeof record.status !== "string" || !REVIEW_QUEUE_RUN_STATUSES.includes(record.status)) {
5178
+ return null;
5179
+ }
5180
+ return {
5181
+ authority: "run-ledger",
5182
+ status: record.status
5183
+ };
5184
+ }
5185
+ function getSupportedReviewActions(kind) {
5186
+ return REVIEW_SUPPORTED_ACTIONS_BY_KIND[kind];
5187
+ }
5188
+ function buildTaskReviewQueueItem(input) {
5189
+ return {
5190
+ version: REVIEW_CONTROL_CONTRACT_VERSION,
5191
+ item_id: `task-review:${input.task_id}`,
5192
+ kind: "task-review",
5193
+ title: input.title,
5194
+ summary: input.summary,
5195
+ task_id: input.task_id,
5196
+ run_id: input.run_id ?? null,
5197
+ attention_state: "review",
5198
+ supported_actions: TASK_REVIEW_ACTIONS,
5199
+ source: {
5200
+ authority: "tasks-folder",
5201
+ stage: "review"
5202
+ }
5203
+ };
5204
+ }
5205
+ function buildRunAttentionQueueItem(input) {
5206
+ return {
5207
+ version: REVIEW_CONTROL_CONTRACT_VERSION,
5208
+ item_id: `run-attention:${input.run_id}`,
5209
+ kind: "run-attention",
5210
+ title: input.title,
5211
+ summary: input.summary,
5212
+ task_id: input.task_id ?? null,
5213
+ run_id: input.run_id,
5214
+ attention_state: input.status,
5215
+ supported_actions: ["resume-run"],
5216
+ source: {
5217
+ authority: "run-ledger",
5218
+ status: input.status
5219
+ }
5220
+ };
5221
+ }
5222
+ function buildTaskTriageQueueItem(input) {
5223
+ return {
5224
+ version: REVIEW_CONTROL_CONTRACT_VERSION,
5225
+ item_id: `task-triage:${input.task_id}`,
5226
+ kind: "task-triage",
5227
+ title: input.title,
5228
+ summary: input.summary,
5229
+ task_id: input.task_id,
5230
+ run_id: input.run_id ?? null,
5231
+ attention_state: "blocked",
5232
+ supported_actions: [],
5233
+ source: {
5234
+ authority: "tasks-folder",
5235
+ stage: "blocked"
5236
+ }
5237
+ };
5238
+ }
5239
+ function normalizeReviewQueueItem(value) {
5240
+ const record = asRecord(value);
5241
+ if (!record)
5242
+ return null;
5243
+ const itemId = asString5(record.item_id);
5244
+ const kind = asString5(record.kind);
5245
+ const title = asString5(record.title);
5246
+ const summary = asString5(record.summary);
5247
+ const attentionState = asString5(record.attention_state);
5248
+ if (!itemId || !kind || !title || !summary || !attentionState) {
5249
+ return null;
5250
+ }
5251
+ if (kind === "task-review") {
5252
+ const taskId = asString5(record.task_id);
5253
+ const source = normalizeTaskSource(record.source, "review");
5254
+ const supportedActions = asActionList(record.supported_actions);
5255
+ if (!taskId || attentionState !== "review" || !source || supportedActions.length !== TASK_REVIEW_ACTIONS.length) {
5256
+ return null;
5257
+ }
5258
+ return {
5259
+ version: REVIEW_CONTROL_CONTRACT_VERSION,
5260
+ item_id: itemId,
5261
+ kind,
5262
+ title,
5263
+ summary,
5264
+ task_id: taskId,
5265
+ run_id: asString5(record.run_id),
5266
+ attention_state: "review",
5267
+ supported_actions: TASK_REVIEW_ACTIONS,
5268
+ source
5269
+ };
5270
+ }
5271
+ if (kind === "run-attention") {
5272
+ const runId = asString5(record.run_id);
5273
+ const source = normalizeRunSource(record.source);
5274
+ const supportedActions = asActionList(record.supported_actions);
5275
+ if (!runId || !source || attentionState !== source.status || supportedActions.length !== 1 || supportedActions[0] !== "resume-run") {
5276
+ return null;
5277
+ }
5278
+ return {
5279
+ version: REVIEW_CONTROL_CONTRACT_VERSION,
5280
+ item_id: itemId,
5281
+ kind,
5282
+ title,
5283
+ summary,
5284
+ task_id: asString5(record.task_id),
5285
+ run_id: runId,
5286
+ attention_state: source.status,
5287
+ supported_actions: ["resume-run"],
5288
+ source
5289
+ };
5290
+ }
5291
+ if (kind === "task-triage") {
5292
+ const taskId = asString5(record.task_id);
5293
+ const source = normalizeTaskSource(record.source, "blocked");
5294
+ const supportedActions = asActionList(record.supported_actions);
5295
+ if (!taskId || attentionState !== "blocked" || !source || supportedActions.length !== 0) {
5296
+ return null;
5297
+ }
5298
+ return {
5299
+ version: REVIEW_CONTROL_CONTRACT_VERSION,
5300
+ item_id: itemId,
5301
+ kind,
5302
+ title,
5303
+ summary,
5304
+ task_id: taskId,
5305
+ run_id: asString5(record.run_id),
5306
+ attention_state: "blocked",
5307
+ supported_actions: [],
5308
+ source
5309
+ };
5310
+ }
5311
+ return null;
5312
+ }
5313
+
5314
+ // src/unattended-stall-policy.ts
5315
+ var fs5 = __toESM(require("fs"));
5316
+ var path5 = __toESM(require("path"));
5317
+ var import_url3 = require("url");
5318
+ var import_meta3 = {};
5319
+ var MODULE_DIR3 = typeof __dirname === "string" ? __dirname : path5.dirname((0, import_url3.fileURLToPath)(import_meta3.url));
5320
+ var UNATTENDED_STALL_POLICY_VERSION = "unattended-stall-policy-v1";
5321
+ var UNATTENDED_STALL_POLICY_FILENAME = "unattended-stall-policy.json";
5322
+ var UNATTENDED_STALL_POLICY_WORKSPACE_PATH = path5.join(
5323
+ "config",
5324
+ UNATTENDED_STALL_POLICY_FILENAME
5325
+ );
5326
+ var DEFAULTS_PATH2 = path5.join(
5327
+ MODULE_DIR3,
5328
+ "defaults",
5329
+ UNATTENDED_STALL_POLICY_FILENAME
5330
+ );
5331
+ function isRecord4(value) {
5332
+ return value !== null && typeof value === "object" && !Array.isArray(value);
5333
+ }
5334
+ function normalizePositiveInteger(value) {
5335
+ return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : void 0;
5336
+ }
5337
+ function normalizeUnattendedStallPolicyOverrides(value) {
5338
+ if (!isRecord4(value)) {
5339
+ throw new Error("devory: unattended stall policy config must be a JSON object");
5340
+ }
5341
+ const overrides = {};
5342
+ const keys = [
5343
+ "heartbeat_stale_after_ms",
5344
+ "heartbeat_missing_after_ms",
5345
+ "progress_stalled_after_ms",
5346
+ "waiting_progress_grace_ms",
5347
+ "looping_event_window",
5348
+ "looping_event_threshold",
5349
+ "repeated_failure_without_progress_threshold"
5350
+ ];
5351
+ for (const key of keys) {
5352
+ const normalized = normalizePositiveInteger(value[key]);
5353
+ if (normalized !== void 0) {
5354
+ overrides[key] = normalized;
5355
+ }
5356
+ }
5357
+ return overrides;
5358
+ }
5359
+ function applyUnattendedStallPolicyOverrides(base, overrides) {
5360
+ return {
5361
+ ...base,
5362
+ ...overrides,
5363
+ version: UNATTENDED_STALL_POLICY_VERSION
5364
+ };
5365
+ }
5366
+ function loadDefaultUnattendedStallPolicy() {
5367
+ return JSON.parse(fs5.readFileSync(DEFAULTS_PATH2, "utf-8"));
5368
+ }
5369
+ function loadWorkspaceUnattendedStallPolicy(factoryRoot) {
5370
+ const filePath = path5.join(factoryRoot, UNATTENDED_STALL_POLICY_WORKSPACE_PATH);
5371
+ if (!fs5.existsSync(filePath))
5372
+ return null;
5373
+ return normalizeUnattendedStallPolicyOverrides(
5374
+ JSON.parse(fs5.readFileSync(filePath, "utf-8"))
5375
+ );
5376
+ }
5377
+ function resolveUnattendedStallPolicy(factoryRoot, runOverrides = {}) {
5378
+ const base = loadDefaultUnattendedStallPolicy();
5379
+ let policy = base;
5380
+ const applied_layers = [
5381
+ "shipped-defaults"
5382
+ ];
5383
+ let workspace_config_path = null;
5384
+ if (factoryRoot) {
5385
+ const workspace = loadWorkspaceUnattendedStallPolicy(factoryRoot);
5386
+ if (workspace) {
5387
+ policy = applyUnattendedStallPolicyOverrides(policy, workspace);
5388
+ applied_layers.push("workspace-config");
5389
+ workspace_config_path = path5.join(
5390
+ factoryRoot,
5391
+ UNATTENDED_STALL_POLICY_WORKSPACE_PATH
5392
+ );
5393
+ }
5394
+ }
5395
+ if (Object.keys(runOverrides).length > 0) {
5396
+ policy = applyUnattendedStallPolicyOverrides(policy, runOverrides);
5397
+ applied_layers.push("run-override");
5398
+ }
5399
+ return {
5400
+ policy,
5401
+ applied_layers,
5402
+ workspace_config_path
5403
+ };
5404
+ }
5405
+
5406
+ // src/routing-input.ts
5407
+ var VALID_ROUTING_EXECUTION_PROFILES = [
5408
+ "balanced",
5409
+ "implementation",
5410
+ "analysis",
5411
+ "review",
5412
+ "documentation"
5413
+ ];
5414
+ var VALID_ROUTING_CONTEXT_INTENSITIES = [
5415
+ "low",
5416
+ "medium",
5417
+ "high"
5418
+ ];
5419
+ var VALID_ROUTING_PRIORITY_LEVELS = ["low", "medium", "high"];
5420
+ function normalizeString(value) {
5421
+ if (typeof value !== "string")
5422
+ return null;
5423
+ const normalized = value.trim();
5424
+ return normalized === "" ? null : normalized;
5425
+ }
5426
+ function normalizeStringList(value) {
5427
+ if (!Array.isArray(value))
5428
+ return [];
5429
+ return [...new Set(
5430
+ value.filter((item) => typeof item === "string").map((item) => item.trim()).filter((item) => item !== "")
5431
+ )];
5432
+ }
5433
+ function normalizeEnumValue(value, validValues) {
5434
+ const normalized = normalizeString(value);
5435
+ if (normalized === null)
5436
+ return null;
5437
+ return validValues.includes(normalized) ? normalized : null;
5438
+ }
5439
+ function normalizeRoutingInput(meta, context = {}) {
5440
+ const executionProfile = normalizeEnumValue(
5441
+ meta.execution_profile,
5442
+ VALID_ROUTING_EXECUTION_PROFILES
5443
+ );
5444
+ return {
5445
+ task_id: normalizeString(meta.id),
5446
+ task_title: normalizeString(meta.title),
5447
+ task_type: normalizeString(meta.type)?.toLowerCase() ?? null,
5448
+ project: normalizeString(meta.project),
5449
+ work_role: normalizeString(context.work_role) ?? normalizeString(context.stage) ?? executionProfile,
5450
+ execution_profile: executionProfile,
5451
+ execution_mode: normalizeString(context.execution_mode),
5452
+ stage: normalizeString(context.stage),
5453
+ cost_environment: normalizeString(context.cost_environment),
5454
+ required_capabilities: normalizeStringList(meta.required_capabilities),
5455
+ preferred_capabilities: normalizeStringList(meta.preferred_capabilities),
5456
+ preferred_models: normalizeStringList(meta.preferred_models),
5457
+ disallowed_models: normalizeStringList(meta.disallowed_models),
5458
+ context_intensity: normalizeEnumValue(
5459
+ meta.context_intensity,
5460
+ VALID_ROUTING_CONTEXT_INTENSITIES
5461
+ ),
5462
+ quality_priority: normalizeEnumValue(
5463
+ meta.quality_priority,
5464
+ VALID_ROUTING_PRIORITY_LEVELS
5465
+ ),
5466
+ speed_priority: normalizeEnumValue(
5467
+ meta.speed_priority,
5468
+ VALID_ROUTING_PRIORITY_LEVELS
5469
+ ),
5470
+ max_cost_tier: normalizeString(meta.max_cost_tier),
5471
+ lane: normalizeString(meta.lane),
5472
+ repo_area: normalizeString(meta.repo_area),
5473
+ repo: normalizeString(meta.repo),
5474
+ branch: normalizeString(meta.branch),
5475
+ decomposition_hint: normalizeString(meta.decomposition_hint),
5476
+ language: null,
5477
+ framework: null,
5478
+ complexity: null,
5479
+ risk_level: null,
5480
+ privacy_sensitivity: null,
5481
+ environment_constraints: {
5482
+ fallback_only: context.fallback_only === true,
5483
+ retry_attempt: typeof context.retry_attempt === "number" && Number.isFinite(context.retry_attempt) ? context.retry_attempt : null,
5484
+ resumed_from_run_id: normalizeString(context.resumed_from_run_id),
5485
+ factory_read_only: context.factory_read_only === true
5486
+ }
5487
+ };
5488
+ }
5489
+
5490
+ // src/routing-decision.ts
5491
+ var ROUTING_DECISION_VERSION = "routing-decision-v1";
5492
+ function slug(value) {
5493
+ return value.replace(/[^a-z0-9-]+/gi, "-").replace(/^-+|-+$/g, "").toLowerCase();
5494
+ }
5495
+ function buildRoutingDecisionId(linkage) {
5496
+ const parts = [
5497
+ "route",
5498
+ linkage.task_id ? slug(linkage.task_id) : "task",
5499
+ linkage.run_id ? slug(linkage.run_id) : "run",
5500
+ linkage.pipeline_run_id ? slug(linkage.pipeline_run_id) : "pipeline",
5501
+ linkage.stage_name ? slug(linkage.stage_name) : "stage",
5502
+ linkage.attempt_number === null || linkage.attempt_number === void 0 ? "attempt-na" : `attempt-${linkage.attempt_number}`
5503
+ ];
5504
+ return parts.join("-");
5505
+ }
5506
+ function attachRoutingDecisionLinkage(decision, linkage = {}, normalizedInput = null) {
5507
+ const resolvedLinkage = {
5508
+ decision_id: typeof linkage.decision_id === "string" && linkage.decision_id !== "" ? linkage.decision_id : buildRoutingDecisionId(linkage),
5509
+ task_id: linkage.task_id ?? null,
5510
+ run_id: linkage.run_id ?? null,
5511
+ pipeline_run_id: linkage.pipeline_run_id ?? null,
5512
+ stage_name: linkage.stage_name ?? null,
5513
+ attempt_number: linkage.attempt_number ?? null
5514
+ };
5515
+ return {
5516
+ ...decision,
5517
+ version: ROUTING_DECISION_VERSION,
5518
+ linkage: resolvedLinkage,
5519
+ normalized_input: normalizedInput
5520
+ };
5521
+ }
5522
+
5523
+ // src/routing-evaluation.ts
5524
+ function buildRoutingOutcomeEvaluation(input) {
5525
+ const validationOutcome = input.validation_outcome ?? null;
5526
+ const reviewOutcome = input.review_outcome ?? null;
5527
+ const manualIntervention = input.manual_intervention ?? null;
5528
+ const evidenceCount = [validationOutcome, reviewOutcome, manualIntervention].filter(
5529
+ (value) => value !== null
5530
+ ).length;
5531
+ const evidenceStatus = evidenceCount === 0 ? "missing" : evidenceCount === 3 ? "complete" : "partial";
5532
+ let evaluationStatus = "inconclusive";
5533
+ if (reviewOutcome === "approved") {
5534
+ evaluationStatus = "successful";
5535
+ } else if (reviewOutcome === "send-back" || reviewOutcome === "blocked" || reviewOutcome === "needs-human-edits" || validationOutcome === "failed") {
5536
+ evaluationStatus = "unsuccessful";
5537
+ } else if (validationOutcome === "passed" && manualIntervention === "performed") {
5538
+ evaluationStatus = "successful";
5539
+ }
5540
+ return {
5541
+ evaluation_status: evaluationStatus,
5542
+ evidence_status: evidenceStatus,
5543
+ validation_outcome: validationOutcome,
5544
+ review_outcome: reviewOutcome,
5545
+ manual_intervention: manualIntervention,
5546
+ retry_count: input.retry_count ?? null,
5547
+ runtime_ms: input.runtime_ms ?? null,
5548
+ spend_units: input.spend_units ?? null,
5549
+ total_tokens: input.total_tokens ?? null,
5550
+ estimated_cost_usd: input.estimated_cost_usd ?? null,
5551
+ review_artifact_path: input.review_artifact_path ?? null,
5552
+ promotion_decision_path: input.promotion_decision_path ?? null,
5553
+ source_artifacts: input.source_artifacts ?? [],
5554
+ updated_at: input.updated_at ?? null
5555
+ };
5556
+ }
5557
+
5558
+ // src/human-question.ts
5559
+ var HUMAN_QUESTION_VERSION = "human-question-v1";
5560
+ var HUMAN_INTERRUPTION_POLICY_MAP = {
5561
+ level_1: {
5562
+ interruption_level: "level_1",
5563
+ work_continues: true,
5564
+ lane_pauses: false,
5565
+ run_halts: false,
5566
+ run_status: "running",
5567
+ blocked_task_status: "blocked"
5568
+ },
5569
+ level_2: {
5570
+ interruption_level: "level_2",
5571
+ work_continues: true,
5572
+ lane_pauses: true,
5573
+ run_halts: false,
5574
+ run_status: "paused_for_review",
5575
+ blocked_task_status: "blocked"
5576
+ },
5577
+ level_3: {
5578
+ interruption_level: "level_3",
5579
+ work_continues: false,
5580
+ lane_pauses: true,
5581
+ run_halts: true,
5582
+ run_status: "failed",
5583
+ blocked_task_status: "blocked"
5584
+ }
5585
+ };
5586
+ function buildHumanQuestionFixture(overrides = {}) {
5587
+ const status = overrides.status ?? "open";
5588
+ const createdAt = overrides.created_at ?? "2026-03-28T10:00:00.000Z";
5589
+ const updatedAt = overrides.updated_at ?? createdAt;
5590
+ return {
5591
+ version: HUMAN_QUESTION_VERSION,
5592
+ question_id: "hq-001",
5593
+ task_id: "factory-136",
5594
+ run_id: "orchestrator-run-001",
5595
+ lane_id: "default",
5596
+ status,
5597
+ category: "execution-ambiguity",
5598
+ summary: "Choose the safe fallback for the failing migration path.",
5599
+ question_detail: "The planned migration may drop existing local state. Which fallback should Devory use?",
5600
+ interruption_level: "level_2",
5601
+ input_mode: "local-api",
5602
+ fallback_behavior: "pause-affected-lane",
5603
+ timeout_policy: {
5604
+ timeout_seconds: 1800,
5605
+ on_timeout: "assume-default"
5606
+ },
5607
+ options: [
5608
+ {
5609
+ id: "keep-current",
5610
+ label: "Keep current behavior",
5611
+ description: "Continue with the existing migration path."
5612
+ },
5613
+ {
5614
+ id: "skip-migration",
5615
+ label: "Skip migration",
5616
+ description: "Bypass the migration and leave current state untouched."
5617
+ }
5618
+ ],
5619
+ recommended_option_id: "skip-migration",
5620
+ answer: status === "answered" ? {
5621
+ selected_option_id: "skip-migration",
5622
+ freeform_response: null,
5623
+ answered_by: "operator",
5624
+ answered_at: updatedAt
5625
+ } : null,
5626
+ created_at: createdAt,
5627
+ updated_at: updatedAt,
5628
+ audit_trail: status === "answered" ? [
5629
+ {
5630
+ event_type: "opened",
5631
+ timestamp: createdAt,
5632
+ actor: "devory",
5633
+ note: "Question opened during execution."
5634
+ },
5635
+ {
5636
+ event_type: "answered",
5637
+ timestamp: updatedAt,
5638
+ actor: "operator",
5639
+ note: "Selected recommended safe fallback."
5640
+ }
5641
+ ] : [
5642
+ {
5643
+ event_type: "opened",
5644
+ timestamp: createdAt,
5645
+ actor: "devory",
5646
+ note: "Question opened during execution."
5647
+ }
5648
+ ],
5649
+ ...overrides
5650
+ };
5651
+ }
5652
+
5653
+ // src/human-question-artifact.ts
5654
+ var HUMAN_QUESTION_ARTIFACT_DIR = "human-questions";
5655
+ var HUMAN_QUESTION_ARTIFACT_TYPE = "human-question";
5656
+ function normaliseQuestionId(questionId) {
5657
+ return questionId.trim().replace(/[^a-zA-Z0-9_-]+/g, "-");
5658
+ }
5659
+ function toFilesystemTimestamp(iso) {
5660
+ return iso.replace(/[:.]/g, "-");
5661
+ }
5662
+ function buildHumanQuestionArtifactRelativePath(record) {
5663
+ const taskScope = record.task_id?.trim() || "unscoped";
5664
+ const timestamp2 = toFilesystemTimestamp(record.created_at);
5665
+ const questionId = normaliseQuestionId(record.question_id);
5666
+ return `${HUMAN_QUESTION_ARTIFACT_DIR}/${taskScope}/${timestamp2}-${questionId}.json`;
5667
+ }
5668
+ function serializeHumanQuestionArtifact(record) {
5669
+ return `${JSON.stringify(record, null, 2)}
5670
+ `;
5671
+ }
5672
+ function isRecord5(value) {
5673
+ return value !== null && typeof value === "object" && !Array.isArray(value);
5674
+ }
5675
+ function parseHumanQuestionArtifact(raw) {
5676
+ let parsed;
5677
+ try {
5678
+ parsed = JSON.parse(raw);
5679
+ } catch {
5680
+ return null;
5681
+ }
5682
+ if (!isRecord5(parsed))
5683
+ return null;
5684
+ if (typeof parsed.question_id !== "string" || parsed.question_id.trim() === "")
5685
+ return null;
5686
+ if (typeof parsed.status !== "string" || parsed.status.trim() === "")
5687
+ return null;
5688
+ if (typeof parsed.created_at !== "string" || parsed.created_at.trim() === "")
5689
+ return null;
5690
+ if (typeof parsed.updated_at !== "string" || parsed.updated_at.trim() === "")
5691
+ return null;
5692
+ return parsed;
5693
+ }
5694
+ function extractHumanQuestionArtifactMetadata(raw) {
5695
+ const record = parseHumanQuestionArtifact(raw);
5696
+ if (!record)
5697
+ return null;
5698
+ return {
5699
+ question_id: record.question_id,
5700
+ task_id: record.task_id,
5701
+ run_id: record.run_id,
5702
+ status: record.status,
5703
+ created_at: record.created_at
5704
+ };
5705
+ }
5706
+
5707
+ // src/human-question-event.ts
5708
+ var HUMAN_QUESTION_EVENT_VERSION = "human-question-event-v1";
5709
+ var HUMAN_QUESTION_EVENT_ARTIFACT_DIR = "human-question-events";
5710
+ var HUMAN_QUESTION_DIGEST_VERSION = "human-question-digest-v1";
5711
+ var HUMAN_QUESTION_DIGEST_ARTIFACT_DIR = "human-question-digests";
5712
+ function toFilesystemTimestamp2(iso) {
5713
+ return iso.replace(/[:.]/g, "-");
5714
+ }
5715
+ function normaliseId(value) {
5716
+ return value.trim().replace(/[^a-zA-Z0-9_-]+/g, "-");
5717
+ }
5718
+ function answerStateFromStatus(status) {
5719
+ if (status === "answered")
5720
+ return "answered";
5721
+ if (status === "dismissed")
5722
+ return "dismissed";
5723
+ if (status === "expired")
5724
+ return "expired";
5725
+ return "waiting";
5726
+ }
5727
+ function buildHumanQuestionLifecycleEvent(input) {
5728
+ return {
5729
+ version: HUMAN_QUESTION_EVENT_VERSION,
5730
+ event_id: `${input.question.question_id}-${input.event_type}-${toFilesystemTimestamp2(input.timestamp)}`,
5731
+ event_type: input.event_type,
5732
+ question_id: input.question.question_id,
5733
+ task_id: input.question.task_id,
5734
+ run_id: input.question.run_id,
5735
+ lane_id: input.question.lane_id,
5736
+ status: input.question.status,
5737
+ interruption_level: input.question.interruption_level,
5738
+ fallback_behavior: input.question.fallback_behavior,
5739
+ summary: input.question.summary,
5740
+ timestamp: input.timestamp,
5741
+ answer_state: answerStateFromStatus(input.question.status),
5742
+ selected_option_id: input.question.answer?.selected_option_id ?? null,
5743
+ blocked_task_id: input.blocked_task_id ?? input.question.task_id ?? null,
5744
+ resumed_run_id: input.resumed_run_id ?? null,
5745
+ resulting_task_stage: input.resulting_task_stage ?? null,
5746
+ note: input.note ?? null
5747
+ };
5748
+ }
5749
+ function buildHumanQuestionLifecycleEventRelativePath(event) {
5750
+ const timestamp2 = toFilesystemTimestamp2(event.timestamp);
5751
+ return `${HUMAN_QUESTION_EVENT_ARTIFACT_DIR}/${timestamp2}-${normaliseId(event.question_id)}-${event.event_type}.json`;
5752
+ }
5753
+ function serializeHumanQuestionLifecycleEvent(event) {
5754
+ return `${JSON.stringify(event, null, 2)}
5755
+ `;
5756
+ }
5757
+ function isRecord6(value) {
5758
+ return value !== null && typeof value === "object" && !Array.isArray(value);
5759
+ }
5760
+ function parseHumanQuestionLifecycleEvent(raw) {
5761
+ let parsed;
5762
+ try {
5763
+ parsed = JSON.parse(raw);
5764
+ } catch {
5765
+ return null;
5766
+ }
5767
+ if (!isRecord6(parsed))
5768
+ return null;
5769
+ if (typeof parsed.event_id !== "string" || typeof parsed.question_id !== "string")
5770
+ return null;
5771
+ if (typeof parsed.event_type !== "string" || typeof parsed.timestamp !== "string")
5772
+ return null;
5773
+ return parsed;
5774
+ }
5775
+ function buildHumanQuestionDigest(questions, generatedAt) {
5776
+ const openQuestions = questions.filter((question) => question.status === "open").sort((left, right) => left.created_at.localeCompare(right.created_at));
5777
+ const entries = openQuestions.map((question) => ({
5778
+ question_id: question.question_id,
5779
+ task_id: question.task_id,
5780
+ run_id: question.run_id,
5781
+ lane_id: question.lane_id,
5782
+ interruption_level: question.interruption_level,
5783
+ category: question.category,
5784
+ summary: question.summary,
5785
+ created_at: question.created_at,
5786
+ age_minutes: Math.max(
5787
+ 0,
5788
+ Math.floor(
5789
+ (new Date(generatedAt).getTime() - new Date(question.created_at).getTime()) / 6e4
5790
+ )
5791
+ )
5792
+ }));
5793
+ const by_interruption_level = {};
5794
+ const by_run_id = {};
5795
+ const by_lane_id = {};
5796
+ for (const entry of entries) {
5797
+ by_interruption_level[entry.interruption_level] = (by_interruption_level[entry.interruption_level] ?? 0) + 1;
5798
+ by_run_id[entry.run_id ?? "unscoped"] = (by_run_id[entry.run_id ?? "unscoped"] ?? 0) + 1;
5799
+ by_lane_id[entry.lane_id ?? "unscoped"] = (by_lane_id[entry.lane_id ?? "unscoped"] ?? 0) + 1;
5800
+ }
5801
+ return {
5802
+ version: HUMAN_QUESTION_DIGEST_VERSION,
5803
+ generated_at: generatedAt,
5804
+ total_open_questions: entries.length,
5805
+ by_interruption_level,
5806
+ by_run_id,
5807
+ by_lane_id,
5808
+ entries
5809
+ };
5810
+ }
5811
+ function buildHumanQuestionDigestRelativePath(generatedAt) {
5812
+ return `${HUMAN_QUESTION_DIGEST_ARTIFACT_DIR}/${toFilesystemTimestamp2(generatedAt)}-digest.json`;
5813
+ }
5814
+ function serializeHumanQuestionDigest(digest) {
5815
+ return `${JSON.stringify(digest, null, 2)}
5816
+ `;
5817
+ }
5818
+ function renderHumanQuestionDigestMarkdown(digest) {
5819
+ const lines = [
5820
+ "---",
5821
+ `version: ${digest.version}`,
5822
+ `generated_at: ${digest.generated_at}`,
5823
+ `total_open_questions: ${digest.total_open_questions}`,
5824
+ "---",
5825
+ "",
5826
+ "# Human Question Digest",
5827
+ "",
5828
+ `Open questions: ${digest.total_open_questions}`,
5829
+ "",
5830
+ "## Open Questions",
5831
+ ""
5832
+ ];
5833
+ if (digest.entries.length === 0) {
5834
+ lines.push("- None");
5835
+ } else {
5836
+ for (const entry of digest.entries) {
5837
+ lines.push(
5838
+ `- ${entry.question_id} \xB7 ${entry.interruption_level} \xB7 ${entry.summary} \xB7 age ${entry.age_minutes}m \xB7 run ${entry.run_id ?? "unscoped"} \xB7 lane ${entry.lane_id ?? "unscoped"}`
5839
+ );
5840
+ }
5841
+ }
5842
+ lines.push("", "## By Interruption Level", "");
5843
+ if (Object.keys(digest.by_interruption_level).length === 0) {
5844
+ lines.push("- None");
5845
+ } else {
5846
+ for (const [key, count] of Object.entries(digest.by_interruption_level)) {
5847
+ lines.push(`- ${key}: ${count}`);
5848
+ }
5849
+ }
5850
+ return `${lines.join("\n")}
5851
+ `;
5852
+ }
5853
+
5854
+ // src/human-interruption-policy.ts
5855
+ var fs6 = __toESM(require("fs"));
5856
+ var path6 = __toESM(require("path"));
5857
+ var import_url4 = require("url");
5858
+ var import_meta4 = {};
5859
+ var MODULE_DIR4 = typeof __dirname === "string" ? __dirname : path6.dirname((0, import_url4.fileURLToPath)(import_meta4.url));
5860
+ var HUMAN_INTERRUPTION_POLICY_VERSION = "human-interruption-policy-v1";
5861
+ var HUMAN_INTERRUPTION_POLICY_FILENAME = "human-interruption-policy.json";
5862
+ var HUMAN_INTERRUPTION_POLICY_WORKSPACE_PATH = path6.join(
5863
+ "config",
5864
+ HUMAN_INTERRUPTION_POLICY_FILENAME
5865
+ );
5866
+ var VALID_HUMAN_NOTIFICATION_MODES = ["immediate", "digest"];
5867
+ var VALID_HUMAN_POLICY_THRESHOLD_KEYS = [
5868
+ "ambiguity",
5869
+ "confirmation",
5870
+ "approval",
5871
+ "destructive_change",
5872
+ "credentials",
5873
+ "external_side_effect"
5874
+ ];
5875
+ var DEFAULTS_PATH3 = path6.join(MODULE_DIR4, "defaults", HUMAN_INTERRUPTION_POLICY_FILENAME);
5876
+ var VALID_INTERRUPTION_LEVELS = ["level_1", "level_2", "level_3"];
5877
+ var VALID_INPUT_MODES = ["local-api", "cli", "digest"];
5878
+ var VALID_FALLBACK_BEHAVIORS = [
5879
+ "continue-other-work",
5880
+ "pause-affected-lane",
5881
+ "halt-run",
5882
+ "assume-default",
5883
+ "skip-task"
5884
+ ];
5885
+ function isRecord7(value) {
5886
+ return value !== null && typeof value === "object" && !Array.isArray(value);
5887
+ }
5888
+ function normalizeEnum(value, validValues) {
5889
+ if (typeof value !== "string")
5890
+ return null;
5891
+ const normalized = value.trim();
5892
+ return validValues.includes(normalized) ? normalized : null;
5893
+ }
5894
+ function normalizePositiveInteger2(value) {
5895
+ if (typeof value === "number" && Number.isInteger(value) && value > 0) {
5896
+ return value;
5897
+ }
5898
+ if (typeof value === "string" && value.trim() !== "") {
5899
+ const parsed = Number.parseInt(value.trim(), 10);
5900
+ if (Number.isInteger(parsed) && parsed > 0)
5901
+ return parsed;
5902
+ }
5903
+ return null;
5904
+ }
5905
+ function normalizeNullablePositiveInteger(value) {
5906
+ if (value === null)
5907
+ return null;
5908
+ const normalized = normalizePositiveInteger2(value);
5909
+ return normalized ?? void 0;
5910
+ }
5911
+ function normalizeInputModes(value) {
5912
+ if (!Array.isArray(value))
5913
+ return null;
5914
+ const normalized = value.map((item) => normalizeEnum(item, VALID_INPUT_MODES)).filter((item) => item !== null);
5915
+ return normalized.length > 0 ? [...new Set(normalized)] : null;
5916
+ }
5917
+ function normalizeHumanInterruptionPolicyOverrides(value) {
5918
+ if (!isRecord7(value)) {
5919
+ throw new Error("devory: human interruption policy config must be a JSON object");
5920
+ }
5921
+ const overrides = {};
5922
+ const defaultInterruptionLevel = normalizeEnum(
5923
+ value.default_interruption_level,
5924
+ VALID_INTERRUPTION_LEVELS
5925
+ );
5926
+ if (defaultInterruptionLevel) {
5927
+ overrides.default_interruption_level = defaultInterruptionLevel;
5928
+ }
5929
+ const defaultInputMode = normalizeEnum(value.default_input_mode, VALID_INPUT_MODES);
5930
+ if (defaultInputMode) {
5931
+ overrides.default_input_mode = defaultInputMode;
5932
+ }
5933
+ const allowedInputModes = normalizeInputModes(value.allowed_input_modes);
5934
+ if (allowedInputModes) {
5935
+ overrides.allowed_input_modes = allowedInputModes;
5936
+ }
5937
+ const defaultFallbackBehavior = normalizeEnum(
5938
+ value.default_fallback_behavior,
5939
+ VALID_FALLBACK_BEHAVIORS
5940
+ );
5941
+ if (defaultFallbackBehavior) {
5942
+ overrides.default_fallback_behavior = defaultFallbackBehavior;
5943
+ }
5944
+ const timeoutSeconds = normalizePositiveInteger2(value.timeout_seconds);
5945
+ if (timeoutSeconds !== null) {
5946
+ overrides.timeout_seconds = timeoutSeconds;
5947
+ }
5948
+ const timeoutOnExpiry = normalizeEnum(value.timeout_on_expiry, VALID_FALLBACK_BEHAVIORS);
5949
+ if (timeoutOnExpiry) {
5950
+ overrides.timeout_on_expiry = timeoutOnExpiry;
5951
+ }
5952
+ const notificationMode = normalizeEnum(value.notification_mode, VALID_HUMAN_NOTIFICATION_MODES);
5953
+ if (notificationMode) {
5954
+ overrides.notification_mode = notificationMode;
5955
+ }
5956
+ const digestCadenceMinutes = normalizeNullablePositiveInteger(value.digest_cadence_minutes);
5957
+ if (digestCadenceMinutes !== void 0) {
5958
+ overrides.digest_cadence_minutes = digestCadenceMinutes;
5959
+ }
5960
+ if (isRecord7(value.interruption_thresholds)) {
5961
+ const thresholdOverrides = {};
5962
+ for (const key of VALID_HUMAN_POLICY_THRESHOLD_KEYS) {
5963
+ const normalized = normalizeEnum(value.interruption_thresholds[key], VALID_INTERRUPTION_LEVELS);
5964
+ if (normalized) {
5965
+ thresholdOverrides[key] = normalized;
5966
+ }
5967
+ }
5968
+ if (Object.keys(thresholdOverrides).length > 0) {
5969
+ overrides.interruption_thresholds = thresholdOverrides;
5970
+ }
5971
+ }
5972
+ return overrides;
5973
+ }
5974
+ function applyHumanInterruptionPolicyOverrides(base, overrides) {
5975
+ const policy = {
5976
+ ...base,
5977
+ interruption_thresholds: {
5978
+ ...base.interruption_thresholds,
5979
+ ...overrides.interruption_thresholds
5980
+ }
5981
+ };
5982
+ if (overrides.default_interruption_level) {
5983
+ policy.default_interruption_level = overrides.default_interruption_level;
5984
+ }
5985
+ if (overrides.default_input_mode) {
5986
+ policy.default_input_mode = overrides.default_input_mode;
5987
+ }
5988
+ if (overrides.allowed_input_modes) {
5989
+ policy.allowed_input_modes = overrides.allowed_input_modes;
5990
+ }
5991
+ if (overrides.default_fallback_behavior) {
5992
+ policy.default_fallback_behavior = overrides.default_fallback_behavior;
5993
+ }
5994
+ if (overrides.timeout_seconds !== void 0) {
5995
+ policy.timeout_seconds = overrides.timeout_seconds;
5996
+ }
5997
+ if (overrides.timeout_on_expiry) {
5998
+ policy.timeout_on_expiry = overrides.timeout_on_expiry;
5999
+ }
6000
+ if (overrides.notification_mode) {
6001
+ policy.notification_mode = overrides.notification_mode;
6002
+ }
6003
+ if (overrides.digest_cadence_minutes !== void 0) {
6004
+ policy.digest_cadence_minutes = overrides.digest_cadence_minutes;
6005
+ }
6006
+ if (!policy.allowed_input_modes.includes(policy.default_input_mode)) {
6007
+ policy.allowed_input_modes = [...policy.allowed_input_modes, policy.default_input_mode];
6008
+ }
6009
+ if (policy.notification_mode === "immediate") {
6010
+ policy.digest_cadence_minutes = null;
6011
+ }
6012
+ return policy;
6013
+ }
6014
+ function loadDefaultHumanInterruptionPolicy() {
6015
+ const raw = fs6.readFileSync(DEFAULTS_PATH3, "utf-8");
6016
+ const parsed = JSON.parse(raw);
6017
+ const normalized = normalizeHumanInterruptionPolicyOverrides(parsed);
6018
+ const base = {
6019
+ version: HUMAN_INTERRUPTION_POLICY_VERSION,
6020
+ default_interruption_level: "level_1",
6021
+ default_input_mode: "local-api",
6022
+ allowed_input_modes: ["local-api", "cli", "digest"],
6023
+ default_fallback_behavior: "continue-other-work",
6024
+ timeout_seconds: 1800,
6025
+ timeout_on_expiry: "assume-default",
6026
+ notification_mode: "digest",
6027
+ digest_cadence_minutes: 30,
6028
+ interruption_thresholds: {
6029
+ ambiguity: "level_1",
6030
+ confirmation: "level_1",
6031
+ approval: "level_2",
6032
+ destructive_change: "level_3",
6033
+ credentials: "level_3",
6034
+ external_side_effect: "level_2"
6035
+ }
6036
+ };
6037
+ return applyHumanInterruptionPolicyOverrides(base, normalized);
6038
+ }
6039
+ function loadWorkspaceHumanInterruptionPolicy(factoryRoot) {
6040
+ const filePath = path6.join(factoryRoot, HUMAN_INTERRUPTION_POLICY_WORKSPACE_PATH);
6041
+ if (!fs6.existsSync(filePath)) {
6042
+ return null;
6043
+ }
6044
+ let parsed;
6045
+ try {
6046
+ parsed = JSON.parse(fs6.readFileSync(filePath, "utf-8"));
6047
+ } catch (error) {
6048
+ throw new Error(
6049
+ `devory: failed to parse ${HUMAN_INTERRUPTION_POLICY_WORKSPACE_PATH}: ${error instanceof Error ? error.message : String(error)}`
6050
+ );
6051
+ }
6052
+ return normalizeHumanInterruptionPolicyOverrides(parsed);
6053
+ }
6054
+ function getTaskHumanInterruptionPolicyOverrides(meta) {
6055
+ const overrides = {};
6056
+ const defaultInterruptionLevel = normalizeEnum(
6057
+ meta.human_default_interruption_level,
6058
+ VALID_INTERRUPTION_LEVELS
6059
+ );
6060
+ if (defaultInterruptionLevel) {
6061
+ overrides.default_interruption_level = defaultInterruptionLevel;
6062
+ }
6063
+ const defaultInputMode = normalizeEnum(meta.human_default_input_mode, VALID_INPUT_MODES);
6064
+ if (defaultInputMode) {
6065
+ overrides.default_input_mode = defaultInputMode;
6066
+ }
6067
+ const allowedInputModes = normalizeInputModes(meta.human_allowed_input_modes);
6068
+ if (allowedInputModes) {
6069
+ overrides.allowed_input_modes = allowedInputModes;
6070
+ }
6071
+ const defaultFallbackBehavior = normalizeEnum(
6072
+ meta.human_default_fallback_behavior,
6073
+ VALID_FALLBACK_BEHAVIORS
6074
+ );
6075
+ if (defaultFallbackBehavior) {
6076
+ overrides.default_fallback_behavior = defaultFallbackBehavior;
6077
+ }
6078
+ const timeoutSeconds = normalizePositiveInteger2(meta.human_timeout_seconds);
6079
+ if (timeoutSeconds !== null) {
6080
+ overrides.timeout_seconds = timeoutSeconds;
6081
+ }
6082
+ const timeoutOnExpiry = normalizeEnum(meta.human_timeout_on_expiry, VALID_FALLBACK_BEHAVIORS);
6083
+ if (timeoutOnExpiry) {
6084
+ overrides.timeout_on_expiry = timeoutOnExpiry;
6085
+ }
6086
+ const notificationMode = normalizeEnum(
6087
+ meta.human_notification_mode,
6088
+ VALID_HUMAN_NOTIFICATION_MODES
6089
+ );
6090
+ if (notificationMode) {
6091
+ overrides.notification_mode = notificationMode;
6092
+ }
6093
+ const digestCadenceMinutes = normalizeNullablePositiveInteger(meta.human_digest_cadence_minutes);
6094
+ if (digestCadenceMinutes !== void 0) {
6095
+ overrides.digest_cadence_minutes = digestCadenceMinutes;
6096
+ }
6097
+ const thresholdOverrides = {};
6098
+ for (const key of VALID_HUMAN_POLICY_THRESHOLD_KEYS) {
6099
+ const fieldName = `human_threshold_${key}`;
6100
+ const normalized = normalizeEnum(meta[fieldName], VALID_INTERRUPTION_LEVELS);
6101
+ if (normalized) {
6102
+ thresholdOverrides[key] = normalized;
6103
+ }
6104
+ }
6105
+ if (Object.keys(thresholdOverrides).length > 0) {
6106
+ overrides.interruption_thresholds = thresholdOverrides;
6107
+ }
6108
+ return overrides;
6109
+ }
6110
+ function resolveHumanInterruptionPolicy(factoryRoot, meta = {}) {
6111
+ let policy = loadDefaultHumanInterruptionPolicy();
6112
+ const appliedLayers = ["shipped-defaults"];
6113
+ const workspaceConfigPath = path6.join(factoryRoot, HUMAN_INTERRUPTION_POLICY_WORKSPACE_PATH);
6114
+ const workspaceOverrides = loadWorkspaceHumanInterruptionPolicy(factoryRoot);
6115
+ if (workspaceOverrides) {
6116
+ policy = applyHumanInterruptionPolicyOverrides(policy, workspaceOverrides);
6117
+ appliedLayers.push("workspace-config");
6118
+ }
6119
+ const taskOverrides = getTaskHumanInterruptionPolicyOverrides(meta);
6120
+ if (Object.keys(taskOverrides).length > 0) {
6121
+ policy = applyHumanInterruptionPolicyOverrides(policy, taskOverrides);
6122
+ appliedLayers.push("task-frontmatter");
6123
+ }
6124
+ return {
6125
+ policy,
6126
+ applied_layers: appliedLayers,
6127
+ workspace_config_path: fs6.existsSync(workspaceConfigPath) ? workspaceConfigPath : null
6128
+ };
6129
+ }
6130
+
6131
+ // src/slack-notification.ts
6132
+ var SLACK_NOTIFICATION_CONFIG_VERSION = "slack-notification-config-v1";
6133
+ var VALID_SLACK_TRANSPORT_KINDS = ["webhook", "bot-token"];
6134
+ var VALID_SLACK_DELIVERY_MODES = [
6135
+ "suppressed",
6136
+ "digest",
6137
+ "immediate",
6138
+ "urgent"
6139
+ ];
6140
+ var VALID_SLACK_DIGEST_GROUP_BY = [
6141
+ "interruption_level",
6142
+ "run",
6143
+ "lane",
6144
+ "age"
6145
+ ];
6146
+ function isRecord8(value) {
6147
+ return value !== null && typeof value === "object" && !Array.isArray(value);
6148
+ }
6149
+ function normalizeString2(value) {
6150
+ if (typeof value !== "string")
6151
+ return null;
6152
+ const normalized = value.trim();
6153
+ return normalized === "" ? null : normalized;
6154
+ }
6155
+ function normalizeBoolean(value, fallback) {
6156
+ return typeof value === "boolean" ? value : fallback;
6157
+ }
6158
+ function normalizePositiveInteger3(value, fallback) {
6159
+ if (value === null)
6160
+ return null;
6161
+ if (typeof value === "number" && Number.isInteger(value) && value > 0)
6162
+ return value;
6163
+ if (typeof value === "string" && value.trim() !== "") {
6164
+ const parsed = Number.parseInt(value.trim(), 10);
6165
+ if (Number.isInteger(parsed) && parsed > 0)
6166
+ return parsed;
6167
+ }
6168
+ return fallback;
6169
+ }
6170
+ function normalizeStringArray(value, fallback) {
6171
+ if (!Array.isArray(value))
6172
+ return [...fallback];
6173
+ const normalized = value.map((entry) => normalizeString2(entry)).filter((entry) => entry !== null);
6174
+ return normalized.length > 0 ? [...new Set(normalized)] : [...fallback];
6175
+ }
6176
+ function normalizeLevel(value, fallback) {
6177
+ return value === "level_1" || value === "level_2" || value === "level_3" ? value : fallback;
6178
+ }
6179
+ function normalizeDeliveryMode(value, fallback) {
6180
+ return value === "suppressed" || value === "digest" || value === "immediate" || value === "urgent" ? value : fallback;
6181
+ }
6182
+ function normalizeDigestGroupBy(value, fallback) {
6183
+ if (!Array.isArray(value))
6184
+ return [...fallback];
6185
+ const normalized = value.filter(
6186
+ (entry) => entry === "interruption_level" || entry === "run" || entry === "lane" || entry === "age"
6187
+ );
6188
+ return normalized.length > 0 ? [...new Set(normalized)] : [...fallback];
6189
+ }
6190
+ function normalizeTransport(value) {
6191
+ if (!isRecord8(value)) {
6192
+ return { kind: "webhook", webhook_url_env: "SLACK_WEBHOOK_URL" };
6193
+ }
6194
+ if (value.kind === "bot-token") {
6195
+ return {
6196
+ kind: "bot-token",
6197
+ bot_token_env: normalizeString2(value.bot_token_env) ?? "SLACK_BOT_TOKEN"
6198
+ };
6199
+ }
6200
+ return {
6201
+ kind: "webhook",
6202
+ webhook_url_env: normalizeString2(value.webhook_url_env) ?? "SLACK_WEBHOOK_URL"
6203
+ };
6204
+ }
6205
+ function normalizeSlackNotificationConfig(value) {
6206
+ if (!isRecord8(value)) {
6207
+ throw new Error("devory: slack notification config must be a JSON object");
6208
+ }
6209
+ const transport = normalizeTransport(value.transport);
6210
+ const defaultChannel = normalizeString2(value.default_channel) ?? "#devory-alerts";
6211
+ const dmRouting = isRecord8(value.dm_routing) ? value.dm_routing : {};
6212
+ const severityRouting = isRecord8(value.severity_routing) ? value.severity_routing : {};
6213
+ const severityLevelModes = isRecord8(severityRouting.level_modes) ? severityRouting.level_modes : {};
6214
+ const digest = isRecord8(value.digest) ? value.digest : {};
6215
+ return {
6216
+ version: SLACK_NOTIFICATION_CONFIG_VERSION,
6217
+ enabled: normalizeBoolean(value.enabled, true),
6218
+ transport,
6219
+ default_channel: defaultChannel,
6220
+ dm_routing: {
6221
+ enabled: normalizeBoolean(dmRouting.enabled, false),
6222
+ resolver_fields: normalizeStringArray(dmRouting.resolver_fields, [
6223
+ "slack_user_id",
6224
+ "owner",
6225
+ "assignee"
6226
+ ]),
6227
+ user_map: isRecord8(dmRouting.user_map) ? Object.fromEntries(
6228
+ Object.entries(dmRouting.user_map).map(([key, mapped]) => [key.trim(), normalizeString2(mapped)]).filter((entry) => entry[0] !== "" && entry[1] !== null)
6229
+ ) : {},
6230
+ fallback_to_default_channel: normalizeBoolean(dmRouting.fallback_to_default_channel, true)
6231
+ },
6232
+ severity_routing: {
6233
+ minimum_level: normalizeLevel(severityRouting.minimum_level, "level_1"),
6234
+ level_modes: {
6235
+ level_1: normalizeDeliveryMode(severityLevelModes["level_1"], "digest"),
6236
+ level_2: normalizeDeliveryMode(severityLevelModes["level_2"], "immediate"),
6237
+ level_3: normalizeDeliveryMode(severityLevelModes["level_3"], "urgent")
6238
+ }
6239
+ },
6240
+ digest: {
6241
+ enabled: normalizeBoolean(digest.enabled, true),
6242
+ default_channel: normalizeString2(digest.default_channel),
6243
+ cadence_minutes: normalizePositiveInteger3(digest.cadence_minutes, 30),
6244
+ max_entries: normalizePositiveInteger3(digest.max_entries, 20) ?? 20,
6245
+ group_by: normalizeDigestGroupBy(digest.group_by, ["interruption_level", "run"])
6246
+ }
3011
6247
  };
3012
- return `[devory] ${featureLabel[feature]} requires a Pro license \u2014 set DEVORY_LICENSE_KEY or create .devory/license to upgrade. This setting will be ignored on Core tier.`;
3013
6248
  }
3014
6249
  // Annotate the CommonJS export names for ESM import in node:
3015
6250
  0 && (module.exports = {
6251
+ ESCALATION_REASONS,
6252
+ EXECUTION_POLICY_FILENAME,
6253
+ EXECUTION_POLICY_VERSION,
6254
+ EXECUTION_POLICY_WORKSPACE_PATH,
6255
+ HUMAN_INTERRUPTION_POLICY_FILENAME,
6256
+ HUMAN_INTERRUPTION_POLICY_MAP,
6257
+ HUMAN_INTERRUPTION_POLICY_VERSION,
6258
+ HUMAN_INTERRUPTION_POLICY_WORKSPACE_PATH,
6259
+ HUMAN_QUESTION_ARTIFACT_DIR,
6260
+ HUMAN_QUESTION_ARTIFACT_TYPE,
6261
+ HUMAN_QUESTION_DIGEST_ARTIFACT_DIR,
6262
+ HUMAN_QUESTION_DIGEST_VERSION,
6263
+ HUMAN_QUESTION_EVENT_ARTIFACT_DIR,
6264
+ HUMAN_QUESTION_EVENT_VERSION,
6265
+ HUMAN_QUESTION_VERSION,
6266
+ PLANNING_DRAFT_COMMIT_STATES,
6267
+ PLANNING_DRAFT_CONTRACT_VERSION,
6268
+ PLANNING_DRAFT_KINDS,
6269
+ PLANNING_DRAFT_PERSISTENCE_MODES,
6270
+ PLANNING_DRAFT_VALIDATION_STATUSES,
6271
+ PROGRESS_EVENT_CATEGORIES,
6272
+ REQUIRED_FIELDS,
6273
+ RESUMABLE_RUN_STATUSES,
6274
+ REVIEW_CONTROL_ACTIONS,
6275
+ REVIEW_CONTROL_CONTRACT_VERSION,
6276
+ REVIEW_CONTROL_MECHANISMS,
6277
+ REVIEW_QUEUE_ITEM_KINDS,
6278
+ REVIEW_QUEUE_RUN_STATUSES,
6279
+ REVIEW_QUEUE_TASK_STAGES,
6280
+ REVIEW_QUEUE_TRIAGE_STAGES,
6281
+ ROUTING_DECISION_VERSION,
6282
+ RUN_LEDGER_VERSION,
6283
+ SLACK_NOTIFICATION_CONFIG_VERSION,
3016
6284
  STANDARDS_FILENAME,
6285
+ TASK_DRAFT_BODY_SECTION_ORDER,
6286
+ TASK_DRAFT_COMMIT_STAGES,
6287
+ TASK_DRAFT_OPTIONAL_FRONTMATTER_FIELDS,
6288
+ TASK_DRAFT_RENDER_CONTRACT_VERSION,
6289
+ TASK_DRAFT_REQUIRED_FRONTMATTER_FIELDS,
6290
+ TASK_MARKDOWN_FRONTMATTER_ORDER,
6291
+ TASK_MARKDOWN_RENDERER_VERSION,
6292
+ TASK_MARKDOWN_SECTION_ORDER,
6293
+ TASK_REVIEW_ACTIONS,
6294
+ TASK_REVIEW_ACTION_STAGE_MAP,
6295
+ UNATTENDED_CHECKPOINT_TRIGGERS,
6296
+ UNATTENDED_CHECKPOINT_VERSION,
6297
+ UNATTENDED_EXECUTION_CONTRACT_VERSION,
6298
+ UNATTENDED_RUN_STATUSES,
6299
+ UNATTENDED_STALL_POLICY_FILENAME,
6300
+ UNATTENDED_STALL_POLICY_VERSION,
6301
+ UNATTENDED_STALL_POLICY_WORKSPACE_PATH,
6302
+ VALID_HUMAN_NOTIFICATION_MODES,
6303
+ VALID_HUMAN_POLICY_THRESHOLD_KEYS,
6304
+ VALID_POLICY_ESCALATION_BEHAVIORS,
6305
+ VALID_ROUTING_CONTEXT_INTENSITIES,
6306
+ VALID_ROUTING_EXECUTION_PROFILES,
6307
+ VALID_ROUTING_PRIORITY_LEVELS,
6308
+ VALID_SLACK_DELIVERY_MODES,
6309
+ VALID_SLACK_DIGEST_GROUP_BY,
6310
+ VALID_SLACK_TRANSPORT_KINDS,
6311
+ WORKER_HEALTH_STATUSES,
6312
+ applyExecutionPolicyOverrides,
6313
+ applyHumanInterruptionPolicyOverrides,
6314
+ applyTaskDraftValidation,
6315
+ applyTaskRoutingOutcomeEvaluation,
6316
+ applyUnattendedStallPolicyOverrides,
6317
+ attachRoutingDecisionLinkage,
6318
+ buildEpicPlanningDraft,
6319
+ buildEpicPlanningDraftFixture,
6320
+ buildExecutionPolicyInjection,
6321
+ buildHumanQuestionArtifactRelativePath,
6322
+ buildHumanQuestionDigest,
6323
+ buildHumanQuestionDigestRelativePath,
6324
+ buildHumanQuestionFixture,
6325
+ buildHumanQuestionLifecycleEvent,
6326
+ buildHumanQuestionLifecycleEventRelativePath,
6327
+ buildMinimalTaskDraftFixture,
6328
+ buildPlanningDraftArtifactPath,
6329
+ buildPlanningDraftStorageRelativePath,
6330
+ buildRichTaskDraftFixture,
6331
+ buildRoutingDecisionId,
6332
+ buildRoutingOutcomeEvaluation,
6333
+ buildRunAttentionQueueItem,
6334
+ buildTaskDraftRenderFixture,
6335
+ buildTaskDraftTargetPath,
6336
+ buildTaskPlanningDraftFixture,
6337
+ buildTaskReviewQueueItem,
6338
+ buildTaskTriageQueueItem,
6339
+ clearLicenseCache,
6340
+ clearLicenseToken,
3017
6341
  detectTier,
6342
+ extractHumanQuestionArtifactMetadata,
3018
6343
  factoryPaths,
3019
6344
  findFactoryContextDir,
6345
+ getLicenseCacheFilePath,
6346
+ getLicenseFilePath,
6347
+ getLicenseStatus,
6348
+ getSupportedReviewActions,
6349
+ getTaskHumanInterruptionPolicyOverrides,
3020
6350
  isFeatureEnabled,
3021
6351
  loadBaseline,
6352
+ loadDefaultExecutionPolicy,
6353
+ loadDefaultHumanInterruptionPolicy,
6354
+ loadDefaultUnattendedStallPolicy,
3022
6355
  loadStandards,
6356
+ loadWorkspaceExecutionPolicy,
6357
+ loadWorkspaceHumanInterruptionPolicy,
6358
+ loadWorkspaceUnattendedStallPolicy,
3023
6359
  mergeStandards,
6360
+ normalizeExecutionPolicyOverrides,
6361
+ normalizeHumanInterruptionPolicyOverrides,
6362
+ normalizePlanningDraft,
6363
+ normalizeReviewQueueItem,
6364
+ normalizeRoutingInput,
6365
+ normalizeRunRecord,
6366
+ normalizeSlackNotificationConfig,
6367
+ normalizeTaskDraft,
6368
+ normalizeTaskRecord,
6369
+ normalizeUnattendedCheckpointArtifact,
6370
+ normalizeUnattendedExecutionSnapshot,
6371
+ normalizeUnattendedStallPolicyOverrides,
3024
6372
  parseFrontmatter,
6373
+ parseHumanQuestionArtifact,
6374
+ parseHumanQuestionLifecycleEvent,
6375
+ renderHumanQuestionDigestMarkdown,
6376
+ renderTaskDraftMarkdown,
6377
+ renderTaskDraftTarget,
6378
+ renderTaskMarkdown,
6379
+ renderTaskMarkdownTarget,
6380
+ renderTaskPlanningDraftTarget,
3025
6381
  resolveBaselinePath,
6382
+ resolveExecutionPolicy,
3026
6383
  resolveFactoryEnvironment,
3027
6384
  resolveFactoryMode,
3028
6385
  resolveFactoryRoot,
6386
+ resolveHumanInterruptionPolicy,
6387
+ resolveUnattendedStallPolicy,
6388
+ serializeHumanQuestionArtifact,
6389
+ serializeHumanQuestionDigest,
6390
+ serializeHumanQuestionLifecycleEvent,
6391
+ serializePlanningDraft,
3029
6392
  serializeStandardsAsDoctrine,
3030
- tierGateMessage
6393
+ tierGateMessage,
6394
+ toPlanningDraftValidationRecord,
6395
+ updateEpicPlanningDraft,
6396
+ validateTask,
6397
+ validateTaskBody,
6398
+ validateTaskDraft,
6399
+ validateTaskMarkdown,
6400
+ writeLicenseToken
3031
6401
  });
3032
6402
  /*! Bundled license information:
3033
6403