@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/package.json CHANGED
@@ -1,7 +1,11 @@
1
1
  {
2
2
  "name": "@devory/core",
3
- "version": "0.1.1",
3
+ "version": "0.3.1",
4
4
  "description": "Shared types, parsing utilities, and path configuration for AI Dev Factory",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/devoryai/devory"
8
+ },
5
9
  "main": "./dist/index.js",
6
10
  "types": "./src/index.ts",
7
11
  "exports": {
@@ -9,14 +13,20 @@
9
13
  },
10
14
  "files": [
11
15
  "dist/",
12
- "src/",
16
+ "src/index.ts",
17
+ "src/defaults/",
13
18
  "README.md"
14
19
  ],
15
20
  "scripts": {
16
21
  "build": "esbuild src/index.ts --bundle --outfile=dist/index.js --platform=node --format=cjs",
17
- "prepublishOnly": "npm run build"
22
+ "prepublishOnly": "npm run build",
23
+ "test": "tsx --test src/parse.test.ts src/license.test.ts src/factory-environment.test.ts src/execution-policy.test.ts src/run-ledger.test.ts src/task-draft.test.ts src/task-markdown-renderer.test.ts src/task-validation.test.ts src/planning-draft.test.ts src/review-control.test.ts src/unattended-execution.test.ts src/unattended-checkpoint.test.ts src/unattended-stall-policy.test.ts src/routing-input.test.ts src/routing-evaluation.test.ts src/human-question.test.ts src/human-question-event.test.ts src/human-interruption-policy.test.ts src/slack-notification.test.ts"
24
+ },
25
+ "dependencies": {
26
+ "js-yaml": "^4.1.0"
18
27
  },
19
28
  "devDependencies": {
29
+ "@types/js-yaml": "^4.0.9",
20
30
  "esbuild": "^0.20.0",
21
31
  "tsx": "^4.19.2",
22
32
  "typescript": "^5.7.3"
@@ -0,0 +1,72 @@
1
+ {
2
+ "commands": {
3
+ "allow": [
4
+ "git status",
5
+ "git diff",
6
+ "git add",
7
+ "npm run test",
8
+ "npm run validate:task"
9
+ ],
10
+ "require_approval": [
11
+ "git push",
12
+ "git commit",
13
+ "npm install",
14
+ "pnpm install",
15
+ "yarn install"
16
+ ],
17
+ "forbid": [
18
+ "git reset --hard",
19
+ "rm -rf /",
20
+ "sudo"
21
+ ]
22
+ },
23
+ "filesystem": {
24
+ "writable_roots": [
25
+ ".",
26
+ "tasks",
27
+ "runs",
28
+ "artifacts"
29
+ ],
30
+ "read_only_roots": [
31
+ ".git"
32
+ ],
33
+ "require_approval_outside_writable_roots": true
34
+ },
35
+ "network": {
36
+ "allow": false,
37
+ "allowed_hosts": [],
38
+ "require_approval_for_hosts": [
39
+ "*"
40
+ ]
41
+ },
42
+ "package_installs": {
43
+ "allow": false,
44
+ "allowed_managers": [],
45
+ "require_approval": true
46
+ },
47
+ "test_execution": {
48
+ "allow": true,
49
+ "allowed_commands": [
50
+ "npm run test",
51
+ "npm run validate:task"
52
+ ],
53
+ "require_approval_commands": []
54
+ },
55
+ "approval_required_actions": [
56
+ "write_outside_workspace",
57
+ "network_access",
58
+ "package_install",
59
+ "destructive_git",
60
+ "secret_access"
61
+ ],
62
+ "forbidden_actions": [
63
+ "destructive_delete",
64
+ "privilege_escalation",
65
+ "force_push_without_request"
66
+ ],
67
+ "escalation": {
68
+ "unmatched_command": "require_approval",
69
+ "out_of_policy_action": "halt_and_escalate",
70
+ "invalid_policy": "fallback_to_defaults"
71
+ }
72
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "version": "human-interruption-policy-v1",
3
+ "default_interruption_level": "level_1",
4
+ "default_input_mode": "local-api",
5
+ "allowed_input_modes": ["local-api", "cli", "digest"],
6
+ "default_fallback_behavior": "continue-other-work",
7
+ "timeout_seconds": 1800,
8
+ "timeout_on_expiry": "assume-default",
9
+ "notification_mode": "digest",
10
+ "digest_cadence_minutes": 30,
11
+ "interruption_thresholds": {
12
+ "ambiguity": "level_1",
13
+ "confirmation": "level_1",
14
+ "approval": "level_2",
15
+ "destructive_change": "level_3",
16
+ "credentials": "level_3",
17
+ "external_side_effect": "level_2"
18
+ }
19
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": "unattended-stall-policy-v1",
3
+ "heartbeat_stale_after_ms": 120000,
4
+ "heartbeat_missing_after_ms": 300000,
5
+ "progress_stalled_after_ms": 600000,
6
+ "waiting_progress_grace_ms": 1200000,
7
+ "looping_event_window": 6,
8
+ "looping_event_threshold": 4,
9
+ "repeated_failure_without_progress_threshold": 3
10
+ }
package/src/index.ts CHANGED
@@ -1,25 +1,23 @@
1
1
  /**
2
2
  * @devory/core — public API
3
3
  *
4
- * Shared types, parsing utilities, path configuration,
5
- * engineering standards, and license tier detection.
4
+ * Shared types, parsing utilities, and path configuration
5
+ * for the AI Dev Factory monorepo.
6
6
  */
7
7
 
8
8
  export { parseFrontmatter } from "./parse.ts";
9
- export type { TaskMeta, ParseResult } from "./parse.ts";
10
9
  export {
11
- factoryPaths,
12
- findFactoryContextDir,
13
- resolveFactoryEnvironment,
14
- resolveFactoryMode,
15
- resolveFactoryRoot,
16
- } from "./factory-environment.ts";
17
- export type {
18
- FactoryEnvironment,
19
- FactoryMode,
20
- FactoryPaths,
21
- FactoryRootSource,
22
- } from "./factory-environment.ts";
10
+ clearLicenseCache,
11
+ clearLicenseToken,
12
+ detectTier,
13
+ getLicenseCacheFilePath,
14
+ getLicenseFilePath,
15
+ getLicenseStatus,
16
+ isFeatureEnabled,
17
+ tierGateMessage,
18
+ writeLicenseToken,
19
+ } from "./license.ts";
20
+ export type { Tier, ProFeature, LicenseInfo, LicenseStatus } from "./license.ts";
23
21
  export {
24
22
  loadStandards,
25
23
  loadBaseline,
@@ -39,9 +37,333 @@ export type {
39
37
  StandardsSourceType,
40
38
  LoadedStandards,
41
39
  } from "./standards.ts";
40
+ export type { TaskMeta, ParseResult } from "./parse.ts";
42
41
  export {
43
- detectTier,
44
- isFeatureEnabled,
45
- tierGateMessage,
46
- } from "./license.ts";
47
- export type { Tier, ProFeature, LicenseInfo } from "./license.ts";
42
+ factoryPaths,
43
+ findFactoryContextDir,
44
+ resolveFactoryEnvironment,
45
+ resolveFactoryMode,
46
+ resolveFactoryRoot,
47
+ } from "./factory-environment.ts";
48
+ export {
49
+ applyTaskRoutingOutcomeEvaluation,
50
+ normalizeRunRecord,
51
+ normalizeTaskRecord,
52
+ RESUMABLE_RUN_STATUSES,
53
+ RUN_LEDGER_VERSION,
54
+ } from "./run-ledger.ts";
55
+ export {
56
+ applyExecutionPolicyOverrides,
57
+ buildExecutionPolicyInjection,
58
+ EXECUTION_POLICY_FILENAME,
59
+ EXECUTION_POLICY_VERSION,
60
+ EXECUTION_POLICY_WORKSPACE_PATH,
61
+ loadDefaultExecutionPolicy,
62
+ loadWorkspaceExecutionPolicy,
63
+ normalizeExecutionPolicyOverrides,
64
+ resolveExecutionPolicy,
65
+ VALID_POLICY_ESCALATION_BEHAVIORS,
66
+ } from "./execution-policy.ts";
67
+ export {
68
+ buildMinimalTaskDraftFixture,
69
+ buildRichTaskDraftFixture,
70
+ buildTaskDraftRenderFixture,
71
+ normalizeTaskDraft,
72
+ renderTaskDraftMarkdown,
73
+ renderTaskDraftTarget,
74
+ TASK_DRAFT_BODY_SECTION_ORDER,
75
+ TASK_DRAFT_OPTIONAL_FRONTMATTER_FIELDS,
76
+ TASK_DRAFT_RENDER_CONTRACT_VERSION,
77
+ TASK_DRAFT_REQUIRED_FRONTMATTER_FIELDS,
78
+ } from "./task-draft.ts";
79
+ export {
80
+ buildTaskDraftTargetPath,
81
+ renderTaskDraftMarkdown as renderTaskMarkdown,
82
+ renderTaskDraftTarget as renderTaskMarkdownTarget,
83
+ TASK_MARKDOWN_FRONTMATTER_ORDER,
84
+ TASK_MARKDOWN_RENDERER_VERSION,
85
+ TASK_MARKDOWN_SECTION_ORDER,
86
+ } from "./task-markdown-renderer.ts";
87
+ export {
88
+ applyTaskDraftValidation,
89
+ REQUIRED_FIELDS,
90
+ toPlanningDraftValidationRecord,
91
+ validateTask,
92
+ validateTaskBody,
93
+ validateTaskDraft,
94
+ validateTaskMarkdown,
95
+ } from "./task-validation.ts";
96
+ export {
97
+ buildEpicPlanningDraft,
98
+ buildEpicPlanningDraftFixture,
99
+ buildPlanningDraftArtifactPath,
100
+ buildPlanningDraftStorageRelativePath,
101
+ buildTaskPlanningDraftFixture,
102
+ normalizePlanningDraft,
103
+ PLANNING_DRAFT_COMMIT_STATES,
104
+ PLANNING_DRAFT_CONTRACT_VERSION,
105
+ PLANNING_DRAFT_KINDS,
106
+ PLANNING_DRAFT_PERSISTENCE_MODES,
107
+ PLANNING_DRAFT_VALIDATION_STATUSES,
108
+ renderTaskPlanningDraftTarget,
109
+ serializePlanningDraft,
110
+ TASK_DRAFT_COMMIT_STAGES,
111
+ updateEpicPlanningDraft,
112
+ } from "./planning-draft.ts";
113
+ export {
114
+ normalizeUnattendedExecutionSnapshot,
115
+ ESCALATION_REASONS,
116
+ PROGRESS_EVENT_CATEGORIES,
117
+ UNATTENDED_EXECUTION_CONTRACT_VERSION,
118
+ UNATTENDED_RUN_STATUSES,
119
+ WORKER_HEALTH_STATUSES,
120
+ } from "./unattended-execution.ts";
121
+ export {
122
+ normalizeUnattendedCheckpointArtifact,
123
+ UNATTENDED_CHECKPOINT_TRIGGERS,
124
+ UNATTENDED_CHECKPOINT_VERSION,
125
+ } from "./unattended-checkpoint.ts";
126
+ export {
127
+ buildRunAttentionQueueItem,
128
+ buildTaskReviewQueueItem,
129
+ buildTaskTriageQueueItem,
130
+ getSupportedReviewActions,
131
+ normalizeReviewQueueItem,
132
+ REVIEW_CONTROL_ACTIONS,
133
+ REVIEW_CONTROL_CONTRACT_VERSION,
134
+ REVIEW_CONTROL_MECHANISMS,
135
+ REVIEW_QUEUE_ITEM_KINDS,
136
+ REVIEW_QUEUE_RUN_STATUSES,
137
+ REVIEW_QUEUE_TASK_STAGES,
138
+ REVIEW_QUEUE_TRIAGE_STAGES,
139
+ TASK_REVIEW_ACTIONS,
140
+ TASK_REVIEW_ACTION_STAGE_MAP,
141
+ } from "./review-control.ts";
142
+ export {
143
+ applyUnattendedStallPolicyOverrides,
144
+ loadDefaultUnattendedStallPolicy,
145
+ loadWorkspaceUnattendedStallPolicy,
146
+ normalizeUnattendedStallPolicyOverrides,
147
+ resolveUnattendedStallPolicy,
148
+ UNATTENDED_STALL_POLICY_FILENAME,
149
+ UNATTENDED_STALL_POLICY_VERSION,
150
+ UNATTENDED_STALL_POLICY_WORKSPACE_PATH,
151
+ } from "./unattended-stall-policy.ts";
152
+ export {
153
+ normalizeRoutingInput,
154
+ VALID_ROUTING_CONTEXT_INTENSITIES,
155
+ VALID_ROUTING_EXECUTION_PROFILES,
156
+ VALID_ROUTING_PRIORITY_LEVELS,
157
+ } from "./routing-input.ts";
158
+ export {
159
+ attachRoutingDecisionLinkage,
160
+ buildRoutingDecisionId,
161
+ ROUTING_DECISION_VERSION,
162
+ } from "./routing-decision.ts";
163
+ export { buildRoutingOutcomeEvaluation } from "./routing-evaluation.ts";
164
+ export {
165
+ buildHumanQuestionFixture,
166
+ HUMAN_INTERRUPTION_POLICY_MAP,
167
+ HUMAN_QUESTION_VERSION,
168
+ } from "./human-question.ts";
169
+ export {
170
+ buildHumanQuestionArtifactRelativePath,
171
+ extractHumanQuestionArtifactMetadata,
172
+ HUMAN_QUESTION_ARTIFACT_DIR,
173
+ HUMAN_QUESTION_ARTIFACT_TYPE,
174
+ parseHumanQuestionArtifact,
175
+ serializeHumanQuestionArtifact,
176
+ } from "./human-question-artifact.ts";
177
+ export {
178
+ buildHumanQuestionDigest,
179
+ buildHumanQuestionDigestRelativePath,
180
+ buildHumanQuestionLifecycleEvent,
181
+ buildHumanQuestionLifecycleEventRelativePath,
182
+ HUMAN_QUESTION_DIGEST_ARTIFACT_DIR,
183
+ HUMAN_QUESTION_DIGEST_VERSION,
184
+ HUMAN_QUESTION_EVENT_ARTIFACT_DIR,
185
+ HUMAN_QUESTION_EVENT_VERSION,
186
+ parseHumanQuestionLifecycleEvent,
187
+ renderHumanQuestionDigestMarkdown,
188
+ serializeHumanQuestionDigest,
189
+ serializeHumanQuestionLifecycleEvent,
190
+ } from "./human-question-event.ts";
191
+ export {
192
+ applyHumanInterruptionPolicyOverrides,
193
+ getTaskHumanInterruptionPolicyOverrides,
194
+ HUMAN_INTERRUPTION_POLICY_FILENAME,
195
+ HUMAN_INTERRUPTION_POLICY_VERSION,
196
+ HUMAN_INTERRUPTION_POLICY_WORKSPACE_PATH,
197
+ loadDefaultHumanInterruptionPolicy,
198
+ loadWorkspaceHumanInterruptionPolicy,
199
+ normalizeHumanInterruptionPolicyOverrides,
200
+ resolveHumanInterruptionPolicy,
201
+ VALID_HUMAN_NOTIFICATION_MODES,
202
+ VALID_HUMAN_POLICY_THRESHOLD_KEYS,
203
+ } from "./human-interruption-policy.ts";
204
+ export {
205
+ normalizeSlackNotificationConfig,
206
+ SLACK_NOTIFICATION_CONFIG_VERSION,
207
+ VALID_SLACK_DELIVERY_MODES,
208
+ VALID_SLACK_DIGEST_GROUP_BY,
209
+ VALID_SLACK_TRANSPORT_KINDS,
210
+ } from "./slack-notification.ts";
211
+ export type {
212
+ FactoryEnvironment,
213
+ FactoryMode,
214
+ FactoryPaths,
215
+ FactoryRootSource,
216
+ } from "./factory-environment.ts";
217
+ export type {
218
+ CostEventRecord,
219
+ FailureRecord,
220
+ OutcomeEvidence,
221
+ ProgressEventRecord,
222
+ RunInterruptionState,
223
+ RoutingEvidenceRecord,
224
+ RoutingFallbackEvidence,
225
+ RoutingInputSnapshot,
226
+ RoutingLedger,
227
+ RoutingRetryEvidence,
228
+ RoutingSelectionEvidence,
229
+ RoutingTimingEvidence,
230
+ RoutingUsageEvidence,
231
+ RunLedgerSummary,
232
+ RunRecord,
233
+ TaskBlockKind,
234
+ TaskBlockState,
235
+ TaskRecord,
236
+ } from "./run-ledger.ts";
237
+ export type {
238
+ ExecutionCommandPolicy,
239
+ ExecutionFilesystemPolicy,
240
+ ExecutionNetworkPolicy,
241
+ ExecutionPackageInstallPolicy,
242
+ ExecutionPolicyEscalation,
243
+ ExecutionPolicyManifest,
244
+ ExecutionPolicyManifestOverrides,
245
+ ExecutionPolicyResolution,
246
+ ExecutionTestPolicy,
247
+ PolicyEscalationBehavior,
248
+ } from "./execution-policy.ts";
249
+ export type {
250
+ TaskDraft,
251
+ } from "./task-draft.ts";
252
+ export type {
253
+ TaskDraftValidationResult,
254
+ ValidationResult,
255
+ } from "./task-validation.ts";
256
+ export type {
257
+ CreateEpicPlanningDraftInput,
258
+ EpicPlanningDraft,
259
+ PlanningDraft,
260
+ PlanningDraftBase,
261
+ PlanningDraftCommitRecord,
262
+ PlanningDraftCommitState,
263
+ PlanningDraftKind,
264
+ PlanningDraftPersistenceMode,
265
+ PlanningDraftStorageRecord,
266
+ PlanningDraftValidationRecord,
267
+ PlanningDraftValidationStatus,
268
+ RenderedTaskDraftTarget,
269
+ TaskDraftCommitStage,
270
+ TaskPlanningDraft,
271
+ UpdateEpicPlanningDraftInput,
272
+ } from "./planning-draft.ts";
273
+ export type {
274
+ ReviewControlAction,
275
+ ReviewControlMechanism,
276
+ ReviewQueueBaseItem,
277
+ ReviewQueueItem,
278
+ ReviewQueueItemKind,
279
+ ReviewQueueRunSource,
280
+ ReviewQueueTaskSource,
281
+ RunAttentionQueueItem,
282
+ TaskReviewAction,
283
+ TaskReviewQueueItem,
284
+ TaskTriageQueueItem,
285
+ } from "./review-control.ts";
286
+ export type {
287
+ UnattendedCheckpointArtifact,
288
+ UnattendedCheckpointArtifactReferences,
289
+ UnattendedCheckpointPolicySnapshot,
290
+ UnattendedCheckpointTrigger,
291
+ } from "./unattended-checkpoint.ts";
292
+ export type {
293
+ CheckpointReferenceRecord,
294
+ EscalationReason,
295
+ EscalationRecord,
296
+ ProgressEventCategory,
297
+ ProgressPointerRecord,
298
+ RecoveryAttemptRecord,
299
+ UnattendedExecutionSnapshot,
300
+ UnattendedRunStatus,
301
+ WorkerHeartbeatRecord,
302
+ WorkerHealthStatus,
303
+ } from "./unattended-execution.ts";
304
+ export type {
305
+ UnattendedStallPolicy,
306
+ UnattendedStallPolicyOverrides,
307
+ UnattendedStallPolicyResolution,
308
+ } from "./unattended-stall-policy.ts";
309
+ export type {
310
+ RoutingDecisionFallbackPath,
311
+ RoutingDecisionLinkage,
312
+ RoutingDecisionRejectedCandidate,
313
+ RoutingDecisionStatus,
314
+ UnifiedRoutingDecision,
315
+ } from "./routing-decision.ts";
316
+ export type {
317
+ BuildRoutingOutcomeEvaluationInput,
318
+ RoutingEvaluationStatus,
319
+ RoutingEvidenceStatus,
320
+ RoutingManualIntervention,
321
+ RoutingOutcomeEvaluation,
322
+ RoutingReviewOutcome,
323
+ RoutingValidationOutcome,
324
+ } from "./routing-evaluation.ts";
325
+ export type {
326
+ HumanInterruptionLevel,
327
+ HumanQuestionAnswerPayload,
328
+ HumanQuestionAuditEvent,
329
+ HumanQuestionFallbackBehavior,
330
+ HumanQuestionInputMode,
331
+ HumanQuestionOption,
332
+ HumanQuestionRecord,
333
+ HumanQuestionStatus,
334
+ HumanQuestionTimeoutPolicy,
335
+ InterruptionPolicyMapping,
336
+ } from "./human-question.ts";
337
+ export type { HumanQuestionArtifactMetadata } from "./human-question-artifact.ts";
338
+ export type {
339
+ HumanQuestionDigestArtifact,
340
+ HumanQuestionDigestEntry,
341
+ HumanQuestionLifecycleEvent,
342
+ HumanQuestionLifecycleEventType,
343
+ } from "./human-question-event.ts";
344
+ export type {
345
+ HumanInterruptionPolicy,
346
+ HumanInterruptionPolicyOverrides,
347
+ HumanInterruptionPolicyResolution,
348
+ HumanNotificationMode,
349
+ HumanPolicyThresholdKey,
350
+ } from "./human-interruption-policy.ts";
351
+ export type {
352
+ SlackDeliveryMode,
353
+ SlackDeliveryRecordLink,
354
+ SlackDeliveryRequest,
355
+ SlackDigestConfig,
356
+ SlackDigestGroupBy,
357
+ SlackDmRoutingConfig,
358
+ SlackNotificationConfig,
359
+ SlackSeverityRoutingConfig,
360
+ SlackTransportConfig,
361
+ SlackTransportKind,
362
+ } from "./slack-notification.ts";
363
+ export type {
364
+ NormalizedRoutingInput,
365
+ RoutingContextIntensity,
366
+ RoutingExecutionProfile,
367
+ RoutingPriorityLevel,
368
+ RoutingRuntimeContext,
369
+ } from "./routing-input.ts";
@@ -1,99 +0,0 @@
1
- import { afterEach, beforeEach, describe, test } from "node:test";
2
- import assert from "node:assert/strict";
3
- import * as fs from "fs";
4
- import * as os from "os";
5
- import * as path from "path";
6
-
7
- import {
8
- factoryPaths,
9
- findFactoryContextDir,
10
- resolveFactoryEnvironment,
11
- resolveFactoryMode,
12
- resolveFactoryRoot,
13
- } from "./factory-environment.ts";
14
-
15
- let tmpDir: string;
16
-
17
- beforeEach(() => {
18
- tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "factory-env-test-"));
19
- delete process.env.DEVORY_FACTORY_ROOT;
20
- delete process.env.FACTORY_ROOT;
21
- delete process.env.DEVORY_FACTORY_MODE;
22
- delete process.env.FACTORY_MODE;
23
- delete process.env.DEVORY_REMOTE_FACTORY_URL;
24
- delete process.env.FACTORY_REMOTE_URL;
25
- });
26
-
27
- afterEach(() => {
28
- fs.rmSync(tmpDir, { recursive: true, force: true });
29
- delete process.env.DEVORY_FACTORY_ROOT;
30
- delete process.env.FACTORY_ROOT;
31
- delete process.env.DEVORY_FACTORY_MODE;
32
- delete process.env.FACTORY_MODE;
33
- delete process.env.DEVORY_REMOTE_FACTORY_URL;
34
- delete process.env.FACTORY_REMOTE_URL;
35
- });
36
-
37
- describe("findFactoryContextDir", () => {
38
- test("returns the containing directory when marker exists", () => {
39
- fs.writeFileSync(path.join(tmpDir, "FACTORY_CONTEXT.md"), "# context");
40
- assert.equal(findFactoryContextDir(tmpDir), tmpDir);
41
- });
42
-
43
- test("walks up parent directories to find the factory marker", () => {
44
- fs.writeFileSync(path.join(tmpDir, "FACTORY_CONTEXT.md"), "# context");
45
- const nested = path.join(tmpDir, "nested", "deep");
46
- fs.mkdirSync(nested, { recursive: true });
47
- assert.equal(findFactoryContextDir(nested), tmpDir);
48
- });
49
- });
50
-
51
- describe("resolveFactoryRoot", () => {
52
- test("uses DEVORY_FACTORY_ROOT first", () => {
53
- process.env.DEVORY_FACTORY_ROOT = "/explicit/path";
54
- process.env.FACTORY_ROOT = "/legacy/path";
55
- assert.deepEqual(resolveFactoryRoot(tmpDir), {
56
- root: "/explicit/path",
57
- source: "env:DEVORY_FACTORY_ROOT",
58
- });
59
- });
60
-
61
- test("walks to the factory marker when env vars are absent", () => {
62
- fs.writeFileSync(path.join(tmpDir, "FACTORY_CONTEXT.md"), "# context");
63
- const nested = path.join(tmpDir, "deep");
64
- fs.mkdirSync(nested);
65
- assert.deepEqual(resolveFactoryRoot(nested), {
66
- root: tmpDir,
67
- source: "git-walk",
68
- });
69
- });
70
- });
71
-
72
- describe("resolveFactoryMode", () => {
73
- test("defaults to local", () => {
74
- assert.equal(resolveFactoryMode(), "local");
75
- });
76
-
77
- test("honors explicit hosted mode", () => {
78
- process.env.DEVORY_FACTORY_MODE = "hosted";
79
- assert.equal(resolveFactoryMode(), "hosted");
80
- });
81
-
82
- test("treats remote url configuration as hosted mode", () => {
83
- process.env.DEVORY_REMOTE_FACTORY_URL = "https://factory.example.com";
84
- assert.equal(resolveFactoryMode(), "hosted");
85
- });
86
- });
87
-
88
- describe("resolveFactoryEnvironment", () => {
89
- test("returns root, mode, and derived paths together", () => {
90
- fs.writeFileSync(path.join(tmpDir, "FACTORY_CONTEXT.md"), "# context");
91
- process.env.DEVORY_FACTORY_MODE = "hosted";
92
-
93
- const result = resolveFactoryEnvironment(tmpDir);
94
- assert.equal(result.root, tmpDir);
95
- assert.equal(result.source, "git-walk");
96
- assert.equal(result.mode, "hosted");
97
- assert.deepEqual(result.paths, factoryPaths(tmpDir));
98
- });
99
- });