@dailephd/my-dev-kit-lab 0.2.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.
Files changed (250) hide show
  1. package/README.md +272 -0
  2. package/benchmarks/contracts/benchmark-project-profiles.json +1199 -0
  3. package/benchmarks/contracts/todo-behavior.md +70 -0
  4. package/benchmarks/contracts/todo-benchmark-case.json +227 -0
  5. package/benchmarks/projects/README.md +34 -0
  6. package/benchmarks/projects/task-analytics-large-mixed/README.md +1 -0
  7. package/benchmarks/projects/task-analytics-large-mixed/py/task_analytics/__init__.py +3 -0
  8. package/benchmarks/projects/task-analytics-large-mixed/py/task_analytics/fixtures.py +6 -0
  9. package/benchmarks/projects/task-analytics-large-mixed/py/task_analytics/metrics.py +29 -0
  10. package/benchmarks/projects/task-analytics-large-mixed/py/task_analytics/models.py +21 -0
  11. package/benchmarks/projects/task-analytics-large-mixed/py/task_analytics/parser.py +16 -0
  12. package/benchmarks/projects/task-analytics-large-mixed/py/task_analytics/pipeline.py +9 -0
  13. package/benchmarks/projects/task-analytics-large-mixed/py/task_analytics/quality.py +8 -0
  14. package/benchmarks/projects/task-analytics-large-mixed/py/task_analytics/reporting.py +11 -0
  15. package/benchmarks/projects/task-analytics-large-mixed/py/tests/test_metrics.py +19 -0
  16. package/benchmarks/projects/task-analytics-large-mixed/py/tests/test_parser.py +15 -0
  17. package/benchmarks/projects/task-analytics-large-mixed/py/tests/test_quality.py +19 -0
  18. package/benchmarks/projects/task-analytics-large-mixed/py/tests/test_reporting.py +15 -0
  19. package/benchmarks/projects/task-analytics-large-mixed/ts/package.json +12 -0
  20. package/benchmarks/projects/task-analytics-large-mixed/ts/src/index.ts +11 -0
  21. package/benchmarks/projects/task-analytics-large-mixed/ts/src/models/analyticsSnapshot.ts +20 -0
  22. package/benchmarks/projects/task-analytics-large-mixed/ts/src/models/project.ts +5 -0
  23. package/benchmarks/projects/task-analytics-large-mixed/ts/src/models/task.ts +10 -0
  24. package/benchmarks/projects/task-analytics-large-mixed/ts/src/reporting/buildProjectLeaderboard.ts +7 -0
  25. package/benchmarks/projects/task-analytics-large-mixed/ts/src/reporting/formatTaskHealthReport.ts +13 -0
  26. package/benchmarks/projects/task-analytics-large-mixed/ts/src/services/buildAnalyticsSnapshot.ts +39 -0
  27. package/benchmarks/projects/task-analytics-large-mixed/ts/src/services/completeTask.ts +10 -0
  28. package/benchmarks/projects/task-analytics-large-mixed/ts/src/services/createTask.ts +21 -0
  29. package/benchmarks/projects/task-analytics-large-mixed/ts/src/services/listTasksByProject.ts +6 -0
  30. package/benchmarks/projects/task-analytics-large-mixed/ts/src/store/projectStore.ts +20 -0
  31. package/benchmarks/projects/task-analytics-large-mixed/ts/src/store/taskStore.ts +44 -0
  32. package/benchmarks/projects/task-analytics-large-mixed/ts/src/validation/projectValidation.ts +12 -0
  33. package/benchmarks/projects/task-analytics-large-mixed/ts/src/validation/taskValidation.ts +18 -0
  34. package/benchmarks/projects/task-analytics-large-mixed/ts/tests/buildAnalyticsSnapshot.test.ts +48 -0
  35. package/benchmarks/projects/task-analytics-large-mixed/ts/tests/completeTask.test.ts +21 -0
  36. package/benchmarks/projects/task-analytics-large-mixed/ts/tests/createTask.test.ts +31 -0
  37. package/benchmarks/projects/task-analytics-large-mixed/ts/tests/listTasksByProject.test.ts +18 -0
  38. package/benchmarks/projects/task-analytics-large-mixed/ts/tests/reporting.test.ts +19 -0
  39. package/benchmarks/projects/task-analytics-large-mixed/ts/tsconfig.json +12 -0
  40. package/benchmarks/projects/task-analytics-large-mixed/ts/vitest.config.ts +5 -0
  41. package/benchmarks/projects/task-workflow-medium-ts/README.md +1 -0
  42. package/benchmarks/projects/task-workflow-medium-ts/package.json +12 -0
  43. package/benchmarks/projects/task-workflow-medium-ts/src/index.ts +9 -0
  44. package/benchmarks/projects/task-workflow-medium-ts/src/models/project.ts +6 -0
  45. package/benchmarks/projects/task-workflow-medium-ts/src/models/task.ts +39 -0
  46. package/benchmarks/projects/task-workflow-medium-ts/src/services/completeTask.ts +15 -0
  47. package/benchmarks/projects/task-workflow-medium-ts/src/services/createTask.ts +26 -0
  48. package/benchmarks/projects/task-workflow-medium-ts/src/services/filterTasks.ts +17 -0
  49. package/benchmarks/projects/task-workflow-medium-ts/src/services/importTasks.ts +33 -0
  50. package/benchmarks/projects/task-workflow-medium-ts/src/services/summarizeTasks.ts +30 -0
  51. package/benchmarks/projects/task-workflow-medium-ts/src/store/taskStore.ts +76 -0
  52. package/benchmarks/projects/task-workflow-medium-ts/src/utils/deterministicId.ts +3 -0
  53. package/benchmarks/projects/task-workflow-medium-ts/src/validation/taskValidation.ts +45 -0
  54. package/benchmarks/projects/task-workflow-medium-ts/tests/completeTask.test.ts +16 -0
  55. package/benchmarks/projects/task-workflow-medium-ts/tests/createTask.test.ts +21 -0
  56. package/benchmarks/projects/task-workflow-medium-ts/tests/filterTasks.test.ts +18 -0
  57. package/benchmarks/projects/task-workflow-medium-ts/tests/importTasks.test.ts +22 -0
  58. package/benchmarks/projects/task-workflow-medium-ts/tests/summarizeTasks.test.ts +29 -0
  59. package/benchmarks/projects/task-workflow-medium-ts/tsconfig.json +12 -0
  60. package/benchmarks/projects/task-workflow-medium-ts/vitest.config.ts +5 -0
  61. package/benchmarks/projects/todo-js/README.md +3 -0
  62. package/benchmarks/projects/todo-js/package.json +11 -0
  63. package/benchmarks/projects/todo-js/src/index.js +2 -0
  64. package/benchmarks/projects/todo-js/src/taskService.js +37 -0
  65. package/benchmarks/projects/todo-js/src/taskStore.js +28 -0
  66. package/benchmarks/projects/todo-js/tests/taskService.test.js +45 -0
  67. package/benchmarks/projects/todo-js/vitest.config.js +5 -0
  68. package/benchmarks/projects/todo-mixed-ts-py/README.md +3 -0
  69. package/benchmarks/projects/todo-mixed-ts-py/package.json +13 -0
  70. package/benchmarks/projects/todo-mixed-ts-py/python/task_service.py +76 -0
  71. package/benchmarks/projects/todo-mixed-ts-py/src/taskCli.ts +38 -0
  72. package/benchmarks/projects/todo-mixed-ts-py/tests/mixedBoundary.test.ts +18 -0
  73. package/benchmarks/projects/todo-mixed-ts-py/tsconfig.json +12 -0
  74. package/benchmarks/projects/todo-mixed-ts-py/vitest.config.ts +5 -0
  75. package/benchmarks/projects/todo-python/README.md +3 -0
  76. package/benchmarks/projects/todo-python/src/__init__.py +4 -0
  77. package/benchmarks/projects/todo-python/src/task_service.py +32 -0
  78. package/benchmarks/projects/todo-python/src/task_store.py +28 -0
  79. package/benchmarks/projects/todo-python/tests/test_task_service.py +52 -0
  80. package/benchmarks/projects/todo-ts/README.md +3 -0
  81. package/benchmarks/projects/todo-ts/package.json +12 -0
  82. package/benchmarks/projects/todo-ts/src/index.ts +2 -0
  83. package/benchmarks/projects/todo-ts/src/taskService.ts +41 -0
  84. package/benchmarks/projects/todo-ts/src/taskStore.ts +34 -0
  85. package/benchmarks/projects/todo-ts/tests/taskService.test.ts +45 -0
  86. package/benchmarks/projects/todo-ts/tsconfig.json +12 -0
  87. package/benchmarks/projects/todo-ts/vitest.config.ts +5 -0
  88. package/dist/scripts/build-gallery.js +3 -0
  89. package/dist/scripts/capture-demo-report.js +3 -0
  90. package/dist/scripts/evaluate-token-savings.js +2 -0
  91. package/dist/scripts/experiments/describeExperiment.js +143 -0
  92. package/dist/scripts/experiments/listExperiments.js +44 -0
  93. package/dist/scripts/experiments/runExperiment.js +199 -0
  94. package/dist/scripts/generate-experiment-plots.js +3 -0
  95. package/dist/scripts/generate-prompt-variants.js +2 -0
  96. package/dist/scripts/render-experiment-report.js +2 -0
  97. package/dist/scripts/run-agent-prompt.js +2 -0
  98. package/dist/scripts/run-controlled-experiment.js +2 -0
  99. package/dist/scripts/run-final-demo.js +3 -0
  100. package/dist/scripts/run-lab-demo.js +5 -0
  101. package/dist/scripts/run-visualization-demos.js +3 -0
  102. package/dist/scripts/security/runCodeql.js +57 -0
  103. package/dist/scripts/security/runDependencyChecks.js +57 -0
  104. package/dist/scripts/security/runFuzzSmoke.js +29 -0
  105. package/dist/scripts/security/runPackageChecks.js +56 -0
  106. package/dist/scripts/security/runSemgrep.js +63 -0
  107. package/dist/scripts/security/validate.js +117 -0
  108. package/dist/scripts/verify-benchmarks.js +202 -0
  109. package/dist/src/agents/adapters/claudeAdapter.js +37 -0
  110. package/dist/src/agents/adapters/codexAdapter.js +110 -0
  111. package/dist/src/agents/adapters/fakeAgentAdapter.js +101 -0
  112. package/dist/src/agents/agentRegistry.js +21 -0
  113. package/dist/src/agents/index.js +7 -0
  114. package/dist/src/agents/parseAgentTokenUsage.js +137 -0
  115. package/dist/src/agents/runAgentPrompt.js +38 -0
  116. package/dist/src/agents/types.js +1 -0
  117. package/dist/src/commands/buildGalleryCommand.js +56 -0
  118. package/dist/src/commands/captureDemoReport.js +116 -0
  119. package/dist/src/commands/evaluateTokenSavings.js +175 -0
  120. package/dist/src/commands/generateExperimentPlotsCommand.js +38 -0
  121. package/dist/src/commands/generatePromptVariants.js +67 -0
  122. package/dist/src/commands/renderExperimentReportCommand.js +131 -0
  123. package/dist/src/commands/runAgentPromptCommand.js +132 -0
  124. package/dist/src/commands/runControlledExperimentCommand.js +174 -0
  125. package/dist/src/commands/runFinalDemoCommand.js +123 -0
  126. package/dist/src/commands/runLabDemo.js +62 -0
  127. package/dist/src/commands/runVisualizationDemosCommand.js +67 -0
  128. package/dist/src/core/commandLine.js +59 -0
  129. package/dist/src/core/countTokens.js +8 -0
  130. package/dist/src/core/fileGlobs.js +100 -0
  131. package/dist/src/core/localProjectTarget.js +75 -0
  132. package/dist/src/core/pathSafety.js +19 -0
  133. package/dist/src/core/pythonCommand.js +30 -0
  134. package/dist/src/core/resolveCommand.js +110 -0
  135. package/dist/src/core/runMeasuredCommand.js +143 -0
  136. package/dist/src/evaluation/benchmarkMetadata.js +207 -0
  137. package/dist/src/evaluation/buildExperimentMatrix.js +75 -0
  138. package/dist/src/evaluation/classifyAgentRunOutcome.js +40 -0
  139. package/dist/src/evaluation/compareExperimentRuns.js +79 -0
  140. package/dist/src/evaluation/compareTokenSavings.js +47 -0
  141. package/dist/src/evaluation/controlledExperimentTypes.js +1 -0
  142. package/dist/src/evaluation/index.js +18 -0
  143. package/dist/src/evaluation/parseAgentAnswer.js +230 -0
  144. package/dist/src/evaluation/projectComplexity.js +126 -0
  145. package/dist/src/evaluation/projectFileTree.js +83 -0
  146. package/dist/src/evaluation/readEvaluationCases.js +59 -0
  147. package/dist/src/evaluation/renderTokenSavingsReportInput.js +55 -0
  148. package/dist/src/evaluation/runControlledExperiment.js +158 -0
  149. package/dist/src/evaluation/runMyDevKitRetrieval.js +197 -0
  150. package/dist/src/evaluation/runRawFullFileBaseline.js +31 -0
  151. package/dist/src/evaluation/scoreCorrectness.js +127 -0
  152. package/dist/src/evaluation/types.js +1 -0
  153. package/dist/src/evaluation/writeExperimentArtifacts.js +104 -0
  154. package/dist/src/evaluation/writeTokenSavingsArtifacts.js +57 -0
  155. package/dist/src/experiments/config.js +24 -0
  156. package/dist/src/experiments/defaultRegistry.js +7 -0
  157. package/dist/src/experiments/errors.js +18 -0
  158. package/dist/src/experiments/index.js +9 -0
  159. package/dist/src/experiments/outputPaths.js +25 -0
  160. package/dist/src/experiments/plugins/contextStrategyComparison/config.js +37 -0
  161. package/dist/src/experiments/plugins/contextStrategyComparison/index.js +3 -0
  162. package/dist/src/experiments/plugins/contextStrategyComparison/plugin.js +83 -0
  163. package/dist/src/experiments/plugins/contextStrategyComparison/resultMapping.js +260 -0
  164. package/dist/src/experiments/plugins/index.js +1 -0
  165. package/dist/src/experiments/registry.js +43 -0
  166. package/dist/src/experiments/results.js +48 -0
  167. package/dist/src/experiments/runner.js +181 -0
  168. package/dist/src/experiments/target.js +8 -0
  169. package/dist/src/experiments/types.js +1 -0
  170. package/dist/src/gallery/index.js +2 -0
  171. package/dist/src/gallery/types.js +1 -0
  172. package/dist/src/gallery/writeGalleryManifest.js +214 -0
  173. package/dist/src/index.js +12 -0
  174. package/dist/src/plots/buildExperimentPlotData.js +137 -0
  175. package/dist/src/plots/index.js +4 -0
  176. package/dist/src/plots/renderSvgChart.js +82 -0
  177. package/dist/src/plots/types.js +1 -0
  178. package/dist/src/plots/writePlotArtifacts.js +46 -0
  179. package/dist/src/prompts/buildPromptContext.js +68 -0
  180. package/dist/src/prompts/generateMyDevKitPrompt.js +106 -0
  181. package/dist/src/prompts/generatePromptVariants.js +36 -0
  182. package/dist/src/prompts/generateRawFullFilePrompt.js +97 -0
  183. package/dist/src/prompts/index.js +7 -0
  184. package/dist/src/prompts/measurePromptComplexity.js +41 -0
  185. package/dist/src/prompts/types.js +1 -0
  186. package/dist/src/prompts/writePromptArtifacts.js +43 -0
  187. package/dist/src/report/buildExperimentReportInput.js +339 -0
  188. package/dist/src/report/experimentReportTypes.js +1 -0
  189. package/dist/src/report/experiments/buildPluginExperimentReport.js +153 -0
  190. package/dist/src/report/experiments/experimentReportModel.js +1 -0
  191. package/dist/src/report/experiments/index.js +4 -0
  192. package/dist/src/report/experiments/renderPluginExperimentReportHtml.js +133 -0
  193. package/dist/src/report/experiments/writePluginExperimentReports.js +30 -0
  194. package/dist/src/report/index.js +8 -0
  195. package/dist/src/report/renderExperimentHtmlReport.js +354 -0
  196. package/dist/src/report/renderHtmlReport.js +103 -0
  197. package/dist/src/report/types.js +10 -0
  198. package/dist/src/report/writeExperimentReportArtifacts.js +38 -0
  199. package/dist/src/report/writeReportArtifacts.js +39 -0
  200. package/dist/src/screenshot/captureReportScreenshot.js +75 -0
  201. package/dist/src/screenshot/index.js +2 -0
  202. package/dist/src/screenshot/types.js +1 -0
  203. package/dist/src/securityValidation/artifacts.js +15 -0
  204. package/dist/src/securityValidation/cliAdversarial/adversarialCliConfig.js +38 -0
  205. package/dist/src/securityValidation/cliAdversarial/dataVolumeChecks.js +194 -0
  206. package/dist/src/securityValidation/cliAdversarial/jsonStdoutChecks.js +359 -0
  207. package/dist/src/securityValidation/cliAdversarial/malformedArtifactChecks.js +284 -0
  208. package/dist/src/securityValidation/cliAdversarial/malformedArtifactFixtures.js +79 -0
  209. package/dist/src/securityValidation/cliAdversarial/pathBoundaryChecks.js +431 -0
  210. package/dist/src/securityValidation/cliAdversarial/pathCases.js +144 -0
  211. package/dist/src/securityValidation/cliAdversarial/readOnlyBoundaryChecks.js +294 -0
  212. package/dist/src/securityValidation/cliAdversarial/runAdversarialCheck.js +149 -0
  213. package/dist/src/securityValidation/cliAdversarial/subprocessSafetyChecks.js +214 -0
  214. package/dist/src/securityValidation/cliAdversarial/tempWorkspace.js +160 -0
  215. package/dist/src/securityValidation/commandRunner.js +136 -0
  216. package/dist/src/securityValidation/config.js +39 -0
  217. package/dist/src/securityValidation/dependencies/parseNpmAudit.js +115 -0
  218. package/dist/src/securityValidation/dependencies/parseNpmLs.js +71 -0
  219. package/dist/src/securityValidation/dependencies/parseNpmOutdated.js +41 -0
  220. package/dist/src/securityValidation/dependencies/runDependencyChecks.js +239 -0
  221. package/dist/src/securityValidation/dependencies/runOsvScanner.js +43 -0
  222. package/dist/src/securityValidation/fuzz/fuzzHarness.js +61 -0
  223. package/dist/src/securityValidation/fuzz/fuzzTargets.js +204 -0
  224. package/dist/src/securityValidation/fuzz/randomInput.js +0 -0
  225. package/dist/src/securityValidation/index.js +34 -0
  226. package/dist/src/securityValidation/packageChecks/forbiddenPackageContents.js +67 -0
  227. package/dist/src/securityValidation/packageChecks/parseNpmPackDryRun.js +56 -0
  228. package/dist/src/securityValidation/packageChecks/runPackageChecks.js +88 -0
  229. package/dist/src/securityValidation/report/renderSecurityReport.js +248 -0
  230. package/dist/src/securityValidation/report/securityReportTypes.js +1 -0
  231. package/dist/src/securityValidation/staticScans/codeql.js +66 -0
  232. package/dist/src/securityValidation/staticScans/semgrep.js +180 -0
  233. package/dist/src/securityValidation/testMatrix.js +535 -0
  234. package/dist/src/securityValidation/types.js +34 -0
  235. package/dist/src/securityValidation/validate/resolveTarget.js +32 -0
  236. package/dist/src/securityValidation/validate/runSecurityValidation.js +169 -0
  237. package/dist/src/securityValidation/validate/verdict.js +73 -0
  238. package/dist/src/visualizationDemos/buildMyDevKitVisualizationCommands.js +59 -0
  239. package/dist/src/visualizationDemos/index.js +4 -0
  240. package/dist/src/visualizationDemos/runVisualizationDemos.js +82 -0
  241. package/dist/src/visualizationDemos/types.js +1 -0
  242. package/dist/src/visualizationDemos/writeVisualizationDemoArtifacts.js +25 -0
  243. package/docs/METRICS.md +286 -0
  244. package/examples/demo-report-input.json +78 -0
  245. package/examples/lab-demo-cases.json +35 -0
  246. package/examples/real-agent-campaign-cases.json +118 -0
  247. package/examples/token-savings-cases.json +122 -0
  248. package/package.json +91 -0
  249. package/tests/fixtures/fake-adversarial-cli.js +152 -0
  250. package/tests/fixtures/fake-my-dev-kit-cli.js +83 -0
@@ -0,0 +1,10 @@
1
+ import type { AnalyticsTask } from "../models/task.js";
2
+ import { AnalyticsTaskStore } from "../store/taskStore.js";
3
+
4
+ export function completeTask(taskStore: AnalyticsTaskStore, taskId: string, updatedDay: number): AnalyticsTask {
5
+ return taskStore.update(taskId, (task) => ({
6
+ ...task,
7
+ completed: true,
8
+ updatedDay
9
+ }));
10
+ }
@@ -0,0 +1,21 @@
1
+ import type { AnalyticsTask } from "../models/task.js";
2
+ import { ProjectStore } from "../store/projectStore.js";
3
+ import { AnalyticsTaskStore } from "../store/taskStore.js";
4
+ import { assertKnownProject } from "../validation/projectValidation.js";
5
+ import { normalizeLabels, validateStoryPoints, validateTaskTitle } from "../validation/taskValidation.js";
6
+
7
+ export function createTask(
8
+ taskStore: AnalyticsTaskStore,
9
+ projectStore: ProjectStore,
10
+ input: Omit<AnalyticsTask, "id" | "completed"> & { completed?: boolean }
11
+ ): AnalyticsTask {
12
+ return taskStore.create({
13
+ title: validateTaskTitle(input.title),
14
+ projectId: assertKnownProject(projectStore, input.projectId),
15
+ assignee: input.assignee.trim(),
16
+ completed: input.completed ?? false,
17
+ storyPoints: validateStoryPoints(input.storyPoints),
18
+ updatedDay: input.updatedDay,
19
+ labels: normalizeLabels(input.labels)
20
+ });
21
+ }
@@ -0,0 +1,6 @@
1
+ import type { AnalyticsTask } from "../models/task.js";
2
+ import { AnalyticsTaskStore } from "../store/taskStore.js";
3
+
4
+ export function listTasksByProject(taskStore: AnalyticsTaskStore, projectId: string): AnalyticsTask[] {
5
+ return taskStore.list().filter((task) => task.projectId === projectId);
6
+ }
@@ -0,0 +1,20 @@
1
+ import type { AnalyticsProject } from "../models/project.js";
2
+
3
+ export class ProjectStore {
4
+ private readonly projects = new Map<string, AnalyticsProject>();
5
+
6
+ constructor(seed: AnalyticsProject[] = []) {
7
+ for (const project of seed) {
8
+ this.projects.set(project.id, { ...project });
9
+ }
10
+ }
11
+
12
+ list(): AnalyticsProject[] {
13
+ return [...this.projects.values()].map((project) => ({ ...project }));
14
+ }
15
+
16
+ get(projectId: string): AnalyticsProject | undefined {
17
+ const project = this.projects.get(projectId);
18
+ return project ? { ...project } : undefined;
19
+ }
20
+ }
@@ -0,0 +1,44 @@
1
+ import type { AnalyticsTask } from "../models/task.js";
2
+
3
+ export class AnalyticsTaskStore {
4
+ private nextSequence = 1;
5
+ private readonly tasks = new Map<string, AnalyticsTask>();
6
+
7
+ constructor(seed: AnalyticsTask[] = []) {
8
+ for (const task of seed) {
9
+ this.tasks.set(task.id, { ...task, labels: [...task.labels] });
10
+ this.nextSequence = Math.max(this.nextSequence, sequenceFromId(task.id) + 1);
11
+ }
12
+ }
13
+
14
+ create(input: Omit<AnalyticsTask, "id">): AnalyticsTask {
15
+ const task: AnalyticsTask = {
16
+ ...input,
17
+ id: `task-${this.nextSequence++}`,
18
+ labels: [...input.labels]
19
+ };
20
+ this.tasks.set(task.id, task);
21
+ return { ...task, labels: [...task.labels] };
22
+ }
23
+
24
+ update(taskId: string, updater: (task: AnalyticsTask) => AnalyticsTask): AnalyticsTask {
25
+ const task = this.tasks.get(taskId);
26
+ if (!task) {
27
+ throw new Error(`Unknown task id: ${taskId}`);
28
+ }
29
+ const updated = updater({ ...task, labels: [...task.labels] });
30
+ this.tasks.set(taskId, { ...updated, labels: [...updated.labels] });
31
+ return { ...updated, labels: [...updated.labels] };
32
+ }
33
+
34
+ list(): AnalyticsTask[] {
35
+ return [...this.tasks.values()]
36
+ .map((task) => ({ ...task, labels: [...task.labels] }))
37
+ .sort((left, right) => left.id.localeCompare(right.id));
38
+ }
39
+ }
40
+
41
+ function sequenceFromId(id: string): number {
42
+ const match = id.match(/-(\d+)$/);
43
+ return match ? Number(match[1]) : 0;
44
+ }
@@ -0,0 +1,12 @@
1
+ import { ProjectStore } from "../store/projectStore.js";
2
+
3
+ export function assertKnownProject(projectStore: ProjectStore, projectId: string): string {
4
+ const normalized = projectId.trim();
5
+ if (!normalized) {
6
+ throw new Error("projectId must not be empty.");
7
+ }
8
+ if (!projectStore.get(normalized)) {
9
+ throw new Error(`Unknown project id: ${normalized}`);
10
+ }
11
+ return normalized;
12
+ }
@@ -0,0 +1,18 @@
1
+ export function validateTaskTitle(title: string): string {
2
+ const normalized = title.trim().replace(/\s+/g, " ");
3
+ if (!normalized) {
4
+ throw new Error("Task title must not be empty.");
5
+ }
6
+ return normalized;
7
+ }
8
+
9
+ export function validateStoryPoints(storyPoints: number): number {
10
+ if (!Number.isInteger(storyPoints) || storyPoints < 1 || storyPoints > 13) {
11
+ throw new Error("storyPoints must be an integer between 1 and 13.");
12
+ }
13
+ return storyPoints;
14
+ }
15
+
16
+ export function normalizeLabels(labels: string[]): string[] {
17
+ return [...new Set(labels.map((label) => label.trim().toLowerCase()).filter(Boolean))].sort();
18
+ }
@@ -0,0 +1,48 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { buildAnalyticsSnapshot } from "../src/services/buildAnalyticsSnapshot.js";
3
+ import { ProjectStore } from "../src/store/projectStore.js";
4
+ import { AnalyticsTaskStore } from "../src/store/taskStore.js";
5
+ import { completeTask } from "../src/services/completeTask.js";
6
+ import { createTask } from "../src/services/createTask.js";
7
+
8
+ describe("buildAnalyticsSnapshot", () => {
9
+ it("computes per-project totals, completion rate, and stale counts", () => {
10
+ const projectStore = new ProjectStore([
11
+ { id: "alpha", name: "Alpha", owner: "Ada" },
12
+ { id: "beta", name: "Beta", owner: "Bo" }
13
+ ]);
14
+ const taskStore = new AnalyticsTaskStore();
15
+ const done = createTask(taskStore, projectStore, {
16
+ title: "Ship metrics",
17
+ projectId: "alpha",
18
+ assignee: "Lee",
19
+ storyPoints: 5,
20
+ updatedDay: 2,
21
+ labels: []
22
+ });
23
+ createTask(taskStore, projectStore, {
24
+ title: "Audit backlog",
25
+ projectId: "alpha",
26
+ assignee: "Sam",
27
+ storyPoints: 3,
28
+ updatedDay: 1,
29
+ labels: ["ops"]
30
+ });
31
+ createTask(taskStore, projectStore, {
32
+ title: "Tune report",
33
+ projectId: "beta",
34
+ assignee: "Ira",
35
+ storyPoints: 8,
36
+ updatedDay: 11,
37
+ labels: ["report"]
38
+ });
39
+ completeTask(taskStore, done.id, 8);
40
+ expect(buildAnalyticsSnapshot(taskStore, projectStore, 15)).toMatchObject({
41
+ totals: { totalTasks: 3, completedTasks: 1, openTasks: 2, staleTasks: 1 },
42
+ projects: [
43
+ { projectId: "alpha", totalTasks: 2, completedTasks: 1, openTasks: 1, staleTasks: 1, completionRate: 50 },
44
+ { projectId: "beta", totalTasks: 1, completedTasks: 0, openTasks: 1, staleTasks: 0, completionRate: 0 }
45
+ ]
46
+ });
47
+ });
48
+ });
@@ -0,0 +1,21 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { ProjectStore } from "../src/store/projectStore.js";
3
+ import { AnalyticsTaskStore } from "../src/store/taskStore.js";
4
+ import { completeTask } from "../src/services/completeTask.js";
5
+ import { createTask } from "../src/services/createTask.js";
6
+
7
+ describe("completeTask", () => {
8
+ it("marks a selected task completed and updates its day marker", () => {
9
+ const projectStore = new ProjectStore([{ id: "alpha", name: "Alpha", owner: "Ada" }]);
10
+ const taskStore = new AnalyticsTaskStore();
11
+ const created = createTask(taskStore, projectStore, {
12
+ title: "Ship metrics",
13
+ projectId: "alpha",
14
+ assignee: "Lee",
15
+ storyPoints: 5,
16
+ updatedDay: 2,
17
+ labels: []
18
+ });
19
+ expect(completeTask(taskStore, created.id, 9)).toMatchObject({ completed: true, updatedDay: 9 });
20
+ });
21
+ });
@@ -0,0 +1,31 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { ProjectStore } from "../src/store/projectStore.js";
3
+ import { AnalyticsTaskStore } from "../src/store/taskStore.js";
4
+ import { createTask } from "../src/services/createTask.js";
5
+
6
+ describe("createTask", () => {
7
+ it("creates deterministic ids and validates projects", () => {
8
+ const projectStore = new ProjectStore([{ id: "alpha", name: "Alpha", owner: "Ada" }]);
9
+ const taskStore = new AnalyticsTaskStore();
10
+ expect(
11
+ createTask(taskStore, projectStore, {
12
+ title: "Ship metrics",
13
+ projectId: "alpha",
14
+ assignee: "Lee",
15
+ storyPoints: 5,
16
+ updatedDay: 2,
17
+ labels: ["Metrics"]
18
+ }).id
19
+ ).toBe("task-1");
20
+ expect(() =>
21
+ createTask(taskStore, projectStore, {
22
+ title: "Ship metrics",
23
+ projectId: "missing",
24
+ assignee: "Lee",
25
+ storyPoints: 5,
26
+ updatedDay: 2,
27
+ labels: []
28
+ })
29
+ ).toThrow("Unknown project id: missing");
30
+ });
31
+ });
@@ -0,0 +1,18 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { ProjectStore } from "../src/store/projectStore.js";
3
+ import { AnalyticsTaskStore } from "../src/store/taskStore.js";
4
+ import { createTask } from "../src/services/createTask.js";
5
+ import { listTasksByProject } from "../src/services/listTasksByProject.js";
6
+
7
+ describe("listTasksByProject", () => {
8
+ it("returns only tasks for the requested project", () => {
9
+ const projectStore = new ProjectStore([
10
+ { id: "alpha", name: "Alpha", owner: "Ada" },
11
+ { id: "beta", name: "Beta", owner: "Bo" }
12
+ ]);
13
+ const taskStore = new AnalyticsTaskStore();
14
+ createTask(taskStore, projectStore, { title: "One", projectId: "alpha", assignee: "A", storyPoints: 2, updatedDay: 1, labels: [] });
15
+ createTask(taskStore, projectStore, { title: "Two", projectId: "beta", assignee: "B", storyPoints: 3, updatedDay: 1, labels: [] });
16
+ expect(listTasksByProject(taskStore, "alpha").map((task) => task.title)).toEqual(["One"]);
17
+ });
18
+ });
@@ -0,0 +1,19 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { buildProjectLeaderboard } from "../src/reporting/buildProjectLeaderboard.js";
3
+ import { formatTaskHealthReport } from "../src/reporting/formatTaskHealthReport.js";
4
+
5
+ const snapshot = {
6
+ generatedAt: "2026-03-15T00:00:00.000Z",
7
+ projects: [
8
+ { projectId: "alpha", totalTasks: 2, completedTasks: 2, openTasks: 0, staleTasks: 0, averageStoryPoints: 4, completionRate: 100 },
9
+ { projectId: "beta", totalTasks: 2, completedTasks: 1, openTasks: 1, staleTasks: 1, averageStoryPoints: 5, completionRate: 50 }
10
+ ],
11
+ totals: { totalTasks: 4, completedTasks: 3, openTasks: 1, staleTasks: 1 }
12
+ };
13
+
14
+ describe("reporting", () => {
15
+ it("ranks projects and formats a deterministic report", () => {
16
+ expect(buildProjectLeaderboard(snapshot)).toEqual(["1. alpha (100% complete, 0 stale)", "2. beta (50% complete, 1 stale)"]);
17
+ expect(formatTaskHealthReport(snapshot)).toContain("Projects: 1. alpha (100% complete, 0 stale) | 2. beta (50% complete, 1 stale)");
18
+ });
19
+ });
@@ -0,0 +1,12 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true,
9
+ "types": ["vitest/globals"]
10
+ },
11
+ "include": ["src/**/*.ts", "tests/**/*.ts"]
12
+ }
@@ -0,0 +1,5 @@
1
+ import { defineConfig } from "vitest/config";
2
+
3
+ export default defineConfig({
4
+ cacheDir: "../../../../.vitest/task-analytics-large-mixed-ts"
5
+ });
@@ -0,0 +1 @@
1
+ Task workflow benchmark project with multiple services, validation rules, filtering, summarization, and deterministic imports.
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "task-workflow-medium-ts-benchmark",
3
+ "private": true,
4
+ "type": "module",
5
+ "scripts": {
6
+ "test": "vitest run"
7
+ },
8
+ "devDependencies": {
9
+ "typescript": "^5.8.3",
10
+ "vitest": "^3.2.4"
11
+ }
12
+ }
@@ -0,0 +1,9 @@
1
+ export * from "./models/project.js";
2
+ export * from "./models/task.js";
3
+ export * from "./services/completeTask.js";
4
+ export * from "./services/createTask.js";
5
+ export * from "./services/filterTasks.js";
6
+ export * from "./services/importTasks.js";
7
+ export * from "./services/summarizeTasks.js";
8
+ export * from "./store/taskStore.js";
9
+ export * from "./validation/taskValidation.js";
@@ -0,0 +1,6 @@
1
+ export type WorkflowProject = {
2
+ id: string;
3
+ name: string;
4
+ slug: string;
5
+ archived: boolean;
6
+ };
@@ -0,0 +1,39 @@
1
+ export type TaskPriority = "low" | "medium" | "high";
2
+
3
+ export type WorkflowTask = {
4
+ id: string;
5
+ title: string;
6
+ normalizedTitle: string;
7
+ projectId: string;
8
+ priority: TaskPriority;
9
+ tags: string[];
10
+ completed: boolean;
11
+ notes: string;
12
+ createdAt: string;
13
+ completedAt?: string;
14
+ importSource?: string;
15
+ };
16
+
17
+ export type TaskFilter = {
18
+ projectId?: string;
19
+ query?: string;
20
+ tag?: string;
21
+ completed?: boolean;
22
+ priority?: TaskPriority;
23
+ };
24
+
25
+ export type TaskSummary = {
26
+ total: number;
27
+ completed: number;
28
+ open: number;
29
+ byProject: Record<string, { total: number; completed: number; open: number }>;
30
+ highPriorityOpenTitles: string[];
31
+ };
32
+
33
+ export type TaskImportInput = {
34
+ title: string;
35
+ projectId: string;
36
+ priority?: TaskPriority;
37
+ tags?: string[];
38
+ notes?: string;
39
+ };
@@ -0,0 +1,15 @@
1
+ import type { WorkflowTask } from "../models/task.js";
2
+ import { TaskWorkflowStore } from "../store/taskStore.js";
3
+
4
+ export function completeTask(store: TaskWorkflowStore, taskId: string): WorkflowTask {
5
+ return store.updateTask(taskId, (task) => {
6
+ if (task.completed) {
7
+ return task;
8
+ }
9
+ return {
10
+ ...task,
11
+ completed: true,
12
+ completedAt: "2026-02-01T00:00:00.000Z"
13
+ };
14
+ });
15
+ }
@@ -0,0 +1,26 @@
1
+ import type { TaskPriority, WorkflowTask } from "../models/task.js";
2
+ import { TaskWorkflowStore } from "../store/taskStore.js";
3
+ import { normalizeTags, validatePriority, validateProjectId, validateTaskTitle } from "../validation/taskValidation.js";
4
+
5
+ export type CreateTaskInput = {
6
+ title: string;
7
+ projectId: string;
8
+ priority?: TaskPriority;
9
+ tags?: string[];
10
+ notes?: string;
11
+ };
12
+
13
+ export function createTask(store: TaskWorkflowStore, input: CreateTaskInput): WorkflowTask {
14
+ const title = validateTaskTitle(input.title);
15
+ const projectId = validateProjectId(input.projectId);
16
+ if (!store.getProject(projectId)) {
17
+ throw new Error(`Unknown project id: ${projectId}`);
18
+ }
19
+ return store.createTask({
20
+ title,
21
+ projectId,
22
+ priority: validatePriority(input.priority),
23
+ tags: normalizeTags(input.tags ?? []),
24
+ notes: input.notes?.trim() ?? ""
25
+ });
26
+ }
@@ -0,0 +1,17 @@
1
+ import type { TaskFilter, WorkflowTask } from "../models/task.js";
2
+ import { TaskWorkflowStore } from "../store/taskStore.js";
3
+
4
+ export function filterTasks(store: TaskWorkflowStore, filter: TaskFilter): WorkflowTask[] {
5
+ const query = filter.query?.trim().toLowerCase();
6
+ return store.listTasks().filter((task) => {
7
+ if (filter.projectId && task.projectId !== filter.projectId) return false;
8
+ if (filter.completed !== undefined && task.completed !== filter.completed) return false;
9
+ if (filter.priority && task.priority !== filter.priority) return false;
10
+ if (filter.tag && !task.tags.includes(filter.tag.trim().toLowerCase())) return false;
11
+ if (query) {
12
+ const haystack = `${task.title} ${task.notes} ${task.tags.join(" ")}`.toLowerCase();
13
+ if (!haystack.includes(query)) return false;
14
+ }
15
+ return true;
16
+ });
17
+ }
@@ -0,0 +1,33 @@
1
+ import type { TaskImportInput, WorkflowTask } from "../models/task.js";
2
+ import { TaskWorkflowStore } from "../store/taskStore.js";
3
+ import { validateImportInput } from "../validation/taskValidation.js";
4
+
5
+ export type ImportTasksResult = {
6
+ imported: WorkflowTask[];
7
+ skippedDuplicates: string[];
8
+ };
9
+
10
+ export function importTasks(store: TaskWorkflowStore, items: TaskImportInput[], importSource: string): ImportTasksResult {
11
+ const imported: WorkflowTask[] = [];
12
+ const skippedDuplicates: string[] = [];
13
+
14
+ for (const item of items) {
15
+ const validated = validateImportInput(item);
16
+ if (!store.getProject(validated.projectId)) {
17
+ throw new Error(`Unknown project id: ${validated.projectId}`);
18
+ }
19
+ const duplicate = store.findDuplicate(validated);
20
+ if (duplicate) {
21
+ skippedDuplicates.push(duplicate.id);
22
+ continue;
23
+ }
24
+ imported.push(
25
+ store.createTask({
26
+ ...validated,
27
+ importSource
28
+ })
29
+ );
30
+ }
31
+
32
+ return { imported, skippedDuplicates };
33
+ }
@@ -0,0 +1,30 @@
1
+ import type { TaskSummary } from "../models/task.js";
2
+ import { TaskWorkflowStore } from "../store/taskStore.js";
3
+
4
+ export function summarizeTasks(store: TaskWorkflowStore): TaskSummary {
5
+ const tasks = store.listTasks();
6
+ const byProject: TaskSummary["byProject"] = {};
7
+
8
+ for (const project of store.listProjects()) {
9
+ byProject[project.id] = { total: 0, completed: 0, open: 0 };
10
+ }
11
+
12
+ for (const task of tasks) {
13
+ byProject[task.projectId] ??= { total: 0, completed: 0, open: 0 };
14
+ byProject[task.projectId].total += 1;
15
+ if (task.completed) {
16
+ byProject[task.projectId].completed += 1;
17
+ } else {
18
+ byProject[task.projectId].open += 1;
19
+ }
20
+ }
21
+
22
+ const completed = tasks.filter((task) => task.completed).length;
23
+ return {
24
+ total: tasks.length,
25
+ completed,
26
+ open: tasks.length - completed,
27
+ byProject,
28
+ highPriorityOpenTitles: tasks.filter((task) => !task.completed && task.priority === "high").map((task) => task.title)
29
+ };
30
+ }
@@ -0,0 +1,76 @@
1
+ import { createDeterministicId } from "../utils/deterministicId.js";
2
+ import type { WorkflowProject } from "../models/project.js";
3
+ import type { TaskImportInput, TaskPriority, WorkflowTask } from "../models/task.js";
4
+ import { normalizeTaskTitle, normalizeTags } from "../validation/taskValidation.js";
5
+
6
+ export class TaskWorkflowStore {
7
+ private nextTaskSequence = 1;
8
+ private readonly projects = new Map<string, WorkflowProject>();
9
+ private readonly tasks = new Map<string, WorkflowTask>();
10
+
11
+ constructor(seed?: { projects?: WorkflowProject[]; tasks?: WorkflowTask[] }) {
12
+ for (const project of seed?.projects ?? []) {
13
+ this.projects.set(project.id, { ...project });
14
+ }
15
+ for (const task of seed?.tasks ?? []) {
16
+ this.tasks.set(task.id, { ...task, tags: [...task.tags] });
17
+ this.nextTaskSequence = Math.max(this.nextTaskSequence, extractSequence(task.id) + 1);
18
+ }
19
+ }
20
+
21
+ addProject(project: WorkflowProject): void {
22
+ this.projects.set(project.id, { ...project });
23
+ }
24
+
25
+ getProject(projectId: string): WorkflowProject | undefined {
26
+ const project = this.projects.get(projectId);
27
+ return project ? { ...project } : undefined;
28
+ }
29
+
30
+ listProjects(): WorkflowProject[] {
31
+ return [...this.projects.values()].map((project) => ({ ...project }));
32
+ }
33
+
34
+ createTask(input: { title: string; projectId: string; priority: TaskPriority; tags: string[]; notes: string; importSource?: string }): WorkflowTask {
35
+ const task: WorkflowTask = {
36
+ id: createDeterministicId("task", this.nextTaskSequence++),
37
+ title: input.title,
38
+ normalizedTitle: normalizeTaskTitle(input.title).toLowerCase(),
39
+ projectId: input.projectId,
40
+ priority: input.priority,
41
+ tags: normalizeTags(input.tags),
42
+ completed: false,
43
+ notes: input.notes,
44
+ createdAt: `2026-01-${String(this.tasks.size + 1).padStart(2, "0")}T00:00:00.000Z`,
45
+ importSource: input.importSource
46
+ };
47
+ this.tasks.set(task.id, task);
48
+ return { ...task, tags: [...task.tags] };
49
+ }
50
+
51
+ updateTask(id: string, updater: (task: WorkflowTask) => WorkflowTask): WorkflowTask {
52
+ const existing = this.tasks.get(id);
53
+ if (!existing) {
54
+ throw new Error(`Unknown task id: ${id}`);
55
+ }
56
+ const updated = updater({ ...existing, tags: [...existing.tags] });
57
+ this.tasks.set(id, { ...updated, tags: [...updated.tags] });
58
+ return { ...updated, tags: [...updated.tags] };
59
+ }
60
+
61
+ listTasks(): WorkflowTask[] {
62
+ return [...this.tasks.values()]
63
+ .map((task) => ({ ...task, tags: [...task.tags] }))
64
+ .sort((left, right) => left.id.localeCompare(right.id));
65
+ }
66
+
67
+ findDuplicate(input: TaskImportInput): WorkflowTask | undefined {
68
+ const normalizedTitle = normalizeTaskTitle(input.title).toLowerCase();
69
+ return this.listTasks().find((task) => task.projectId === input.projectId && task.normalizedTitle === normalizedTitle);
70
+ }
71
+ }
72
+
73
+ function extractSequence(id: string): number {
74
+ const match = id.match(/-(\d+)$/);
75
+ return match ? Number(match[1]) : 0;
76
+ }
@@ -0,0 +1,3 @@
1
+ export function createDeterministicId(prefix: string, sequence: number): string {
2
+ return `${prefix}-${sequence}`;
3
+ }
@@ -0,0 +1,45 @@
1
+ import type { TaskImportInput, TaskPriority } from "../models/task.js";
2
+
3
+ const VALID_PRIORITIES = new Set<TaskPriority>(["low", "medium", "high"]);
4
+
5
+ export function normalizeTaskTitle(title: string): string {
6
+ return title.trim().replace(/\s+/g, " ");
7
+ }
8
+
9
+ export function validateTaskTitle(title: string): string {
10
+ const normalized = normalizeTaskTitle(title);
11
+ if (!normalized) {
12
+ throw new Error("Task title must not be empty.");
13
+ }
14
+ return normalized;
15
+ }
16
+
17
+ export function validateProjectId(projectId: string): string {
18
+ const normalized = projectId.trim();
19
+ if (!normalized) {
20
+ throw new Error("projectId must not be empty.");
21
+ }
22
+ return normalized;
23
+ }
24
+
25
+ export function validatePriority(priority: TaskPriority | undefined): TaskPriority {
26
+ const resolved = priority ?? "medium";
27
+ if (!VALID_PRIORITIES.has(resolved)) {
28
+ throw new Error(`Unsupported priority: ${priority}`);
29
+ }
30
+ return resolved;
31
+ }
32
+
33
+ export function validateImportInput(input: TaskImportInput): Required<TaskImportInput> {
34
+ return {
35
+ title: validateTaskTitle(input.title),
36
+ projectId: validateProjectId(input.projectId),
37
+ priority: validatePriority(input.priority),
38
+ tags: normalizeTags(input.tags ?? []),
39
+ notes: input.notes?.trim() ?? ""
40
+ };
41
+ }
42
+
43
+ export function normalizeTags(tags: string[]): string[] {
44
+ return [...new Set(tags.map((tag) => tag.trim().toLowerCase()).filter(Boolean))].sort();
45
+ }
@@ -0,0 +1,16 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { completeTask } from "../src/services/completeTask.js";
3
+ import { createTask } from "../src/services/createTask.js";
4
+ import { TaskWorkflowStore } from "../src/store/taskStore.js";
5
+
6
+ describe("completeTask", () => {
7
+ it("marks only the selected task as completed", () => {
8
+ const store = new TaskWorkflowStore({
9
+ projects: [{ id: "alpha", name: "Alpha", slug: "alpha", archived: false }]
10
+ });
11
+ const first = createTask(store, { title: "One", projectId: "alpha" });
12
+ createTask(store, { title: "Two", projectId: "alpha" });
13
+ expect(completeTask(store, first.id).completed).toBe(true);
14
+ expect(store.listTasks().map((task) => task.completed)).toEqual([true, false]);
15
+ });
16
+ });