@dailephd/my-dev-kit-lab 0.8.2 → 0.9.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.
Files changed (122) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +19 -3
  3. package/benchmarks/contracts/agent-success-rate-project-profiles.json +33 -0
  4. package/benchmarks/contracts/agent-success-rate-tasks.json +671 -0
  5. package/benchmarks/projects/agent-success-inventory-node/.gitattributes +2 -0
  6. package/benchmarks/projects/agent-success-inventory-node/README.md +74 -0
  7. package/benchmarks/projects/agent-success-inventory-node/package.json +14 -0
  8. package/benchmarks/projects/agent-success-inventory-node/src/fulfillmentReport.js +38 -0
  9. package/benchmarks/projects/agent-success-inventory-node/src/index.js +26 -0
  10. package/benchmarks/projects/agent-success-inventory-node/src/inventoryStore.js +114 -0
  11. package/benchmarks/projects/agent-success-inventory-node/src/quantity.js +19 -0
  12. package/benchmarks/projects/agent-success-inventory-node/src/reservationService.js +45 -0
  13. package/benchmarks/projects/agent-success-inventory-node/tests/quantity-edge.check.mjs +51 -0
  14. package/benchmarks/projects/agent-success-inventory-node/tests/quantity-primary.check.mjs +46 -0
  15. package/benchmarks/projects/agent-success-inventory-node/tests/regression.check.mjs +115 -0
  16. package/benchmarks/projects/agent-success-inventory-node/tests/report-edge.check.mjs +109 -0
  17. package/benchmarks/projects/agent-success-inventory-node/tests/report-primary.check.mjs +47 -0
  18. package/benchmarks/projects/agent-success-inventory-node/tests/reservation-edge.check.mjs +63 -0
  19. package/benchmarks/projects/agent-success-inventory-node/tests/reservation-primary.check.mjs +44 -0
  20. package/benchmarks/projects/agent-success-task-board-node/.gitattributes +2 -0
  21. package/benchmarks/projects/agent-success-task-board-node/README.md +77 -0
  22. package/benchmarks/projects/agent-success-task-board-node/package.json +14 -0
  23. package/benchmarks/projects/agent-success-task-board-node/src/index.js +24 -0
  24. package/benchmarks/projects/agent-success-task-board-node/src/projectSummary.js +15 -0
  25. package/benchmarks/projects/agent-success-task-board-node/src/taskService.js +58 -0
  26. package/benchmarks/projects/agent-success-task-board-node/src/taskStore.js +58 -0
  27. package/benchmarks/projects/agent-success-task-board-node/src/validation.js +31 -0
  28. package/benchmarks/projects/agent-success-task-board-node/tests/import-edge.check.mjs +65 -0
  29. package/benchmarks/projects/agent-success-task-board-node/tests/import-primary.check.mjs +31 -0
  30. package/benchmarks/projects/agent-success-task-board-node/tests/regression.check.mjs +107 -0
  31. package/benchmarks/projects/agent-success-task-board-node/tests/summary-edge.check.mjs +67 -0
  32. package/benchmarks/projects/agent-success-task-board-node/tests/summary-primary.check.mjs +54 -0
  33. package/benchmarks/projects/agent-success-task-board-node/tests/title-edge.check.mjs +39 -0
  34. package/benchmarks/projects/agent-success-task-board-node/tests/title-primary.check.mjs +29 -0
  35. package/dist/src/agents/adapters/claudeAdapter.js +6 -3
  36. package/dist/src/agents/adapters/codexAdapter.js +51 -10
  37. package/dist/src/agents/index.js +1 -0
  38. package/dist/src/agents/normalizeAgentTelemetry.js +50 -0
  39. package/dist/src/agents/parseAgentTokenUsage.js +71 -49
  40. package/dist/src/agents/providerBuildObservation.js +97 -0
  41. package/dist/src/audits/core/projectInventory.js +1 -0
  42. package/dist/src/cli/help.js +44 -1
  43. package/dist/src/commands/runExperimentDescribeCommand.js +83 -1
  44. package/dist/src/commands/runExperimentRunCommand.js +309 -6
  45. package/dist/src/core/processTree.js +55 -0
  46. package/dist/src/core/runMeasuredCommand.js +108 -22
  47. package/dist/src/evaluation/agentSuccess/applyPatchToSandbox.js +37 -0
  48. package/dist/src/evaluation/agentSuccess/assessBaseline.js +23 -0
  49. package/dist/src/evaluation/agentSuccess/corpusTypes.js +74 -0
  50. package/dist/src/evaluation/agentSuccess/extractPatchCandidate.js +46 -0
  51. package/dist/src/evaluation/agentSuccess/index.js +14 -0
  52. package/dist/src/evaluation/agentSuccess/parseUnifiedDiff.js +210 -0
  53. package/dist/src/evaluation/agentSuccess/patchTypes.js +5 -0
  54. package/dist/src/evaluation/agentSuccess/readAgentSuccessCorpus.js +183 -0
  55. package/dist/src/evaluation/agentSuccess/runVerificationCheck.js +100 -0
  56. package/dist/src/evaluation/agentSuccess/taskPaths.js +34 -0
  57. package/dist/src/evaluation/agentSuccess/taskTypes.js +14 -0
  58. package/dist/src/evaluation/agentSuccess/validateAgentSuccessCorpus.js +307 -0
  59. package/dist/src/evaluation/agentSuccess/validateAgentSuccessTask.js +297 -0
  60. package/dist/src/evaluation/agentSuccess/validatePatchPolicy.js +66 -0
  61. package/dist/src/evaluation/agentSuccess/verificationTypes.js +1 -0
  62. package/dist/src/evaluation/benchmarkSandbox/createBenchmarkSandbox.js +127 -0
  63. package/dist/src/evaluation/benchmarkSandbox/errors.js +11 -0
  64. package/dist/src/evaluation/benchmarkSandbox/gitExecutor.js +62 -0
  65. package/dist/src/evaluation/benchmarkSandbox/index.js +8 -0
  66. package/dist/src/evaluation/benchmarkSandbox/minimalHostEnv.js +37 -0
  67. package/dist/src/evaluation/benchmarkSandbox/pathPolicy.js +49 -0
  68. package/dist/src/evaluation/benchmarkSandbox/removeBenchmarkSandbox.js +74 -0
  69. package/dist/src/evaluation/benchmarkSandbox/treeSnapshot.js +57 -0
  70. package/dist/src/evaluation/benchmarkSandbox/types.js +3 -0
  71. package/dist/src/evaluation/changeSet/captureChangeSet.js +98 -0
  72. package/dist/src/evaluation/changeSet/index.js +2 -0
  73. package/dist/src/evaluation/changeSet/types.js +9 -0
  74. package/dist/src/experiments/defaultRegistry.js +2 -0
  75. package/dist/src/experiments/plugins/agentSuccessRate/agentTaskProjection.js +12 -0
  76. package/dist/src/experiments/plugins/agentSuccessRate/analysis.js +425 -0
  77. package/dist/src/experiments/plugins/agentSuccessRate/analysisArtifact.js +155 -0
  78. package/dist/src/experiments/plugins/agentSuccessRate/analysisTypes.js +65 -0
  79. package/dist/src/experiments/plugins/agentSuccessRate/attemptEvidence.js +7 -0
  80. package/dist/src/experiments/plugins/agentSuccessRate/campaignCheckpoint.js +216 -0
  81. package/dist/src/experiments/plugins/agentSuccessRate/campaignCheckpointStore.js +588 -0
  82. package/dist/src/experiments/plugins/agentSuccessRate/campaignEvidenceQuality.js +77 -0
  83. package/dist/src/experiments/plugins/agentSuccessRate/campaignExecution.js +314 -0
  84. package/dist/src/experiments/plugins/agentSuccessRate/campaignIdentity.js +111 -0
  85. package/dist/src/experiments/plugins/agentSuccessRate/campaignProgress.js +173 -0
  86. package/dist/src/experiments/plugins/agentSuccessRate/campaignQueue.js +11 -0
  87. package/dist/src/experiments/plugins/agentSuccessRate/campaignRecovery.js +256 -0
  88. package/dist/src/experiments/plugins/agentSuccessRate/campaignResumableExecution.js +207 -0
  89. package/dist/src/experiments/plugins/agentSuccessRate/config.js +123 -0
  90. package/dist/src/experiments/plugins/agentSuccessRate/contextGeneration.js +497 -0
  91. package/dist/src/experiments/plugins/agentSuccessRate/execution.js +441 -0
  92. package/dist/src/experiments/plugins/agentSuccessRate/executionArtifact.js +126 -0
  93. package/dist/src/experiments/plugins/agentSuccessRate/executionTypes.js +2 -0
  94. package/dist/src/experiments/plugins/agentSuccessRate/index.js +26 -0
  95. package/dist/src/experiments/plugins/agentSuccessRate/metadata.js +28 -0
  96. package/dist/src/experiments/plugins/agentSuccessRate/metrics.js +76 -0
  97. package/dist/src/experiments/plugins/agentSuccessRate/plugin.js +517 -0
  98. package/dist/src/experiments/plugins/agentSuccessRate/realAgentExecution.js +272 -0
  99. package/dist/src/experiments/plugins/agentSuccessRate/realAgentPrompt.js +43 -0
  100. package/dist/src/experiments/plugins/agentSuccessRate/repairAnalysis.js +148 -0
  101. package/dist/src/experiments/plugins/agentSuccessRate/repairExecution.js +103 -0
  102. package/dist/src/experiments/plugins/agentSuccessRate/repairFeedback.js +91 -0
  103. package/dist/src/experiments/plugins/agentSuccessRate/repairPolicy.js +88 -0
  104. package/dist/src/experiments/plugins/agentSuccessRate/timeoutPresets.js +9 -0
  105. package/dist/src/experiments/plugins/agentSuccessRate/types.js +1 -0
  106. package/dist/src/experiments/plugins/warmIndexReuse/plugin.js +2 -3
  107. package/dist/src/report/experiments/agentSuccessCampaignProgressReportModel.js +1 -0
  108. package/dist/src/report/experiments/agentSuccessRateReportModel.js +6 -0
  109. package/dist/src/report/experiments/buildAgentSuccessCampaignProgressReport.js +6 -0
  110. package/dist/src/report/experiments/buildAgentSuccessRateReport.js +266 -0
  111. package/dist/src/report/experiments/buildPluginExperimentReport.js +27 -2
  112. package/dist/src/report/experiments/index.js +4 -0
  113. package/dist/src/report/experiments/renderAgentSuccessCampaignProgressHtml.js +6 -0
  114. package/dist/src/report/experiments/renderAgentSuccessCampaignProgressText.js +25 -0
  115. package/dist/src/report/experiments/renderAgentSuccessRateHtml.js +171 -0
  116. package/dist/src/report/experiments/renderAgentSuccessRateText.js +146 -0
  117. package/dist/src/report/experiments/renderPluginExperimentReportHtml.js +3 -0
  118. package/dist/src/report/experiments/renderPluginExperimentReportText.js +5 -0
  119. package/dist/src/report/experiments/writeAgentSuccessCampaignProgressReport.js +135 -0
  120. package/dist/src/report/experiments/writePluginExperimentReports.js +4 -3
  121. package/docs/METRICS.md +79 -0
  122. package/package.json +1 -1
@@ -0,0 +1,671 @@
1
+ [
2
+ {
3
+ "schemaVersion": "my-dev-kit-lab-agent-success-task-v1",
4
+ "id": "asr-board-title-normalization",
5
+ "title": "Normalize task titles on creation",
6
+ "benchmarkProject": "agent-success-task-board-node",
7
+ "projectProfileRef": "agent-success-task-board-node",
8
+ "taskLocality": "localized",
9
+ "query": "task title validation: trim surrounding whitespace, reject blank titles",
10
+ "sourceRoots": [
11
+ "src"
12
+ ],
13
+ "rawIncludeGlobs": [
14
+ "src/**/*.js"
15
+ ],
16
+ "instruction": "Task creation must normalize titles. A title is stored without its surrounding whitespace, while whitespace inside the title is kept. A title that is empty or contains only whitespace must be rejected with the board's documented invalid-title error (code INVALID_TITLE), and a rejected creation must not change the store or consume a task id. Titles that are already trimmed must keep working exactly as before. Fix the implementation under src/; do not change tests, package.json or the README.",
17
+ "expectedEditFiles": [
18
+ "src/validation.js"
19
+ ],
20
+ "allowedEditFiles": [
21
+ "src/validation.js"
22
+ ],
23
+ "protectedFiles": [
24
+ ".gitattributes",
25
+ "README.md",
26
+ "package.json",
27
+ "tests/import-edge.check.mjs",
28
+ "tests/import-primary.check.mjs",
29
+ "tests/regression.check.mjs",
30
+ "tests/summary-edge.check.mjs",
31
+ "tests/summary-primary.check.mjs",
32
+ "tests/title-edge.check.mjs",
33
+ "tests/title-primary.check.mjs"
34
+ ],
35
+ "taskChecks": [
36
+ {
37
+ "id": "title-primary",
38
+ "executable": "node",
39
+ "args": [
40
+ "--test",
41
+ "tests/title-primary.check.mjs"
42
+ ],
43
+ "timeoutMs": 30000
44
+ },
45
+ {
46
+ "id": "title-edge",
47
+ "executable": "node",
48
+ "args": [
49
+ "--test",
50
+ "tests/title-edge.check.mjs"
51
+ ],
52
+ "timeoutMs": 30000
53
+ }
54
+ ],
55
+ "regressionChecks": [
56
+ {
57
+ "id": "regression",
58
+ "executable": "node",
59
+ "args": [
60
+ "--test",
61
+ "tests/regression.check.mjs"
62
+ ],
63
+ "timeoutMs": 30000
64
+ }
65
+ ],
66
+ "behaviorFacts": [
67
+ {
68
+ "id": "title-trimmed",
69
+ "text": "Created and imported tasks store the title without surrounding whitespace.",
70
+ "required": true,
71
+ "verificationCheckIds": [
72
+ "title-primary"
73
+ ]
74
+ },
75
+ {
76
+ "id": "title-blank-rejected",
77
+ "text": "Empty and whitespace-only titles are rejected with INVALID_TITLE.",
78
+ "required": true,
79
+ "verificationCheckIds": [
80
+ "title-primary",
81
+ "title-edge"
82
+ ]
83
+ },
84
+ {
85
+ "id": "title-no-side-effects",
86
+ "text": "A rejected title leaves the store and id sequence unchanged and interior whitespace is preserved.",
87
+ "required": false,
88
+ "verificationCheckIds": [
89
+ "title-edge"
90
+ ]
91
+ },
92
+ {
93
+ "id": "title-existing-behavior",
94
+ "text": "Existing creation, completion and import behavior is unchanged.",
95
+ "required": false,
96
+ "verificationCheckIds": [
97
+ "regression"
98
+ ]
99
+ }
100
+ ],
101
+ "deterministicFixture": {
102
+ "id": "asr-board-title-normalization-reference",
103
+ "patch": "diff --git a/src/validation.js b/src/validation.js\nindex f3fa377..1ce3d58 100644\n--- a/src/validation.js\n+++ b/src/validation.js\n@@ -14,10 +14,14 @@ export class ValidationError extends Error {\n * Returns the normalized title for a task, or throws ValidationError(INVALID_TITLE).\n */\n export function validateTitle(title) {\n- if (typeof title !== \"string\" || title.length === 0) {\n- throw new ValidationError(INVALID_TITLE, \"title must be a non-empty string\");\n+ if (typeof title !== \"string\") {\n+ throw new ValidationError(INVALID_TITLE, \"title must be a string\");\n }\n- return title;\n+ const normalized = title.trim();\n+ if (normalized.length === 0) {\n+ throw new ValidationError(INVALID_TITLE, \"title must not be blank\");\n+ }\n+ return normalized;\n }\n \n /**\n",
104
+ "notes": "Replaces the title-length test in validateTitle with a trim-then-test normalization."
105
+ }
106
+ },
107
+ {
108
+ "schemaVersion": "my-dev-kit-lab-agent-success-task-v1",
109
+ "id": "asr-board-import-idempotency",
110
+ "title": "Make task import idempotent",
111
+ "benchmarkProject": "agent-success-task-board-node",
112
+ "projectProfileRef": "agent-success-task-board-node",
113
+ "taskLocality": "cross-module",
114
+ "query": "external task import: normalized external id identity, duplicate detection across import calls",
115
+ "sourceRoots": [
116
+ "src"
117
+ ],
118
+ "rawIncludeGlobs": [
119
+ "src/**/*.js"
120
+ ],
121
+ "instruction": "Importing external tasks must be idempotent. External ids identify tasks after normalization (surrounding whitespace removed and letters compared case-insensitively), and the normalized form is what the board stores and reports. Re-importing a task whose external id is already known, from an earlier import call or earlier in the same call, must not create a duplicate and must be reported as skipped. Import order must stay deterministic, new distinct external ids must still be importable, and manually created tasks must never be removed. The identity rules are shared between the task store and the import service; make them consistent. Fix the implementation under src/; do not change tests, package.json or the README.",
122
+ "expectedEditFiles": [
123
+ "src/taskService.js",
124
+ "src/taskStore.js"
125
+ ],
126
+ "allowedEditFiles": [
127
+ "src/taskService.js",
128
+ "src/taskStore.js"
129
+ ],
130
+ "protectedFiles": [
131
+ ".gitattributes",
132
+ "README.md",
133
+ "package.json",
134
+ "tests/import-edge.check.mjs",
135
+ "tests/import-primary.check.mjs",
136
+ "tests/regression.check.mjs",
137
+ "tests/summary-edge.check.mjs",
138
+ "tests/summary-primary.check.mjs",
139
+ "tests/title-edge.check.mjs",
140
+ "tests/title-primary.check.mjs"
141
+ ],
142
+ "taskChecks": [
143
+ {
144
+ "id": "import-primary",
145
+ "executable": "node",
146
+ "args": [
147
+ "--test",
148
+ "tests/import-primary.check.mjs"
149
+ ],
150
+ "timeoutMs": 30000
151
+ },
152
+ {
153
+ "id": "import-edge",
154
+ "executable": "node",
155
+ "args": [
156
+ "--test",
157
+ "tests/import-edge.check.mjs"
158
+ ],
159
+ "timeoutMs": 30000
160
+ }
161
+ ],
162
+ "regressionChecks": [
163
+ {
164
+ "id": "regression",
165
+ "executable": "node",
166
+ "args": [
167
+ "--test",
168
+ "tests/regression.check.mjs"
169
+ ],
170
+ "timeoutMs": 30000
171
+ }
172
+ ],
173
+ "behaviorFacts": [
174
+ {
175
+ "id": "import-no-duplicates",
176
+ "text": "Re-importing the same external task across calls does not create a duplicate.",
177
+ "required": true,
178
+ "verificationCheckIds": [
179
+ "import-primary"
180
+ ]
181
+ },
182
+ {
183
+ "id": "import-normalized-identity",
184
+ "text": "External ids are compared and stored in one normalized form.",
185
+ "required": true,
186
+ "verificationCheckIds": [
187
+ "import-primary",
188
+ "import-edge"
189
+ ]
190
+ },
191
+ {
192
+ "id": "import-in-call-and-order",
193
+ "text": "Duplicates inside one call are collapsed and import order is deterministic.",
194
+ "required": false,
195
+ "verificationCheckIds": [
196
+ "import-edge"
197
+ ]
198
+ },
199
+ {
200
+ "id": "import-existing-behavior",
201
+ "text": "Exact re-imports, distinct imports and manual tasks keep working.",
202
+ "required": false,
203
+ "verificationCheckIds": [
204
+ "regression"
205
+ ]
206
+ }
207
+ ],
208
+ "deterministicFixture": {
209
+ "id": "asr-board-import-idempotency-reference",
210
+ "patch": "diff --git a/src/taskService.js b/src/taskService.js\nindex e9d2084..e0ae2f5 100644\n--- a/src/taskService.js\n+++ b/src/taskService.js\n@@ -38,7 +38,7 @@ export class TaskService {\n throw new ValidationError(INVALID_EXTERNAL_ID, \"external id must be a non-empty string\");\n }\n const externalId = normalizeExternalId(item.externalId);\n- if (this.store.findByExternalId(item.externalId)) {\n+ if (this.store.findByExternalId(externalId)) {\n skipped.push(externalId);\n continue;\n }\ndiff --git a/src/taskStore.js b/src/taskStore.js\nindex ecb50cd..93de902 100644\n--- a/src/taskStore.js\n+++ b/src/taskStore.js\n@@ -10,7 +10,7 @@ export class TaskNotFoundError extends Error {\n * Returns the identity key used to compare external task identifiers.\n */\n export function normalizeExternalId(externalId) {\n- return String(externalId).trim();\n+ return String(externalId).trim().toLowerCase();\n }\n \n /**\n",
211
+ "notes": "Lower-cases the identity key in the store and looks up the normalized key in the service."
212
+ }
213
+ },
214
+ {
215
+ "schemaVersion": "my-dev-kit-lab-agent-success-task-v1",
216
+ "id": "asr-board-project-summary",
217
+ "title": "Keep project identity and summaries consistent",
218
+ "benchmarkProject": "agent-success-task-board-node",
219
+ "projectProfileRef": "agent-success-task-board-node",
220
+ "taskLocality": "broad-change",
221
+ "query": "project identity normalization across task creation, import and per-project summaries",
222
+ "sourceRoots": [
223
+ "src"
224
+ ],
225
+ "rawIncludeGlobs": [
226
+ "src/**/*.js"
227
+ ],
228
+ "instruction": "Every task must belong to a normalized project identity (project id trimmed and lower-cased; tasks without a project belong to inbox), and creation, import and summaries must all use that same identity. A blank project id must be rejected with the documented invalid-project error (code INVALID_PROJECT) before anything is stored. A project summary must count only the tasks that belong to exactly the requested project (requested ids are normalized the same way), its completed count must be derived from those tasks only and can never exceed its total, and unrelated tasks, including tasks of projects with similar names, must never change it. Summaries stay deterministic. Fix the implementation under src/; do not change tests, package.json or the README.",
229
+ "expectedEditFiles": [
230
+ "src/projectSummary.js",
231
+ "src/taskService.js",
232
+ "src/validation.js"
233
+ ],
234
+ "allowedEditFiles": [
235
+ "src/projectSummary.js",
236
+ "src/taskService.js",
237
+ "src/validation.js"
238
+ ],
239
+ "protectedFiles": [
240
+ ".gitattributes",
241
+ "README.md",
242
+ "package.json",
243
+ "tests/import-edge.check.mjs",
244
+ "tests/import-primary.check.mjs",
245
+ "tests/regression.check.mjs",
246
+ "tests/summary-edge.check.mjs",
247
+ "tests/summary-primary.check.mjs",
248
+ "tests/title-edge.check.mjs",
249
+ "tests/title-primary.check.mjs"
250
+ ],
251
+ "taskChecks": [
252
+ {
253
+ "id": "summary-primary",
254
+ "executable": "node",
255
+ "args": [
256
+ "--test",
257
+ "tests/summary-primary.check.mjs"
258
+ ],
259
+ "timeoutMs": 30000
260
+ },
261
+ {
262
+ "id": "summary-edge",
263
+ "executable": "node",
264
+ "args": [
265
+ "--test",
266
+ "tests/summary-edge.check.mjs"
267
+ ],
268
+ "timeoutMs": 30000
269
+ }
270
+ ],
271
+ "regressionChecks": [
272
+ {
273
+ "id": "regression",
274
+ "executable": "node",
275
+ "args": [
276
+ "--test",
277
+ "tests/regression.check.mjs"
278
+ ],
279
+ "timeoutMs": 30000
280
+ }
281
+ ],
282
+ "behaviorFacts": [
283
+ {
284
+ "id": "project-identity-normalized",
285
+ "text": "Created and imported tasks keep one normalized project identity and blank project ids are rejected.",
286
+ "required": true,
287
+ "verificationCheckIds": [
288
+ "summary-primary",
289
+ "summary-edge"
290
+ ]
291
+ },
292
+ {
293
+ "id": "summary-filters-project",
294
+ "text": "A summary contains exactly the tasks of the requested project.",
295
+ "required": true,
296
+ "verificationCheckIds": [
297
+ "summary-primary"
298
+ ]
299
+ },
300
+ {
301
+ "id": "summary-consistent-counts",
302
+ "text": "Completed never exceeds total and unrelated tasks never change a summary.",
303
+ "required": true,
304
+ "verificationCheckIds": [
305
+ "summary-edge"
306
+ ]
307
+ },
308
+ {
309
+ "id": "summary-deterministic",
310
+ "text": "Summaries are deterministic and list task ids in creation order.",
311
+ "required": false,
312
+ "verificationCheckIds": [
313
+ "summary-edge"
314
+ ]
315
+ },
316
+ {
317
+ "id": "summary-existing-behavior",
318
+ "text": "Single-project summaries and the other board behaviors are unchanged.",
319
+ "required": false,
320
+ "verificationCheckIds": [
321
+ "regression"
322
+ ]
323
+ }
324
+ ],
325
+ "deterministicFixture": {
326
+ "id": "asr-board-project-summary-reference",
327
+ "patch": "diff --git a/src/projectSummary.js b/src/projectSummary.js\nindex 8b24e3b..7de36a9 100644\n--- a/src/projectSummary.js\n+++ b/src/projectSummary.js\n@@ -1,12 +1,14 @@\n+import { normalizeProjectId } from \"./validation.js\";\n+\n /**\n * Summarizes the tasks of one project. Task ids are listed in creation order.\n */\n export function summarizeProject(store, projectId) {\n- const all = store.list();\n- const tasks = all.filter((task) => task.projectId.startsWith(projectId));\n- const completed = all.filter((task) => task.completed).length;\n+ const normalizedProjectId = normalizeProjectId(projectId);\n+ const tasks = store.list().filter((task) => task.projectId === normalizedProjectId);\n+ const completed = tasks.filter((task) => task.completed).length;\n return {\n- projectId,\n+ projectId: normalizedProjectId,\n total: tasks.length,\n completed,\n open: tasks.length - completed,\ndiff --git a/src/taskService.js b/src/taskService.js\nindex e9d2084..9de3034 100644\n--- a/src/taskService.js\n+++ b/src/taskService.js\n@@ -1,5 +1,5 @@\n import { TaskNotFoundError, normalizeExternalId } from \"./taskStore.js\";\n-import { INVALID_EXTERNAL_ID, ValidationError, validateTitle } from \"./validation.js\";\n+import { INVALID_EXTERNAL_ID, ValidationError, normalizeProjectId, validateTitle } from \"./validation.js\";\n \n const DEFAULT_PROJECT = \"inbox\";\n \n@@ -10,10 +10,11 @@ export class TaskService {\n \n createTask({ title, projectId = DEFAULT_PROJECT } = {}) {\n const normalizedTitle = validateTitle(title);\n+ const normalizedProjectId = normalizeProjectId(projectId);\n return this.store.add({\n id: this.store.nextId(),\n title: normalizedTitle,\n- projectId,\n+ projectId: normalizedProjectId,\n externalId: null,\n completed: false\n });\n@@ -43,11 +44,12 @@ export class TaskService {\n continue;\n }\n const title = validateTitle(item.title);\n+ const projectId = normalizeProjectId(item.projectId ?? DEFAULT_PROJECT);\n imported.push(\n this.store.add({\n id: this.store.nextId(),\n title,\n- projectId: item.projectId ?? DEFAULT_PROJECT,\n+ projectId,\n externalId,\n completed: false\n })\ndiff --git a/src/validation.js b/src/validation.js\nindex f3fa377..81e36dd 100644\n--- a/src/validation.js\n+++ b/src/validation.js\n@@ -27,5 +27,9 @@ export function normalizeProjectId(projectId) {\n if (typeof projectId !== \"string\") {\n throw new ValidationError(INVALID_PROJECT, \"project id must be a string\");\n }\n- return projectId.trim();\n+ const normalized = projectId.trim().toLowerCase();\n+ if (normalized.length === 0) {\n+ throw new ValidationError(INVALID_PROJECT, \"project id must not be blank\");\n+ }\n+ return normalized;\n }\n",
328
+ "notes": "Normalizes project ids in validation, uses them in the service for creation and import, and filters summaries by exact identity."
329
+ }
330
+ },
331
+ {
332
+ "schemaVersion": "my-dev-kit-lab-agent-success-task-v1",
333
+ "id": "asr-inventory-quantity-boundary",
334
+ "title": "Enforce positive-integer quantities",
335
+ "benchmarkProject": "agent-success-inventory-node",
336
+ "projectProfileRef": "agent-success-inventory-node",
337
+ "taskLocality": "localized",
338
+ "query": "quantity validation: positive integers only for stock and reservations",
339
+ "sourceRoots": [
340
+ "src"
341
+ ],
342
+ "rawIncludeGlobs": [
343
+ "src/**/*.js"
344
+ ],
345
+ "instruction": "Stock and reservation quantities must be positive integers. Zero, negative numbers, fractional numbers, NaN, infinities, negative zero and values that are not numbers must all be rejected with the documented quantity error (code INVALID_QUANTITY), and the rejection must happen before any inventory or reservation changes. Valid positive integers must keep working. Fix the implementation under src/; do not change tests, package.json or the README.",
346
+ "expectedEditFiles": [
347
+ "src/quantity.js"
348
+ ],
349
+ "allowedEditFiles": [
350
+ "src/quantity.js"
351
+ ],
352
+ "protectedFiles": [
353
+ ".gitattributes",
354
+ "README.md",
355
+ "package.json",
356
+ "tests/quantity-edge.check.mjs",
357
+ "tests/quantity-primary.check.mjs",
358
+ "tests/regression.check.mjs",
359
+ "tests/report-edge.check.mjs",
360
+ "tests/report-primary.check.mjs",
361
+ "tests/reservation-edge.check.mjs",
362
+ "tests/reservation-primary.check.mjs"
363
+ ],
364
+ "taskChecks": [
365
+ {
366
+ "id": "quantity-primary",
367
+ "executable": "node",
368
+ "args": [
369
+ "--test",
370
+ "tests/quantity-primary.check.mjs"
371
+ ],
372
+ "timeoutMs": 30000
373
+ },
374
+ {
375
+ "id": "quantity-edge",
376
+ "executable": "node",
377
+ "args": [
378
+ "--test",
379
+ "tests/quantity-edge.check.mjs"
380
+ ],
381
+ "timeoutMs": 30000
382
+ }
383
+ ],
384
+ "regressionChecks": [
385
+ {
386
+ "id": "regression",
387
+ "executable": "node",
388
+ "args": [
389
+ "--test",
390
+ "tests/regression.check.mjs"
391
+ ],
392
+ "timeoutMs": 30000
393
+ }
394
+ ],
395
+ "behaviorFacts": [
396
+ {
397
+ "id": "quantity-positive-integer",
398
+ "text": "Only positive integers are accepted as quantities.",
399
+ "required": true,
400
+ "verificationCheckIds": [
401
+ "quantity-primary",
402
+ "quantity-edge"
403
+ ]
404
+ },
405
+ {
406
+ "id": "quantity-reject-before-mutation",
407
+ "text": "A rejected quantity never changes stock or reservations.",
408
+ "required": true,
409
+ "verificationCheckIds": [
410
+ "quantity-primary",
411
+ "quantity-edge"
412
+ ]
413
+ },
414
+ {
415
+ "id": "quantity-nonnumeric",
416
+ "text": "Nonnumeric values are rejected.",
417
+ "required": false,
418
+ "verificationCheckIds": [
419
+ "quantity-edge"
420
+ ]
421
+ },
422
+ {
423
+ "id": "quantity-valid-still-work",
424
+ "text": "Valid positive quantities keep working.",
425
+ "required": false,
426
+ "verificationCheckIds": [
427
+ "quantity-primary",
428
+ "regression"
429
+ ]
430
+ }
431
+ ],
432
+ "deterministicFixture": {
433
+ "id": "asr-inventory-quantity-boundary-reference",
434
+ "patch": "diff --git a/src/quantity.js b/src/quantity.js\nindex eff9546..0cf840f 100644\n--- a/src/quantity.js\n+++ b/src/quantity.js\n@@ -12,7 +12,7 @@ export class QuantityError extends Error {\n * Returns the quantity when it is a valid stock or reservation quantity, or throws QuantityError.\n */\n export function assertPositiveInteger(value) {\n- if (typeof value !== \"number\" || value < 0) {\n+ if (!Number.isInteger(value) || value <= 0) {\n throw new QuantityError(\"quantity must be a positive integer\");\n }\n return value;\n",
435
+ "notes": "Replaces the sign-only test with an integer-and-positive test."
436
+ }
437
+ },
438
+ {
439
+ "schemaVersion": "my-dev-kit-lab-agent-success-task-v1",
440
+ "id": "asr-inventory-reservation-atomicity",
441
+ "title": "Make reservations atomic",
442
+ "benchmarkProject": "agent-success-inventory-node",
443
+ "projectProfileRef": "agent-success-inventory-node",
444
+ "taskLocality": "cross-module",
445
+ "query": "reservation service and inventory store: check before mutate, duplicate reservation ids, failed reservations",
446
+ "sourceRoots": [
447
+ "src"
448
+ ],
449
+ "rawIncludeGlobs": [
450
+ "src/**/*.js"
451
+ ],
452
+ "instruction": "A reservation must either complete or change nothing. When stock is insufficient (or the sku is unknown) no stock may be consumed and no reservation record may be created. A reservation id that already has a record must be rejected with DUPLICATE_RESERVATION without consuming stock again or altering the existing record, and an id whose earlier attempt failed must remain usable. A successful reservation decrements the matching stock exactly once, and unrelated inventory items must never change. The reservation service and the inventory store currently apply and check their changes in an unsafe order; make them consistent. Fix the implementation under src/; do not change tests, package.json or the README.",
453
+ "expectedEditFiles": [
454
+ "src/inventoryStore.js",
455
+ "src/reservationService.js"
456
+ ],
457
+ "allowedEditFiles": [
458
+ "src/inventoryStore.js",
459
+ "src/reservationService.js"
460
+ ],
461
+ "protectedFiles": [
462
+ ".gitattributes",
463
+ "README.md",
464
+ "package.json",
465
+ "tests/quantity-edge.check.mjs",
466
+ "tests/quantity-primary.check.mjs",
467
+ "tests/regression.check.mjs",
468
+ "tests/report-edge.check.mjs",
469
+ "tests/report-primary.check.mjs",
470
+ "tests/reservation-edge.check.mjs",
471
+ "tests/reservation-primary.check.mjs"
472
+ ],
473
+ "taskChecks": [
474
+ {
475
+ "id": "reservation-primary",
476
+ "executable": "node",
477
+ "args": [
478
+ "--test",
479
+ "tests/reservation-primary.check.mjs"
480
+ ],
481
+ "timeoutMs": 30000
482
+ },
483
+ {
484
+ "id": "reservation-edge",
485
+ "executable": "node",
486
+ "args": [
487
+ "--test",
488
+ "tests/reservation-edge.check.mjs"
489
+ ],
490
+ "timeoutMs": 30000
491
+ }
492
+ ],
493
+ "regressionChecks": [
494
+ {
495
+ "id": "regression",
496
+ "executable": "node",
497
+ "args": [
498
+ "--test",
499
+ "tests/regression.check.mjs"
500
+ ],
501
+ "timeoutMs": 30000
502
+ }
503
+ ],
504
+ "behaviorFacts": [
505
+ {
506
+ "id": "reserve-insufficient-no-consumption",
507
+ "text": "Insufficient stock never leaves partially decremented inventory.",
508
+ "required": true,
509
+ "verificationCheckIds": [
510
+ "reservation-primary"
511
+ ]
512
+ },
513
+ {
514
+ "id": "reserve-failed-no-record",
515
+ "text": "Failed reservations create no reservation record and their ids stay reusable.",
516
+ "required": true,
517
+ "verificationCheckIds": [
518
+ "reservation-primary",
519
+ "reservation-edge"
520
+ ]
521
+ },
522
+ {
523
+ "id": "reserve-duplicate-id",
524
+ "text": "A duplicate reservation id never consumes stock twice.",
525
+ "required": true,
526
+ "verificationCheckIds": [
527
+ "reservation-edge"
528
+ ]
529
+ },
530
+ {
531
+ "id": "reserve-unrelated-items",
532
+ "text": "Unrelated inventory items are unchanged by failed and successful reservations.",
533
+ "required": false,
534
+ "verificationCheckIds": [
535
+ "reservation-edge"
536
+ ]
537
+ },
538
+ {
539
+ "id": "reserve-existing-behavior",
540
+ "text": "Valid reservations, cancellation and fulfillment keep working.",
541
+ "required": false,
542
+ "verificationCheckIds": [
543
+ "regression"
544
+ ]
545
+ }
546
+ ],
547
+ "deterministicFixture": {
548
+ "id": "asr-inventory-reservation-atomicity-reference",
549
+ "patch": "diff --git a/src/inventoryStore.js b/src/inventoryStore.js\nindex edd48fe..87d9da0 100644\n--- a/src/inventoryStore.js\n+++ b/src/inventoryStore.js\n@@ -71,10 +71,10 @@ export class InventoryStore {\n if (!item) {\n throw new UnknownSkuError(sku);\n }\n- item.available -= quantity;\n- if (item.available < 0) {\n+ if (item.available < quantity) {\n throw new InsufficientStockError(sku);\n }\n+ item.available -= quantity;\n return { ...item };\n }\n \n@@ -89,6 +89,9 @@ export class InventoryStore {\n }\n \n addReservation(record) {\n+ if (this.#reservations.has(record.id)) {\n+ throw new DuplicateReservationError(record.id);\n+ }\n this.#reservations.set(record.id, { ...record });\n return { ...record };\n }\ndiff --git a/src/reservationService.js b/src/reservationService.js\nindex a7b29c5..f8b183b 100644\n--- a/src/reservationService.js\n+++ b/src/reservationService.js\n@@ -1,4 +1,5 @@\n import {\n+ DuplicateReservationError,\n RESERVATION_ACTIVE,\n RESERVATION_FULFILLED,\n UnknownReservationError\n@@ -15,8 +16,11 @@ export class ReservationService {\n */\n reserve({ reservationId, sku, quantity }) {\n assertPositiveInteger(quantity);\n- this.store.addReservation({ id: reservationId, sku, quantity, status: RESERVATION_ACTIVE });\n+ if (this.store.getReservation(reservationId)) {\n+ throw new DuplicateReservationError(reservationId);\n+ }\n this.store.takeStock(sku, quantity);\n+ this.store.addReservation({ id: reservationId, sku, quantity, status: RESERVATION_ACTIVE });\n return this.store.getReservation(reservationId);\n }\n \n",
550
+ "notes": "Checks stock before mutating it and records the reservation only after stock was taken."
551
+ }
552
+ },
553
+ {
554
+ "schemaVersion": "my-dev-kit-lab-agent-success-task-v1",
555
+ "id": "asr-inventory-fulfillment-report",
556
+ "title": "Make reservation lifecycle and report consistent",
557
+ "benchmarkProject": "agent-success-inventory-node",
558
+ "projectProfileRef": "agent-success-inventory-node",
559
+ "taskLocality": "broad-change",
560
+ "query": "reservation lifecycle states, cancellation stock restoration and fulfillment report totals",
561
+ "sourceRoots": [
562
+ "src"
563
+ ],
564
+ "rawIncludeGlobs": [
565
+ "src/**/*.js"
566
+ ],
567
+ "instruction": "Reservation lifecycle state must be represented the same way by the store, the reservation service and the fulfillment report, using the exported status values (active, fulfilled, cancelled) only. Cancelling an active reservation restores exactly its reserved quantity exactly once; cancelling again must not restore stock again, and a fulfilled reservation must never be restored by a cancellation. A cancelled reservation must not become fulfilled. The fulfillment report must distinguish active, fulfilled and cancelled reservations for every sku, its figures must agree with the underlying store (received equals available plus active plus fulfilled quantity for each row), unrelated skus must not be affected, and the report must stay deterministic. Fix the implementation under src/; do not change tests, package.json or the README.",
568
+ "expectedEditFiles": [
569
+ "src/fulfillmentReport.js",
570
+ "src/inventoryStore.js",
571
+ "src/reservationService.js"
572
+ ],
573
+ "allowedEditFiles": [
574
+ "src/fulfillmentReport.js",
575
+ "src/inventoryStore.js",
576
+ "src/reservationService.js"
577
+ ],
578
+ "protectedFiles": [
579
+ ".gitattributes",
580
+ "README.md",
581
+ "package.json",
582
+ "tests/quantity-edge.check.mjs",
583
+ "tests/quantity-primary.check.mjs",
584
+ "tests/regression.check.mjs",
585
+ "tests/report-edge.check.mjs",
586
+ "tests/report-primary.check.mjs",
587
+ "tests/reservation-edge.check.mjs",
588
+ "tests/reservation-primary.check.mjs"
589
+ ],
590
+ "taskChecks": [
591
+ {
592
+ "id": "report-primary",
593
+ "executable": "node",
594
+ "args": [
595
+ "--test",
596
+ "tests/report-primary.check.mjs"
597
+ ],
598
+ "timeoutMs": 30000
599
+ },
600
+ {
601
+ "id": "report-edge",
602
+ "executable": "node",
603
+ "args": [
604
+ "--test",
605
+ "tests/report-edge.check.mjs"
606
+ ],
607
+ "timeoutMs": 30000
608
+ }
609
+ ],
610
+ "regressionChecks": [
611
+ {
612
+ "id": "regression",
613
+ "executable": "node",
614
+ "args": [
615
+ "--test",
616
+ "tests/regression.check.mjs"
617
+ ],
618
+ "timeoutMs": 30000
619
+ }
620
+ ],
621
+ "behaviorFacts": [
622
+ {
623
+ "id": "cancel-restores-once",
624
+ "text": "Cancellation restores the reserved quantity exactly once.",
625
+ "required": true,
626
+ "verificationCheckIds": [
627
+ "report-primary",
628
+ "report-edge"
629
+ ]
630
+ },
631
+ {
632
+ "id": "report-distinguishes-states",
633
+ "text": "The report separates active, fulfilled and cancelled reservations.",
634
+ "required": true,
635
+ "verificationCheckIds": [
636
+ "report-primary"
637
+ ]
638
+ },
639
+ {
640
+ "id": "report-agrees-with-store",
641
+ "text": "Reported inventory and reservation totals agree with the store.",
642
+ "required": true,
643
+ "verificationCheckIds": [
644
+ "report-primary",
645
+ "report-edge"
646
+ ]
647
+ },
648
+ {
649
+ "id": "report-deterministic-isolated",
650
+ "text": "The report is deterministic and unrelated skus are unaffected.",
651
+ "required": false,
652
+ "verificationCheckIds": [
653
+ "report-edge"
654
+ ]
655
+ },
656
+ {
657
+ "id": "report-existing-behavior",
658
+ "text": "Reserving, fulfilling and simple reports keep working.",
659
+ "required": false,
660
+ "verificationCheckIds": [
661
+ "regression"
662
+ ]
663
+ }
664
+ ],
665
+ "deterministicFixture": {
666
+ "id": "asr-inventory-fulfillment-report-reference",
667
+ "patch": "diff --git a/src/fulfillmentReport.js b/src/fulfillmentReport.js\nindex 4ec4a37..fa82ec3 100644\n--- a/src/fulfillmentReport.js\n+++ b/src/fulfillmentReport.js\n@@ -1,4 +1,4 @@\n-import { RESERVATION_FULFILLED } from \"./inventoryStore.js\";\n+import { RESERVATION_ACTIVE, RESERVATION_CANCELLED, RESERVATION_FULFILLED } from \"./inventoryStore.js\";\n \n function bucket(reservations) {\n return {\n@@ -19,9 +19,9 @@ export function buildFulfillmentReport(store) {\n sku: item.sku,\n received: item.received,\n available: item.available,\n- active: bucket(mine.filter((reservation) => reservation.status !== RESERVATION_FULFILLED)),\n+ active: bucket(mine.filter((reservation) => reservation.status === RESERVATION_ACTIVE)),\n fulfilled: bucket(mine.filter((reservation) => reservation.status === RESERVATION_FULFILLED)),\n- cancelled: bucket([])\n+ cancelled: bucket(mine.filter((reservation) => reservation.status === RESERVATION_CANCELLED))\n };\n });\n const sum = (selector) => rows.reduce((total, row) => total + selector(row), 0);\ndiff --git a/src/inventoryStore.js b/src/inventoryStore.js\nindex edd48fe..fa579cf 100644\n--- a/src/inventoryStore.js\n+++ b/src/inventoryStore.js\n@@ -4,6 +4,16 @@ export const RESERVATION_ACTIVE = \"active\";\n export const RESERVATION_FULFILLED = \"fulfilled\";\n export const RESERVATION_CANCELLED = \"cancelled\";\n \n+const RESERVATION_STATUSES = new Set([RESERVATION_ACTIVE, RESERVATION_FULFILLED, RESERVATION_CANCELLED]);\n+\n+export class InvalidReservationStatusError extends Error {\n+ constructor(status) {\n+ super(`invalid reservation status: ${String(status)}`);\n+ this.name = \"InvalidReservationStatusError\";\n+ this.code = \"INVALID_RESERVATION_STATUS\";\n+ }\n+}\n+\n export class UnknownSkuError extends Error {\n constructor(sku) {\n super(`unknown sku: ${sku}`);\n@@ -104,6 +114,9 @@ export class InventoryStore {\n }\n \n setReservationStatus(id, status) {\n+ if (!RESERVATION_STATUSES.has(status)) {\n+ throw new InvalidReservationStatusError(status);\n+ }\n const record = this.#reservations.get(id);\n if (!record) {\n throw new UnknownReservationError(id);\ndiff --git a/src/reservationService.js b/src/reservationService.js\nindex a7b29c5..e4ccad3 100644\n--- a/src/reservationService.js\n+++ b/src/reservationService.js\n@@ -1,10 +1,19 @@\n import {\n RESERVATION_ACTIVE,\n+ RESERVATION_CANCELLED,\n RESERVATION_FULFILLED,\n UnknownReservationError\n } from \"./inventoryStore.js\";\n import { assertPositiveInteger } from \"./quantity.js\";\n \n+export class InvalidTransitionError extends Error {\n+ constructor(id, from, to) {\n+ super(`reservation ${id} cannot move from ${from} to ${to}`);\n+ this.name = \"InvalidTransitionError\";\n+ this.code = \"INVALID_TRANSITION\";\n+ }\n+}\n+\n export class ReservationService {\n constructor(store) {\n this.store = store;\n@@ -28,8 +37,14 @@ export class ReservationService {\n if (!record) {\n throw new UnknownReservationError(reservationId);\n }\n+ if (record.status === RESERVATION_CANCELLED) {\n+ return record;\n+ }\n+ if (record.status !== RESERVATION_ACTIVE) {\n+ throw new InvalidTransitionError(reservationId, record.status, RESERVATION_CANCELLED);\n+ }\n this.store.returnStock(record.sku, record.quantity);\n- return this.store.setReservationStatus(reservationId, \"canceled\");\n+ return this.store.setReservationStatus(reservationId, RESERVATION_CANCELLED);\n }\n \n /**\n@@ -40,6 +55,12 @@ export class ReservationService {\n if (!record) {\n throw new UnknownReservationError(reservationId);\n }\n+ if (record.status === RESERVATION_FULFILLED) {\n+ return record;\n+ }\n+ if (record.status !== RESERVATION_ACTIVE) {\n+ throw new InvalidTransitionError(reservationId, record.status, RESERVATION_FULFILLED);\n+ }\n return this.store.setReservationStatus(reservationId, RESERVATION_FULFILLED);\n }\n }\n",
668
+ "notes": "Validates lifecycle statuses in the store, enforces active-only transitions in the service and buckets the report by exact status."
669
+ }
670
+ }
671
+ ]