@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/README.md +12 -0
- package/dist/index.js +3464 -94
- package/package.json +13 -3
- package/src/defaults/execution-policy.json +72 -0
- package/src/defaults/human-interruption-policy.json +19 -0
- package/src/defaults/unattended-stall-policy.json +10 -0
- package/src/index.ts +342 -20
- package/src/factory-environment.test.ts +0 -99
- package/src/factory-environment.ts +0 -103
- package/src/license.ts +0 -152
- package/src/parse.test.ts +0 -161
- package/src/parse.ts +0 -103
- package/src/standards.ts +0 -283
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/
|
|
221
|
+
// src/license.ts
|
|
87
222
|
var fs = __toESM(require("fs"));
|
|
88
223
|
var path = __toESM(require("path"));
|
|
89
|
-
var
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
94
|
-
|
|
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
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
103
|
-
|
|
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
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
|
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(
|
|
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/
|
|
3436
|
+
// src/factory-environment.ts
|
|
2957
3437
|
var fs3 = __toESM(require("fs"));
|
|
2958
3438
|
var path3 = __toESM(require("path"));
|
|
2959
|
-
var
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
const
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
if (fs3.existsSync(
|
|
2970
|
-
|
|
2971
|
-
|
|
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
|
-
|
|
2976
|
-
|
|
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
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
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
|
-
|
|
2991
|
-
key,
|
|
3496
|
+
root,
|
|
2992
3497
|
source,
|
|
2993
|
-
|
|
3498
|
+
mode: resolveFactoryMode(env),
|
|
3499
|
+
paths: factoryPaths(root)
|
|
2994
3500
|
};
|
|
2995
3501
|
}
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
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
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
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
|
|