@weldr/runr 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,130 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
- import fs from 'node:fs';
3
- import path from 'node:path';
4
- import os from 'node:os';
5
- import { writeContextPackArtifact, readContextPackArtifact, formatContextPackStatus } from '../artifact.js';
6
- describe('writeContextPackArtifact', () => {
7
- let tempDir;
8
- beforeEach(() => {
9
- tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'artifact-test-'));
10
- });
11
- afterEach(() => {
12
- fs.rmSync(tempDir, { recursive: true, force: true });
13
- });
14
- it('writes full pack when enabled', () => {
15
- const pack = {
16
- version: 1,
17
- generated_at: '2025-12-24T01:00:00.000Z',
18
- verification: {
19
- tier0: ['pnpm lint'],
20
- tier1: [],
21
- tier2: []
22
- },
23
- reference_files: [],
24
- scope: {
25
- allowlist: ['src/**'],
26
- denylist: []
27
- },
28
- patterns: {
29
- tsconfig: null,
30
- eslint: null,
31
- package_json: null
32
- },
33
- blockers: {
34
- scope_violations: [],
35
- lockfile_restrictions: false,
36
- common_errors: []
37
- }
38
- };
39
- writeContextPackArtifact(tempDir, pack);
40
- const artifactPath = path.join(tempDir, 'artifacts', 'context-pack.json');
41
- expect(fs.existsSync(artifactPath)).toBe(true);
42
- const content = JSON.parse(fs.readFileSync(artifactPath, 'utf-8'));
43
- expect(content.enabled).toBe(true);
44
- expect(content.pack_version).toBe(1);
45
- expect(content.generated_at).toBe('2025-12-24T01:00:00.000Z');
46
- expect(content.estimated_tokens).toBeGreaterThan(0);
47
- expect(content.verification.tier0).toEqual(['pnpm lint']);
48
- });
49
- it('writes disabled stub when pack is null', () => {
50
- writeContextPackArtifact(tempDir, null);
51
- const artifactPath = path.join(tempDir, 'artifacts', 'context-pack.json');
52
- expect(fs.existsSync(artifactPath)).toBe(true);
53
- const content = JSON.parse(fs.readFileSync(artifactPath, 'utf-8'));
54
- expect(content.enabled).toBe(false);
55
- expect(content.pack_version).toBe(1);
56
- expect(content.generated_at).toBeDefined();
57
- expect(content.verification).toBeUndefined();
58
- });
59
- it('creates artifacts directory if missing', () => {
60
- const artifactsDir = path.join(tempDir, 'artifacts');
61
- expect(fs.existsSync(artifactsDir)).toBe(false);
62
- writeContextPackArtifact(tempDir, null);
63
- expect(fs.existsSync(artifactsDir)).toBe(true);
64
- });
65
- });
66
- describe('readContextPackArtifact', () => {
67
- let tempDir;
68
- beforeEach(() => {
69
- tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'artifact-test-'));
70
- });
71
- afterEach(() => {
72
- fs.rmSync(tempDir, { recursive: true, force: true });
73
- });
74
- it('returns null for missing file', () => {
75
- const result = readContextPackArtifact(tempDir);
76
- expect(result).toBeNull();
77
- });
78
- it('parses existing artifact', () => {
79
- const artifactsDir = path.join(tempDir, 'artifacts');
80
- fs.mkdirSync(artifactsDir, { recursive: true });
81
- const artifact = {
82
- enabled: true,
83
- pack_version: 1,
84
- generated_at: '2025-12-24T01:00:00.000Z',
85
- estimated_tokens: 500
86
- };
87
- fs.writeFileSync(path.join(artifactsDir, 'context-pack.json'), JSON.stringify(artifact));
88
- const result = readContextPackArtifact(tempDir);
89
- expect(result).not.toBeNull();
90
- expect(result?.enabled).toBe(true);
91
- expect(result?.estimated_tokens).toBe(500);
92
- });
93
- it('returns null for invalid JSON', () => {
94
- const artifactsDir = path.join(tempDir, 'artifacts');
95
- fs.mkdirSync(artifactsDir, { recursive: true });
96
- fs.writeFileSync(path.join(artifactsDir, 'context-pack.json'), 'not json');
97
- const result = readContextPackArtifact(tempDir);
98
- expect(result).toBeNull();
99
- });
100
- });
101
- describe('formatContextPackStatus', () => {
102
- it('formats null as not found', () => {
103
- expect(formatContextPackStatus(null)).toBe('context_pack: (not found)');
104
- });
105
- it('formats disabled artifact', () => {
106
- const artifact = {
107
- enabled: false,
108
- pack_version: 1,
109
- generated_at: '2025-12-24T01:00:00.000Z'
110
- };
111
- expect(formatContextPackStatus(artifact)).toBe('context_pack: disabled');
112
- });
113
- it('formats enabled artifact with tokens', () => {
114
- const artifact = {
115
- enabled: true,
116
- pack_version: 1,
117
- generated_at: '2025-12-24T01:00:00.000Z',
118
- estimated_tokens: 493
119
- };
120
- expect(formatContextPackStatus(artifact)).toBe('context_pack: present (493 tokens)');
121
- });
122
- it('handles missing token count', () => {
123
- const artifact = {
124
- enabled: true,
125
- pack_version: 1,
126
- generated_at: '2025-12-24T01:00:00.000Z'
127
- };
128
- expect(formatContextPackStatus(artifact)).toBe('context_pack: present (? tokens)');
129
- });
130
- });
@@ -1,191 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import path from 'node:path';
3
- import { buildContextPack, formatContextPackForPrompt, estimatePackTokens } from '../pack.js';
4
- // Use the actual repo root for integration-style tests
5
- const REPO_ROOT = path.resolve(__dirname, '../../..');
6
- describe('buildContextPack', () => {
7
- describe('verification commands', () => {
8
- it('extracts verification commands from config', () => {
9
- const pack = buildContextPack({
10
- repoRoot: REPO_ROOT,
11
- targetRoot: 'apps/tactical-grid',
12
- config: {
13
- verification: {
14
- tier0: ['pnpm lint', 'pnpm typecheck'],
15
- tier1: ['pnpm test'],
16
- tier2: []
17
- }
18
- }
19
- });
20
- expect(pack.verification.tier0).toEqual(['pnpm lint', 'pnpm typecheck']);
21
- expect(pack.verification.tier1).toEqual(['pnpm test']);
22
- expect(pack.verification.tier2).toEqual([]);
23
- });
24
- it('handles missing verification config', () => {
25
- const pack = buildContextPack({
26
- repoRoot: REPO_ROOT,
27
- targetRoot: 'apps/tactical-grid',
28
- config: {}
29
- });
30
- expect(pack.verification.tier0).toEqual([]);
31
- expect(pack.verification.tier1).toEqual([]);
32
- });
33
- });
34
- describe('reference files', () => {
35
- it('resolves RNG pattern reference', () => {
36
- const pack = buildContextPack({
37
- repoRoot: REPO_ROOT,
38
- targetRoot: 'apps/tactical-grid',
39
- config: {},
40
- references: [{ pattern: 'RNG pattern from deckbuilder' }]
41
- });
42
- expect(pack.reference_files.length).toBeGreaterThan(0);
43
- const rngRef = pack.reference_files.find((r) => r.path.includes('rng.ts'));
44
- expect(rngRef).toBeDefined();
45
- expect(rngRef?.content).toContain('nextInt');
46
- expect(rngRef?.content).toContain('1103515245'); // LCG constant
47
- });
48
- it('resolves explicit hint path', () => {
49
- const pack = buildContextPack({
50
- repoRoot: REPO_ROOT,
51
- targetRoot: 'apps/tactical-grid',
52
- config: {},
53
- references: [
54
- {
55
- pattern: 'custom reference',
56
- hint: 'apps/deckbuilder/src/engine/rng.ts'
57
- }
58
- ]
59
- });
60
- expect(pack.reference_files.length).toBe(1);
61
- expect(pack.reference_files[0].path).toBe('apps/deckbuilder/src/engine/rng.ts');
62
- expect(pack.reference_files[0].reason).toBe('custom reference');
63
- });
64
- it('handles unknown pattern gracefully', () => {
65
- const pack = buildContextPack({
66
- repoRoot: REPO_ROOT,
67
- targetRoot: 'apps/tactical-grid',
68
- config: {},
69
- references: [{ pattern: 'nonexistent magic pattern' }]
70
- });
71
- expect(pack.reference_files).toEqual([]);
72
- });
73
- });
74
- describe('scope constraints', () => {
75
- it('extracts scope from config', () => {
76
- const pack = buildContextPack({
77
- repoRoot: REPO_ROOT,
78
- targetRoot: 'apps/tactical-grid',
79
- config: {
80
- scope: {
81
- allowlist: ['apps/tactical-grid/**'],
82
- denylist: ['**/node_modules/**']
83
- }
84
- }
85
- });
86
- expect(pack.scope.allowlist).toEqual(['apps/tactical-grid/**']);
87
- expect(pack.scope.denylist).toEqual(['**/node_modules/**']);
88
- });
89
- });
90
- describe('config patterns', () => {
91
- it('finds nearest tsconfig.json', () => {
92
- const pack = buildContextPack({
93
- repoRoot: REPO_ROOT,
94
- targetRoot: 'apps/deckbuilder',
95
- config: {}
96
- });
97
- expect(pack.patterns.tsconfig).not.toBeNull();
98
- expect(pack.patterns.tsconfig?.path).toContain('tsconfig.json');
99
- expect(pack.patterns.tsconfig?.content).toContain('compilerOptions');
100
- });
101
- it('finds nearest eslint config', () => {
102
- const pack = buildContextPack({
103
- repoRoot: REPO_ROOT,
104
- targetRoot: 'apps/deckbuilder',
105
- config: {}
106
- });
107
- expect(pack.patterns.eslint).not.toBeNull();
108
- expect(pack.patterns.eslint?.path).toMatch(/eslint\.config/);
109
- });
110
- it('finds nearest package.json', () => {
111
- const pack = buildContextPack({
112
- repoRoot: REPO_ROOT,
113
- targetRoot: 'apps/deckbuilder',
114
- config: {}
115
- });
116
- expect(pack.patterns.package_json).not.toBeNull();
117
- expect(pack.patterns.package_json?.content).toContain('scripts');
118
- });
119
- it('finds config even for nonexistent target (upward search or fallback)', () => {
120
- const pack = buildContextPack({
121
- repoRoot: REPO_ROOT,
122
- targetRoot: 'apps/nonexistent-app',
123
- config: {}
124
- });
125
- // Should find config via upward search (repo root) or fallback to deckbuilder
126
- expect(pack.patterns.tsconfig).not.toBeNull();
127
- expect(pack.patterns.tsconfig?.content).toContain('compilerOptions');
128
- });
129
- });
130
- describe('version and metadata', () => {
131
- it('includes version 1', () => {
132
- const pack = buildContextPack({
133
- repoRoot: REPO_ROOT,
134
- targetRoot: 'apps/tactical-grid',
135
- config: {}
136
- });
137
- expect(pack.version).toBe(1);
138
- });
139
- it('includes generated_at timestamp', () => {
140
- const pack = buildContextPack({
141
- repoRoot: REPO_ROOT,
142
- targetRoot: 'apps/tactical-grid',
143
- config: {}
144
- });
145
- expect(pack.generated_at).toMatch(/^\d{4}-\d{2}-\d{2}T/);
146
- });
147
- });
148
- });
149
- describe('formatContextPackForPrompt', () => {
150
- it('formats verification commands', () => {
151
- const pack = buildContextPack({
152
- repoRoot: REPO_ROOT,
153
- targetRoot: 'apps/tactical-grid',
154
- config: {
155
- verification: {
156
- tier0: ['pnpm lint', 'pnpm typecheck'],
157
- tier1: ['pnpm test']
158
- }
159
- }
160
- });
161
- const formatted = formatContextPackForPrompt(pack);
162
- expect(formatted).toContain('Verification Commands');
163
- expect(formatted).toContain('tier0: pnpm lint && pnpm typecheck');
164
- expect(formatted).toContain('tier1: pnpm test');
165
- });
166
- it('includes reference file content', () => {
167
- const pack = buildContextPack({
168
- repoRoot: REPO_ROOT,
169
- targetRoot: 'apps/tactical-grid',
170
- config: {},
171
- references: [{ pattern: 'RNG pattern' }]
172
- });
173
- const formatted = formatContextPackForPrompt(pack);
174
- expect(formatted).toContain('Reference Files');
175
- expect(formatted).toContain('nextInt');
176
- });
177
- });
178
- describe('estimatePackTokens', () => {
179
- it('estimates token count based on character length', () => {
180
- const pack = buildContextPack({
181
- repoRoot: REPO_ROOT,
182
- targetRoot: 'apps/tactical-grid',
183
- config: {
184
- verification: { tier0: ['pnpm lint'] }
185
- }
186
- });
187
- const tokens = estimatePackTokens(pack);
188
- expect(tokens).toBeGreaterThan(0);
189
- expect(tokens).toBeLessThan(10000); // Sanity check
190
- });
191
- });
@@ -1,116 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { compareFingerprints } from '../fingerprint.js';
3
- function makeFingerprint(overrides = {}) {
4
- return {
5
- node_version: 'v20.0.0',
6
- package_manager: 'npm',
7
- lockfile_hash: 'abc123def456',
8
- worker_versions: {
9
- codex: 'codex-cli 0.70.0',
10
- claude: '2.0.50 (Claude Code)'
11
- },
12
- created_at: '2025-01-01T00:00:00.000Z',
13
- ...overrides
14
- };
15
- }
16
- describe('compareFingerprints', () => {
17
- it('returns empty array when fingerprints match', () => {
18
- const original = makeFingerprint();
19
- const current = makeFingerprint();
20
- const diffs = compareFingerprints(original, current);
21
- expect(diffs).toEqual([]);
22
- });
23
- it('detects node version change', () => {
24
- const original = makeFingerprint({ node_version: 'v20.0.0' });
25
- const current = makeFingerprint({ node_version: 'v22.0.0' });
26
- const diffs = compareFingerprints(original, current);
27
- expect(diffs).toHaveLength(1);
28
- expect(diffs[0]).toEqual({
29
- field: 'node_version',
30
- original: 'v20.0.0',
31
- current: 'v22.0.0'
32
- });
33
- });
34
- it('detects package manager change', () => {
35
- const original = makeFingerprint({ package_manager: 'npm' });
36
- const current = makeFingerprint({ package_manager: 'pnpm' });
37
- const diffs = compareFingerprints(original, current);
38
- expect(diffs).toHaveLength(1);
39
- expect(diffs[0].field).toBe('package_manager');
40
- });
41
- it('detects lockfile hash change', () => {
42
- const original = makeFingerprint({ lockfile_hash: 'abc123' });
43
- const current = makeFingerprint({ lockfile_hash: 'def456' });
44
- const diffs = compareFingerprints(original, current);
45
- expect(diffs).toHaveLength(1);
46
- expect(diffs[0].field).toBe('lockfile_hash');
47
- });
48
- it('detects worker version change', () => {
49
- const original = makeFingerprint({
50
- worker_versions: { codex: '0.70.0', claude: '2.0.50' }
51
- });
52
- const current = makeFingerprint({
53
- worker_versions: { codex: '0.80.0', claude: '2.0.50' }
54
- });
55
- const diffs = compareFingerprints(original, current);
56
- expect(diffs).toHaveLength(1);
57
- expect(diffs[0].field).toBe('worker:codex');
58
- expect(diffs[0].original).toBe('0.70.0');
59
- expect(diffs[0].current).toBe('0.80.0');
60
- });
61
- it('handles null lockfile gracefully', () => {
62
- const original = makeFingerprint({ lockfile_hash: null });
63
- const current = makeFingerprint({ lockfile_hash: 'abc123' });
64
- const diffs = compareFingerprints(original, current);
65
- expect(diffs).toHaveLength(1);
66
- expect(diffs[0].field).toBe('lockfile_hash');
67
- expect(diffs[0].original).toBeNull();
68
- });
69
- it('handles null package manager gracefully', () => {
70
- const original = makeFingerprint({ package_manager: null });
71
- const current = makeFingerprint({ package_manager: null });
72
- const diffs = compareFingerprints(original, current);
73
- expect(diffs).toEqual([]);
74
- });
75
- it('handles null worker version gracefully', () => {
76
- const original = makeFingerprint({
77
- worker_versions: { codex: null, claude: '2.0.50' }
78
- });
79
- const current = makeFingerprint({
80
- worker_versions: { codex: '0.80.0', claude: '2.0.50' }
81
- });
82
- const diffs = compareFingerprints(original, current);
83
- expect(diffs).toHaveLength(1);
84
- expect(diffs[0].original).toBeNull();
85
- expect(diffs[0].current).toBe('0.80.0');
86
- });
87
- it('detects multiple changes at once', () => {
88
- const original = makeFingerprint({
89
- node_version: 'v20.0.0',
90
- lockfile_hash: 'old-hash',
91
- worker_versions: { codex: '0.70.0', claude: '2.0.50' }
92
- });
93
- const current = makeFingerprint({
94
- node_version: 'v22.0.0',
95
- lockfile_hash: 'new-hash',
96
- worker_versions: { codex: '0.80.0', claude: '2.0.50' }
97
- });
98
- const diffs = compareFingerprints(original, current);
99
- expect(diffs).toHaveLength(3);
100
- expect(diffs.map((d) => d.field).sort()).toEqual([
101
- 'lockfile_hash',
102
- 'node_version',
103
- 'worker:codex'
104
- ]);
105
- });
106
- it('ignores created_at timestamp differences', () => {
107
- const original = makeFingerprint({
108
- created_at: '2025-01-01T00:00:00.000Z'
109
- });
110
- const current = makeFingerprint({
111
- created_at: '2025-06-15T12:30:00.000Z'
112
- });
113
- const diffs = compareFingerprints(original, current);
114
- expect(diffs).toEqual([]);
115
- });
116
- });
@@ -1,185 +0,0 @@
1
- /**
2
- * Policy block tests for Phase 7B.
3
- *
4
- * These tests verify:
5
- * 1. Run creates state.policy correctly from CLI/config
6
- * 2. Resume without overrides keeps policy unchanged (via getEffectivePolicy)
7
- * 3. Legacy states (without policy block) are handled correctly
8
- */
9
- import { describe, it, expect } from 'vitest';
10
- import { createInitialOrchestratorState, getEffectivePolicy } from '../state-machine.js';
11
- // Sample config for testing
12
- const sampleConfig = {
13
- tracks: [
14
- {
15
- name: 'Track A',
16
- steps: [{ task: 'tasks/a.md' }]
17
- },
18
- {
19
- name: 'Track B',
20
- steps: [{ task: 'tasks/b.md' }]
21
- }
22
- ]
23
- };
24
- describe('Policy Block', () => {
25
- describe('createInitialOrchestratorState', () => {
26
- it('creates state.policy correctly from CLI options', () => {
27
- const state = createInitialOrchestratorState(sampleConfig, '/test/repo', {
28
- timeBudgetMinutes: 60,
29
- maxTicks: 25,
30
- collisionPolicy: 'serialize',
31
- fast: true,
32
- autoResume: true,
33
- parallel: 1,
34
- ownershipRequired: true
35
- });
36
- // Policy block should exist
37
- expect(state.policy).toBeDefined();
38
- // Policy values should match options
39
- expect(state.policy.time_budget_minutes).toBe(60);
40
- expect(state.policy.max_ticks).toBe(25);
41
- expect(state.policy.collision_policy).toBe('serialize');
42
- expect(state.policy.fast).toBe(true);
43
- expect(state.policy.auto_resume).toBe(true);
44
- expect(state.policy.parallel).toBe(1);
45
- expect(state.policy.ownership_required).toBe(true);
46
- });
47
- it('sets default values for optional policy fields', () => {
48
- const state = createInitialOrchestratorState(sampleConfig, '/test/repo', {
49
- timeBudgetMinutes: 120,
50
- maxTicks: 50,
51
- collisionPolicy: 'force'
52
- // fast, autoResume, parallel not provided
53
- });
54
- expect(state.policy).toBeDefined();
55
- expect(state.policy.fast).toBe(false);
56
- expect(state.policy.auto_resume).toBe(false);
57
- expect(state.policy.parallel).toBe(2); // Default: track count
58
- expect(state.policy.ownership_required).toBe(false);
59
- });
60
- it('writes both policy block and legacy fields for backward compatibility', () => {
61
- const state = createInitialOrchestratorState(sampleConfig, '/test/repo', {
62
- timeBudgetMinutes: 90,
63
- maxTicks: 30,
64
- collisionPolicy: 'fail',
65
- fast: true
66
- });
67
- // Policy block
68
- expect(state.policy.time_budget_minutes).toBe(90);
69
- expect(state.policy.max_ticks).toBe(30);
70
- expect(state.policy.collision_policy).toBe('fail');
71
- expect(state.policy.fast).toBe(true);
72
- // Legacy fields (should match)
73
- expect(state.time_budget_minutes).toBe(90);
74
- expect(state.max_ticks).toBe(30);
75
- expect(state.collision_policy).toBe('fail');
76
- expect(state.fast).toBe(true);
77
- });
78
- });
79
- describe('getEffectivePolicy', () => {
80
- it('returns policy block when present', () => {
81
- const state = createInitialOrchestratorState(sampleConfig, '/test/repo', {
82
- timeBudgetMinutes: 45,
83
- maxTicks: 15,
84
- collisionPolicy: 'serialize',
85
- fast: true,
86
- autoResume: true
87
- });
88
- const policy = getEffectivePolicy(state);
89
- expect(policy.time_budget_minutes).toBe(45);
90
- expect(policy.max_ticks).toBe(15);
91
- expect(policy.collision_policy).toBe('serialize');
92
- expect(policy.fast).toBe(true);
93
- expect(policy.auto_resume).toBe(true);
94
- });
95
- it('falls back to legacy fields when policy block is missing (v0 state)', () => {
96
- // Simulate a legacy v0 state without policy block
97
- const legacyState = {
98
- orchestrator_id: 'orch20240101120000',
99
- repo_path: '/test/repo',
100
- tracks: [
101
- {
102
- id: 'track-1',
103
- name: 'Track A',
104
- steps: [{ task_path: 'tasks/a.md' }],
105
- current_step: 0,
106
- status: 'pending'
107
- }
108
- ],
109
- active_runs: {},
110
- file_claims: {},
111
- status: 'running',
112
- started_at: '2024-01-01T12:00:00Z',
113
- // No policy block - legacy v0 state
114
- collision_policy: 'force',
115
- time_budget_minutes: 180,
116
- max_ticks: 100,
117
- fast: true
118
- };
119
- const policy = getEffectivePolicy(legacyState);
120
- // Should extract from legacy fields
121
- expect(policy.time_budget_minutes).toBe(180);
122
- expect(policy.max_ticks).toBe(100);
123
- expect(policy.collision_policy).toBe('force');
124
- expect(policy.fast).toBe(true);
125
- // Defaults for fields not in v0
126
- expect(policy.auto_resume).toBe(false);
127
- expect(policy.parallel).toBe(1); // track count
128
- });
129
- it('handles legacy state with fast=undefined', () => {
130
- const legacyState = {
131
- orchestrator_id: 'orch20240101120000',
132
- repo_path: '/test/repo',
133
- tracks: [],
134
- active_runs: {},
135
- file_claims: {},
136
- status: 'running',
137
- started_at: '2024-01-01T12:00:00Z',
138
- collision_policy: 'serialize',
139
- time_budget_minutes: 60,
140
- max_ticks: 25
141
- // fast is undefined (missing in v0)
142
- };
143
- const policy = getEffectivePolicy(legacyState);
144
- expect(policy.fast).toBe(false); // Default when undefined
145
- });
146
- });
147
- describe('Resume policy immutability', () => {
148
- it('resume without overrides keeps policy unchanged', () => {
149
- // Create initial state
150
- const state = createInitialOrchestratorState(sampleConfig, '/test/repo', {
151
- timeBudgetMinutes: 60,
152
- maxTicks: 25,
153
- collisionPolicy: 'serialize',
154
- fast: true
155
- });
156
- // Simulate "resuming" by getting effective policy
157
- const policyBeforeResume = getEffectivePolicy(state);
158
- const policyAfterResume = getEffectivePolicy(state);
159
- // Policy should be identical
160
- expect(policyAfterResume).toEqual(policyBeforeResume);
161
- });
162
- it('policy values remain stable across multiple reads', () => {
163
- const state = createInitialOrchestratorState(sampleConfig, '/test/repo', {
164
- timeBudgetMinutes: 42,
165
- maxTicks: 17,
166
- collisionPolicy: 'force',
167
- fast: false,
168
- autoResume: true
169
- });
170
- // Read policy multiple times
171
- const policy1 = getEffectivePolicy(state);
172
- const policy2 = getEffectivePolicy(state);
173
- const policy3 = getEffectivePolicy(state);
174
- // All reads should return identical values
175
- expect(policy1).toEqual(policy2);
176
- expect(policy2).toEqual(policy3);
177
- // And match original options
178
- expect(policy1.time_budget_minutes).toBe(42);
179
- expect(policy1.max_ticks).toBe(17);
180
- expect(policy1.collision_policy).toBe('force');
181
- expect(policy1.fast).toBe(false);
182
- expect(policy1.auto_resume).toBe(true);
183
- });
184
- });
185
- });
@@ -1,65 +0,0 @@
1
- /**
2
- * Schema version tests for Phase 7C.
3
- *
4
- * These tests verify:
5
- * 1. Artifacts include schema_version field
6
- * 2. Schema version is the expected value
7
- */
8
- import { describe, it, expect } from 'vitest';
9
- import { buildWaitResult, buildSummaryArtifact, buildStopArtifact } from '../artifacts.js';
10
- import { createInitialOrchestratorState } from '../state-machine.js';
11
- import { ORCHESTRATOR_ARTIFACT_SCHEMA_VERSION } from '../types.js';
12
- const sampleConfig = {
13
- tracks: [
14
- {
15
- name: 'Test Track',
16
- steps: [{ task: 'tasks/test.md' }]
17
- }
18
- ]
19
- };
20
- describe('Schema Versioning', () => {
21
- describe('Orchestration Artifacts', () => {
22
- it('buildWaitResult includes schema_version', () => {
23
- const state = createInitialOrchestratorState(sampleConfig, '/test/repo', {
24
- timeBudgetMinutes: 60,
25
- maxTicks: 25,
26
- collisionPolicy: 'serialize'
27
- });
28
- // Mark as complete for testing
29
- state.status = 'complete';
30
- state.ended_at = new Date().toISOString();
31
- const result = buildWaitResult(state, '/test/repo');
32
- expect(result.schema_version).toBe(ORCHESTRATOR_ARTIFACT_SCHEMA_VERSION);
33
- expect(result.schema_version).toBe(1);
34
- });
35
- it('buildSummaryArtifact includes schema_version', () => {
36
- const state = createInitialOrchestratorState(sampleConfig, '/test/repo', {
37
- timeBudgetMinutes: 60,
38
- maxTicks: 25,
39
- collisionPolicy: 'serialize'
40
- });
41
- state.status = 'complete';
42
- state.ended_at = new Date().toISOString();
43
- const summary = buildSummaryArtifact(state, '/test/repo');
44
- expect(summary.schema_version).toBe(ORCHESTRATOR_ARTIFACT_SCHEMA_VERSION);
45
- expect(summary.schema_version).toBe(1);
46
- });
47
- it('buildStopArtifact includes schema_version', () => {
48
- const state = createInitialOrchestratorState(sampleConfig, '/test/repo', {
49
- timeBudgetMinutes: 60,
50
- maxTicks: 25,
51
- collisionPolicy: 'serialize'
52
- });
53
- state.status = 'stopped';
54
- state.ended_at = new Date().toISOString();
55
- const stopArtifact = buildStopArtifact(state, '/test/repo');
56
- expect(stopArtifact.schema_version).toBe(ORCHESTRATOR_ARTIFACT_SCHEMA_VERSION);
57
- expect(stopArtifact.schema_version).toBe(1);
58
- });
59
- });
60
- describe('Schema version constant', () => {
61
- it('ORCHESTRATOR_ARTIFACT_SCHEMA_VERSION is 1', () => {
62
- expect(ORCHESTRATOR_ARTIFACT_SCHEMA_VERSION).toBe(1);
63
- });
64
- });
65
- });