@gitdocket/core 0.0.0 → 0.1.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 +27 -6
- package/src/bundle.ts +127 -0
- package/src/cache.ts +557 -0
- package/src/config.ts +96 -0
- package/src/engine-semantics.ts +26 -0
- package/src/filestore.ts +63 -0
- package/src/id-allocation.ts +288 -0
- package/src/index.ts +199 -0
- package/src/indexmd.ts +146 -0
- package/src/init.ts +338 -0
- package/src/intents.ts +262 -0
- package/src/lint.ts +240 -0
- package/src/ops.ts +320 -0
- package/src/orientation.ts +88 -0
- package/src/overview.ts +593 -0
- package/src/packet.ts +119 -0
- package/src/parse.ts +200 -0
- package/src/prompt-routing.ts +651 -0
- package/src/schema.ts +56 -0
- package/src/search.ts +147 -0
- package/src/shipped-history.json +53 -0
- package/src/shipped.ts +96 -0
- package/src/state-of-play.ts +370 -0
- package/src/states.ts +85 -0
- package/src/upgrade.ts +177 -0
- package/src/verify.ts +122 -0
- package/src/version.ts +6 -0
- package/src/workflows.ts +481 -0
- package/README.md +0 -5
|
@@ -0,0 +1,651 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AGENT_INTENT_IDS,
|
|
3
|
+
AGENT_INTENTS,
|
|
4
|
+
type AgentIntentContract,
|
|
5
|
+
type AgentIntentId,
|
|
6
|
+
DOCKET_INTENT_IDS,
|
|
7
|
+
DOCKET_INTENTS,
|
|
8
|
+
type DocketIntentContract,
|
|
9
|
+
type DocketIntentId,
|
|
10
|
+
type IntentEntrypoint,
|
|
11
|
+
} from "./intents";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Evaluation fixtures for the harness-owned natural-language routing step.
|
|
15
|
+
*
|
|
16
|
+
* These are expected outcomes, not a substring classifier. Deterministic
|
|
17
|
+
* tests can prove that every generated surface exposes the same contract and
|
|
18
|
+
* budgets; a bounded dogfood replay is still required to observe model choice.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
export type PromptRoutingTrait =
|
|
22
|
+
| "ambiguous-wording"
|
|
23
|
+
| "combined-intents"
|
|
24
|
+
| "direct-work-regression"
|
|
25
|
+
| "negative-constraint"
|
|
26
|
+
| "pickup-control"
|
|
27
|
+
| "orientation-no-write-regression"
|
|
28
|
+
| "tracked-reference-resolution";
|
|
29
|
+
|
|
30
|
+
export interface PromptRoutingFixture {
|
|
31
|
+
id: string;
|
|
32
|
+
prompt: string;
|
|
33
|
+
expectedIntent: AgentIntentId;
|
|
34
|
+
/** Additional independently authorized intents composed by the prompt. */
|
|
35
|
+
composedIntents?: readonly AgentIntentId[];
|
|
36
|
+
expectedEntrypoint: IntentEntrypoint;
|
|
37
|
+
/** The complete Docket command budget before further scope is granted. */
|
|
38
|
+
allowedCommands: readonly string[];
|
|
39
|
+
/** Exact repository write paths authorized by a direct-work prompt. */
|
|
40
|
+
permittedWritePaths?: readonly string[];
|
|
41
|
+
/** Canonical upper bound on evidence the selected path may inspect. */
|
|
42
|
+
maximumInspectionScope: string;
|
|
43
|
+
/** Whether this prompt itself authorizes repository writes. */
|
|
44
|
+
writesPermitted: boolean;
|
|
45
|
+
/** Operations made explicitly unavailable by the prompt or intent. */
|
|
46
|
+
forbiddenActions: readonly string[];
|
|
47
|
+
/** Generated-surface text that must expose this fixture's boundary. */
|
|
48
|
+
surfaceEvidence?: readonly string[];
|
|
49
|
+
traits?: readonly PromptRoutingTrait[];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const scope = (id: AgentIntentId): string => AGENT_INTENTS[id].inspectionScope;
|
|
53
|
+
|
|
54
|
+
const DIRECT_WORK_FORBIDDEN_ACTIONS = [
|
|
55
|
+
"docket task create",
|
|
56
|
+
"docket task start",
|
|
57
|
+
"task or index mutation",
|
|
58
|
+
"adopt existing .docket/active-task",
|
|
59
|
+
"clear existing .docket/active-task",
|
|
60
|
+
] as const;
|
|
61
|
+
|
|
62
|
+
export const PROMPT_ROUTING_FIXTURES: readonly PromptRoutingFixture[] = [
|
|
63
|
+
{
|
|
64
|
+
id: "direct-concrete-ux-request",
|
|
65
|
+
prompt:
|
|
66
|
+
"Work on this UX concern: in ui/mobile-settings-card.css only, change the mobile card gap from 8px to 12px. Do not change anything else.",
|
|
67
|
+
expectedIntent: "direct-work",
|
|
68
|
+
expectedEntrypoint: {
|
|
69
|
+
kind: "direct",
|
|
70
|
+
value: "the user's concrete requested work",
|
|
71
|
+
},
|
|
72
|
+
allowedCommands: [],
|
|
73
|
+
permittedWritePaths: ["ui/mobile-settings-card.css"],
|
|
74
|
+
maximumInspectionScope: scope("direct-work"),
|
|
75
|
+
writesPermitted: true,
|
|
76
|
+
forbiddenActions: [
|
|
77
|
+
...DIRECT_WORK_FORBIDDEN_ACTIONS,
|
|
78
|
+
"edit outside ui/mobile-settings-card.css",
|
|
79
|
+
],
|
|
80
|
+
surfaceEvidence: [
|
|
81
|
+
"concrete direct request proceeds",
|
|
82
|
+
"generic implementation language is not pickup authority",
|
|
83
|
+
],
|
|
84
|
+
traits: ["direct-work-regression", "negative-constraint"],
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
id: "direct-generic-fix",
|
|
88
|
+
prompt:
|
|
89
|
+
"Fix the label typo in src/format-label.ts only, changing 'Setings' to 'Settings'.",
|
|
90
|
+
expectedIntent: "direct-work",
|
|
91
|
+
expectedEntrypoint: {
|
|
92
|
+
kind: "direct",
|
|
93
|
+
value: "the user's concrete requested work",
|
|
94
|
+
},
|
|
95
|
+
allowedCommands: [],
|
|
96
|
+
permittedWritePaths: ["src/format-label.ts"],
|
|
97
|
+
maximumInspectionScope: scope("direct-work"),
|
|
98
|
+
writesPermitted: true,
|
|
99
|
+
forbiddenActions: [
|
|
100
|
+
...DIRECT_WORK_FORBIDDEN_ACTIONS,
|
|
101
|
+
"edit outside src/format-label.ts",
|
|
102
|
+
],
|
|
103
|
+
surfaceEvidence: [
|
|
104
|
+
"concrete direct request proceeds",
|
|
105
|
+
"generic implementation language is not pickup authority",
|
|
106
|
+
],
|
|
107
|
+
traits: ["direct-work-regression"],
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
id: "pickup-ambiguous-tracked-reference",
|
|
111
|
+
prompt: "Continue the login task in Docket.",
|
|
112
|
+
expectedIntent: "pickup",
|
|
113
|
+
expectedEntrypoint: {
|
|
114
|
+
kind: "command",
|
|
115
|
+
value: 'docket search "login" --json',
|
|
116
|
+
},
|
|
117
|
+
allowedCommands: ['docket search "login" --json'],
|
|
118
|
+
maximumInspectionScope: scope("pickup"),
|
|
119
|
+
writesPermitted: false,
|
|
120
|
+
forbiddenActions: [
|
|
121
|
+
"docket task start",
|
|
122
|
+
"docket index",
|
|
123
|
+
"adopt or clear .docket/active-task",
|
|
124
|
+
"substitute the top ready task",
|
|
125
|
+
],
|
|
126
|
+
surfaceEvidence: [
|
|
127
|
+
"reference remains ambiguous",
|
|
128
|
+
"never fall back to an unrelated top-ready item",
|
|
129
|
+
],
|
|
130
|
+
traits: [
|
|
131
|
+
"ambiguous-wording",
|
|
132
|
+
"pickup-control",
|
|
133
|
+
"tracked-reference-resolution",
|
|
134
|
+
],
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
id: "orientation-whats-next",
|
|
138
|
+
prompt: "What's next in Docket?",
|
|
139
|
+
expectedIntent: "orientation",
|
|
140
|
+
expectedEntrypoint: {
|
|
141
|
+
kind: "command",
|
|
142
|
+
value: "docket overview --json",
|
|
143
|
+
},
|
|
144
|
+
allowedCommands: ["docket overview --json"],
|
|
145
|
+
maximumInspectionScope: scope("orientation"),
|
|
146
|
+
writesPermitted: false,
|
|
147
|
+
forbiddenActions: [
|
|
148
|
+
"docket task start",
|
|
149
|
+
"docket index",
|
|
150
|
+
"docket-groom",
|
|
151
|
+
"repository-wide search",
|
|
152
|
+
],
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
id: "orientation-ordinary-review",
|
|
156
|
+
prompt: "Let's review where the project stands before deciding what to do.",
|
|
157
|
+
expectedIntent: "orientation",
|
|
158
|
+
expectedEntrypoint: {
|
|
159
|
+
kind: "command",
|
|
160
|
+
value: "docket overview --json",
|
|
161
|
+
},
|
|
162
|
+
allowedCommands: ["docket overview --json"],
|
|
163
|
+
maximumInspectionScope: scope("orientation"),
|
|
164
|
+
writesPermitted: false,
|
|
165
|
+
forbiddenActions: ["docket task start", "docket index", "docket-groom"],
|
|
166
|
+
traits: ["ambiguous-wording"],
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
id: "orientation-whats-next-review-only",
|
|
170
|
+
prompt: "What's next in Docket? Don't start it, let's just review.",
|
|
171
|
+
expectedIntent: "orientation",
|
|
172
|
+
expectedEntrypoint: {
|
|
173
|
+
kind: "command",
|
|
174
|
+
value: "docket overview --json",
|
|
175
|
+
},
|
|
176
|
+
allowedCommands: ["docket overview --json"],
|
|
177
|
+
maximumInspectionScope: scope("orientation"),
|
|
178
|
+
writesPermitted: false,
|
|
179
|
+
forbiddenActions: [
|
|
180
|
+
"docket task start",
|
|
181
|
+
"docket index",
|
|
182
|
+
"docket-groom",
|
|
183
|
+
"repository-wide search",
|
|
184
|
+
],
|
|
185
|
+
traits: [
|
|
186
|
+
"ambiguous-wording",
|
|
187
|
+
"negative-constraint",
|
|
188
|
+
"orientation-no-write-regression",
|
|
189
|
+
],
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
id: "groom-explicit-hygiene",
|
|
193
|
+
prompt: "Groom the backlog and report stale or inconsistent tasks.",
|
|
194
|
+
expectedIntent: "backlog-hygiene",
|
|
195
|
+
expectedEntrypoint: { kind: "workflow", value: "docket-groom" },
|
|
196
|
+
allowedCommands: ["docket ready --json", "docket task list --json"],
|
|
197
|
+
maximumInspectionScope: scope("backlog-hygiene"),
|
|
198
|
+
writesPermitted: false,
|
|
199
|
+
forbiddenActions: [
|
|
200
|
+
"apply proposed fixes before confirmation",
|
|
201
|
+
"inspect unrelated product implementation",
|
|
202
|
+
],
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
id: "groom-review-audit-no-fixes",
|
|
206
|
+
prompt: "Review task hygiene for stale tickets, but don't apply any fixes.",
|
|
207
|
+
expectedIntent: "backlog-hygiene",
|
|
208
|
+
expectedEntrypoint: { kind: "workflow", value: "docket-groom" },
|
|
209
|
+
allowedCommands: ["docket ready --json", "docket task list --json"],
|
|
210
|
+
maximumInspectionScope: scope("backlog-hygiene"),
|
|
211
|
+
writesPermitted: false,
|
|
212
|
+
forbiddenActions: ["docket task move", "docket task close", "docket index"],
|
|
213
|
+
traits: ["ambiguous-wording", "negative-constraint"],
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
id: "pickup-named-start",
|
|
217
|
+
prompt: "Start DKT-12.",
|
|
218
|
+
expectedIntent: "pickup",
|
|
219
|
+
expectedEntrypoint: { kind: "workflow", value: "docket-pickup" },
|
|
220
|
+
allowedCommands: ["docket task start DKT-12 --json"],
|
|
221
|
+
maximumInspectionScope: scope("pickup"),
|
|
222
|
+
writesPermitted: true,
|
|
223
|
+
forbiddenActions: ["docket task close", "docket-groom"],
|
|
224
|
+
surfaceEvidence: ["supplies a Docket ID"],
|
|
225
|
+
traits: ["pickup-control"],
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
id: "pickup-explicit-next-docket-task",
|
|
229
|
+
prompt: "Pick up the next Docket task.",
|
|
230
|
+
expectedIntent: "pickup",
|
|
231
|
+
expectedEntrypoint: { kind: "workflow", value: "docket-pickup" },
|
|
232
|
+
allowedCommands: ["docket task start --json"],
|
|
233
|
+
maximumInspectionScope: scope("pickup"),
|
|
234
|
+
writesPermitted: true,
|
|
235
|
+
forbiddenActions: [
|
|
236
|
+
"docket task create",
|
|
237
|
+
"docket index",
|
|
238
|
+
"start any task other than the engine-selected top ready task",
|
|
239
|
+
],
|
|
240
|
+
surfaceEvidence: [
|
|
241
|
+
"permitted only for explicit next-Docket-task or backlog selection",
|
|
242
|
+
],
|
|
243
|
+
traits: ["pickup-control"],
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
id: "pickup-named-continuation",
|
|
247
|
+
prompt: "Continue work on DKT-12, but don't close it.",
|
|
248
|
+
expectedIntent: "pickup",
|
|
249
|
+
expectedEntrypoint: { kind: "workflow", value: "docket-pickup" },
|
|
250
|
+
allowedCommands: ["docket task start DKT-12 --json"],
|
|
251
|
+
maximumInspectionScope: scope("pickup"),
|
|
252
|
+
writesPermitted: true,
|
|
253
|
+
forbiddenActions: ["docket task close", "docket index", "docket-groom"],
|
|
254
|
+
traits: ["negative-constraint"],
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
id: "epic-supervision-named-run",
|
|
258
|
+
prompt: "Run epic DKT-42 and come back when the whole epic is done.",
|
|
259
|
+
expectedIntent: "epic-supervision",
|
|
260
|
+
expectedEntrypoint: { kind: "workflow", value: "docket-epic" },
|
|
261
|
+
allowedCommands: [
|
|
262
|
+
"docket task list --epic DKT-42 --all --json",
|
|
263
|
+
"docket ready --json",
|
|
264
|
+
"docket task start <child-ID> --json",
|
|
265
|
+
"docket task close <child-ID> --json",
|
|
266
|
+
"docket index",
|
|
267
|
+
],
|
|
268
|
+
maximumInspectionScope: scope("epic-supervision"),
|
|
269
|
+
writesPermitted: true,
|
|
270
|
+
forbiddenActions: [
|
|
271
|
+
"mutate unrelated tasks",
|
|
272
|
+
"share one checkout between concurrent workers",
|
|
273
|
+
"treat child completion as epic completion",
|
|
274
|
+
"create a competing orchestration database",
|
|
275
|
+
],
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
id: "task-move-named",
|
|
279
|
+
prompt: "Move DKT-12 to blocked and note that the API decision is pending.",
|
|
280
|
+
expectedIntent: "task-management",
|
|
281
|
+
expectedEntrypoint: {
|
|
282
|
+
kind: "command",
|
|
283
|
+
value:
|
|
284
|
+
'docket task move DKT-12 blocked --note "the API decision is pending" --json',
|
|
285
|
+
},
|
|
286
|
+
allowedCommands: [
|
|
287
|
+
'docket task move DKT-12 blocked --note "the API decision is pending" --json',
|
|
288
|
+
],
|
|
289
|
+
maximumInspectionScope: scope("task-management"),
|
|
290
|
+
writesPermitted: true,
|
|
291
|
+
forbiddenActions: ["docket task start", "docket-groom"],
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
id: "task-close-named",
|
|
295
|
+
prompt: "Close DKT-12 after checking its acceptance criteria.",
|
|
296
|
+
expectedIntent: "task-management",
|
|
297
|
+
expectedEntrypoint: { kind: "workflow", value: "docket-close" },
|
|
298
|
+
allowedCommands: ["docket task close DKT-12 --json", "docket index"],
|
|
299
|
+
maximumInspectionScope: scope("task-management"),
|
|
300
|
+
writesPermitted: true,
|
|
301
|
+
forbiddenActions: ["docket task start", "docket-groom"],
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
id: "task-create-named",
|
|
305
|
+
prompt: "Create a p1 task named Harden exports, but don't start it.",
|
|
306
|
+
expectedIntent: "task-management",
|
|
307
|
+
expectedEntrypoint: { kind: "workflow", value: "docket-task" },
|
|
308
|
+
allowedCommands: [
|
|
309
|
+
'docket task create --title "Harden exports" --priority p1 --json',
|
|
310
|
+
"docket index",
|
|
311
|
+
],
|
|
312
|
+
maximumInspectionScope: scope("task-management"),
|
|
313
|
+
writesPermitted: true,
|
|
314
|
+
forbiddenActions: ["docket task start", "docket-groom"],
|
|
315
|
+
traits: ["negative-constraint"],
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
id: "task-create-and-start",
|
|
319
|
+
prompt: "Track a p1 task named Harden exports and start it.",
|
|
320
|
+
expectedIntent: "task-management",
|
|
321
|
+
composedIntents: ["pickup"],
|
|
322
|
+
expectedEntrypoint: { kind: "workflow", value: "docket-task" },
|
|
323
|
+
allowedCommands: [
|
|
324
|
+
'docket task create --title "Harden exports" --priority p1 --json',
|
|
325
|
+
"docket index",
|
|
326
|
+
"docket task start <created-ID> --json",
|
|
327
|
+
],
|
|
328
|
+
maximumInspectionScope: scope("task-management"),
|
|
329
|
+
writesPermitted: true,
|
|
330
|
+
forbiddenActions: [
|
|
331
|
+
"bare docket task start --json",
|
|
332
|
+
"start a task other than the newly created ID",
|
|
333
|
+
"docket-groom",
|
|
334
|
+
],
|
|
335
|
+
surfaceEvidence: ["supplies a Docket ID"],
|
|
336
|
+
traits: ["combined-intents", "pickup-control"],
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
id: "maintenance-standup",
|
|
340
|
+
prompt: "Prepare the weekly standup report without changing anything.",
|
|
341
|
+
expectedIntent: "project-maintenance",
|
|
342
|
+
expectedEntrypoint: { kind: "workflow", value: "docket-standup" },
|
|
343
|
+
allowedCommands: ["docket task list --json", "docket ready --json"],
|
|
344
|
+
maximumInspectionScope: scope("project-maintenance"),
|
|
345
|
+
writesPermitted: false,
|
|
346
|
+
forbiddenActions: ["task mutation", "docket index", "docket-groom"],
|
|
347
|
+
traits: ["negative-constraint"],
|
|
348
|
+
},
|
|
349
|
+
];
|
|
350
|
+
|
|
351
|
+
export type PromptRoutingDiagnosticCode =
|
|
352
|
+
| "duplicate-fixture"
|
|
353
|
+
| "missing-intent-coverage"
|
|
354
|
+
| "missing-trait-coverage"
|
|
355
|
+
| "entrypoint-drift"
|
|
356
|
+
| "inspection-scope-drift"
|
|
357
|
+
| "write-boundary-drift"
|
|
358
|
+
| "empty-command-budget"
|
|
359
|
+
| "direct-work-regression"
|
|
360
|
+
| "ambiguous-pickup-regression"
|
|
361
|
+
| "pickup-control-regression"
|
|
362
|
+
| "combined-intent-regression"
|
|
363
|
+
| "orientation-no-write-regression";
|
|
364
|
+
|
|
365
|
+
export interface PromptRoutingDiagnostic {
|
|
366
|
+
code: PromptRoutingDiagnosticCode;
|
|
367
|
+
fixture?: string;
|
|
368
|
+
message: string;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const sameEntrypoint = (a: IntentEntrypoint, b: IntentEntrypoint): boolean =>
|
|
372
|
+
a.kind === b.kind && a.value === b.value;
|
|
373
|
+
|
|
374
|
+
/** Release guard for fixture completeness and canonical scope/mutation data. */
|
|
375
|
+
export function validatePromptRoutingFixtures(
|
|
376
|
+
fixtures: readonly PromptRoutingFixture[],
|
|
377
|
+
intents: Readonly<Record<AgentIntentId, AgentIntentContract>> = AGENT_INTENTS,
|
|
378
|
+
): PromptRoutingDiagnostic[] {
|
|
379
|
+
const diagnostics: PromptRoutingDiagnostic[] = [];
|
|
380
|
+
const seen = new Set<string>();
|
|
381
|
+
|
|
382
|
+
for (const fixture of fixtures) {
|
|
383
|
+
if (seen.has(fixture.id)) {
|
|
384
|
+
diagnostics.push({
|
|
385
|
+
code: "duplicate-fixture",
|
|
386
|
+
fixture: fixture.id,
|
|
387
|
+
message: "fixture id is duplicated",
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
seen.add(fixture.id);
|
|
391
|
+
|
|
392
|
+
const intent = intents[fixture.expectedIntent];
|
|
393
|
+
if (fixture.maximumInspectionScope !== intent.inspectionScope) {
|
|
394
|
+
diagnostics.push({
|
|
395
|
+
code: "inspection-scope-drift",
|
|
396
|
+
fixture: fixture.id,
|
|
397
|
+
message: `maximum inspection scope differs from ${intent.id}`,
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
if (
|
|
401
|
+
fixture.allowedCommands.length === 0 &&
|
|
402
|
+
fixture.expectedIntent !== "direct-work"
|
|
403
|
+
) {
|
|
404
|
+
diagnostics.push({
|
|
405
|
+
code: "empty-command-budget",
|
|
406
|
+
fixture: fixture.id,
|
|
407
|
+
message: "fixture has no bounded command budget",
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
if (intent.mode === "read-only" && fixture.writesPermitted) {
|
|
411
|
+
diagnostics.push({
|
|
412
|
+
code: "write-boundary-drift",
|
|
413
|
+
fixture: fixture.id,
|
|
414
|
+
message: "a read-only intent cannot permit repository writes",
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
if (
|
|
418
|
+
intent.defaultEntrypoint.kind !== "named-operation" &&
|
|
419
|
+
!fixture.traits?.includes("tracked-reference-resolution") &&
|
|
420
|
+
!sameEntrypoint(fixture.expectedEntrypoint, intent.defaultEntrypoint)
|
|
421
|
+
) {
|
|
422
|
+
diagnostics.push({
|
|
423
|
+
code: "entrypoint-drift",
|
|
424
|
+
fixture: fixture.id,
|
|
425
|
+
message: `entrypoint differs from ${intent.id}`,
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
for (const id of AGENT_INTENT_IDS) {
|
|
431
|
+
if (!fixtures.some((fixture) => fixture.expectedIntent === id)) {
|
|
432
|
+
diagnostics.push({
|
|
433
|
+
code: "missing-intent-coverage",
|
|
434
|
+
message: `no prompt fixture covers ${id}`,
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const directFixtures = fixtures.filter((fixture) =>
|
|
440
|
+
fixture.traits?.includes("direct-work-regression"),
|
|
441
|
+
);
|
|
442
|
+
const directFixturesValid =
|
|
443
|
+
directFixtures.length >= 2 &&
|
|
444
|
+
directFixtures.every(
|
|
445
|
+
(fixture) =>
|
|
446
|
+
fixture.expectedIntent === "direct-work" &&
|
|
447
|
+
sameEntrypoint(fixture.expectedEntrypoint, {
|
|
448
|
+
kind: "direct",
|
|
449
|
+
value: "the user's concrete requested work",
|
|
450
|
+
}) &&
|
|
451
|
+
fixture.allowedCommands.length === 0 &&
|
|
452
|
+
fixture.permittedWritePaths?.length === 1 &&
|
|
453
|
+
fixture.writesPermitted &&
|
|
454
|
+
DIRECT_WORK_FORBIDDEN_ACTIONS.every((action) =>
|
|
455
|
+
fixture.forbiddenActions.includes(action),
|
|
456
|
+
),
|
|
457
|
+
);
|
|
458
|
+
if (!directFixturesValid) {
|
|
459
|
+
diagnostics.push({
|
|
460
|
+
code: "direct-work-regression",
|
|
461
|
+
message:
|
|
462
|
+
"direct-work fixtures must authorize one requested path, zero Docket commands, and no tracker or active-marker mutation",
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
const ambiguousPickup = fixtures.find(
|
|
467
|
+
(fixture) => fixture.id === "pickup-ambiguous-tracked-reference",
|
|
468
|
+
);
|
|
469
|
+
if (
|
|
470
|
+
ambiguousPickup?.expectedIntent !== "pickup" ||
|
|
471
|
+
ambiguousPickup.expectedEntrypoint.kind !== "command" ||
|
|
472
|
+
ambiguousPickup.allowedCommands.length !== 1 ||
|
|
473
|
+
ambiguousPickup.allowedCommands[0] !== 'docket search "login" --json' ||
|
|
474
|
+
ambiguousPickup.writesPermitted ||
|
|
475
|
+
!ambiguousPickup.forbiddenActions.includes("docket task start") ||
|
|
476
|
+
!ambiguousPickup.forbiddenActions.includes("substitute the top ready task")
|
|
477
|
+
) {
|
|
478
|
+
diagnostics.push({
|
|
479
|
+
code: "ambiguous-pickup-regression",
|
|
480
|
+
fixture: ambiguousPickup?.id,
|
|
481
|
+
message:
|
|
482
|
+
"an ambiguous tracked reference must allow focused resolution only and forbid pickup mutation or top-ready substitution",
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
const namedPickup = fixtures.find(
|
|
487
|
+
(fixture) => fixture.id === "pickup-named-start",
|
|
488
|
+
);
|
|
489
|
+
const nextPickup = fixtures.find(
|
|
490
|
+
(fixture) => fixture.id === "pickup-explicit-next-docket-task",
|
|
491
|
+
);
|
|
492
|
+
if (
|
|
493
|
+
namedPickup?.allowedCommands[0] !== "docket task start DKT-12 --json" ||
|
|
494
|
+
nextPickup?.allowedCommands[0] !== "docket task start --json"
|
|
495
|
+
) {
|
|
496
|
+
diagnostics.push({
|
|
497
|
+
code: "pickup-control-regression",
|
|
498
|
+
message:
|
|
499
|
+
"named pickup must stay named while explicit next-Docket pickup retains the bare engine selection command",
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
const createAndStart = fixtures.find(
|
|
504
|
+
(fixture) => fixture.id === "task-create-and-start",
|
|
505
|
+
);
|
|
506
|
+
if (
|
|
507
|
+
createAndStart?.expectedIntent !== "task-management" ||
|
|
508
|
+
!createAndStart.composedIntents?.includes("pickup") ||
|
|
509
|
+
!createAndStart.allowedCommands.includes(
|
|
510
|
+
"docket task start <created-ID> --json",
|
|
511
|
+
) ||
|
|
512
|
+
createAndStart.allowedCommands.includes("docket task start --json")
|
|
513
|
+
) {
|
|
514
|
+
diagnostics.push({
|
|
515
|
+
code: "combined-intent-regression",
|
|
516
|
+
fixture: createAndStart?.id,
|
|
517
|
+
message:
|
|
518
|
+
"track-and-start must compose task creation with named pickup of the created ID, never bare pickup",
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
for (const trait of ["ambiguous-wording", "negative-constraint"] as const) {
|
|
522
|
+
if (!fixtures.some((fixture) => fixture.traits?.includes(trait))) {
|
|
523
|
+
diagnostics.push({
|
|
524
|
+
code: "missing-trait-coverage",
|
|
525
|
+
message: `no prompt fixture covers ${trait}`,
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
const orientationNoWrite = fixtures.find((fixture) =>
|
|
531
|
+
fixture.traits?.includes("orientation-no-write-regression"),
|
|
532
|
+
);
|
|
533
|
+
let orientationNoWriteValid = false;
|
|
534
|
+
if (orientationNoWrite) {
|
|
535
|
+
orientationNoWriteValid =
|
|
536
|
+
orientationNoWrite.expectedIntent === "orientation" &&
|
|
537
|
+
sameEntrypoint(orientationNoWrite.expectedEntrypoint, {
|
|
538
|
+
kind: "command",
|
|
539
|
+
value: "docket overview --json",
|
|
540
|
+
}) &&
|
|
541
|
+
!orientationNoWrite.writesPermitted &&
|
|
542
|
+
orientationNoWrite.allowedCommands.length === 1 &&
|
|
543
|
+
orientationNoWrite.allowedCommands[0] === "docket overview --json" &&
|
|
544
|
+
orientationNoWrite.forbiddenActions.includes("docket task start") &&
|
|
545
|
+
orientationNoWrite.forbiddenActions.includes("docket index") &&
|
|
546
|
+
orientationNoWrite.forbiddenActions.includes("repository-wide search");
|
|
547
|
+
}
|
|
548
|
+
if (!orientationNoWriteValid) {
|
|
549
|
+
diagnostics.push({
|
|
550
|
+
code: "orientation-no-write-regression",
|
|
551
|
+
fixture: orientationNoWrite?.id,
|
|
552
|
+
message:
|
|
553
|
+
"review-only orientation must select overview, forbid writes, and stay out of broad repository search",
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
return diagnostics;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
export type IntentDiscoveryDiagnosticCode =
|
|
561
|
+
| "description-overlap"
|
|
562
|
+
| "missing-description-signature"
|
|
563
|
+
| "example-overlap";
|
|
564
|
+
|
|
565
|
+
export interface IntentDiscoveryDiagnostic {
|
|
566
|
+
code: IntentDiscoveryDiagnosticCode;
|
|
567
|
+
intent: DocketIntentId;
|
|
568
|
+
otherIntent?: DocketIntentId;
|
|
569
|
+
message: string;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
const DISCOVERY_SIGNATURES: Readonly<
|
|
573
|
+
Record<DocketIntentId, readonly string[]>
|
|
574
|
+
> = {
|
|
575
|
+
orientation: ["read-only orientation", "what comes next"],
|
|
576
|
+
"backlog-hygiene": ["backlog hygiene audit", "stale or inconsistent"],
|
|
577
|
+
pickup: [
|
|
578
|
+
"start or resume explicitly tracked docket work",
|
|
579
|
+
"active-task state",
|
|
580
|
+
],
|
|
581
|
+
"epic-supervision": ["run a named epic to completion", "serial fallback"],
|
|
582
|
+
"task-management": ["explicit task operation", "only the work item"],
|
|
583
|
+
"project-maintenance": [
|
|
584
|
+
"explicitly named docket maintenance",
|
|
585
|
+
"using that workflow",
|
|
586
|
+
],
|
|
587
|
+
};
|
|
588
|
+
|
|
589
|
+
const normalized = (text: string): string =>
|
|
590
|
+
text
|
|
591
|
+
.toLowerCase()
|
|
592
|
+
.replace(/[^a-z0-9]+/g, " ")
|
|
593
|
+
.trim();
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* Release guard for mutually exclusive discovery summaries. Signatures are
|
|
597
|
+
* deliberately narrow labels, not prompt-matching rules.
|
|
598
|
+
*/
|
|
599
|
+
export function validateIntentDiscoveryDescriptions(
|
|
600
|
+
intents: Readonly<
|
|
601
|
+
Record<DocketIntentId, DocketIntentContract>
|
|
602
|
+
> = DOCKET_INTENTS,
|
|
603
|
+
): IntentDiscoveryDiagnostic[] {
|
|
604
|
+
const diagnostics: IntentDiscoveryDiagnostic[] = [];
|
|
605
|
+
|
|
606
|
+
for (const id of DOCKET_INTENT_IDS) {
|
|
607
|
+
const description = normalized(intents[id].discovery);
|
|
608
|
+
const own = DISCOVERY_SIGNATURES[id];
|
|
609
|
+
if (
|
|
610
|
+
!own.every((signature) => description.includes(normalized(signature)))
|
|
611
|
+
) {
|
|
612
|
+
diagnostics.push({
|
|
613
|
+
code: "missing-description-signature",
|
|
614
|
+
intent: id,
|
|
615
|
+
message: `discovery summary is missing the exclusive ${id} signature`,
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
for (const other of DOCKET_INTENT_IDS) {
|
|
619
|
+
if (other === id) continue;
|
|
620
|
+
for (const signature of DISCOVERY_SIGNATURES[other]) {
|
|
621
|
+
if (!description.includes(normalized(signature))) continue;
|
|
622
|
+
diagnostics.push({
|
|
623
|
+
code: "description-overlap",
|
|
624
|
+
intent: id,
|
|
625
|
+
otherIntent: other,
|
|
626
|
+
message: `discovery summary includes the ${other} signature “${signature}”`,
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
const examples = new Map<string, DocketIntentId>();
|
|
633
|
+
for (const id of DOCKET_INTENT_IDS) {
|
|
634
|
+
for (const example of intents[id].positiveExamples) {
|
|
635
|
+
const key = normalized(example);
|
|
636
|
+
const previous = examples.get(key);
|
|
637
|
+
if (previous && previous !== id) {
|
|
638
|
+
diagnostics.push({
|
|
639
|
+
code: "example-overlap",
|
|
640
|
+
intent: id,
|
|
641
|
+
otherIntent: previous,
|
|
642
|
+
message: `positive example “${example}” belongs to both intents`,
|
|
643
|
+
});
|
|
644
|
+
} else {
|
|
645
|
+
examples.set(key, id);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
return diagnostics;
|
|
651
|
+
}
|
package/src/schema.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Zod schemas for the OKF task profile. Two rules from OKF carry through
|
|
2
|
+
// everywhere: only `type` is required on a concept, and unknown types/fields
|
|
3
|
+
// are tolerated, never rejected (catchall + generic Concept fallback).
|
|
4
|
+
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import type { DocketConfig } from "./config";
|
|
7
|
+
import { DECISION_STATES, PRIORITIES, STATES, WORK_ITEM_TYPES } from "./states";
|
|
8
|
+
|
|
9
|
+
const base = {
|
|
10
|
+
type: z.string().min(1),
|
|
11
|
+
title: z.string().optional(),
|
|
12
|
+
description: z.string().optional(),
|
|
13
|
+
tags: z.array(z.string()).default([]),
|
|
14
|
+
aliases: z.array(z.string()).default([]),
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export function buildSchemas(config: DocketConfig) {
|
|
18
|
+
const workItemId = new RegExp(`^${config.project}-\\d+$`);
|
|
19
|
+
const decisionId = new RegExp(`^${config.ids.decision_prefix}-\\d+$`);
|
|
20
|
+
|
|
21
|
+
const workItem = z
|
|
22
|
+
.object({
|
|
23
|
+
...base,
|
|
24
|
+
type: z.enum(WORK_ITEM_TYPES),
|
|
25
|
+
id: z.string().regex(workItemId, `expected ${config.project}-<n>`),
|
|
26
|
+
status: z.enum(STATES),
|
|
27
|
+
epic: z.string().optional(),
|
|
28
|
+
spec: z.string().optional(),
|
|
29
|
+
depends_on: z.array(z.string()).default([]),
|
|
30
|
+
priority: z.enum(PRIORITIES).default("p2"),
|
|
31
|
+
rank: z.number().optional(),
|
|
32
|
+
assignee: z.string().optional(),
|
|
33
|
+
})
|
|
34
|
+
.catchall(z.unknown());
|
|
35
|
+
|
|
36
|
+
const decision = z
|
|
37
|
+
.object({
|
|
38
|
+
...base,
|
|
39
|
+
type: z.literal("Decision"),
|
|
40
|
+
id: z
|
|
41
|
+
.string()
|
|
42
|
+
.regex(decisionId, `expected ${config.ids.decision_prefix}-<n>`),
|
|
43
|
+
status: z.enum(DECISION_STATES).default("accepted"),
|
|
44
|
+
supersedes: z.string().optional(),
|
|
45
|
+
})
|
|
46
|
+
.catchall(z.unknown());
|
|
47
|
+
|
|
48
|
+
const generic = z.object(base).catchall(z.unknown());
|
|
49
|
+
|
|
50
|
+
return { workItem, decision, generic };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export type Schemas = ReturnType<typeof buildSchemas>;
|
|
54
|
+
export type WorkItemFrontmatter = z.infer<Schemas["workItem"]>;
|
|
55
|
+
export type DecisionFrontmatter = z.infer<Schemas["decision"]>;
|
|
56
|
+
export type GenericFrontmatter = z.infer<Schemas["generic"]>;
|