@company-semantics/contracts 0.97.0 → 0.98.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.
- package/package.json +1 -1
- package/src/ralph/__tests__/prd-groups.test.ts +249 -0
- package/src/ralph/index.ts +3 -0
- package/src/ralph/prd.ts +41 -0
package/package.json
CHANGED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import type {
|
|
3
|
+
RalphPRD,
|
|
4
|
+
RalphFeatureGroup,
|
|
5
|
+
RalphExecutionPolicy,
|
|
6
|
+
} from '../index.js'
|
|
7
|
+
|
|
8
|
+
describe('RalphFeatureGroup and RalphExecutionPolicy types', () => {
|
|
9
|
+
describe('RalphPRD with groups field', () => {
|
|
10
|
+
it('typechecks with groups containing RalphFeatureGroup entries', () => {
|
|
11
|
+
const prd = {
|
|
12
|
+
version: '2.1',
|
|
13
|
+
repo: 'company-semantics-backend',
|
|
14
|
+
features: [],
|
|
15
|
+
stopCondition: 'All features pass',
|
|
16
|
+
groups: [
|
|
17
|
+
{
|
|
18
|
+
id: 'auth-group',
|
|
19
|
+
description: 'Authentication features',
|
|
20
|
+
features: ['auth/login', 'auth/logout'],
|
|
21
|
+
},
|
|
22
|
+
],
|
|
23
|
+
} satisfies RalphPRD
|
|
24
|
+
|
|
25
|
+
expect(prd.groups).toHaveLength(1)
|
|
26
|
+
expect(prd.groups![0].id).toBe('auth-group')
|
|
27
|
+
expect(prd.groups![0].features).toEqual(['auth/login', 'auth/logout'])
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('typechecks with multiple groups', () => {
|
|
31
|
+
const prd = {
|
|
32
|
+
version: '2.1',
|
|
33
|
+
repo: 'company-semantics-backend',
|
|
34
|
+
features: [],
|
|
35
|
+
stopCondition: 'All features pass',
|
|
36
|
+
groups: [
|
|
37
|
+
{
|
|
38
|
+
id: 'group-a',
|
|
39
|
+
description: 'First group',
|
|
40
|
+
features: ['feat-1'],
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: 'group-b',
|
|
44
|
+
description: 'Second group',
|
|
45
|
+
features: ['feat-2', 'feat-3'],
|
|
46
|
+
sharedContext: 'Shared context for group B',
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
} satisfies RalphPRD
|
|
50
|
+
|
|
51
|
+
expect(prd.groups).toHaveLength(2)
|
|
52
|
+
})
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
describe('backward compatibility — PRD without groups field', () => {
|
|
56
|
+
it('typechecks without groups field (existing PRD shape)', () => {
|
|
57
|
+
const prd = {
|
|
58
|
+
version: '2.0',
|
|
59
|
+
repo: 'company-semantics-contracts',
|
|
60
|
+
features: [
|
|
61
|
+
{
|
|
62
|
+
id: 'test/feature',
|
|
63
|
+
category: 'functional' as const,
|
|
64
|
+
description: 'A test feature',
|
|
65
|
+
steps: ['Step 1'],
|
|
66
|
+
passes: false,
|
|
67
|
+
priority: 'high' as const,
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
stopCondition: 'All features pass',
|
|
71
|
+
} satisfies RalphPRD
|
|
72
|
+
|
|
73
|
+
expect(prd.groups).toBeUndefined()
|
|
74
|
+
expect(prd.features).toHaveLength(1)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('typechecks with all v2.0 optional fields but no groups', () => {
|
|
78
|
+
const prd = {
|
|
79
|
+
version: '2.0',
|
|
80
|
+
repo: 'company-semantics-backend',
|
|
81
|
+
features: [],
|
|
82
|
+
stopCondition: 'Done',
|
|
83
|
+
agentContract: {
|
|
84
|
+
mode: 'afk' as const,
|
|
85
|
+
allowedActions: ['Edit'],
|
|
86
|
+
forbiddenActions: [],
|
|
87
|
+
autonomyLevel: 'bounded' as const,
|
|
88
|
+
},
|
|
89
|
+
failurePolicy: {
|
|
90
|
+
maxRetries: 2,
|
|
91
|
+
onRepeatedFailure: 'halt_and_report' as const,
|
|
92
|
+
retryScope: 'current_feature_only' as const,
|
|
93
|
+
},
|
|
94
|
+
scope: {
|
|
95
|
+
allowedPaths: ['src/**'],
|
|
96
|
+
forbiddenPaths: ['node_modules/**'],
|
|
97
|
+
refactoringAllowed: true,
|
|
98
|
+
},
|
|
99
|
+
commitPolicy: {
|
|
100
|
+
strategy: 'per_feature' as const,
|
|
101
|
+
commitOn: 'acceptance_pass' as const,
|
|
102
|
+
rollbackOnFailure: 'uncommitted_only' as const,
|
|
103
|
+
onHalt: 'leave_committed' as const,
|
|
104
|
+
logRollback: true,
|
|
105
|
+
},
|
|
106
|
+
} satisfies RalphPRD
|
|
107
|
+
|
|
108
|
+
expect(prd.groups).toBeUndefined()
|
|
109
|
+
expect(prd.agentContract).toBeDefined()
|
|
110
|
+
})
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
describe('RalphExecutionPolicy defaults', () => {
|
|
114
|
+
it('accepts all required fields', () => {
|
|
115
|
+
const policy = {
|
|
116
|
+
parallelism: 1,
|
|
117
|
+
preferredExecutor: 'same',
|
|
118
|
+
serializeFiles: [],
|
|
119
|
+
} satisfies RalphExecutionPolicy
|
|
120
|
+
|
|
121
|
+
expect(policy.parallelism).toBe(1)
|
|
122
|
+
expect(policy.preferredExecutor).toBe('same')
|
|
123
|
+
expect(policy.serializeFiles).toEqual([])
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
it('accepts preferredExecutor: any', () => {
|
|
127
|
+
const policy = {
|
|
128
|
+
parallelism: 4,
|
|
129
|
+
preferredExecutor: 'any',
|
|
130
|
+
serializeFiles: ['src/config.ts'],
|
|
131
|
+
} satisfies RalphExecutionPolicy
|
|
132
|
+
|
|
133
|
+
expect(policy.preferredExecutor).toBe('any')
|
|
134
|
+
expect(policy.serializeFiles).toEqual(['src/config.ts'])
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
it('accepts high parallelism with multiple serialized files', () => {
|
|
138
|
+
const policy = {
|
|
139
|
+
parallelism: 10,
|
|
140
|
+
preferredExecutor: 'any',
|
|
141
|
+
serializeFiles: ['package.json', 'pnpm-lock.yaml', 'src/index.ts'],
|
|
142
|
+
} satisfies RalphExecutionPolicy
|
|
143
|
+
|
|
144
|
+
expect(policy.parallelism).toBe(10)
|
|
145
|
+
expect(policy.serializeFiles).toHaveLength(3)
|
|
146
|
+
})
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
describe('RalphFeatureGroup optional fields', () => {
|
|
150
|
+
it('typechecks with no optional fields', () => {
|
|
151
|
+
const group = {
|
|
152
|
+
id: 'minimal-group',
|
|
153
|
+
description: 'Minimal group with required fields only',
|
|
154
|
+
features: ['feat/one'],
|
|
155
|
+
} satisfies RalphFeatureGroup
|
|
156
|
+
|
|
157
|
+
expect(group.id).toBe('minimal-group')
|
|
158
|
+
expect(group.sharedContext).toBeUndefined()
|
|
159
|
+
expect(group.completionCriteria).toBeUndefined()
|
|
160
|
+
expect(group.executionPolicy).toBeUndefined()
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
it('typechecks with sharedContext only', () => {
|
|
164
|
+
const group = {
|
|
165
|
+
id: 'shared-ctx-group',
|
|
166
|
+
description: 'Group with shared context',
|
|
167
|
+
features: ['feat/a', 'feat/b'],
|
|
168
|
+
sharedContext: 'All features share the auth domain',
|
|
169
|
+
} satisfies RalphFeatureGroup
|
|
170
|
+
|
|
171
|
+
expect(group.sharedContext).toBe('All features share the auth domain')
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
it('typechecks with completionCriteria only', () => {
|
|
175
|
+
const group = {
|
|
176
|
+
id: 'completion-group',
|
|
177
|
+
description: 'Group with completion criteria',
|
|
178
|
+
features: ['feat/x'],
|
|
179
|
+
completionCriteria: 'Integration test suite passes end-to-end',
|
|
180
|
+
} satisfies RalphFeatureGroup
|
|
181
|
+
|
|
182
|
+
expect(group.completionCriteria).toBe(
|
|
183
|
+
'Integration test suite passes end-to-end',
|
|
184
|
+
)
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
it('typechecks with executionPolicy only', () => {
|
|
188
|
+
const group = {
|
|
189
|
+
id: 'policy-group',
|
|
190
|
+
description: 'Group with execution policy',
|
|
191
|
+
features: ['feat/p', 'feat/q'],
|
|
192
|
+
executionPolicy: {
|
|
193
|
+
parallelism: 2,
|
|
194
|
+
preferredExecutor: 'any',
|
|
195
|
+
serializeFiles: ['shared.ts'],
|
|
196
|
+
},
|
|
197
|
+
} satisfies RalphFeatureGroup
|
|
198
|
+
|
|
199
|
+
expect(group.executionPolicy).toBeDefined()
|
|
200
|
+
expect(group.executionPolicy!.parallelism).toBe(2)
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
it('typechecks with all optional fields present', () => {
|
|
204
|
+
const group = {
|
|
205
|
+
id: 'full-group',
|
|
206
|
+
description: 'Group with all optional fields',
|
|
207
|
+
features: ['feat/1', 'feat/2', 'feat/3'],
|
|
208
|
+
sharedContext: 'All features modify the database schema',
|
|
209
|
+
completionCriteria: 'Migration runs cleanly and all tests pass',
|
|
210
|
+
executionPolicy: {
|
|
211
|
+
parallelism: 1,
|
|
212
|
+
preferredExecutor: 'same',
|
|
213
|
+
serializeFiles: ['drizzle/schema.ts'],
|
|
214
|
+
},
|
|
215
|
+
} satisfies RalphFeatureGroup
|
|
216
|
+
|
|
217
|
+
expect(group.sharedContext).toBeDefined()
|
|
218
|
+
expect(group.completionCriteria).toBeDefined()
|
|
219
|
+
expect(group.executionPolicy).toBeDefined()
|
|
220
|
+
expect(group.executionPolicy!.preferredExecutor).toBe('same')
|
|
221
|
+
})
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
describe('RalphPRD groups integration', () => {
|
|
225
|
+
it('groups field references RalphFeatureGroup type correctly', () => {
|
|
226
|
+
const group: RalphFeatureGroup = {
|
|
227
|
+
id: 'test-group',
|
|
228
|
+
description: 'Test group',
|
|
229
|
+
features: ['a', 'b'],
|
|
230
|
+
executionPolicy: {
|
|
231
|
+
parallelism: 3,
|
|
232
|
+
preferredExecutor: 'any',
|
|
233
|
+
serializeFiles: [],
|
|
234
|
+
},
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const prd: RalphPRD = {
|
|
238
|
+
version: '2.1',
|
|
239
|
+
repo: 'company-semantics-app',
|
|
240
|
+
features: [],
|
|
241
|
+
stopCondition: 'Done',
|
|
242
|
+
groups: [group],
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
expect(prd.groups).toHaveLength(1)
|
|
246
|
+
expect(prd.groups![0]).toBe(group)
|
|
247
|
+
})
|
|
248
|
+
})
|
|
249
|
+
})
|
package/src/ralph/index.ts
CHANGED
package/src/ralph/prd.ts
CHANGED
|
@@ -288,6 +288,45 @@ export type RalphCommitPolicy = {
|
|
|
288
288
|
logRollback: boolean;
|
|
289
289
|
};
|
|
290
290
|
|
|
291
|
+
// =============================================================================
|
|
292
|
+
// PRD v2.1 Schema Extensions
|
|
293
|
+
// =============================================================================
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Controls how features within a group are distributed and executed.
|
|
297
|
+
*
|
|
298
|
+
* @see company-semantics-control/scripts/ralph/src/group/types.ts for runtime equivalent
|
|
299
|
+
*/
|
|
300
|
+
export type RalphExecutionPolicy = {
|
|
301
|
+
/** Max concurrent executors for this group (default 1) */
|
|
302
|
+
parallelism: number;
|
|
303
|
+
/** 'same' prefers all features on one executor, 'any' allows distribution */
|
|
304
|
+
preferredExecutor: 'same' | 'any';
|
|
305
|
+
/** Files that must not be modified concurrently (hard constraint) */
|
|
306
|
+
serializeFiles: string[];
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* A named group of related PRD features that share research context
|
|
311
|
+
* and have coordinated execution and completion semantics.
|
|
312
|
+
*
|
|
313
|
+
* @see company-semantics-control/scripts/ralph/src/group/types.ts for runtime equivalent (TaskGroup)
|
|
314
|
+
*/
|
|
315
|
+
export type RalphFeatureGroup = {
|
|
316
|
+
/** Unique identifier for this group within the PRD */
|
|
317
|
+
id: string;
|
|
318
|
+
/** Human-readable description of the group's purpose */
|
|
319
|
+
description: string;
|
|
320
|
+
/** Feature IDs that belong to this group */
|
|
321
|
+
features: string[];
|
|
322
|
+
/** Optional shared context for all features in the group */
|
|
323
|
+
sharedContext?: string;
|
|
324
|
+
/** Optional criteria for group-level completion beyond individual feature completion */
|
|
325
|
+
completionCriteria?: string;
|
|
326
|
+
/** Optional execution policy controlling parallelism and serialization */
|
|
327
|
+
executionPolicy?: RalphExecutionPolicy;
|
|
328
|
+
};
|
|
329
|
+
|
|
291
330
|
/**
|
|
292
331
|
* A complete Ralph PRD document.
|
|
293
332
|
*
|
|
@@ -321,4 +360,6 @@ export type RalphPRD = {
|
|
|
321
360
|
nonGoals?: string[];
|
|
322
361
|
/** Rollback and commit semantics */
|
|
323
362
|
commitPolicy?: RalphCommitPolicy;
|
|
363
|
+
/** Task groups with shared research, execution policies, and completion tracking (v2.1) */
|
|
364
|
+
groups?: RalphFeatureGroup[];
|
|
324
365
|
};
|